你的第一個神經網絡文本分類任務——讓機器也懂感情

引言

本文是學習完集智學園《PyTorch入門課程:火炬上的深度學習》系列課以後的梳理。git

該系列課程包含大量的實操任務,如文本分類,手寫數字識別,翻譯器,做曲機,AI遊戲等。而在實操中還融入了大量機器學習領域的基礎和經典知識,如反向傳播原理,神經元原理剖析,NLP領域的RNN,LSTM,word2vec,以及圖像領域的遷移學習,卷積神經網絡等等,很是適合想要入門機器學習的同窗,是當前網絡上絕無僅有的好課程。github

我的學完後受益不淺,在此真誠地向張江老師表示感謝正則表達式

同系列文章:算法

  1. 用線性迴歸擬合案例,透徹理解深度學習的反向傳播
  2. 你的第一個神經網絡——共享單車預測器

正文

本文的任務,是但願基於電商的商品評論文本數據實現一個神經網絡模型,用於判斷一段評論是好評仍是差評。這個任務本質上是一個二分類問題。同理,二分類問題還能夠解決諸如篩選無心義評論,判斷一張圖片屬於貓還狗等任務。區別就在於輸入的數據是文本,圖片,仍是其餘數據類型。數組

開始以前先容許我作一個簡單的劃重點,以便你們對全文內容有一個初步的認識。咱們要完成這個任務的核心分爲兩點:bash

首先,是數據處理環節。在你的第一個神經網絡——共享單車預測器中,我也花了大量的篇幅來說數據處理,其中用到了one-hot,歸一化等基本的數據處理操做。而在本任務中,數據處理更是重中之重。主要涉及到,如何讓機器識別文本,並用其進行計算的問題。在這裏,咱們主要使用了最簡單的詞袋模型(BOW)。其中對中文的處理還須要使用到正則匹配以及jieba分詞工具網絡

其次,就是基本的神經網絡訓練步驟,其實這個步驟和你的第一個神經網絡——共享單車預測器一文的步驟基本大同小異,無非就是創建模型,輸入數據,獲得預測值,反向傳播計算loss,再進行梯度運算調整權重,並不斷重複這整個過程,直到loss再也不降低,因此對訓練步驟的說明就再也不詳細展開了,還不瞭解的同窗能夠回過頭看上一篇文章。不一樣之處僅僅在於,分類任務的損失函數和迴歸任務不一樣,這一點待會兒會具體說明。app

下面就開始正式操做環節,讓咱們先來看數據處理。機器學習

1. 數據預處理

先來看一下咱們用於建模的數據格式。分紅了兩個文本文件,good.txt和bad.txt,分別存放好評和差評,每一行表明一條評論。數據來源於京東(2013年),可在集智學園github上獲取數據文件。函數

將文件按行讀取並依次存儲,咱們能夠在讀取的時候就過濾掉標點,並對句子進行分詞。這樣能夠獲得兩個數組,一個好評文本數組,一個差評文本數組。

其中過濾標點能夠直接使用正則表達式,分詞則能夠直接使用結巴分詞庫,這個工具能夠準確地把一個句子分割爲幾個有意義的詞語。一句話特定詞語出現的次數多,好比「很好」,「贊」等,一般咱們就能夠認爲這句話是一句正向評論。因此中文詞語纔是咱們要分析的最小單元。

import re #正則表達式的包
import jieba #結巴分詞包
# 正向文本
with open('good.txt', 'r') as fr:
    for idx, line in enumerate(fr):
        if True:
            #過濾標點符號
            line = re.sub("[\s+\.\!\/_,$%^*(+\"\'「」《》?「]+|[+——!,。?、~@#¥%……&*():]+", "", line)  
        #分詞
        words = jieba.lcut(line)
        if len(words) > 0:
            all_words += words
            pos_sentences.append(words)

# 負向文本處理方法相同,獲得數組neg_sentences
複製代碼
BOW(詞袋模型)

要讓計算機可以處理文本,首先就要想辦法將文本向量化。BOW方法就是一個很是容易理解的文本向量化方法。我舉一個簡單的例子你們就能一目瞭然。它的思路就是把文本所包含的全部詞彙數量做爲向量的維度,把詞語在當前句子中出現的頻數做爲對應位置的值。

句子1:「我喜歡跳舞,小明也喜歡。」
句子2:「我也喜歡唱歌。」
複製代碼

上面是兩句話,咱們如今想要把這兩句話用向量表示。從這兩句話中咱們能夠提取出一個詞典,包含了這兩句話中的全部詞彙。

詞典 = {1:「我」,2:「喜歡」,3:「跳舞」,4:「小明」,5:「也」,6:「唱歌」}
複製代碼

文本所包含的全部詞彙數量做爲向量的維度,把詞語在當前句子中出現的頻數做爲對應位置的值。那麼,咱們就馬上有了句子的向量表示。

句子 1:[1, 2, 1, 1, 1, 0]
句子 2:[1, 1, 0, 0, 1, 1]
複製代碼

回到咱們的任務中來。依照上面的思路,咱們就須要創建一個包含全部詞彙的大字典,並統計每一個詞語的詞頻,從而就能獲得每一個句子的向量表示。這裏使用collections工具可讓詞頻統計更加簡單 。

from collections import Counter #蒐集器,可讓統計詞頻更簡單

diction = {} # 要創建的大字典
cnt = Counter(all_words)
for word, freg in cnt.items():
    diction[word] = [len(diction), freg] # 在字典中存儲每一個詞語的編號,以及詞頻
複製代碼

創建好大字典後,開始逐行處理評論文本

dataset = [] # 全部句子的向量表示集合,即咱們訓練,測試要使用到的數據
# 處理正向評論
for sentence in pos_sentences:
    new_sentence = []
    for l in sentence:
        if l in diction:
            new_sentence.append(word2index(l, diction))
    dataset.append(sentence2vec(new_sentence, diction))
    labels.append(0) #正標籤爲0
    sentences.append(sentence)

# 其中
# word2index是根據單詞得到編碼函數
# sentence2vec是把目標句子轉化爲向量函數
# 這裏不詳細展現,你們能夠嘗試本身編寫
# 源代碼能夠從文章開頭提到的集智學園github地址中下載
複製代碼

datasetlabel就包含了咱們須要的全部信息,包括文本數據和對應標籤。接下去,咱們就能夠進入訓練模型的步驟了

接下去,就能夠開始訓練模型了。下面的部分代碼量比較多,再次強調,訓練過程代碼的詳細說明,與你的第一個神經網絡——共享單車預測器這篇文章大同小異,因此本文和它重複的部分代碼不會再作詳細解釋。因此下面的代碼都是紙老虎而已。

2. 開始訓練

2.1 構建輸入和目標函數,構建模型

即處理初始數據,基於dataset和label把數據分爲訓練集,校驗集和測試集

#對整個數據集進行劃分,分爲:訓練集、校準集和測試集,其中校準和測試集合的長度都是整個數據集的10分之一
test_size = len(dataset) // 10
train_data = dataset[2 * test_size :]
train_label = labels[2 * test_size :]

valid_data = dataset[: test_size]
valid_label = labels[: test_size]

test_data = dataset[test_size : 2 * test_size]
test_label = labels[test_size : 2 * test_size]
複製代碼

使用pytorch能夠快速創建一個簡單的神經網絡模型

# 輸入維度爲詞典的大小:每一段評論的詞袋模型
model = nn.Sequential(
    nn.Linear(len(diction), 10),
    nn.ReLU(),
    nn.Linear(10, 2),
    nn.LogSoftmax(),
)
複製代碼
  1. 輸入文本向量,長度爲字典大小
  2. 通過一層非線性變換relu
  3. 通過一層線性變換
  4. 通過歸一化logSoftmax

這裏的爲何輸出是二維,咱們的label不是1或者0嗎?

其實爲了這裏爲了方便計算,咱們將標籤作了one-hot編碼,one-hot編碼的做用在上篇文章也提到過,是由於這裏的0和1並無「1比0大」這樣的概念,就像星期一星期二同樣,他們都是類型變量,爲了不類型變量0和1的數值大小影響了神經網絡的訓練。

2.1 訓練 + 校驗過程

先直接上代碼

# 損失函數爲交叉熵
cost = torch.nn.NLLLoss()
# 優化算法爲Adam,能夠自動調節學習率
optimizer = torch.optim.Adam(model.parameters(), lr = 0.01)
records = []

#循環10個Epoch
losses = []
for epoch in range(10):
    for i, data in enumerate(zip(train_data, train_label)):
        x, y = data
        x = torch.tensor(x, requires_grad = True, dtype = torch.float).view(1, -1)
        y = torch.tensor([y], dtype = torch.long)
        optimizer.zero_grad()
        predict = model(x)
        loss = cost(predict, y)
        # 將損失函數數值加入到列表中
        losses.append(loss.data.numpy())
        # 開始進行梯度反傳
        loss.backward()
        # 開始對參數進行一步優化
        optimizer.step()

       # 每隔3000步,跑一下校驗數據集的數據,輸出臨時結果
        if i % 3000 == 0:
            rights = []
            val_losses = []
            for j, val in enumerate(zip(valid_data, valid_label)):
                x, y = val
                x = torch.tensor(x, requires_grad = True, dtype = torch.float).view(1, -1)
                y = torch.tensor([y], dtype = torch.long)
                predict = model(x)
                # 調用rightness函數計算準確度
                right = rightness(predict, y)
                rights.append(right)
                loss = cost(predict, y)
                val_losses.append(loss.data.numpy())
            # 將校驗集合上面的平均準確度計算出來
            right_ratio = 1.0 * np.sum([i[0] for i in rights]) / np.sum([i[1] for i in rights])
            print('第{}輪,訓練損失:{:.2f}, 校驗損失:{:.2f}, 校驗準確率: {:.2f}'.format(epoch, np.mean(losses), np.mean(val_losses), right_ratio))
            records.append([np.mean(losses), np.mean(val_losses), right_ratio])
複製代碼

這裏想要着重強調如下幾點:

首先,上面的代碼和上篇文章略有不一樣的是,這裏的校驗過程和訓練過程寫在了一塊兒,但思路仍是同樣的,使用校驗集數據在訓練好的模型上跑,觀察校驗集val_loss的變化狀況。這樣的結果會更加客觀

其次,針對分類問題,還能夠計算結果的準確度rightness。對真實標籤和預測出的值進行比較,計算預測的準確度。其中真實標籤和預測值都是二維矩陣

def rightness(predictions, labels):
   # """計算預測錯誤率的函數
   # 其中predictions是模型給出的一組預測結果
   # batch_size行num_classes列的矩陣
   # labels是數據之中的正確答案""",

   # 對於任意一行(一個樣本)的輸出值的第1個維度,求最大,獲得每一行的最大元素的下標
   pred = torch.max(predictions.data, 1)[1] 

   # 將下標與labels中包含的類別進行比較,並累計獲得比較正確的數量
   rights = pred.eq(labels.data.view_as(pred)).sum()

   # 返回正確的數量和這一次一共比較了多少元素
   return rights, len(labels)
複製代碼

最後,也是最重要的,就是損失函數torch.nn.NLLLoss(),即交叉熵。

二分類的交叉熵公式以下:

這也是當前任務使用的方法,其中:

  • y —— 表示樣本的label,正樣本是1,負樣本是0
  • p —— 表示樣本預測爲正的機率。

神經網絡對於分類問題的預測值一般是一個機率,在當前任務中,好比預測吐出了[0.8, 0.2],這意味着,神經網絡預測當前樣本爲1的機率更大(第一位的數值更大)。交叉熵是用於計算分類問題的預測損失,即若是真實樣本是1,那就對比[0.8, 0.2]和[1,0]之間的「差」,這個「差」值越小,說明預測和真實就越接近。

當loss再也不降低時,模型基本完成訓練,下圖是繪製了訓練集loss,校驗集loss和準確度的變化狀況。

咱們能夠認爲,校驗集loss和訓練集loss重合的部分,模型的效果是最好的,再繼續訓練下去,雖然訓練集的loss還在持續降低,可是校驗集loss卻不降反升,這個時候模型已通過擬合了。

3. 測試模型效果。

咱們取測試集數據,查看模型的預測效果。

rights = []
for i, data in enumerate(zip(test_data, test_label)):
    x, y = data
    x = torch.tensor(x, requires_grad = True, dtype = torch.float).view(1, -1)
    y = torch.tensor([y], dtype = torch.long)
    predict = model(x)
    right = rightness(predict, y)
    rights.append(right)

right_ratio = 1.0 * np.sum([i[0] for i in rights]) / np.sum([i[1] for i in rights])
print(' 測試準確率: {:.2f}'.format(right_ratio))
複製代碼

最終輸出準確率:0.91。

到這裏,整個任務就完成了,咱們獲得了一個能夠分辨好評仍是差評的文本分類器,而且這個分類器的準確率可達91%

結束

感謝您看到這裏。文本分類器的坑填完了。

在從此的一段時間裏,我還會嘗試圖像識別,文本翻譯,AI遊戲等真實案例。全部學習案例都來自張江老師的PyTorch與深度學習課程。

望與你們共勉。

相關文章
相關標籤/搜索