最近有學習關於文本分類的深度學習模型,最先接觸的就是TextCNN模型,該模型看起來非常簡單效果也非常好,在此簡單記錄下整個模型的搭建以及訓練過程。通過本博文,你可以自己搭建並訓練一個簡單的文本分類模型,本文的代碼註釋非常詳細。
使用的開發環境:python3(Anaconda管理)、Tensorflow1.13.1
本文主要分爲以下幾個部分進行展開講解:
(1)TextCNN原理
(2)模型的搭建
(3)訓練數據的準備
(4)模型的訓練
(5)知識點補充
TextCNN原理
如圖所示,展示了整個textcnn的模型結構,主要流程可分爲以下幾步:
1)將中文文本通過embedding層轉換爲詞向量,圖中詞向量以三維爲例(例如“我”對應的是[0.2, 0.1, 0.3]詞向量),在本文代碼中採用的是64維。(本圖中省略了將中文文本轉換爲對應詞id的過程)
2)通過不同的滑動窗口進行卷積處理,圖中以滑動窗口分別爲2、3、4爲例,並且每種滑動窗口的卷積核個數爲2,實際使用過程中每種滑動窗口的卷積核個數可自己設定,本文代碼中採用的是64。
3)對卷積操作生成的特徵矩陣使用最大池化處理。
4)將池化後的特徵矩陣進行拼接。
5)將特徵矩陣進行扁平化或壓縮維度,圖中未繪製。
6)連接全連接層1
7)連接全連接層2,通過activation=softmax輸出每個類別的概率
網絡模型的搭建
在模型的搭建之前,我們先建立一個python項目,目錄結構如圖所示:
+testCnnProject
+cnews
-cnews.test.txt
-cnews.train.txt
-cnews.val.txt
-cnews.vocab.txt
+log
-main.py
-model.py
-dataGenerator.py
其中的cnews文件夾先不用管在後面的數據準備中會進行講解,log文件夾是用來存放我們訓練過程中生成的tensorboard文件以及模型的權重,model.py是用來存放textcnn模型的文件,dataGenerator.py使用來生成數據的文件,main.py是該項目的入口用於調用訓練模型。
下面我們開始在model.py文件中編寫textcnn模型文件。
該模型使用的是tensorflow包中自帶的keras模塊,無需另外安裝keras包,代碼中有詳細的註釋,很好理解:
model.py
from tensorflow import keras
def text_cnn(seq_length, vocab_size, embedding_dim, num_cla, kernel_num):
"""
seq_length: 輸入的文字序列長度
vocab_size: 詞彙庫的大小
embedding_dim: 生成詞向量的特徵維度
num_cla: 分類類別
kernel_num::卷積層的卷積核數
"""
# 定義輸入層
inputX = keras.layers.Input(shape=(seq_length,), dtype='int32')
# 嵌入層,將詞彙的one-hot編碼轉爲詞向量
embOut = keras.layers.Embedding(vocab_size, embedding_dim, input_length=seq_length)(inputX)
# 分別使用長度爲3,4,5的詞窗口去執行卷積, 接着進行最大池化處理
conv1 = keras.layers.Conv1D(kernel_num, 3, padding='valid', strides=1, activation='relu')(embOut)
maxp1 = keras.layers.MaxPool1D(pool_size=int(conv1.shape[1]))(conv1)
conv2 = keras.layers.Conv1D(kernel_num, 4, padding='valid', strides=1, activation='relu')(embOut)
maxp2 = keras.layers.MaxPool1D(pool_size=int(conv2.shape[1]))(conv2)
conv3 = keras.layers.Conv1D(kernel_num, 5, padding='valid', strides=1, activation='relu')(embOut)
maxp3 = keras.layers.MaxPool1D(pool_size=int(conv3.shape[1]))(conv3)
# 合併三個經過卷積和池化後的輸出向量
combineCnn = keras.layers.Concatenate(axis=-1)([maxp1, maxp2, maxp3])
# 扁平化
flatCnn = keras.layers.Flatten()(combineCnn)
# 全連接層1,節點數爲128
desen1 = keras.layers.Dense(128)(flatCnn)
# 在全連接層1和2之間添加dropout減少訓練過程中的過擬合,隨機丟棄25%的結點值
dropout = keras.layers.Dropout(0.25)(desen1)
# 爲全連接層添加激活函數
densen1Relu = keras.layers.ReLU()(dropout)
# 全連接層2(輸出層)
predictY = keras.layers.Dense(num_cla, activation='sofmax')(densen1Relu)
# 指定模型的輸入輸出層
model = keras.models.Model(inputs=inputX, ouputs=predictY)
# 指定loss的計算方法,設置優化器,編譯模型
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
return model
訓練文件的準備
簡單介紹下數據集,數據集包含十個類別,每個文本對應一個一類。網上找的,具體來源不詳。
網盤地址:https://pan.baidu.com/s/1w452Z5eXbQSDQfgEBNUdlg ,提取密碼:8cwv
該數據集大概有66M,其中有4個文件:
cnews.train.txt (包含50000個文本,每行代表一個文本,最前面是該文本對應的標籤,標籤與文本之間用製表符隔開)
cnews.test.txt (包含10000個測試文本,格式與trian相同)
cnews.eval.txt (包含5000個驗證文本,格式與train相同)
cnews.vocab.txt (包含一個分詞詞典,其實就是一個字典,並沒有進行分詞處理)
下載好後按照之前講的文件結構放好文件,接着在dataGenerator.py文件中編寫用於生成數據的代碼:
dataGenerator.py
from tensorflow import keras
from sklearn.preprocessing import LabelEncoder
import random
def content2idList(content, word2id_dict):
"""
該函數的目的是將文本轉換爲對應的漢字數字id
content:輸入的文本
word2id_dict:用於查找轉換的字典
"""
idList = []
for word in content: # 遍歷每一個漢字
if word in word2id_dict: # 當剛文字在字典中時才進行轉換,否則丟棄
idList.append(word2id_dict[word])
return idList
def generatorInfo(batch_size, seq_length, num_classes, file_name):
"""
batch_size:生成數據的batch size
seq_length:輸入文字序列長度
num_classes:文本的類別數
file_name:讀取文件的路徑
"""
# 讀取詞庫文件
with open('./cnews/cnews.vocab.txt', encoding='utf-8') as file:
vocabulary_list = [k.strip() for k in file.readlines()]
word2id_dict = dict([(b, a) for a, b in enumerate(vocabulary_list)])
# 讀取文本文件
with open(file_name, encoding='utf-8') as file:
line_list = [k.strip() for k in file.readlines()]
data_label_list = [] # 創建數據標籤文件
data_content_list = [] # 創建數據文本文件
for k in line_list:
t = k.split(maxsplit=1)
data_label_list.append(t[0])
data_content_list.append(t[1])
data_id_list = [content2idList(content, word2id_dict) for content in data_content_list] # 將文本數據轉換拿爲數字序列
# 將list數據類型轉換爲ndarray數據類型,並按照seq_length長度去統一化文本序列長度,
# 若長度超過設定值將其截斷保留後半部分,若長度不足前面補0
data_X = keras.preprocessing.sequence.pad_sequences(data_id_list, seq_length, truncating='pre')
labelEncoder = LabelEncoder()
data_y = labelEncoder.fit_transform(data_label_list) # 將文字標籤轉爲數字標籤
data_Y = keras.utils.to_categorical(data_y, num_classes) # 將數字標籤轉爲one-hot標籤
while True:
selected_index = random.sample(list(range(len(data_y))), k=batch_size) # 按照數據集合的長度隨機抽取batch_size個數據的index
batch_X = data_X[selected_index] # 隨機抽取的文本信息(數字化序列)
batch_Y = data_Y[selected_index] # 隨機抽取的標籤信息(one-hot編碼)
yield (batch_X, batch_Y)
網絡模型的訓練
現在我們的訓練數據已經準備到位,模型也已經搭建完成,接着我們開始訓練模型:
main.py
from model import text_cnn
from dataGenerator import generatorInfo
from tensorflow import keras
vocab_size = 5000 # 詞彙庫大小
seq_length = 600 # 輸入文本序列長度
embedding_dim = 64 # embedding層輸出詞向量維度
num_classes = 10 # 分類類別
kernel_num=64 # 卷積核數
trianBatchSize = 64 # 訓練時的batch size
evalBatchSize = 200 # 驗證時的batch size
steps_per_epoch = 50000 // trianBatchSize # 一個epoch對應的訓練步數
epoch = 2 # 訓練的epoch數
logdir = './log' # tensorbard訓練信息以及train_weights的保存位置
trainFileName = './cnews/cnews.train.txt' # 訓練文件的路徑
evalFileName = './cnews/cnews.test.txt' # 驗證文件的路徑
model = text_cnn(seq_length=seq_length, # 初始化模型
vocab_size=vocab_size,
embedding_dim=embedding_dim,
num_cla=num_classes,
kernel_num=kernel_num)
trainGenerator = generatorInfo(trianBatchSize, seq_length, num_classes, trainFileName)
evalGenerator = generatorInfo(evalBatchSize, seq_length, num_classes, evalFileName)
def lrSchedule(epoch): # 自定義學習率變化
lr = keras.backend.get_value(model.optimizer.lr)
if epoch % 1 == 0 and epoch != 0:
lr = lr * 0.5
return lr
log = keras.callbacks.TensorBoard(log_dir=logdir, update_freq='epoch') # 調用tensorboard
reduceLr = keras.callbacks.LearningRateScheduler(lrSchedule, verbose=1) # 調用自定義學習率函數
model.fit_generator(generator=trainGenerator,
steps_per_epoch=steps_per_epoch,
epochs=epoch,
validation_data=evalGenerator,
validation_steps=10,
callbacks=[log, reduceLr])
model.save_weights(logdir + 'train_weights.h5')
知識點補充
首先說下我個人理解的文本分類整個完整流程:
(1)使用類似jieba的中文分詞庫對整個訓練集進行中文分詞
(2)統計詞彙的出現評率,刪除部分低頻詞
(3)根據禁用詞庫濾除禁用詞
(4)根據生成的詞庫對訓練集進行分詞處理,只保留詞庫中已有的詞彙,詞彙之間用空格隔開
(5)分詞後使用word2vec訓練詞向量模型(該步驟是可選項,可以不做)
(6)搭建文本訓練模型,例如TextCNN(若進行了第五步操作,使用訓練好的詞向量weights初始化embdding層參數)
(7)訓練模型,調參有更高的需求的可以改進模型