天天看點

【自然語言處理(NLP)】基于循環神經網絡實作情感分類 【自然語言處理(NLP)】基于循環神經網絡實作情感分類任務描述一、環境配置二、資料準備三、模型配置四、模型訓練五、模型評估六、模型預測總結

【自然語言處理(NLP)】基于循環神經網絡實作情感分類

作者簡介:在校大學生一枚,華為雲享專家,阿裡雲星級部落客,騰雲先鋒(TDP)成員,雲曦智劃項目總負責人,全國高等學校計算機教學與産業實踐資源建設專家委員會(TIPCC)志願者,以及程式設計愛好者,期待和大家一起學習,一起進步~

.

部落格首頁:ぃ靈彧が的學習日志

.

本文專欄:機器學習

.

專欄寄語:若你決定燦爛,山無遮,海無攔

.

【自然語言處理(NLP)】基于循環神經網絡實作情感分類 【自然語言處理(NLP)】基于循環神經網絡實作情感分類任務描述一、環境配置二、資料準備三、模型配置四、模型訓練五、模型評估六、模型預測總結

(文章目錄)

任務描述

本示例教程示範如何在IMDB資料集上用RNN網絡完成文本分類的任務。

IMDB資料集是一個對電影評論标注為正向評論與負向評論的資料集,共有25000條文本資料作為訓練集,25000條文本資料作為測試集。 該資料集的官方位址為: http://ai.stanford.edu/~amaas/data/sentiment/

一、環境配置

導入相關包

import paddle
import numpy as np
import matplotlib.pyplot as plt
import paddle.nn as nn

print(paddle.__version__)  # 檢視目前版本

# cpu/gpu環境選擇,在 paddle.set_device() 輸入對應運作裝置。
device = paddle.set_device('gpu')
           

二、資料準備

  1. 由于IMDB是NLP領域中常見的資料集,飛槳架構将其内置,路徑為 paddle.text.datasets.Imdb。通過 mode 參數可以控制訓練集與測試集。
print('loading dataset...')
train_dataset = paddle.text.datasets.Imdb(mode='train')
test_dataset = paddle.text.datasets.Imdb(mode='test')
print('loading finished')
           
  1. 建構了訓練集與測試集後,可以通過 word_idx 擷取資料集的詞表。在飛槳架構2.0版本中,推薦使用padding的方式來對同一個batch中長度不一的資料進行補齊,是以在字典中,我們還會添加一個特殊的詞,用來在後續對batch中較短的句子進行填充。
word_dict = train_dataset.word_idx  # 擷取資料集的詞表

# add a pad token to the dict for later padding the sequence
word_dict['<pad>'] = len(word_dict)

for k in list(word_dict)[:5]:
    print("{}:{}".format(k.decode('ASCII'), word_dict[k]))

print("...")

for k in list(word_dict)[-5:]:
    print("{}:{}".format(k if isinstance(k, str) else k.decode('ASCII'), word_dict[k]))

print("totally {} words".format(len(word_dict)))
           

(一)、參數設定

  1. 在這裡我們設定一下詞表大小,embedding的大小,batch_size,等等
vocab_size = len(word_dict) + 1
print(vocab_size)
emb_size = 256
seq_len = 200
batch_size = 32
epochs = 2
pad_id = word_dict['<pad>']

classes = ['negative', 'positive']

# 生成句子清單
def ids_to_str(ids):
    # print(ids)
    words = []
    for k in ids:
        w = list(word_dict)[k]
        words.append(w if isinstance(w, str) else w.decode('ASCII'))
    return " ".join(words)
           
  1. 在這裡,取出一條資料列印出來看看,可以用 docs 擷取資料的list,用 labels 擷取資料的label值,列印出來對資料有一個初步的印象。
# 取出來第一條資料看看樣子。
sent = train_dataset.docs[0]
label = train_dataset.labels[1]
print('sentence list id is:', sent)
print('sentence label id is:', label)
print('--------------------------')
print('sentence list is: ', ids_to_str(sent))
print('sentence label is: ', classes[label])
           

(二)、用padding的方式對齊資料

文本資料中,每一句話的長度都是不一樣的,為了友善後續的神經網絡的計算,常見的處理方式是把資料集中的資料都統一成同樣長度的資料。這包括:對于較長的資料進行截斷處理,對于較短的資料用特殊的詞<pad>進行填充。接下來的代碼會對資料集中的資料進行這樣的處理。

# 讀取資料歸一化處理
def create_padded_dataset(dataset):
    padded_sents = []
    labels = []
    for batch_id, data in enumerate(dataset):
        sent, label = data[0], data[1]
        padded_sent = np.concatenate([sent[:seq_len], [pad_id] * (seq_len - len(sent))]).astype('int32')
        padded_sents.append(padded_sent)
        labels.append(label)
    return np.array(padded_sents), np.array(labels)

# 對train、test資料進行執行個體化
train_sents, train_labels = create_padded_dataset(train_dataset)
test_sents, test_labels = create_padded_dataset(test_dataset)

# 檢視資料大小及舉例内容
print(train_sents.shape)
print(train_labels.shape)
print(test_sents.shape)
print(test_labels.shape)

for sent in train_sents[:3]:
    print(ids_to_str(sent))
           

(三)、用Dataset 與 DataLoader 加載

将前面準備好的訓練集與測試集用Dataset 與 DataLoader封裝後,完成資料的加載。

class IMDBDataset(paddle.io.Dataset):
    '''
    繼承paddle.io.Dataset類進行封裝資料
    '''
    def __init__(self, sents, labels):
        self.sents = sents
        self.labels = labels
    
    def __getitem__(self, index):
        data = self.sents[index]
        label = self.labels[index]

        return data, label

    def __len__(self):
        return len(self.sents)
    
train_dataset = IMDBDataset(train_sents, train_labels)
test_dataset = IMDBDataset(test_sents, test_labels)

train_loader = paddle.io.DataLoader(train_dataset, return_list=True,
                                    shuffle=True, batch_size=batch_size, drop_last=True)
test_loader = paddle.io.DataLoader(test_dataset, return_list=True,
                                    shuffle=True, batch_size=batch_size, drop_last=True)
           

三、模型配置

<b>樣本出現的時間順序對于自然語言處理、語音識别、手寫體識别等應用非常重要</b>。

對了适應這種需求,就出現了題主所說的另一種神經網絡結構——循環神經網絡RNN。

本示例中,我們将會使用一個序列特性的RNN網絡,在查找到每個詞對應的embedding後,簡單的取平均,作為一個句子的表示。然後用

Linear

進行線性變換。為了防止過拟合,我們還使用了

Dropout

RNN對具有序列特性的資料非常有效,它能挖掘資料中的時序資訊以及語義資訊,利用了RNN的這種能力,使深度學習模型在解決語音識别、語言模型、機器翻譯以及時序分析等NLP領域的問題時有所突破。

在普通的全連接配接網絡或CNN中,每層神經元的信号隻能向上一層傳播,樣本的處理在各個時刻獨立,是以又被成為前向神經網絡(Feed-forward Neural Networks)。而在RNN中,神經元的輸出可以在下一個時間戳直接作用到自身,即第i層神經元在m時刻的輸入,除了(i-1)層神經元在該時刻的輸出外,還包括其自身在(m-1)時刻的輸出!表示成圖就是這樣的:

【自然語言處理(NLP)】基于循環神經網絡實作情感分類 【自然語言處理(NLP)】基于循環神經網絡實作情感分類任務描述一、環境配置二、資料準備三、模型配置四、模型訓練五、模型評估六、模型預測總結

可以看到在隐含層節點之間增加了互連。為了分析友善,我們常将RNN在時間上進行展開,得到如圖所示的結構:

【自然語言處理(NLP)】基于循環神經網絡實作情感分類 【自然語言處理(NLP)】基于循環神經網絡實作情感分類任務描述一、環境配置二、資料準備三、模型配置四、模型訓練五、模型評估六、模型預測總結

(t+1)時刻網絡的最終結果O(t+1)是該時刻輸入和所有曆史共同作用的結果!這就達到了對時間序列模組化的目的。

import paddle.nn as nn
import paddle

# 定義RNN網絡
class MyRNN(paddle.nn.Layer):
    def __init__(self):
        super(MyRNN, self).__init__()
        self.embedding = nn.Embedding(vocab_size, 256)
        self.rnn = nn.SimpleRNN(256, 256, num_layers=2, direction='forward',dropout=0.5)
        self.linear = nn.Linear(in_features=256*2, out_features=2)
        self.dropout = nn.Dropout(0.5)
    

    def forward(self, inputs):
        emb = self.dropout(self.embedding(inputs))
        #output形狀大小為[batch_size,seq_len,num_directions * hidden_size]
        #hidden形狀大小為[num_layers * num_directions, batch_size, hidden_size]
        #把前向的hidden與後向的hidden合并在一起
        output, hidden = self.rnn(emb)
        hidden = paddle.concat((hidden[-2,:,:], hidden[-1,:,:]), axis = 1)
        #hidden形狀大小為[batch_size, hidden_size * num_directions]
        hidden = self.dropout(hidden)
        return self.linear(hidden) 
           

四、模型訓練

(一)、可視化定義

# 可視化定義
def draw_process(title,color,iters,data,label):
    plt.title(title, fontsize=24)
    plt.xlabel("iter", fontsize=20)
    plt.ylabel(label, fontsize=20)
    plt.plot(iters, data,color=color,label=label) 
    plt.legend()
    plt.grid()
    plt.show()
           

(二)、對模型進行封裝

# 對模型進行封裝
def train(model):
    model.train()
    opt = paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters())
    steps = 0
    Iters, total_loss, total_acc = [], [], []

    for epoch in range(epochs):
        for batch_id, data in enumerate(train_loader):
            steps += 1
            sent = data[0]
            label = data[1]
            
            logits = model(sent)
            loss = paddle.nn.functional.cross_entropy(logits, label)
            acc = paddle.metric.accuracy(logits, label)

            if batch_id % 500 == 0:  # 500個epoch輸出一次結果
                Iters.append(steps)
                total_loss.append(loss.numpy()[0])
                total_acc.append(acc.numpy()[0])

                print("epoch: {}, batch_id: {}, loss is: {}".format(epoch, batch_id, loss.numpy()))
            
            loss.backward()
            opt.step()
            opt.clear_grad()

        # evaluate model after one epoch
        model.eval()
        accuracies = []
        losses = []
        
        for batch_id, data in enumerate(test_loader):
            
            sent = data[0]
            label = data[1]

            logits = model(sent)
            loss = paddle.nn.functional.cross_entropy(logits, label)
            acc = paddle.metric.accuracy(logits, label)
            
            accuracies.append(acc.numpy())
            losses.append(loss.numpy())
        
        avg_acc, avg_loss = np.mean(accuracies), np.mean(losses)

        print("[validation] accuracy: {}, loss: {}".format(avg_acc, avg_loss))
        
        model.train()

        # 儲存模型
        paddle.save(model.state_dict(),str(epoch)+"_model_final.pdparams")
    
    # 可視化檢視
    draw_process("trainning loss","red",Iters,total_loss,"trainning loss")
    draw_process("trainning acc","green",Iters,total_acc,"trainning acc")
        
model = MyRNN()
train(model)
           

輸出結果如下圖1所示:

【自然語言處理(NLP)】基于循環神經網絡實作情感分類 【自然語言處理(NLP)】基于循環神經網絡實作情感分類任務描述一、環境配置二、資料準備三、模型配置四、模型訓練五、模型評估六、模型預測總結

五、模型評估

'''
模型評估
'''
model_state_dict = paddle.load('1_model_final.pdparams')  # 導入模型
model = MyRNN()
model.set_state_dict(model_state_dict) 
model.eval()
accuracies = []
losses = []

for batch_id, data in enumerate(test_loader):
    
    sent = data[0]
    label = data[1]

    logits = model(sent)
    loss = paddle.nn.functional.cross_entropy(logits, label)
    acc = paddle.metric.accuracy(logits, label)
    
    accuracies.append(acc.numpy())
    losses.append(loss.numpy())

avg_acc, avg_loss = np.mean(accuracies), np.mean(losses)
print("[validation] accuracy: {}, loss: {}".format(avg_acc, avg_loss))
           

六、模型預測

def ids_to_str(ids):
    words = []
    for k in ids:
        w = list(word_dict)[k]
        words.append(w if isinstance(w, str) else w.decode('UTF-8'))
    return " ".join(words)

label_map = {0:"negative", 1:"positive"}

# 導入模型
model_state_dict = paddle.load('1_model_final.pdparams')
model = MyRNN()
model.set_state_dict(model_state_dict) 
model.eval()

for batch_id, data in enumerate(test_loader):
    
    sent = data[0]
    results = model(sent)

    predictions = []
    for probs in results:
        # 映射分類label
        idx = np.argmax(probs)
        labels = label_map[idx]
        predictions.append(labels)
    
    for i,pre in enumerate(predictions):
        print(' 資料: {} \n 情感: {}'.format(ids_to_str(sent[0]), pre))
        break
    break
           

輸出結果如下圖2所示:

【自然語言處理(NLP)】基于循環神經網絡實作情感分類 【自然語言處理(NLP)】基于循環神經網絡實作情感分類任務描述一、環境配置二、資料準備三、模型配置四、模型訓練五、模型評估六、模型預測總結

總結

本系列文章内容為根據清華社出版的《機器學習實踐》所作的相關筆記和感悟,其中代碼均為基于百度飛槳開發,若有任何侵權和不妥之處,請私信于我,定積極配合處理,看到必回!!!