天天看點

基于tensorflow+RNN的新浪新聞文本分類

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,如下圖所示:

基于tensorflow+RNN的新浪新聞文本分類

image.png

檔案夾結構如下圖所示:

基于tensorflow+RNN的新浪新聞文本分類

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鍵,進入指令模式,代碼塊左邊的豎線會顯示藍色,如下圖所示:

基于tensorflow+RNN的新浪新聞文本分類

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項,如下圖所示:

基于tensorflow+RNN的新浪新聞文本分類

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對象。

基于tensorflow+RNN的新浪新聞文本分類

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_ )           

複制

上面一段代碼的運作結果如下圖所示:

基于tensorflow+RNN的新浪新聞文本分類

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_)           

複制

上面一段代碼的運作結果如下圖所示:

基于tensorflow+RNN的新浪新聞文本分類

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,如下圖所示:

基于tensorflow+RNN的新浪新聞文本分類

image.png