本文實(shí)例講述了Python使用循環(huán)神經(jīng)網(wǎng)絡(luò)解決文本分類問(wèn)題的方法。分享給大家供大家參考,具體如下:
1、概念
1.1、循環(huán)神經(jīng)網(wǎng)絡(luò)
循環(huán)神經(jīng)網(wǎng)絡(luò)(Recurrent Neural Network, RNN)是一類以序列數(shù)據(jù)為輸入,在序列的演進(jìn)方向進(jìn)行遞歸且所有節(jié)點(diǎn)(循環(huán)單元)按鏈?zhǔn)竭B接的遞歸神經(jīng)網(wǎng)絡(luò)。
卷積網(wǎng)絡(luò)的輸入只有輸入數(shù)據(jù)X,而循環(huán)神經(jīng)網(wǎng)絡(luò)除了輸入數(shù)據(jù)X之外,每一步的輸出會(huì)作為下一步的輸入,如此循環(huán),并且每一次采用相同的激活函數(shù)和參數(shù)。在每次循環(huán)中,x0乘以系數(shù)U得到s0,再經(jīng)過(guò)系數(shù)W輸入到下一次,以此循環(huán)構(gòu)成循環(huán)神經(jīng)網(wǎng)絡(luò)的正向傳播。
在反向傳播中要求損失函數(shù)E對(duì)參數(shù)W的導(dǎo)數(shù),通過(guò)鏈?zhǔn)角髮?dǎo)法則可以得到右下的公式
循環(huán)神經(jīng)網(wǎng)絡(luò)與卷積神經(jīng)網(wǎng)絡(luò)作比較,卷積神經(jīng)網(wǎng)絡(luò)是一個(gè)輸出經(jīng)過(guò)網(wǎng)絡(luò)產(chǎn)生一個(gè)輸出。而循環(huán)神經(jīng)網(wǎng)絡(luò)可以實(shí)現(xiàn)一個(gè)輸入多個(gè)輸出(生成圖片描述)、多個(gè)輸入一個(gè)輸出(文本分類)、多輸入多輸出(機(jī)器翻譯、視頻解說(shuō))。
RNN使用的是tan激活函數(shù),輸出在-1到1之間,容易梯度消失。距離輸出較遠(yuǎn)的步驟對(duì)于梯度貢獻(xiàn)很小。
將底層的輸出作為高層的輸入就構(gòu)成了多層的RNN網(wǎng)絡(luò),而且高層之間也可以進(jìn)行傳遞,并且可以采用殘差連接防止過(guò)擬合。
1.2、長(zhǎng)短期記憶網(wǎng)絡(luò)
RNN的每次傳播之間只有一個(gè)參數(shù)W,用這一個(gè)參數(shù)很難描述大量的、復(fù)雜的信息需求,為了解決這個(gè)問(wèn)題引入了長(zhǎng)短期記憶網(wǎng)絡(luò)(Long Short Term Memory,LSTM)。這個(gè)網(wǎng)絡(luò)可以進(jìn)行選擇性機(jī)制,選擇性的輸入、輸出需要使用的信息以及選擇性地遺忘不需要的信息。選擇性機(jī)制的實(shí)現(xiàn)是通過(guò)Sigmoid門實(shí)現(xiàn)的,sigmoid函數(shù)的輸出介于0到1之間,0代表遺忘,1代表記憶,0.5代表記憶50%
LSTM網(wǎng)絡(luò)結(jié)構(gòu)如下圖所示,
如上右圖所示為本輪運(yùn)算的隱含狀態(tài)state,當(dāng)前狀態(tài)由上一狀態(tài)和遺忘門結(jié)果作點(diǎn)積,再加上傳入們結(jié)果得到
如下左圖所示為遺忘門結(jié)構(gòu),上一輪的輸出ht-1和數(shù)據(jù)xt在經(jīng)過(guò)遺忘門選擇是否遺忘之后,產(chǎn)生遺忘結(jié)果ft
如下中圖所示為傳入門結(jié)構(gòu),ht-1和xt在經(jīng)過(guò)遺忘門的結(jié)果it和tanh的結(jié)果Ct作點(diǎn)積運(yùn)算得到本次運(yùn)算的輸入
如下右圖所示為輸出門結(jié)構(gòu),ht-1和xt經(jīng)過(guò)遺忘門的結(jié)果ot與當(dāng)狀態(tài)作點(diǎn)積產(chǎn)生本次的輸出
如下實(shí)現(xiàn)LSTM網(wǎng)絡(luò),首先定義_generate_params函數(shù)用于生成每個(gè)門所需的參數(shù),調(diào)用該函數(shù)定義輸入門、輸出門、遺忘門、和中間狀態(tài)tanh的參數(shù)。每個(gè)門的參數(shù)都是三個(gè),輸入x、h的權(quán)重和偏置值。
接著開(kāi)始進(jìn)行LSTM的每輪循環(huán)計(jì)算,輸入門計(jì)算就是將輸入embedded_input矩陣乘以輸入門參數(shù)x_in,再加上h和對(duì)應(yīng)參數(shù)相乘的結(jié)果,最后再加上偏置值b_in經(jīng)過(guò)sigmoid便得到輸入門結(jié)果。
同理進(jìn)行矩陣相乘加偏置操作得到遺忘門、輸出門的結(jié)果。中間態(tài)tanh與三個(gè)門的操作類似,只不過(guò)最后經(jīng)過(guò)tanh函數(shù)。
將上一個(gè)隱含態(tài)state乘以遺忘門加上輸入門乘以中間態(tài)的結(jié)果就得到當(dāng)前的隱含態(tài)state
將當(dāng)前的state經(jīng)過(guò)tanh函數(shù)再加上輸出門就得到本輪的輸出h
經(jīng)過(guò)多輪輸入循環(huán)得到的就是LSTM網(wǎng)絡(luò)的最后輸出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
# 實(shí)現(xiàn)LSTM網(wǎng)絡(luò) # 生成Cell網(wǎng)格所需參數(shù) def _generate_paramas(x_size, h_size, b_size): x_w = tf.get_variable( 'x_weight' , x_size) h_w = tf.get_variable( 'h_weight' , h_size) bias = tf.get_variable( 'bias' , b_size, initializer = tf.constant_initializer( 0.0 )) return x_w, h_w, bias scale = 1.0 / math.sqrt(embedding_size + lstm_nodes[ - 1 ]) / 3.0 lstm_init = tf.random_uniform_initializer( - scale, scale) with tf.variable_scope( 'lstm_nn' , initializer = lstm_init): # 輸入門參數(shù) with tf.variable_scope( 'input' ): x_in, h_in, b_in = _generate_paramas( x_size = [embedding_size, lstm_nodes[ 0 ]], h_size = [lstm_nodes[ 0 ], lstm_nodes[ 0 ]], b_size = [ 1 , lstm_nodes[ 0 ]] ) # 輸出門參數(shù) with tf.variable_scope( 'output' ): x_out, h_out, b_out = _generate_paramas( x_size = [embedding_size, lstm_nodes[ 0 ]], h_size = [lstm_nodes[ 0 ], lstm_nodes[ 0 ]], b_size = [ 1 , lstm_nodes[ 0 ]] ) # 遺忘門參數(shù) with tf.variable_scope( 'forget' ): x_f, h_f, b_f = _generate_paramas( x_size = [embedding_size, lstm_nodes[ 0 ]], h_size = [lstm_nodes[ 0 ], lstm_nodes[ 0 ]], b_size = [ 1 , lstm_nodes[ 0 ]] ) # 中間狀態(tài)參數(shù) with tf.variable_scope( 'mid_state' ): x_m, h_m, b_m = _generate_paramas( x_size = [embedding_size, lstm_nodes[ 0 ]], h_size = [lstm_nodes[ 0 ], lstm_nodes[ 0 ]], b_size = [ 1 , lstm_nodes[ 0 ]] ) # 兩個(gè)初始化狀態(tài),隱含狀態(tài)state和初始輸入h state = tf.Variable(tf.zeros([batch_size, lstm_nodes[ 0 ]]), trainable = False ) h = tf.Variable(tf.zeros([batch_size, lstm_nodes[ 0 ]]), trainable = False ) # 遍歷LSTM每輪循環(huán),即每個(gè)詞的輸入過(guò)程 for i in range (max_words): # 取出每輪輸入,三維數(shù)組embedd_inputs的第二維代表訓(xùn)練的輪數(shù) embedded_input = embedded_inputs[:, i, :] # 將取出的結(jié)果reshape為二維 embedded_input = tf.reshape(embedded_input, [batch_size, embedding_size]) # 遺忘門計(jì)算 forget_gate = tf.sigmoid(tf.matmul(embedded_input, x_f) + tf.matmul(h, h_f) + b_f) # 輸入門計(jì)算 input_gate = tf.sigmoid(tf.matmul(embedded_input, x_in) + tf.matmul(h, h_in) + b_in) # 輸出門 output_gate = tf.sigmoid(tf.matmul(embedded_input, x_out) + tf.matmul(h, h_out) + b_out) # 中間狀態(tài) mid_state = tf.tanh(tf.matmul(embedded_input, x_m) + tf.matmul(h, h_m) + b_m) # 計(jì)算隱含狀態(tài)state和輸入h state = state * forget_gate + input_gate * mid_state h = output_gate + tf.tanh(state) # 最后遍歷的結(jié)果就是LSTM的輸出 last_output = h |
1.3、文本分類
文本分類問(wèn)題就是對(duì)輸入的文本字符串進(jìn)行分析判斷,之后再輸出結(jié)果。字符串無(wú)法直接輸入到RNN網(wǎng)絡(luò),因此在輸入之前需要先對(duì)文本拆分成單個(gè)詞組,將詞組進(jìn)行embedding編碼成一個(gè)向量,每輪輸入一個(gè)詞組,當(dāng)最后一個(gè)詞組輸入完畢時(shí)得到輸出結(jié)果也是一個(gè)向量。embedding將一個(gè)詞對(duì)應(yīng)為一個(gè)向量,向量的每一個(gè)維度對(duì)應(yīng)一個(gè)浮點(diǎn)值,動(dòng)態(tài)調(diào)整這些浮點(diǎn)值使得embedding編碼和詞的意思相關(guān)。這樣網(wǎng)絡(luò)的輸入輸出都是向量,再最后進(jìn)行全連接操作對(duì)應(yīng)到不同的分類即可。
RNN網(wǎng)絡(luò)不可避免地帶來(lái)問(wèn)題就是最后的輸出結(jié)果受最近的輸入較大,而之前較遠(yuǎn)的輸入可能無(wú)法影響結(jié)果,這就是信息瓶頸問(wèn)題,為了解決這個(gè)問(wèn)題引入了雙向LSTM。雙向LSTM不僅增加了反向信息傳播,而且每一輪的都會(huì)有一個(gè)輸出,將這些輸出進(jìn)行組合之后再傳給全連接層。
另一個(gè)文本分類模型就是HAN(Hierarchy Attention Network),首先將文本分為句子、詞語(yǔ)級(jí)別,將輸入的詞語(yǔ)進(jìn)行編碼然后相加得到句子的編碼,然后再將句子編碼相加得到最后的文本編碼。而attention是指在每一個(gè)級(jí)別的編碼進(jìn)行累加前,加入一個(gè)加權(quán)值,根據(jù)不同的權(quán)值對(duì)編碼進(jìn)行累加。
由于輸入的文本長(zhǎng)度不統(tǒng)一,所以無(wú)法直接使用神經(jīng)網(wǎng)絡(luò)進(jìn)行學(xué)習(xí),為了解決這個(gè)問(wèn)題,可以將輸入文本的長(zhǎng)度統(tǒng)一為一個(gè)最大值,勉強(qiáng)采用卷積神經(jīng)網(wǎng)絡(luò)進(jìn)行學(xué)習(xí),即TextCNN。文本卷積網(wǎng)絡(luò)的卷積過(guò)程采用的是多通道一維卷積,與二維卷積相比一維卷積就是卷積核只在一個(gè)方向上移動(dòng)。例如下左圖所示,1×1+5×2+2×2+4×3+3×3+3×4=48,之后卷積核向下移動(dòng)一格得到45,以此類推。如下右圖所示,輸入長(zhǎng)短不一的多個(gè)詞匯。首先將其全部填充為六通道的embedding數(shù)組,然后采用六通道的一維卷積核從上到下進(jìn)行卷積,得到一維的數(shù)組,然后再經(jīng)過(guò)池化層和全連接層后輸出。
可以看到CNN網(wǎng)絡(luò)不能完美處理輸入長(zhǎng)短不一的序列式問(wèn)題,但是它可以并行處理多個(gè)詞組,效率更高,而RNN可以更好地處理序列式的輸入,將兩者的優(yōu)勢(shì)結(jié)合起來(lái)就構(gòu)成了R-CNN模型。首先通過(guò)雙向RNN網(wǎng)絡(luò)對(duì)輸入進(jìn)行特征提取,再使用CNN進(jìn)一步提取,之后通過(guò)池化層將每一步的特征融合在一起,最后經(jīng)過(guò)全連接層進(jìn)行分類。
無(wú)論什么模型都需要使用embedding將輸入轉(zhuǎn)化為一個(gè)向量,當(dāng)輸入過(guò)大時(shí),轉(zhuǎn)化的embedding層參數(shù)就會(huì)過(guò)大,不僅不利于存儲(chǔ),還會(huì)造成過(guò)擬合,因此需要對(duì)embedding層進(jìn)行壓縮。原來(lái)的embedding編碼是一個(gè)參數(shù)對(duì)應(yīng)一個(gè)輸入,例如wait對(duì)應(yīng)參數(shù)x1,for對(duì)應(yīng)x2,the對(duì)應(yīng)x3。如果輸入過(guò)多,編碼參數(shù)就會(huì)很大,可以采用兩個(gè)參數(shù)對(duì)組合的方式來(lái)編碼輸入,例如wait對(duì)應(yīng)(x1,x2),for對(duì)應(yīng)(x1,x3)...,這樣就可以極大的節(jié)省參數(shù)的數(shù)量,這就是共享壓縮。
2、通過(guò)Text RNN進(jìn)行文本分類
2.1、數(shù)據(jù)預(yù)處理
在網(wǎng)上下載的文本分類數(shù)據(jù)集文件如下,分為測(cè)試集和訓(xùn)練集數(shù)據(jù),每個(gè)訓(xùn)練集下有四個(gè)文件夾,每個(gè)文件夾是一個(gè)分類,每個(gè)分類有1000個(gè)txt文件,每個(gè)文件中有一條該分類的文本
通過(guò)os.walk遍歷所有訓(xùn)練集文件,將分類文本通過(guò)jieba庫(kù)拆分成單個(gè)詞組,用空格分隔。然后將分類文本添加到開(kāi)頭,并用制表符分隔,最后將結(jié)果輸出到train_segment.txt,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
# 將文件中的句子通過(guò)jieba庫(kù)拆分為單個(gè)詞 def segment_word(input_file, output_file): # 循環(huán)遍歷訓(xùn)練數(shù)據(jù)集的每一個(gè)文件 for root, folders, files in os.walk(input_file): print ( 'root:' , root) for folder in folders: print ( 'dir:' , folder) for file in files: file_dir = os.path.join(root, file ) with open (file_dir, 'rb' ) as in_file: # 讀取文件中的文本 sentence = in_file.read() # 通過(guò)jieba函數(shù)庫(kù)將句子拆分為單個(gè)詞組 words = jieba.cut(sentence) # 文件夾路徑最后兩個(gè)字即為分類名 content = root[ - 2 :] + '\t' # 去除詞組中的空格,排除為空的詞組 for word in words: word = word.strip( ' ' ) if word ! = '': content + = word + ' ' # 換行并將文本寫入輸出文件 content + = '\n' with open (output_file, 'a' ) as outfile: outfile.write(content.strip( ' ' )) |
結(jié)果如下:
由于一些詞組出現(xiàn)次數(shù)很少,不具有統(tǒng)計(jì)意義,所以需要排除,通過(guò)get_list()方法統(tǒng)計(jì)每個(gè)詞組出現(xiàn)的頻率。利用python自帶的dictionary數(shù)據(jù)類型可以輕易實(shí)現(xiàn)詞組數(shù)據(jù)統(tǒng)計(jì),格式為{"keyword":frequency},frequency記錄keyword出現(xiàn)的次數(shù)。如果一個(gè)詞組是新出現(xiàn)的則作為新詞條加入詞典,否則將frequency值+1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
# 統(tǒng)計(jì)每個(gè)詞出現(xiàn)的頻率 def get_list(segment_file, out_file): # 通過(guò)詞典保存每個(gè)詞組出現(xiàn)的頻率 word_dict = {} with open (segment_file, 'r' ) as seg_file: lines = seg_file.readlines() # 遍歷文件的每一行 for line in lines: line = line.strip( '\r\n' ) # 將一行按空格拆分為每個(gè)詞,統(tǒng)計(jì)詞典 for word in line.split( ' ' ): # 如果這個(gè)詞組沒(méi)有在word_dict詞典中出現(xiàn)過(guò),則新建詞典項(xiàng)并設(shè)為0 word_dict.setdefault(word, 0 ) # 將詞典word_dict中詞組word對(duì)應(yīng)的項(xiàng)計(jì)數(shù)加一 word_dict[word] + = 1 # 將詞典中的列表排序,關(guān)鍵字為列表下標(biāo)為1的項(xiàng),且逆序 sorted_list = sorted (word_dict.items(), key = lambda d: d[ 1 ], reverse = True ) with open (out_file, 'w' ) as outfile: # 將排序后的每條詞典項(xiàng)寫入文件 for item in sorted_list: outfile.write( '%s\t%d\n' % (item[ 0 ], item[ 1 ])) |
統(tǒng)計(jì)結(jié)果如下:
2.2、數(shù)據(jù)讀入
直接使用詞組無(wú)法進(jìn)行編碼學(xué)習(xí),需要將詞組轉(zhuǎn)化為embedding編碼,根據(jù)剛才生成的train_list列表,按照從前往后的順序?yàn)槊總€(gè)詞組編號(hào),如果詞組頻率小于閾值則排除掉。通過(guò)Word_list類來(lái)構(gòu)建訓(xùn)練數(shù)據(jù)、測(cè)試數(shù)據(jù)的詞組對(duì)象,在類的構(gòu)造函數(shù)__init__()實(shí)現(xiàn)詞組的編碼。并定義類方法sentence2id將拆分好的句子詞組轉(zhuǎn)化為對(duì)應(yīng)的id數(shù)組,如果詞組列表中沒(méi)有該詞,則將該值置為-1。
在定義類之前首先規(guī)定一些超參數(shù)供后續(xù)使用:
1
2
3
4
5
6
7
8
9
10
11
|
# 定義超參數(shù) embedding_size = 32 # 每個(gè)詞組向量的長(zhǎng)度 max_words = 10 # 一個(gè)句子最大詞組長(zhǎng)度 lstm_layers = 2 # lstm網(wǎng)絡(luò)層數(shù) lstm_nodes = [ 64 , 64 ] # lstm每層結(jié)點(diǎn)數(shù) fc_nodes = 64 # 全連接層結(jié)點(diǎn)數(shù) batch_size = 100 # 每個(gè)批次樣本數(shù)據(jù) lstm_grads = 1.0 # lstm網(wǎng)絡(luò)梯度 learning_rate = 0.001 # 學(xué)習(xí)率 word_threshold = 10 # 詞表頻率門限,低于該值的詞語(yǔ)不統(tǒng)計(jì) num_classes = 4 # 最后的分類結(jié)果有4類 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class Word_list: def __init__( self , filename): # 用詞典類型來(lái)保存需要統(tǒng)計(jì)的詞組及其頻率 self ._word_dic = {} with open (filename, 'r' ,encoding = 'GB2312' ,errors = 'ignore' ) as f: lines = f.readlines() for line in lines: word, freq = line.strip( '\r\n' ).split( '\t' ) freq = int (freq) # 如果詞組的頻率小于閾值,跳過(guò)不統(tǒng)計(jì) if freq < word_threshold: continue # 詞組列表中每個(gè)詞組都是不重復(fù)的,按序添加到word_dic中即可,下一個(gè)詞組id就是當(dāng)前word_dic的長(zhǎng)度 word_id = len ( self ._word_dic) self ._word_dic[word] = word_id def sentence2id( self , sentence): # 將以空格分割的句子返回word_dic中對(duì)應(yīng)詞組的id,若不存在返回-1 sentence_id = [ self ._word_dic.get(word, - 1 ) for word in sentence.split()] return sentence_id train_list = Word_list(train_list_dir) |
定義TextData類來(lái)完成數(shù)據(jù)的讀入和管理,在__init__()函數(shù)中讀取剛才處理好的train_segment.txt文件,根據(jù)制表符分割類別標(biāo)記和句子詞組,將類別和句子分別轉(zhuǎn)化為數(shù)字id。如果句子的詞組超過(guò)了最大閾值,則截去后面多余的,如果不夠則用-1填充。定義類函數(shù)_shuffle_data()用于清洗數(shù)據(jù),next_batch()用于按批次返回?cái)?shù)據(jù)和標(biāo)簽,get_size()用于返回詞組總條數(shù)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
class TextData: def __init__( self , segment_file, word_list): self .inputs = [] self .labels = [] # 通過(guò)詞典管理文本類別 self .label_dic = { '體育' : 0 , '校園' : 1 , '女性' : 2 , '出版' : 3 } self .index = 0 with open (segment_file, 'r' ) as f: lines = f.readlines() for line in lines: # 文本按制表符分割,前面為類別,后面為句子 label, content = line.strip( '\r\n' ).split( '\t' )[ 0 : 2 ] self .content_size = len (content) # 將類別轉(zhuǎn)換為數(shù)字id label_id = self .label_dic.get(label) # 將句子轉(zhuǎn)化為embedding數(shù)組 content_id = word_list.sentence2id(content) # 如果句子的詞組長(zhǎng)超過(guò)最大值,截取max_words長(zhǎng)度以內(nèi)的id值 content_id = content_id[ 0 :max_words] # 如果不夠則填充-1,直到max_words長(zhǎng)度 padding_num = max_words - len (content_id) content_id = content_id + [ - 1 for i in range (padding_num)] self .inputs.append(content_id) self .labels.append(label_id) self .inputs = np.asarray( self .inputs, dtype = np.int32) self .labels = np.asarray( self .labels, dtype = np.int32) self ._shuffle_data() # 對(duì)數(shù)據(jù)按照(input,label)對(duì)來(lái)打亂順序 def _shuffle_data( self ): r_index = np.random.permutation( len ( self .inputs)) self .inputs = self .inputs[r_index] self .labels = self .labels[r_index] # 返回一個(gè)批次的數(shù)據(jù) def next_batch( self , batch_size): # 當(dāng)前索引+批次大小得到批次的結(jié)尾索引 end_index = self .index + batch_size # 如果結(jié)尾索引大于樣本總數(shù),則打亂所有樣本從頭開(kāi)始 if end_index > len ( self .inputs): self ._shuffle_data() self .index = 0 end_index = batch_size # 按索引返回一個(gè)批次的數(shù)據(jù) batch_inputs = self .inputs[ self .index:end_index] batch_labels = self .labels[ self .index:end_index] self .index = end_index return batch_inputs, batch_labels # 獲取詞表數(shù)目 def get_size( self ): return self .content_size # 訓(xùn)練數(shù)據(jù)集對(duì)象 train_set = TextData(train_segment_dir, train_list) # print(data_set.next_batch(10)) # 訓(xùn)練數(shù)據(jù)集詞組條數(shù) train_list_size = train_set.get_size() |
2.3、構(gòu)建計(jì)算圖模型
定義函數(shù)create_model來(lái)實(shí)現(xiàn)計(jì)算圖模型的構(gòu)建。首先定義模型輸入的占位符,分別為輸入文本inputs、輸出標(biāo)簽outputs、Dropout的比率keep_prob。
首先構(gòu)建embedding層,將輸入的inputs編碼抽取出來(lái)拼接成一個(gè)矩陣,例如輸入[1,8,3]則抽取embeding[1]、embedding[8]和embedding[3]拼接成一個(gè)矩陣
接下來(lái)構(gòu)建LSTM網(wǎng)絡(luò),這里構(gòu)建了兩層網(wǎng)絡(luò),每層的結(jié)點(diǎn)數(shù)在之前的參數(shù)lstm_node[]數(shù)組中定義。每個(gè)cell的構(gòu)建通過(guò)函數(shù)tf.contrib.rnn.BasicLSTMCell實(shí)現(xiàn),之后經(jīng)過(guò)Dropout操作。再將兩個(gè)cell合并為一個(gè)LSTM網(wǎng)絡(luò),通過(guò)函數(shù)tf.nn.dynamic_rnn將輸入embedded_inputs輸入到LSTM網(wǎng)絡(luò)中進(jìn)行訓(xùn)練得到輸出rnn_output。這是一個(gè)三維數(shù)組,第二維表示訓(xùn)練的步數(shù),我們只取最后一維的結(jié)果,即下標(biāo)值為-1.
接下來(lái)構(gòu)建全連接層,通過(guò)tf.layers.dense函數(shù)定義全連接層,再經(jīng)過(guò)一個(gè)dropout操作后將輸出映射到類別上,類別的種類的參數(shù)num_classes,得到估計(jì)值logits
接下來(lái)就可以求損失、精確率等評(píng)估值了。計(jì)算算預(yù)測(cè)值logits與標(biāo)簽值outputs之間的交叉熵?fù)p失值,接下來(lái)通過(guò)arg_max計(jì)算預(yù)測(cè)值,進(jìn)而求準(zhǔn)確率
接下來(lái)定義訓(xùn)練方法,通過(guò)梯度裁剪應(yīng)用到變量上以防止梯度消失。
最后將輸入占位符、損失等評(píng)估值、其他訓(xùn)練參數(shù)返回到調(diào)用函數(shù)的外部。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
# 創(chuàng)建計(jì)算圖模型 def create_model(list_size, num_classes): # 定義輸入輸出占位符 inputs = tf.placeholder(tf.int32, (batch_size, max_words)) outputs = tf.placeholder(tf.int32, (batch_size,)) # 定義是否dropout的比率 keep_prob = tf.placeholder(tf.float32, name = 'keep_rate' ) # 記錄訓(xùn)練的總次數(shù) global_steps = tf.Variable(tf.zeros([], tf.float32), name = 'global_steps' , trainable = False ) # 將輸入轉(zhuǎn)化為embedding編碼 with tf.variable_scope( 'embedding' , initializer = tf.random_normal_initializer( - 1.0 , 1.0 )): embeddings = tf.get_variable( 'embedding' , [list_size, embedding_size], tf.float32) # 將指定行的embedding數(shù)值抽取出來(lái) embedded_inputs = tf.nn.embedding_lookup(embeddings, inputs) # 實(shí)現(xiàn)LSTM網(wǎng)絡(luò) scale = 1.0 / math.sqrt(embedding_size + lstm_nodes[ - 1 ]) / 3.0 lstm_init = tf.random_uniform_initializer( - scale, scale) with tf.variable_scope( 'lstm_nn' , initializer = lstm_init): # 構(gòu)建兩層的lstm,每層結(jié)點(diǎn)數(shù)為lstm_nodes[i] cells = [] for i in range (lstm_layers): cell = tf.contrib.rnn.BasicLSTMCell(lstm_nodes[i], state_is_tuple = True ) # 實(shí)現(xiàn)Dropout操作 cell = tf.contrib.rnn.DropoutWrapper(cell, output_keep_prob = keep_prob) cells.append(cell) # 合并兩個(gè)lstm的cell cell = tf.contrib.rnn.MultiRNNCell(cells) # 將embedded_inputs輸入到RNN中進(jìn)行訓(xùn)練 initial_state = cell.zero_state(batch_size, tf.float32) # runn_output:[batch_size,num_timestep,lstm_outputs[-1] rnn_output, _ = tf.nn.dynamic_rnn(cell, embedded_inputs, initial_state = initial_state) last_output = rnn_output[:, - 1 , :] # 構(gòu)建全連接層 fc_init = tf.uniform_unit_scaling_initializer(factor = 1.0 ) with tf.variable_scope( 'fc' , initializer = fc_init): fc1 = tf.layers.dense(last_output, fc_nodes, activation = tf.nn.relu, name = 'fc1' ) fc1_drop = tf.contrib.layers.dropout(fc1, keep_prob) logits = tf.layers.dense(fc1_drop, num_classes, name = 'fc2' ) # 定義評(píng)估指標(biāo) with tf.variable_scope( 'matrics' ): # 計(jì)算損失值 softmax_loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits = logits, labels = outputs) loss = tf.reduce_mean(softmax_loss) # 計(jì)算預(yù)測(cè)值,求第1維中最大值的下標(biāo),例如[1,1,5,3,2] argmax=> 2 y_pred = tf.argmax(tf.nn.softmax(logits), 1 , output_type = tf.int32) # 求準(zhǔn)確率 correct_prediction = tf.equal(outputs, y_pred) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) # 定義訓(xùn)練方法 with tf.variable_scope( 'train_op' ): train_var = tf.trainable_variables() # for var in train_var: # print(var) # 對(duì)梯度進(jìn)行裁剪防止梯度消失或者梯度爆炸 grads, _ = tf.clip_by_global_norm(tf.gradients(loss, train_var), clip_norm = lstm_grads) # 將梯度應(yīng)用到變量上去 optimizer = tf.train.AdamOptimizer(learning_rate = learning_rate) train_op = optimizer.apply_gradients( zip (grads, train_var), global_steps) # 以元組的方式將結(jié)果返回 return ((inputs, outputs, keep_prob), (loss, accuracy), (train_op, global_steps)) # 調(diào)用構(gòu)建函數(shù),接收解析返回的參數(shù) placeholders, matrics, others = create_model(train_list_size, num_classes) inputs, outputs, keep_prob = placeholders loss, accuracy = matrics train_op, global_steps = others |
2.4、進(jìn)行訓(xùn)練
通過(guò)Session運(yùn)行計(jì)算圖模型,從train_set中按批次獲取訓(xùn)練集數(shù)據(jù)并填充占位符,運(yùn)行sess.run,獲取損失值、準(zhǔn)確率等中間值打印
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# 進(jìn)行訓(xùn)練 init_op = tf.global_variables_initializer() train_keep_prob = 0.8 # 訓(xùn)練集的dropout比率 train_steps = 10000 with tf.Session() as sess: sess.run(init_op) for i in range (train_steps): # 按批次獲取訓(xùn)練集數(shù)據(jù) batch_inputs, batch_labels = train_set.next_batch(batch_size) # 運(yùn)行計(jì)算圖 res = sess.run([loss, accuracy, train_op, global_steps], feed_dict = {inputs: batch_inputs, outputs: batch_labels, keep_prob: train_keep_prob}) loss_val, acc_val, _, g_step_val = res if g_step_val % 20 = = 0 : print ( '第%d輪訓(xùn)練,損失:%3.3f,準(zhǔn)確率:%3.5f' % (g_step_val, loss_val, acc_val)) |
在我的數(shù)據(jù)集進(jìn)行一萬(wàn)輪訓(xùn)練后,訓(xùn)練集的準(zhǔn)確率在90%左右徘徊
源代碼及相關(guān)數(shù)據(jù)文件:https://github.com/SuperTory/MachineLearning/tree/master/TextRNN
希望本文所述對(duì)大家Python程序設(shè)計(jì)有所幫助。
原文鏈接:https://blog.csdn.net/theVicTory/article/details/101017006