前言¶
除了卷積神經網絡,深度學習中還有循環神經網絡也是很常用的,循環神經網絡更常用於自然語言處理任務上。我們在這一章中,我們就來學習如何使用PaddlePaddle來實現一個循環神經網絡,並使用該網絡完成情感分析的模型訓練。
訓練模型¶
創建一個text_classification.py的Python文件。首先導入Python庫,fluid和numpy庫我們在前幾章都有使用過,這裏就不重複了。這裏主要結束是imdb庫,這個是一個數據集的庫,這個是數據集是一個英文的電影評論數據集,每一條數據都會有兩個分類,分別是正面和負面。
import paddle
import paddle.dataset.imdb as imdb
import paddle.fluid as fluid
import numpy as np
循環神經網絡發展到現在,已經有不少性能很好的升級版的循環神經網絡,比如長短期記憶網絡等。一下的代碼片段是一個比較簡單的循環神經網絡,首先是經過一個fluid.layers.embedding(),這個是接口是接受數據的ID輸入,因爲輸入數據時一個句子,但是在訓練的時候我們是把每個單詞轉換成對應的ID,再輸入到網絡中,所以這裏使用到了embedding接口。然後是一個全連接層,接着是一個循環神經網絡塊,在循環神經網絡塊之後再經過一個sequence_last_step接口,這個接口通常是使用在序列函數的最後一步。最後的輸出層的激活函數是Softmax,大小爲2,因爲數據的結果有2個,爲正負面。
def rnn_net(ipt, input_dim):
# 以數據的IDs作爲輸入
emb = fluid.layers.embedding(input=ipt, size=[input_dim, 128], is_sparse=True)
sentence = fluid.layers.fc(input=emb, size=128, act='tanh')
rnn = fluid.layers.DynamicRNN()
with rnn.block():
word = rnn.step_input(sentence)
prev = rnn.memory(shape=[128])
hidden = fluid.layers.fc(input=[word, prev], size=128, act='relu')
rnn.update_memory(prev, hidden)
rnn.output(hidden)
last = fluid.layers.sequence_last_step(rnn())
out = fluid.layers.fc(input=last, size=2, act='softmax')
return out
下面的代碼片段是一個簡單的長短期記憶網絡,這個網絡是有循環神經網絡演化過來的。當較長的序列數據,循環神經網絡的訓練過程中容易出現梯度消失或爆炸現象,而長短期記憶網絡就可以解決這個問題。在網絡的開始同樣是經過一個embedding接口,接着是一個全連接層,緊接的是一個dynamic_lstm長短期記憶操作接口,有這個接口,我們很容易就搭建一個長短期記憶網絡。然後是經過兩個序列池操作,該序列池的類型是最大化。最後也是一個大小爲2的輸出層。
# 定義長短期記憶網絡
def lstm_net(ipt, input_dim):
# 以數據的IDs作爲輸入
emb = fluid.layers.embedding(input=ipt, size=[input_dim, 128], is_sparse=True)
# 第一個全連接層
fc1 = fluid.layers.fc(input=emb, size=128)
# 進行一個長短期記憶操作
lstm1, _ = fluid.layers.dynamic_lstm(input=fc1, size=128)
# 第一個最大序列池操作
fc2 = fluid.layers.sequence_pool(input=fc1, pool_type='max')
# 第二個最大序列池操作
lstm2 = fluid.layers.sequence_pool(input=lstm1, pool_type='max')
# 以softmax作爲全連接的輸出層,大小爲2,也就是正負面
out = fluid.layers.fc(input=[fc2, lstm2], size=2, act='softmax')
return out
這裏可以先定義一個輸入層,這樣要注意的是我們使用的數據屬於序列數據,所以我們可以設置lod_level爲1,當該參數不爲0時,表示輸入的數據爲序列數據,默認lod_level的值是0.
# 定義輸入數據, lod_level不爲0指定輸入數據爲序列數據
words = fluid.layers.data(name='words', shape=[1], dtype='int64', lod_level=1)
label = fluid.layers.data(name='label', shape=[1], dtype='int64')
然後是讀取數據字典,因爲我們的數據是以數據標籤的放方式表示數據一個句子。所以每個句子都是以一串整數來表示的,每個數字都是對應一個單詞。所以這個數據集就會有一個數據集字典,這個字典是訓練數據中出現單詞對應的數字標籤。
# 獲取數據字典
print("加載數據字典中...")
word_dict = imdb.word_dict()
# 獲取數據字典長度
dict_dim = len(word_dict)
輸出信息:
加載數據字典中...
這裏可以獲取我們上面定義的網絡作爲我們之後訓練的網絡模型,這兩個網絡讀者都可以試試,可以對比它們的差別。
# 獲取長短期記憶網絡
model = lstm_net(words, dict_dim)
# 獲取循環神經網絡
# model = rnn_net(words, dict_dim)
接着定義損失函數,這裏同樣是一個分類任務,所以使用的損失函數也是交叉熵損失函數。這裏也可以使用fluid.layers.accuracy()接口定義一個輸出分類準確率的函數,可以方便在訓練的時候,輸出測試時的分類準確率,觀察模型收斂的情況。
# 獲取損失函數和準確率
cost = fluid.layers.cross_entropy(input=model, label=label)
avg_cost = fluid.layers.mean(cost)
acc = fluid.layers.accuracy(input=model, label=label)
這裏克隆一個測試測試程序,用於之後的測試和預測數據使用的。
# 獲取預測程序
test_program = fluid.default_main_program().clone(for_test=True)
然後是定義優化方法,這裏使用的時Adagrad優化方法,Adagrad優化方法多用於處理稀疏數據,設置學習率爲0.002。
# 定義優化方法
optimizer = fluid.optimizer.AdagradOptimizer(learning_rate=0.002)
opt = optimizer.minimize(avg_cost)
接着創建一個執行器,這次是的數據集比之前使用的數據集要大不少,所以訓練起來先對比較慢,如果讀取有GPU環境,可以嘗試使用GPU來訓練,使用方式是使用fluid.CUDAPlace(0)來創建執行器。
# 創建一個執行器
place = fluid.CPUPlace()
# place = fluid.CUDAPlace(0)
exe = fluid.Executor(place)
# 進行參數初始化
exe.run(fluid.default_startup_program())
然後把訓練數據和測試數據讀取到內存中,因爲數據集比較大,爲了加快數據的數據,使用paddle.reader.shuffle()接口來將數據先按照設置的大小讀取到緩存中。讀入緩存的大小可以根據硬件環境內存大小來設置。
# 獲取訓練和預測數據
print("加載訓練數據中...")
train_reader = paddle.batch(paddle.reader.shuffle(imdb.train(word_dict), 25000), batch_size=128)
print("加載測試數據中...")
test_reader = paddle.batch(imdb.test(word_dict), batch_size=128)
輸出信息:
加載訓練數據中...
加載測試數據中...
定義數據數據的維度,數據的順序是一條句子數據對應一個標籤。
# 定義輸入數據的維度
feeder = fluid.DataFeeder(place=place, feed_list=[words, label])
現在就可以開始訓練了,這裏設置訓練的循環是1次,讀者可以根據情況設置更多的訓練輪數,來讓模型完全收斂。我們在訓練中,每40個Batch打印一層訓練信息和進行一次測試,測試是使用測試集進行預測並輸出損失值和準確率,測試完成之後,對之前預測的結果進行求平均值。
# 開始訓練
for pass_id in range(1):
# 進行訓練
train_cost = 0
for batch_id, data in enumerate(train_reader()):
train_cost = exe.run(program=fluid.default_main_program(),
feed=feeder.feed(data),
fetch_list=[avg_cost])
if batch_id % 40 == 0:
print('Pass:%d, Batch:%d, Cost:%0.5f' % (pass_id, batch_id, train_cost[0]))
# 進行測試
test_costs = []
test_accs = []
for batch_id, data in enumerate(test_reader()):
test_cost, test_acc = exe.run(program=test_program,
feed=feeder.feed(data),
fetch_list=[avg_cost, acc])
test_costs.append(test_cost[0])
test_accs.append(test_acc[0])
# 計算平均預測損失在和準確率
test_cost = (sum(test_costs) / len(test_costs))
test_acc = (sum(test_accs) / len(test_accs))
print('Test:%d, Cost:%0.5f, ACC:%0.5f' % (pass_id, test_cost, test_acc))
輸出信息:
Pass:0, Batch:0, Cost:0.69274
Test:0, Cost:0.69329, ACC:0.50175
Pass:0, Batch:40, Cost:0.61183
Test:0, Cost:0.61142, ACC:0.82659
Pass:0, Batch:80, Cost:0.55504
Test:0, Cost:0.54904, ACC:0.83959
Pass:0, Batch:120, Cost:0.51100
Test:0, Cost:0.50026, ACC:0.84318
Pass:0, Batch:160, Cost:0.46800
Test:0, Cost:0.46199, ACC:0.84533
預測數據¶
我們先定義三個句子,第一句是中性的,第二句偏向正面,第三句偏向負面。然後把這些句子讀取到一個列表中。
# 定義預測數據
reviews_str = ['read the book forget the movie', 'this is a great movie', 'this is very bad']
# 把每個句子拆成一個個單詞
reviews = [c.split() for c in reviews_str]
然後把句子轉換成編碼,根據數據集的字典,把句子中的單詞轉換成對應標籤。
# 獲取結束符號的標籤
UNK = word_dict['<unk>']
# 獲取每句話對應的標籤
lod = []
for c in reviews:
# 需要把單詞進行字符串編碼轉換
lod.append([word_dict.get(words.encode('utf-8'), UNK) for words in c])
獲取輸入數據的維度和大小。
# 獲取每句話的單詞數量
base_shape = [[len(c) for c in lod]]
將要預測的數據轉換成張量,準備開始預測。
# 生成預測數據
tensor_words = fluid.create_lod_tensor(lod, base_shape, place)
開始預測,使用的program是克隆的測試程序。預測數據是通過feed鍵值對的方式傳入到預測程序中,爲了符合輸入數據的格式,label中使用了一個假的label輸入到程序中。fetch_list的值是網絡的分類器。
# 預測獲取預測結果,因爲輸入的是3個數據,所以要模擬3個label的輸入
results = exe.run(program=test_program,
feed={'words': tensor_words, 'label': np.array([[0], [0], [0]]).astype('int64')},
fetch_list=[model])
最後可以把預測結果輸出,因爲我們使用了3條數據進行預測,所以輸出也會有3個結果。每個結果是類別的概率。
# 打印每句話的正負面概率
for i, r in enumerate(results[0]):
print("\'%s\'的預測結果爲:正面概率爲:%0.5f,負面概率爲:%0.5f" % (reviews_str[i], r[0], r[1]))
輸出信息:
'read the book forget the movie'的預測結果爲:正面概率爲:0.53604,負面概率爲:0.46396
'this is a great movie'的預測結果爲:正面概率爲:0.67564,負面概率爲:0.32436
'this is very bad'的預測結果爲:正面概率爲:0.35406,負面概率爲:0.64594
到處爲止,本章就結束了。希望讀者經過學習完這一章,可以對PaddlePaddle的使用有更深一步的認識。在下一章中,我們來使用PaddlePaddle實現一個生成對抗網絡,生成對抗網絡這一兩年中可以說時非常火的,同樣也非長有趣。那麼我們下一章見吧。
同步到百度AI Studio平臺:http://aistudio.baidu.com/aistudio/projectdetail/29347
同步到科賽網K-Lab平臺:https://www.kesci.com/home/project/5bf8cb78954d6e001066d7d8
項目代碼GitHub地址:https://github.com/yeyupiaoling/LearnPaddle2/tree/master/note5
注意: 最新代碼以GitHub上的爲準
上一章:《PaddlePaddle從入門到煉丹》四——卷積神經網絡¶
下一章:《PaddlePaddle從入門到煉丹》六——生成對抗網絡¶
參考資料¶
- https://blog.csdn.net/u010089444/article/details/76725843
- http://ai.stanford.edu/~amaas/data/sentiment/
- https://github.com/PaddlePaddle/book/tree/develop/06.understand_sentiment