前言

除了卷積神經網絡,深度學習中還有循環神經網絡也是很常用的,循環神經網絡更常用於自然語言處理任務上。我們在這一章中,我們就來學習如何使用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從入門到煉丹》六——生成對抗網絡


參考資料

  1. https://blog.csdn.net/u010089444/article/details/76725843
  2. http://ai.stanford.edu/~amaas/data/sentiment/
  3. https://github.com/PaddlePaddle/book/tree/develop/06.understand_sentiment
小夜