tensorflow2.10使用BERT實(shí)現(xiàn)Semantic Similarity過(guò)程解析
前言
本文詳細(xì)解釋了在 tensorflow-gpu 基礎(chǔ)上,實(shí)現(xiàn)用 BERT + BILSTM 計(jì)算文本相似度的過(guò)程,主要的配置如下:
tensorflow-gpu == 2.10.0 python == 3.10 transformers == 4.26.1
數(shù)據(jù)處理
這里導(dǎo)入了后續(xù)步驟需要用到的庫(kù),包括 NumPy、Pandas、TensorFlow 和 Transformers。同時(shí)設(shè)置了幾個(gè)重要的參數(shù)。其中,max_length 表示輸入文本的最大長(zhǎng)度,batch_size 表示每個(gè)批次訓(xùn)練的樣本數(shù)量,epochs 表示訓(xùn)練集訓(xùn)練次數(shù),labels 列表包含了三個(gè)分類標(biāo)簽,分別為“矛盾”、“蘊(yùn)含” 和 “中性”。
import numpy as np import pandas as pd import tensorflow as tf import transformers max_length = 128 batch_size = 32 epochs = 2 labels = ["contradiction", "entailment", "neutral"]
這里使用 Pandas 庫(kù)讀取了 SNLI 數(shù)據(jù)集中的訓(xùn)練集、驗(yàn)證集和測(cè)試集。其中,訓(xùn)練集只讀取了前 30 萬(wàn)條數(shù)據(jù)。接著打印了各數(shù)據(jù)集的樣本數(shù)。然后,打印了訓(xùn)練集中的三組樣本,每組樣本包括兩個(gè)句子和分類標(biāo)簽。
train_df = pd.read_csv("SNLI_Corpus/snli_1.0_train.csv", nrows=300000)
valid_df = pd.read_csv("SNLI_Corpus/snli_1.0_dev.csv")
test_df = pd.read_csv("SNLI_Corpus/snli_1.0_test.csv")
print(f"訓(xùn)練集樣本數(shù) : {train_df.shape[0]}")
print(f"驗(yàn)證集樣本數(shù): {valid_df.shape[0]}")
print(f"測(cè)試集樣本數(shù): {test_df.shape[0]}")
print()
print(f"句子一: {train_df.loc[5, 'sentence1']}")
print(f"句子二: {train_df.loc[5, 'sentence2']}")
print(f"相似度: {train_df.loc[5, 'similarity']}")
print()
print(f"句子一: {train_df.loc[3, 'sentence1']}")
print(f"句子二: {train_df.loc[3, 'sentence2']}")
print(f"相似度: {train_df.loc[3, 'similarity']}")
print()
print(f"句子一: {train_df.loc[4, 'sentence1']}")
print(f"句子二: {train_df.loc[4, 'sentence2']}")
print(f"相似度: {train_df.loc[4, 'similarity']}")
打?。?/p>
訓(xùn)練集樣本數(shù) : 300000
驗(yàn)證集樣本數(shù): 10000
測(cè)試集樣本數(shù): 10000
句子一: Children smiling and waving at camera
句子二: The kids are frowning
相似度: contradiction
句子一: Children smiling and waving at camera
句子二: They are smiling at their parents
相似度: neutral
句子一: Children smiling and waving at camera
句子二: There are children present
相似度: entailment
首先使用 dropna 函數(shù)刪除訓(xùn)練集中的缺失數(shù)據(jù)。然后對(duì)訓(xùn)練集、驗(yàn)證集
測(cè)試集中的分類標(biāo)簽為“-”的數(shù)據(jù)進(jìn)行了刪除操作。接著使用 sample 函數(shù)進(jìn)行了打亂處理,并使用 reset_index 函數(shù)重置了索引。最后,打印了處理后的各個(gè)數(shù)據(jù)集樣本數(shù)。
train_df.dropna(axis=0, inplace=True)
train_df = ( train_df[train_df.similarity != "-"].sample(frac=1.0, random_state=30).reset_index(drop=True) )
valid_df = ( valid_df[valid_df.similarity != "-"].sample(frac=1.0, random_state=30).reset_index(drop=True) )
test_df = ( test_df[test_df.similarity != "-"].sample(frac=1.0, random_state=30).reset_index(drop=True) )
print(f"處理后訓(xùn)練集樣本數(shù) : {train_df.shape[0]}")
print(f"處理后驗(yàn)證集樣本數(shù): {valid_df.shape[0]}")
print(f"處理后測(cè)試集樣本數(shù): {test_df.shape[0]}")
打?。?/p>
處理后訓(xùn)練集樣本數(shù) : 299616
處理后驗(yàn)證集樣本數(shù): 9842
處理后測(cè)試集樣本數(shù): 9824
這里將訓(xùn)練集、驗(yàn)證集和測(cè)試集中的分類標(biāo)簽轉(zhuǎn)換為數(shù)字,并將標(biāo)簽轉(zhuǎn)換為 one-hot 編碼格式。具體來(lái)說(shuō)就是使用 apply 函數(shù)將 "contradiction" 標(biāo)簽轉(zhuǎn)換為數(shù)字 0,將 "entailment" 標(biāo)簽轉(zhuǎn)換為數(shù)字 1,將 "neutral" 標(biāo)簽轉(zhuǎn)換為數(shù)字 2。然后,使用 to_categorical 函數(shù)將數(shù)字標(biāo)簽轉(zhuǎn)換為 one-hot 編碼格式。最終使用 y_train、y_val 和 y_test 存儲(chǔ)了訓(xùn)練集、驗(yàn)證集和測(cè)試集的 one-hot 編碼標(biāo)簽結(jié)果。
train_df["label"] = train_df["similarity"].apply(lambda x: 0 if x == "contradiction" else 1 if x == "entailment" else 2) y_train = tf.keras.utils.to_categorical(train_df.label, num_classes=3) valid_df["label"] = valid_df["similarity"].apply(lambda x: 0 if x == "contradiction" else 1 if x == "entailment" else 2) y_val = tf.keras.utils.to_categorical(valid_df.label, num_classes=3) test_df["label"] = test_df["similarity"].apply(lambda x: 0 if x == "contradiction" else 1 if x == "entailment" else 2) y_test = tf.keras.utils.to_categorical(test_df.label, num_classes=3)
模型搭建
這里定義了一個(gè)繼承自 tf.keras.utils.Sequence 的類 BertSemanticDataGenerator ,用于生成 BERT 模型訓(xùn)練所需的數(shù)據(jù)。
在初始化時(shí),需要傳入句子對(duì)的數(shù)組 sentence_pairs 和對(duì)應(yīng)的標(biāo)簽 labels,同時(shí)可以指定批次大小 batch_size ,shuffle 表示是否要打亂數(shù)據(jù), include_targets 表示是否包含標(biāo)簽信息。類中還定義了一個(gè) BERT 分詞器 tokenizer,使用了 bert-base-uncased 預(yù)訓(xùn)練模型。
同時(shí)實(shí)現(xiàn)了 __len__ 、 __getitem__ 、on_epoch_end 三個(gè)方法, __len__ 用于獲取數(shù)據(jù)集可以按照 batch_size 均分的批次數(shù)量 ,__getitem__ 首先使用索引從 self.sentence_pairs 中獲取批數(shù)據(jù),然后使用指定的編碼器對(duì)這些句子對(duì)進(jìn)行編碼,使其適用于 BERT 模型的輸入,最后返回輸入和標(biāo)簽。on_epoch_end 方法在每輪訓(xùn)練之后判斷是否需要打亂數(shù)據(jù)集。
class BertSemanticDataGenerator(tf.keras.utils.Sequence):
def __init__( self, sentence_pairs, labels, batch_size=batch_size, shuffle=True, include_targets=True ):
self.sentence_pairs = sentence_pairs
self.labels = labels
self.shuffle = shuffle
self.batch_size = batch_size
self.include_targets = include_targets
self.tokenizer = transformers.BertTokenizer.from_pretrained("bert-base-uncased", do_lower_case=True )
self.indexes = np.arange(len(self.sentence_pairs))
self.on_epoch_end()
def __len__(self):
return len(self.sentence_pairs) // self.batch_size
def __getitem__(self, idx):
indexes = self.indexes[idx * self.batch_size : (idx + 1) * self.batch_size]
sentence_pairs = self.sentence_pairs[indexes]
encoded = self.tokenizer.batch_encode_plus( sentence_pairs.tolist(), add_special_tokens=True,
max_length=max_length, return_attention_mask=True, return_token_type_ids=True,
pad_to_max_length=True, return_tensors="tf")
input_ids = np.array(encoded["input_ids"], dtype="int32")
attention_masks = np.array(encoded["attention_mask"], dtype="int32")
token_type_ids = np.array(encoded["token_type_ids"], dtype="int32")
if self.include_targets:
labels = np.array(self.labels[indexes], dtype="int32")
return [input_ids, attention_masks, token_type_ids], labels
else:
return [input_ids, attention_masks, token_type_ids]
def on_epoch_end(self):
if self.shuffle:
np.random.RandomState(30).shuffle(self.indexes)
這里使用 TensorFlow2 和 Transformers 庫(kù)實(shí)現(xiàn)了一個(gè)基于 BERT 的文本分類模型。以下是代碼的主要步驟。
首先,定義了三個(gè)輸入張量:input_ids、attention_masks 和 token_type_ids ,這些張量的形狀都是 (max_length,) ,其中 max_length 是預(yù)處理后的文本序列的最大長(zhǎng)度。
接下來(lái),定義了一個(gè) BERT 模型 bert_model 。通過(guò)調(diào)用 TFBertModel.from_pretrained 方法,該模型從預(yù)先訓(xùn)練好的 BERT 模型中加載參數(shù)。同時(shí),將 bert_model.trainable 設(shè)置為 False ,以避免在訓(xùn)練過(guò)程中更新 BERT 模型的參數(shù)。
然后,將 input_ids、attention_masks 和 token_type_ids 作為輸入傳入 bert_model ,得到 bert_output 。獲取 BERT 模型的最后一個(gè)隱藏狀態(tài)(last_hidden_state),作為 LSTM 層的輸入。
接著,使用 Bi-LSTM 層對(duì) sequence_output 進(jìn)行處理,生成一個(gè)具有 64 個(gè)輸出單元的 LSTM 層,返回整個(gè)序列。然后,將 Bi-LSTM 層的輸出分別進(jìn)行全局平均池化和全局最大池化,得到 avg_pool 和 max_pool 。將這兩個(gè)輸出連接起來(lái),形成一個(gè)維度為 128 的向量,通過(guò) Dropout 層后,經(jīng)過(guò)一個(gè) Dense 層輸出最終的分類結(jié)果。
最后,使用 tf.keras.models.Model 方法,將 input_ids、attention_masks 和 token_type_ids 作為輸入,output 作為輸出,定義一個(gè)完整的神經(jīng)網(wǎng)絡(luò)模型。并使用 model.compile 方法編譯模型,指定了優(yōu)化器 Adam 、損失函數(shù)為 categorical_crossentropy 、評(píng)估指標(biāo)為 acc 。
input_ids = tf.keras.layers.Input(shape=(max_length,), dtype=tf.int32, name="input_ids")
attention_masks = tf.keras.layers.Input(shape=(max_length,), dtype=tf.int32, name="attention_masks")
token_type_ids = tf.keras.layers.Input(shape=(max_length,), dtype=tf.int32, name="token_type_ids")
bert_model = transformers.TFBertModel.from_pretrained("bert-base-uncased")
bert_model.trainable = False
bert_output = bert_model.bert(input_ids, attention_mask=attention_masks, token_type_ids=token_type_ids)
sequence_output = bert_output.last_hidden_state
bi_lstm = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(64, return_sequences=True))(sequence_output)
avg_pool = tf.keras.layers.GlobalAveragePooling1D()(bi_lstm)
max_pool = tf.keras.layers.GlobalMaxPooling1D()(bi_lstm)
concat = tf.keras.layers.concatenate([avg_pool, max_pool])
dropout = tf.keras.layers.Dropout(0.5)(concat)
output = tf.keras.layers.Dense(3, activation="softmax")(dropout)
model = tf.keras.models.Model(inputs=[input_ids, attention_masks, token_type_ids], outputs=output)
model.compile( optimizer=tf.keras.optimizers.Adam(), loss="categorical_crossentropy", metrics=["acc"],)
model.summary()
打印模型結(jié)構(gòu)可以看到, BERT 的參數(shù)都被凍結(jié)了:
Model: "model"
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_ids (InputLayer) [(None, 128)] 0 []
attention_masks (InputLayer) [(None, 128)] 0 []
token_type_ids (InputLayer) [(None, 128)] 0 []
bert (TFBertMainLayer) TFBaseModelOutputWi 109482240 ['input_ids[0][0]',
thPoolingAndCrossAt 'attention_masks[0][0]',
tentions(last_hidde 'token_type_ids[0][0]']
n_state=(None, 128,
768),
pooler_output=(Non
e, 768),
past_key_values=No
ne, hidden_states=N
one, attentions=Non
e, cross_attentions
=None)
bidirectional (Bidirectional) (None, 128, 128) 426496 ['bert[0][0]']
global_average_pooling1d (Glob (None, 128) 0 ['bidirectional[0][0]']
alAveragePooling1D)
global_max_pooling1d (GlobalMa (None, 128) 0 ['bidirectional[0][0]']
xPooling1D)
concatenate (Concatenate) (None, 256) 0 ['global_average_pooling1d[0][0]'
, 'global_max_pooling1d[0][0]']
dropout_37 (Dropout) (None, 256) 0 ['concatenate[0][0]']
dense (Dense) (None, 3) 771 ['dropout_37[0][0]']
==================================================================================================
Total params: 109,909,507
Trainable params: 427,267
Non-trainable params: 109,482,240
模型訓(xùn)練
首先,將訓(xùn)練集和驗(yàn)證集傳入 BertSemanticDataGenerator 對(duì)象中,創(chuàng)建一個(gè)訓(xùn)練數(shù)據(jù)生成器 train_data 和一個(gè)驗(yàn)證數(shù)據(jù)生成器 valid_data。然后,通過(guò)調(diào)用 model.fit() 方法,對(duì)模型進(jìn)行訓(xùn)練。其中,訓(xùn)練數(shù)據(jù)為 train_data,驗(yàn)證數(shù)據(jù)為 valid_data。 use_multiprocessing 和 workers 參數(shù)用于指定在訓(xùn)練期間使用的進(jìn)程數(shù),以加快訓(xùn)練速度。
最后,訓(xùn)練歷史記錄存儲(chǔ)在 history 變量中,可以使用這些歷史數(shù)據(jù)來(lái)分析模型的訓(xùn)練效果。
train_data = BertSemanticDataGenerator( train_df[["sentence1", "sentence2"]].values.astype("str"), y_train, batch_size=batch_size, shuffle=True)
valid_data = BertSemanticDataGenerator( valid_df[["sentence1", "sentence2"]].values.astype("str"), y_val, batch_size=batch_size, shuffle=False)
history = model.fit( train_data, validation_data=valid_data, epochs=epochs, use_multiprocessing=True, workers=-1 )
Epoch 1/2
11/9363 [..............................] - ETA: 16:31 - loss: 1.1949 - acc: 0.3580
31/9363 [..............................] - ETA: 13:51 - loss: 1.1223 - acc: 0.3831
...
Epoch 2/2
...
9363/9363 [==============================] - ETA: 0s - loss: 0.5691 - acc: 0.7724
9363/9363 [==============================] - 791s 84ms/step - loss: 0.5691 - acc: 0.7724 - val_loss: 0.4635 - val_acc: 0.8226
微調(diào)模型
這里是對(duì)訓(xùn)練好的 BERT 模型進(jìn)行 fine-tuning,即對(duì)其進(jìn)行微調(diào)以適應(yīng)新任務(wù)。具體來(lái)說(shuō)就是通過(guò)將 bert_model.trainable 設(shè)置為 True ,可以使得 BERT 模型中的參數(shù)可以在 fine-tuning 過(guò)程中進(jìn)行更新。然后使用 tf.keras.optimizers.Adam(1e-5) 作為優(yōu)化器,以較小的學(xué)習(xí)率進(jìn)行微調(diào)。同時(shí)使用 categorical_crossentropy 作為損失函數(shù),用來(lái)評(píng)估模型輸出的預(yù)測(cè)分布與實(shí)際標(biāo)簽分布之間的差異。最后,通過(guò) model.summary() 函數(shù)查看模型的結(jié)構(gòu)和參數(shù)信息,可以發(fā)現(xiàn)所有的參數(shù)現(xiàn)在都可以訓(xùn)練了。
bert_model.trainable = True model.compile( optimizer=tf.keras.optimizers.Adam(1e-5), loss="categorical_crossentropy", metrics=["accuracy"] ) model.summary()
打印:
Model: "model"
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_ids (InputLayer) [(None, 128)] 0 []
attention_masks (InputLayer) [(None, 128)] 0 []
token_type_ids (InputLayer) [(None, 128)] 0 []
bert (TFBertMainLayer) TFBaseModelOutputWi 109482240 ['input_ids[0][0]',
thPoolingAndCrossAt 'attention_masks[0][0]',
tentions(last_hidde 'token_type_ids[0][0]']
n_state=(None, 128,
768),
pooler_output=(Non
e, 768),
past_key_values=No
ne, hidden_states=N
one, attentions=Non
e, cross_attentions
=None)
bidirectional (Bidirectional) (None, 128, 128) 426496 ['bert[0][0]']
global_average_pooling1d (Glob (None, 128) 0 ['bidirectional[0][0]']
alAveragePooling1D)
global_max_pooling1d (GlobalMa (None, 128) 0 ['bidirectional[0][0]']
xPooling1D)
concatenate (Concatenate) (None, 256) 0 ['global_average_pooling1d[0][0]'
, 'global_max_pooling1d[0][0]']
dropout_37 (Dropout) (None, 256) 0 ['concatenate[0][0]']
dense (Dense) (None, 3) 771 ['dropout_37[0][0]']
==================================================================================================
Total params: 109,909,507
Trainable params: 109,909,507
Non-trainable params: 0
接著上面的模型,繼續(xù)進(jìn)行微調(diào)訓(xùn)練,我們可以看到這次的準(zhǔn)確率比之前有所提升。
history = model.fit( train_data, validation_data=valid_data, epochs=epochs, use_multiprocessing=True, workers=-1,)
打?。?/p>
Epoch 1/2
7/9363 [..............................] - ETA: 24:41 - loss: 0.5716 - accuracy: 0.7946
...
Epoch 2/2
...
9363/9363 [==============================] - 1500s 160ms/step - loss: 0.3201 - accuracy: 0.8845 - val_loss: 0.2933 - val_accuracy: 0.8974
模型評(píng)估
使用測(cè)試數(shù)據(jù)對(duì)模型的性能進(jìn)行評(píng)估。
test_data = BertSemanticDataGenerator( test_df[["sentence1", "sentence2"]].values.astype("str"), y_test, batch_size=batch_size, shuffle=False)
model.evaluate(test_data, verbose=1)
307/307 [==============================] - 18s 57ms/step - loss: 0.2916 - accuracy: 0.8951
推理測(cè)試
這里定義了一個(gè)名為 check_similarity 的函數(shù),該函數(shù)可以用來(lái)檢查兩個(gè)句子的語(yǔ)義相似度。傳入的參數(shù)是兩個(gè)句子 sentence1 和 sentence2 。首先將這兩個(gè)句子組成一個(gè) np.array 格式方便處理,然后通過(guò) BertSemanticDataGenerator 函數(shù)創(chuàng)建一個(gè)數(shù)據(jù)生成器生成模型需要的測(cè)試數(shù)據(jù)格式,使用訓(xùn)練好的函數(shù)返回句子對(duì)的預(yù)測(cè)概率,最后取預(yù)測(cè)概率最高的類別作為預(yù)測(cè)結(jié)果。
def check_similarity(sentence1, sentence2):
sentence_pairs = np.array([[str(sentence1), str(sentence2)]])
test_data = BertSemanticDataGenerator( sentence_pairs, labels=None, batch_size=1, shuffle=False, include_targets=False )
proba = model.predict(test_data[0])[0]
idx = np.argmax(proba)
proba = f"{proba[idx]: .2f}%"
pred = labels[idx]
return pred, proba
sentence1 = "Male in a blue jacket decides to lay in the grass"
sentence2 = "The guy wearing a blue jacket is laying on the green grass"
check_similarity(sentence1, sentence2)
打?。?/p>
('entailment', ' 0.51%')
以上就是tensorflow2.10使用BERT實(shí)現(xiàn)Semantic Similarity過(guò)程解析的詳細(xì)內(nèi)容,更多關(guān)于tensorflow Semantic Similarity的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python實(shí)現(xiàn)文件操作幫助類的示例代碼
在使用Python進(jìn)行業(yè)務(wù)開(kāi)發(fā)的時(shí)候,需要將一些數(shù)據(jù)保存到本地文件存儲(chǔ),方便后面進(jìn)行數(shù)據(jù)分析展示,本文就來(lái)用Python制作一個(gè)文件操作幫助類,需要的可以參考一下2023-03-03
python 實(shí)現(xiàn)方陣的對(duì)角線遍歷示例
今天小編就為大家分享一篇python 實(shí)現(xiàn)方陣的對(duì)角線遍歷示例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11
Python基于pandas繪制散點(diǎn)圖矩陣代碼實(shí)例
這篇文章主要介紹了Python基于pandas繪制散點(diǎn)圖矩陣代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
解決pycharm工程啟動(dòng)卡住沒(méi)反應(yīng)的問(wèn)題
今天小編就為大家分享一篇解決pycharm工程啟動(dòng)卡住沒(méi)反應(yīng)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-01-01
python題解LeetCode303區(qū)域和檢索示例詳解
這篇文章主要為大家介紹了python題解LeetCode303區(qū)域和檢索示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
Eclipse和PyDev搭建完美Python開(kāi)發(fā)環(huán)境教程(Windows篇)
這篇文章主要介紹了Eclipse和PyDev搭建完美Python開(kāi)發(fā)環(huán)境教程(Windows篇),具有一定的參考價(jià)值,感興趣的小伙伴可以了解一下。2016-11-11
通過(guò)pycharm的database設(shè)置進(jìn)行數(shù)據(jù)庫(kù)的可視化方式
這篇文章主要介紹了通過(guò)pycharm的database設(shè)置進(jìn)行數(shù)據(jù)庫(kù)的可視化方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
anaconda安裝后打不開(kāi)解決方式(親測(cè)有效)
Anaconda是一個(gè)和Canopy類似的科學(xué)計(jì)算環(huán)境,但用起來(lái)更加方便,下面這篇文章主要給大家介紹了關(guān)于anaconda安裝后打不開(kāi)解決的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09
python實(shí)現(xiàn)關(guān)閉第三方窗口的方法
今天小編就為大家分享一篇python實(shí)現(xiàn)關(guān)閉第三方窗口的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-06-06

