bert 文本分类实战
前言:
? ? ? ?由于課題需要,學習自然語言處理(NLP),于是在網上找了找文章和代碼進行學習,在此記錄,課題代碼就不展示了,使用網上的代碼和大家分享。思想和代碼大部分參考蘇神,在此感謝。
任務目標:
? ? ?希望bert模型解決的問題: 輸入:一段話; 輸出:這段話屬于的類別。
任務實現原理:
? ? ? 本次模型為監督學習模型,根據已有標簽的文本數據集,對bert模型進行訓練。使用訓練好的模型對句子進行預測,輸出得到句子的類別。本質上屬于多分類問題。
? ? ? 大致流程為,數據集預處理;劃分數據集();對數據集加工處理(文本數據編碼成符合bert輸入的向量);構建模型(bert模型導入與使用); 將數據送入模型進行訓練和預測。
模型總體結構:
?具體代碼:
數據集預處理:
? ? ?將數據集讀入,并打亂數據的排列順序。
mainPath = 'bert多文本分類//' rc = pd.read_csv(mainPath + 'data/tnews/toutiao_news_dataset.txt', delimiter="_!_", names=['labels', 'text'], header=None, encoding='utf-8') rc = shuffle(rc) #打亂順序劃分數據集
? ? ?將數據集劃分為訓練集和測試集(驗證集)
# 構建全部所需數據集 data_list = [] for d in rc.iloc[:].itertuples(): #itertuples(): 將DataFrame迭代為元祖。data_list.append((d.text, d.labels))# 取一部分數據做訓練和驗證 train_data = data_list[0:20000] valid_data = data_list[20000:22000]?數據加工處理:
修改原有的字典:
修改原因:蘇神解讀,本來 Tokenizer 有自己的 _tokenize 方法,我這里重寫了這個方法,是要保證 tokenize 之后的結果,跟原來的字符串長度等長(如果算上兩個標記,那么就是等長再加 2)。 Tokenizer 自帶的 _tokenize 會自動去掉空格,然后有些字符會粘在一塊輸出,導致 tokenize 之后的列表不等于原來字符串的長度了,這樣如果做序列標注的任務會很麻煩。主要就是用 [unused1] 來表示空格類字符,而其余的不在列表的字符用 [UNK] 表示,其中 [unused*] 這些標記是未經訓練的(隨即初始化),是 Bert 預留出來用來增量添加詞匯的標記,所以我們可以用它們來指代任何新字符。
#vocabPath里存儲了大量的詞語,每個詞語對應的著一個編號 例如10640 posts # 將詞表中的詞編號轉換為字典 # 字典的形式為 '侖': 796, #得到最原始的字典 tokenDict = {} with codecs.open(vocabPath, 'r', encoding='utf-8') as reader:for line in reader:token = line.strip() # 去除首尾空格tokenDict[token] = len(tokenDict)#原始的字典存在著瑕疵,在原始的字典上需要根據自己的數據集,創造自己的字典 # 重寫tokenizer class OurTokenizer(Tokenizer):def _tokenize(self, content):reList = []for t in content:if t in self._token_dict:reList.append(t)elif self._is_space(t):# 用[unused1]來表示空格類字符reList.append('[unused1]')else:# 不在列表的字符用[UNK]表示reList.append('[UNK]')return reList#使用新的字典 tokenizer = OurTokenizer(tokenDict)?
? ?文本數據根據字典編碼成符合bert輸入的向量,逐批生成數據([X1,X2],Y),從而可以丟到模型中訓練。
def seqPadding(X, padding=0):L = [len(x) for x in X]ML = max(L)return np.array([np.concatenate([x, [padding] * (ML - len(x))]) if len(x) < ML else x for x in X])class data_generator:def __init__(self, data, batch_size=32, shuffle=True): #構造函數,使用時執行self.data = dataself.batch_size = batch_sizeself.shuffle = shuffleself.steps = len(self.data) // self.batch_sizeif len(self.data) % self.batch_size != 0:self.steps += 1def __len__(self):return self.stepsdef __iter__(self):while True:idxs = list(range(len(self.data))) #數據元組下標if self.shuffle:np.random.shuffle(idxs) #是否打亂數據下標順序X1, X2, Y = [], [], []for i in idxs:d = self.data[i]text = d[0][:maxlen]x1, x2 = tokenizer.encode(first=text) # encode方法可以一步到位地生成對應模型的輸入。y = d[1] X1.append(x1) ## x1 是字對應的索引 # x2 是句子對應的索引 X2.append(x2)Y.append([y])if len(X1) == self.batch_size or i == idxs[-1]:X1 = seqPadding(X1) #如果等于batchsize或者最后一個值后面補充0X2 = seqPadding(X2)Y = seqPadding(Y)yield [X1, X2], Y[X1, X2, Y] = [], [], []構建模型和訓練:
? ? 加載bert模型,并對bert模型的輸出進行調整,使bert模型能夠完成我們的任務目標。
# 設置預訓練bert模型的路徑 configPath = mainPath + 'chinese_roberta_wwm_ext_L-12_H-768_A-12/bert_config.json' ckpPath = mainPath + 'chinese_roberta_wwm_ext_L-12_H-768_A-12/bert_model.ckpt' vocabPath = mainPath + 'chinese_roberta_wwm_ext_L-12_H-768_A-12/vocab.txt'# bert模型設置 bert_model = load_trained_model_from_checkpoint(configPath, ckpPath, seq_len=None) # 加載預訓練模型 for l in bert_model.layers:l.trainable = Truex1_in = Input(shape=(None,)) x2_in = Input(shape=(None,))x = bert_model([x1_in, x2_in])# 取出[CLS]對應的向量用來做分類 x = Lambda(lambda x: x[:, 0])(x) p = Dense(15, activation='softmax')(x)model = Model([x1_in, x2_in], p) model.compile(loss='sparse_categorical_crossentropy', optimizer=Adam(1e-5), metrics=['accuracy']) model.summary()train_D = data_generator(train_data) valid_D = data_generator(valid_data)model.fit_generator(train_D.__iter__(), steps_per_epoch=len(train_D), epochs=5, validation_data=valid_D.__iter__(),validation_steps=len(valid_D))模型預測:
#測試的數據集 str1 = "上港主場1-2負于國安,遭遇聯賽兩連敗,上港到底輸在哪?" str2 = "普京總統會見了拜登總統" str3 = "這3輛10萬出頭小鋼炮,隨便改改輕松秒奔馳,第一輛還是限量款" predict_D = data_generator([(str1, 0), (str2, 3), (str3, 10)], shuffle=False) #獲取總的標簽類別 #array(['體育', '軍事', '農業', '國際', '娛樂', '房產', '教育', '文化', '旅游', '民生故事', '汽車','電競游戲', '科技', '證券股票', '財經'], dtype=object) output_label2id_file = os.path.join(mainPath, "model/keras_class/label2id.pkl") if os.path.exists(output_label2id_file):with open(output_label2id_file, 'rb') as w:labes = pickle.load(w)#加載保存的模型 from keras_bert import get_custom_objects custom_objects = get_custom_objects() model = load_model(mainPath + 'model/keras_class/tnews.h5', custom_objects=custom_objects) #使用生成器獲取測試的數據 tmpData = predict_D.__iter__() #預測 preds = model.predict_generator(tmpData, steps=len(predict_D), verbose=1) # 求每行最大值得下標,其中,axis=1表示按行計算 index_maxs = np.argmax(preds, axis=1) result = [(x, labes[x]) for x in index_maxs] print(result)輸出preds,index_maxs, result
?完整代碼
import pickle from keras_bert import load_trained_model_from_checkpoint, Tokenizer from keras.layers import * from keras.models import Model from keras.optimizers import Adam from sklearn.preprocessing import LabelEncoder from sklearn.utils import shuffle from keras.utils.vis_utils import plot_model import codecs, gc import keras.backend as K import os import pandas as pd import numpy as np# 文件主路徑定義 mainPath = '你的目錄/keras_bert文本分類實例/'# 從文件中讀取數據,獲取訓練集和驗證集 rc = pd.read_csv(mainPath + 'data/tnews/toutiao_news_dataset.txt', delimiter="_!_", names=['labels', 'text'],header=None, encoding='utf-8') #delimiterrc = shuffle(rc) # shuffle數據,打亂# 把類別轉換為數字 # 一共15個類別:"教育","科技","軍事","旅游","國際","證券股票","農業","電競游戲", # "民生故事","文化","娛樂","體育","財經","房產","汽車" class_le = LabelEncoder() rc.iloc[:, 0] = class_le.fit_transform(rc.iloc[:, 0].values)# 保存標簽文件 output_label2id_file = os.path.join(mainPath, "model/keras_class/label2id.pkl") if not os.path.exists(output_label2id_file):with open(output_label2id_file, 'wb') as w:pickle.dump(class_le.classes_, w)# 構建全部所需數據集 data_list = [] for d in rc.iloc[:].itertuples():data_list.append((d.text, d.labels))# 取一部分數據做訓練和驗證 train_data = data_list[0:20000] valid_data = data_list[20000:22000]maxlen = 100 # 設置序列長度為100,要保證序列長度不超過512# 設置預訓練模型 configPath = mainPath + 'chinese_roberta_wwm_ext_L-12_H-768_A-12/bert_config.json' ckpPath = mainPath + 'chinese_roberta_wwm_ext_L-12_H-768_A-12/bert_model.ckpt' vocabPath = mainPath + 'chinese_roberta_wwm_ext_L-12_H-768_A-12/vocab.txt'# 將詞表中的詞編號轉換為字典 tokenDict = {} with codecs.open(vocabPath, 'r', encoding='utf-8') as reader:for line in reader:token = line.strip()tokenDict[token] = len(tokenDict)# 重寫tokenizer class OurTokenizer(Tokenizer):def _tokenize(self, content):reList = []for t in content:if t in self._token_dict:reList.append(t)elif self._is_space(t):# 用[unused1]來表示空格類字符reList.append('[unused1]')else:# 不在列表的字符用[UNK]表示reList.append('[UNK]')return reListtokenizer = OurTokenizer(tokenDict)def seqPadding(X, padding=0):L = [len(x) for x in X]ML = max(L)return np.array([np.concatenate([x, [padding] * (ML - len(x))]) if len(x) < ML else x for x in X])class data_generator: #先將數據變成元組的形式在喂入生成器def __init__(self, data, batch_size=32, shuffle=True):self.data = dataself.batch_size = batch_sizeself.shuffle = shuffleself.steps = len(self.data) // self.batch_sizeif len(self.data) % self.batch_size != 0:self.steps += 1def __len__(self):return self.stepsdef __iter__(self):while True:idxs = list(range(len(self.data)))if self.shuffle:np.random.shuffle(idxs)X1, X2, Y = [], [], []for i in idxs:d = self.data[i]text = d[0][:maxlen]x1, x2 = tokenizer.encode(first=text)y = d[1]X1.append(x1)X2.append(x2)Y.append([y])if len(X1) == self.batch_size or i == idxs[-1]:X1 = seqPadding(X1)X2 = seqPadding(X2)Y = seqPadding(Y)yield [X1, X2], Y[X1, X2, Y] = [], [], []# bert模型設置 bert_model = load_trained_model_from_checkpoint(configPath, ckpPath, seq_len=None) # 加載預訓練模型for l in bert_model.layers:l.trainable = Truex1_in = Input(shape=(None,)) x2_in = Input(shape=(None,))x = bert_model([x1_in, x2_in])# 取出[CLS]對應的向量用來做分類 x = Lambda(lambda x: x[:, 0])(x) p = Dense(15, activation='softmax')(x)model = Model([x1_in, x2_in], p) model.compile(loss='sparse_categorical_crossentropy', optimizer=Adam(1e-5), metrics=['accuracy']) model.summary()train_D = data_generator(train_data) valid_D = data_generator(valid_data)model.fit_generator(train_D.__iter__(), steps_per_epoch=len(train_D), epochs=5, validation_data=valid_D.__iter__(),validation_steps=len(valid_D))model.save(mainPath + 'model/keras_class/tnews.h5', True, True)# 保存模型結構圖 plot_model(model, to_file='model/keras_class/tnews.png', show_shapes=True)參考鏈接
https://blog.csdn.net/qq_39290990/article/details/121672141
總結
以上是生活随笔為你收集整理的bert 文本分类实战的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 橡胶耐臭氧龟裂的静态拉伸试验,你知道吗?
- 下一篇: 【初学者OpenMV】 02 OpenM