2018年10月11日筆記
tensorflow是谷歌google的深度學習架構,tensor中文叫做張量,flow叫做流。
RNN是recurrent neural network的簡稱,中文叫做循環神經網絡。
文本分類是NLP(自然語言處理)的經典任務。
0.程式設計環境
作業系統:Win10
tensorflow版本:1.6
tensorboard版本:1.6
python版本:3.6
1.緻謝聲明
本文是作者學習《使用卷積神經網絡以及循環神經網絡進行中文文本分類》的成果,感激前輩;
github連結:https://github.com/gaussic/text-classification-cnn-rnn
2.配置環境
使用循環神經網絡模型要求有較高的機器配置,如果使用CPU版tensorflow會花費大量時間。
讀者在有nvidia顯示卡的情況下,安裝GPU版tensorflow會提高計算速度。
安裝教程連結:https://blog.csdn.net/qq_36556893/article/details/79433298
如果沒有nvidia顯示卡,但有visa信用卡,請閱讀我的另一篇文章《在谷歌雲伺服器上搭建深度學習平台》,連結:https://www.jianshu.com/p/893d622d1b5a
3.下載下傳并解壓資料集
資料集下載下傳連結: https://pan.baidu.com/s/1oLZZF4AHT5X_bzNl2aF2aQ 提取碼: 5sea
下載下傳壓縮檔案cnews.zip完成後,選擇解壓到cnews,如下圖所示:

image.png
檔案夾結構如下圖所示:
image.png
cnew檔案夾中有4個檔案:
1.訓練集檔案cnews.train.txt
2.測試集檔案cnew.test.txt
3.驗證集檔案cnews.val.txt
4.詞彙表檔案cnews.vocab.txt
共有10個類别,65000個樣本資料,其中訓練集50000條,測試集10000條,驗證集5000條。
4.完整代碼
代碼檔案需要放到和cnews檔案夾同級目錄。
給讀者提供完整代碼,旨在讀者能夠直接運作代碼,有直覺的感性認識。
如果要了解其中代碼的細節,請閱讀後面的章節。
import warnings
warnings.filterwarnings('ignore')
import time
startTime = time.time()
def printUsedTime():
used_time = time.time() - startTime
print('used time: %.2f seconds' %used_time)
with open('./cnews/cnews.train.txt', encoding='utf8') as file:
line_list = [k.strip() for k in file.readlines()]
train_label_list = [k.split()[0] for k in line_list]
train_content_list = [k.split(maxsplit=1)[1] for k in line_list]
with open('./cnews/cnews.vocab.txt', encoding='utf8') as file:
vocabulary_list = [k.strip() for k in file.readlines()]
print('0.load train data finished')
printUsedTime()
word2id_dict = dict([(b, a) for a, b in enumerate(vocabulary_list)])
content2idList = lambda content : [word2id_dict[word] for word in content if word in word2id_dict]
train_idlist_list = [content2idList(content) for content in train_content_list]
vocabolary_size = 5000 # 詞彙表達小
sequence_length = 150 # 序列長度
embedding_size = 64 # 詞向量大小
num_hidden_units = 256 # LSTM細胞隐藏層大小
num_fc1_units = 64 #第1個全連接配接下一層的大小
dropout_keep_probability = 0.5 # dropout保留比例
num_classes = 10 # 類别數量
learning_rate = 1e-3 # 學習率
batch_size = 64 # 每批訓練大小
import tensorflow.contrib.keras as kr
train_X = kr.preprocessing.sequence.pad_sequences(train_idlist_list, sequence_length)
from sklearn.preprocessing import LabelEncoder
labelEncoder = LabelEncoder()
train_y = labelEncoder.fit_transform(train_label_list)
train_Y = kr.utils.to_categorical(train_y, num_classes)
import tensorflow as tf
tf.reset_default_graph()
X_holder = tf.placeholder(tf.int32, [None, sequence_length])
Y_holder = tf.placeholder(tf.float32, [None, num_classes])
print('1.data preparation finished')
printUsedTime()
embedding = tf.get_variable('embedding',
[vocabolary_size, embedding_size])
embedding_inputs = tf.nn.embedding_lookup(embedding,
X_holder)
gru_cell = tf.contrib.rnn.GRUCell(num_hidden_units)
outputs, state = tf.nn.dynamic_rnn(gru_cell,
embedding_inputs,
dtype=tf.float32)
last_cell = outputs[:, -1, :]
full_connect1 = tf.layers.dense(last_cell,
num_fc1_units)
full_connect1_dropout = tf.contrib.layers.dropout(full_connect1,
dropout_keep_probability)
full_connect1_activate = tf.nn.relu(full_connect1_dropout)
full_connect2 = tf.layers.dense(full_connect1_activate,
num_classes)
predict_Y = tf.nn.softmax(full_connect2)
cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(labels=Y_holder,
logits=full_connect2)
loss = tf.reduce_mean(cross_entropy)
optimizer = tf.train.AdamOptimizer(learning_rate)
train = optimizer.minimize(loss)
isCorrect = tf.equal(tf.argmax(Y_holder,1), tf.argmax(predict_Y, 1))
accuracy = tf.reduce_mean(tf.cast(isCorrect, tf.float32))
print('2.build model finished')
printUsedTime()
init = tf.global_variables_initializer()
session = tf.Session()
session.run(init)
print('3.initialize variable finished')
printUsedTime()
with open('./cnews/cnews.test.txt', encoding='utf8') as file:
line_list = [k.strip() for k in file.readlines()]
test_label_list = [k.split()[0] for k in line_list]
test_content_list = [k.split(maxsplit=1)[1] for k in line_list]
test_idlist_list = [content2idList(content) for content in test_content_list]
test_X = kr.preprocessing.sequence.pad_sequences(test_idlist_list, sequence_length)
test_y = labelEncoder.transform(test_label_list)
test_Y = kr.utils.to_categorical(test_y, num_classes)
print('4.load test data finished')
printUsedTime()
print('5.begin model training')
import random
for i in range(5000):
selected_index = random.sample(list(range(len(train_y))), k=batch_size)
batch_X = train_X[selected_index]
batch_Y = train_Y[selected_index]
session.run(train, {X_holder:batch_X, Y_holder:batch_Y})
step = i + 1
if step % 100 == 0:
selected_index = random.sample(list(range(len(test_y))), k=200)
batch_X = test_X[selected_index]
batch_Y = test_Y[selected_index]
loss_value, accuracy_value = session.run([loss, accuracy], {X_holder:batch_X, Y_holder:batch_Y})
print('step:%d loss:%.4f accuracy:%.4f' %(step, loss_value, accuracy_value))
printUsedTime()
複制
上面一段代碼的運作結果如下(隻截取前1000次疊代):
0.load train data finished
used time: 1.17 seconds
1.data preparation finished
used time: 15.78 seconds
2.building model finished
used time: 16.90 seconds
3.initialize variable finished
used time: 17.00 seconds
4.load test data finished
used time: 20.29 seconds
5.begin model training
step:100 loss:1.6444 accuracy:0.3800
used time: 41.93 seconds
step:200 loss:1.1358 accuracy:0.5750
used time: 64.20 seconds
step:300 loss:1.1324 accuracy:0.5950
used time: 86.21 seconds
step:400 loss:0.8088 accuracy:0.7300
used time: 108.22 seconds
step:500 loss:0.6240 accuracy:0.8200
used time: 130.22 seconds
step:600 loss:0.6347 accuracy:0.8000
used time: 152.16 seconds
step:700 loss:0.7305 accuracy:0.7900
used time: 174.48 seconds
step:800 loss:0.5374 accuracy:0.8650
used time: 196.56 seconds
step:900 loss:0.5020 accuracy:0.8650
used time: 218.81 seconds
step:1000 loss:0.5872 accuracy:0.8150
used time: 241.07 seconds
5.資料準備
讀者閱讀下文中的行數時,可以先把代碼複制到jupyter notebook的代碼塊中。
在代碼塊中按Esc鍵,進入指令模式,代碼塊左邊的豎線會顯示藍色,如下圖所示:
image.png
在指令模式下,點選L鍵,會顯示代碼行數。
本文作者解釋每行代碼含義如下:
第1行代碼導入warnings庫;
第2行代碼中ignore中文叫做忽略,即不列印警告資訊;
第3行代碼導入time庫;
第4行代碼把程式開始時間指派給變量startTime;
第5-7行代碼定義printUsedTime函數,作用是列印程式運作時間;
第8行代碼調用open方法打開文本檔案;
第9行代碼使用清單推導式得到文本檔案中的行内容清單指派給變量label_list;
第10行代碼得到訓練集的标簽清單指派給變量train_label_list;
第11行代碼得到訓練集的内容清單指派給變量train_content_list。
第12-13行代碼得到詞彙表檔案cnews.vocab.txt中的詞彙清單指派給變量vocabulary_list;
第14行代碼列印提示資訊0.load train data finished,表示加載訓練集資料完成;
第15行代碼列印程式運作至此步使用的時間;
第16行代碼使用清單推導式得到詞彙及其id對應的清單,并調用dict方法将清單強制轉換為字典。
列印變量word2id_dict的前5項,如下圖所示:
image.png
第17行代碼使用清單推導式和匿名函數定義函數content2idlist,函數作用是将文章中的每個字轉換為id;
第18行代碼使用清單推導式得到的結果是清單的清單,總清單train_idlist_list中的元素是每篇文章中的字對應的id清單;
第19-27這9行代碼設定卷積神經網絡的超參數;
第28-33這6行代碼獲得能夠用于模型訓練的特征矩陣和預測目标值;
第28行代碼導入tensorflow.contrib.keras庫,取别名kr;
第29行代碼将每個樣本統一長度為seq_length,即600;
第30行代碼導入sklearn.preprocessing庫的labelEncoder方法;
第31行代碼執行個體化LabelEncoder對象;
第32行代碼調用LabelEncoder對象的fit_transform方法做标簽編碼;
第33行代碼調用keras.untils庫的to_categorical方法将标簽編碼的結果再做Ont-Hot編碼。
第34行代碼導入tensorflow庫,取别名tf;
第35行代碼重置tensorflow圖,加強代碼的健壯性;
第36-37行代碼中placeholder中文叫做占位符,将每次訓練的特征矩陣X和預測目标值Y指派給變量X_holder和Y_holder;
第38行代碼列印提示資訊1.data preparation finished,表示資料準備完成
第39行代碼列印程式運作至此步使用的時間;
import warnings
warnings.filterwarnings('ignore')
import time
startTime = time.time()
def printUsedTime():
used_time = time.time() - startTime
print('used time: %.2f seconds' %used_time)
with open('./cnews/cnews.train.txt', encoding='utf8') as file:
line_list = [k.strip() for k in file.readlines()]
train_label_list = [k.split()[0] for k in line_list]
train_content_list = [k.split(maxsplit=1)[1] for k in line_list]
with open('./cnews/cnews.vocab.txt', encoding='utf8') as file:
vocabulary_list = [k.strip() for k in file.readlines()]
print('0.load train data finished')
printUsedTime()
word2id_dict = dict([(b, a) for a, b in enumerate(vocabulary_list)])
content2idList = lambda content : [word2id_dict[word] for word in content if word in word2id_dict]
train_idlist_list = [content2idList(content) for content in train_content_list]
vocabolary_size = 5000 # 詞彙表達小
sequence_length = 150 # 序列長度
embedding_size = 64 # 詞向量大小
num_hidden_units = 256 # LSTM細胞隐藏層大小
num_fc1_units = 64 #第1個全連接配接下一層的大小
dropout_keep_probability = 0.5 # dropout保留比例
num_classes = 10 # 類别數量
learning_rate = 1e-3 # 學習率
batch_size = num_classes * 5 # 每批訓練大小
import tensorflow.contrib.keras as kr
train_X = kr.preprocessing.sequence.pad_sequences(train_idlist_list, sequence_length)
from sklearn.preprocessing import LabelEncoder
labelEncoder = LabelEncoder()
train_y = labelEncoder.fit_transform(train_label_list)
train_Y = kr.utils.to_categorical(train_y, num_classes)
import tensorflow as tf
tf.reset_default_graph()
X_holder = tf.placeholder(tf.int32, [None, sequence_length])
Y_holder = tf.placeholder(tf.float32, [None, num_classes])
print('1.data preparation finished')
printUsedTime()
複制
6.搭建神經網絡
第1-2行代碼調用tf庫的get_variable方法執行個體化可以更新的模型參數embedding,矩陣形狀為
vocabulary_size*embedding_size
,即
5000*64
;
第3-4行代碼調用tf.nn庫的embedding_lookup方法将輸入資料做詞嵌入,得到新變量embedding_inputs的形狀為
batch_size*sequence_length*embedding_size
,即
50*100*64
;
了解word2vec原理,推薦閱讀文章連結:https://www.jianshu.com/p/471d9bfbd72f
第5行代碼調用tf.contrib.rnn.GRUCell方法執行個體化GRU細胞對象;
第6-8行代碼調用tf.nn.dynamic_rnn方法動态計算循環神經網絡中的結果,outputs是每個細胞的h的結果,state是最後一個細胞的h和c的結果,LSTM網絡中h是短時記憶矩陣,c是長時記憶矩陣,想要了解c和h,請自行查找和學習LSTM理論;
第9行代碼擷取最後一個細胞的h,即最後一個細胞的短時記憶矩陣,等價于
state.h
;
第10-11行代碼添加全連接配接層,tf.layers.dense方法結果指派給變量full_connect1,形狀為
batch_size*num_fc1_units
,即
50*128
;
第12-13行代碼調用tf.contrib.layers.dropout方法,方法需要2個參數,第1個參數是輸入資料,第2個參數是保留比例;
第14行代碼調用tf.nn.relu方法,即激活函數;
第15-16行代碼添加全連接配接層,tf.layers.dense方法結果指派給變量full_connect2,形狀為
batch_size*num_classes
,即
50*10
;
第17行代碼調用tf.nn.softmax方法,方法結果是預測機率值;
第18-20行代碼使用交叉熵作為損失函數;
第21行代碼調用tf.train.AdamOptimizer方法定義優化器optimizer;
第22行代碼調用優化器對象的minimize方法,即最小化損失;
第23-24行代碼計算預測準确率;
第25行代碼列印提示資訊2.build model finished,表示搭建神經網絡完成;
第26行代碼列印程式運作至此步使用的時間;
embedding = tf.get_variable('embedding',
[vocabolary_size, embedding_size])
embedding_inputs = tf.nn.embedding_lookup(embedding,
X_holder)
gru_cell = tf.contrib.rnn.GRUCell(num_hidden_units)
outputs, state = tf.nn.dynamic_rnn(gru_cell,
embedding_inputs,
dtype=tf.float32)
last_cell = outputs[:, -1, :]
full_connect1 = tf.layers.dense(last_cell,
num_fc1_units)
full_connect1_dropout = tf.contrib.layers.dropout(full_connect1,
dropout_keep_probability)
full_connect1_activate = tf.nn.relu(full_connect1_dropout)
full_connect2 = tf.layers.dense(full_connect1_activate,
num_classes)
predict_Y = tf.nn.softmax(full_connect2)
cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(labels=Y_holder,
logits=full_connect2)
loss = tf.reduce_mean(cross_entropy)
optimizer = tf.train.AdamOptimizer(learning_rate)
train = optimizer.minimize(loss)
isCorrect = tf.equal(tf.argmax(Y_holder,1), tf.argmax(predict_Y, 1))
accuracy = tf.reduce_mean(tf.cast(isCorrect, tf.float32))
print('2.build model finished')
printUsedTime()
複制
7.參數初始化
對于神經網絡模型,重要是其中的W、b這兩個參數。
開始神經網絡模型訓練之前,這兩個變量需要初始化。
第1行代碼調用tf.global_variables_initializer執行個體化tensorflow中的Operation對象。
image.png
第2行代碼調用tf.Session方法執行個體化會話對象;
第3行代碼調用tf.Session對象的run方法做變量初始化。
第4行代碼列印提示資訊3.initialize variable finished,表示參數初始化完成;
第5行代碼列印程式運作至此步使用的時間;
init = tf.global_variables_initializer()
session = tf.Session()
session.run(init)
print('3.initialize variable finished')
printUsedTime()
複制
8.模型訓練
第1-8行代碼擷取文本檔案cnews.test.txt,即測試集中的資料;
第9行代碼列印提示資訊4.load test data finished,表示加載測試集資料完成;
第10行代碼列印程式運作至此步使用的時間;
第11行代碼列印提示資訊5.begin model training,表示開始模型訓練;
第12行代碼導入random庫;
第13行表示模型疊代訓練5000次;
第14-16行代碼從訓練集中選取batch_size大小,即50個樣本做批量梯度下降;
第17行代碼每運作1次,表示模型訓練1次;
第18行代碼記錄目前步數,指派給變量step;
第19行代碼表示每間隔100步列印;
第20-22行代碼從測試集中随機選取200個樣本;
第23行代碼表示計算損失值loss_value、準确率accuracy_value;
第24行代碼表示列印步數step、損失值loss_value、準确率accuracy_value;
第25行代碼列印程式運作至此步使用的時間。
with open('./cnews/cnews.test.txt', encoding='utf8') as file:
line_list = [k.strip() for k in file.readlines()]
test_label_list = [k.split()[0] for k in line_list]
test_content_list = [k.split(maxsplit=1)[1] for k in line_list]
test_idlist_list = [content2idList(content) for content in test_content_list]
test_X = kr.preprocessing.sequence.pad_sequences(test_idlist_list, sequence_length)
test_y = labelEncoder.transform(test_label_list)
test_Y = kr.utils.to_categorical(test_y, num_classes)
print('4.load test data finished')
printUsedTime()
print('5.begin model training')
import random
for i in range(5000):
selected_index = random.sample(list(range(len(train_y))), k=batch_size)
batch_X = train_X[selected_index]
batch_Y = train_Y[selected_index]
session.run(train, {X_holder:batch_X, Y_holder:batch_Y})
step = i + 1
if step % 100 == 0:
selected_index = random.sample(list(range(len(test_y))), k=200)
batch_X = test_X[selected_index]
batch_Y = test_Y[selected_index]
loss_value, accuracy_value = session.run([loss, accuracy], {X_holder:batch_X, Y_holder:batch_Y})
print('step:%d loss:%.4f accuracy:%.4f' %(step, loss_value, accuracy_value))
printUsedTime()
複制
9.詞彙表
經過前文5-8章的講解,已經完成循環神經網絡的訓練。
本項目提供詞彙表檔案cnews.vocab.txt,但在實踐中需要自己統計語料的詞彙表。
下面代碼可以由内容清單content_list産生詞彙表:
from collections import Counter
def getVocabularyList(content_list, vocabulary_size):
allContent_str = ''.join(content_list)
counter = Counter(allContent_str)
vocabulary_list = [k[0] for k in counter.most_common(vocabulary_size)]
return vocabulary_list
def makeVocabularyFile(content_list, vocabulary_size):
vocabulary_list = getVocabularyList(content_list, vocabulary_size)
with open('vocabulary.txt', 'w', encoding='utf8') as file:
for vocabulary in vocabulary_list:
file.write(vocabulary + '\n')
makeVocabularyFile(train_content_list, 5000)
複制
本段代碼産生的檔案,與提供的詞彙表檔案cnews.vocab.txt稍有不同。
造成原因有2點:
1.詞彙表檔案的第1個字
<PAD>
是補全字,無實際含義,與
kr.preprocessing.sequence.pad_sequences
方法補全的0對應;
2.源代碼作者使用了訓練集、驗證集、測試集作為總語料庫,上面一段代碼隻使用了訓練集作為語料庫。
10.混淆矩陣
import numpy as np
import pandas as pd
from sklearn.metrics import confusion_matrix
def predictAll(test_X, batch_size=100):
predict_value_list = []
for i in range(0, len(test_X), batch_size):
selected_X = test_X[i: i + batch_size]
predict_value = session.run(predict_Y, {X_holder:selected_X})
predict_value_list.extend(predict_value)
return np.array(predict_value_list)
Y = predictAll(test_X)
y = np.argmax(Y, axis=1)
predict_label_list = labelEncoder.inverse_transform(y)
pd.DataFrame(confusion_matrix(test_label_list, predict_label_list),
columns=labelEncoder.classes_,
index=labelEncoder.classes_ )
複制
上面一段代碼的運作結果如下圖所示:
image.png
從上圖的結果可以看出,家居類新聞分類效果較差。
11.報告表
下面一段代碼能夠成功運作的前提是已經運作第10章代碼。
import numpy as np
from sklearn.metrics import precision_recall_fscore_support
def eval_model(y_true, y_pred, labels):
# 計算每個分類的Precision, Recall, f1, support
p, r, f1, s = precision_recall_fscore_support(y_true, y_pred)
# 計算總體的平均Precision, Recall, f1, support
tot_p = np.average(p, weights=s)
tot_r = np.average(r, weights=s)
tot_f1 = np.average(f1, weights=s)
tot_s = np.sum(s)
res1 = pd.DataFrame({
u'Label': labels,
u'Precision': p,
u'Recall': r,
u'F1': f1,
u'Support': s
})
res2 = pd.DataFrame({
u'Label': ['總體'],
u'Precision': [tot_p],
u'Recall': [tot_r],
u'F1': [tot_f1],
u'Support': [tot_s]
})
res2.index = [999]
res = pd.concat([res1, res2])
return res[['Label', 'Precision', 'Recall', 'F1', 'Support']]
eval_model(test_label_list, predict_label_list, labelEncoder.classes_)
複制
上面一段代碼的運作結果如下圖所示:
image.png
12.總結
1.本文是作者第5個NLP小型項目,資料共有65000條。
2.分類模型的評估名額F1score為0.89左右,總體來說這個分類模型比CNN模型效果差,而且訓練時間更久;
3.本文為了節省讀者的實驗時間,設定sequence_length為150,疊代5000次總共花費1123秒,即18分43秒;
4.如果設定sequence_length為300,疊代5000次總共花費時間2184秒,即36分24秒,評估名額F1score為0.9282,如下圖所示:
image.png