句子文本數據如何作爲機器學習(深度學習)模型的輸入(pytorch)

在我們做機器學習/深度學習時,如何表示一個文本數據並讓計算機理解呢,很多深度學習框架,如pytorch,在接受文本數據時,我們都會採用Embedding層作爲第一層,那它的作用是啥呢?

以中文爲例,在這之前,我們都會根據數據集中的所有文本數據構建出一個高頻字/詞典,中文一般都是構建字典,也就是將句子進行字符級切分,構成字典。

比如,給定句子:“我我我我是是是你你爸爸,爸爸”,構建出來的字典文件如下:

<PAD> 0
我 1
是 2
你 3
爸 4
,5
<UNK> 6

說明:

  1. 每個字後面都對應一個索引,表示當前字的排列位置,在字典文件中寫不寫這個索引值隨你自己,不過我們在具體代碼實現時,在python中會有兩個dict類型變量來加載所生成的字典文件中的內容:char2id(或word2id)(根據字獲取此字的索引值)、id2char(或id2word)(根據索引值獲取具體字)
  2. 在上述字典文件中,多了幾個在句子中沒有出現的字,<PAD>表示填充(Padding)字符,因爲在進行文本數據輸入時,如RNN網絡,每次輸入都是一個句子序列,但每個句子長度不一定都是一樣的,爲了統一長度(這個長度根據情況你自己定),長句進行截斷,短句進行填充,填充的字符就是<PAD>。雖然我們構建了字典,但是捨棄了一些低頻詞,所以明顯是有可能遇到字典中沒有的字,那麼這個詞就用<UNK>表示。在實際編碼過程中,根據情況添加這些類似的字符是很常見的。
  3. 我們通常不會將數據集中的出現的所有字加進字典,只會採用出現頻率最高的字。具體做法就是,統計這些所有字出現的頻率次數,按照次數進行降序排列,取前n(根據情況定義)個字。
  4. 其實也不一定自己構建字典,也可以下載一份字典,如開源bert模型就提供了一份字典。

有了字典我們如何表示文本呢?

one-hot向量表示法

表示一個字(我),以上面講解的句子和字典爲例。

  1. 令字典長度爲len_dict = 7,初始化一個維度爲len_dict = 7的全零向量(數組)vec = [0,0,0,0,0,0,0]
  2. 獲取要表示的字()在字典中的索引,爲index = 1
  3. 將向量vec中對應索引index位置的值置爲1,vec[index] = 1。
  4. 最終字“”的one-hot表示爲[0,1,0,0,0,0,0]

如果用此種方法表示句子的話,就將句子中的每個字的one-hot表示出來就行了。如果句子長度爲len_sen,那麼就會得到一個大小爲len_sen * len_dict 的矩陣,每一行就代表句子中對應字的one-hot表示。

很明顯,one-hot方法只是單純的表示了一個字/詞,無法體現一個字詞的其它隱藏特徵,如字詞間的相關性。並且如果模型整個後續過程都採用one-hot表示的數據進行計算,那會帶來很大的計算壓力。

所以爲了獲取更低緯度的文本特徵,出現word2vec方法。

word2vec

本文不講解具體的word2vec實現原理及方法,相關論文可參考word2vec 相關論文。總的來說,就是採用更低緯度的向量來表示一個字/單詞。如下圖:
在這裏插入圖片描述
上圖表明瞭經過word2vec之後,每個字/詞的向量維度降低了(這個維度又稱爲word_embedding_dim,這個數值是自己定義的,我一般設置成300)。總的來說,我們可以用更低維的向量表示無窮多的字/詞。
爲什麼呢?假設字/詞典大小爲len_dict,那麼one-hot向量維度也爲len_dict,但one-hot向量中每次只能有一個爲1,其餘爲0,來是一個字/詞,最多隻能表示len_dict個字/詞。但經過word2vec後,其中每個向量中的數值可以爲任意實數,所以可以表示爲無窮個字/詞。

那麼有哪些方法獲得這份word2vec後向量呢?
1、訓練gensim模塊的Word2Vec模型獲取。
前提: 首先你需要一份文本數據集,如果是一篇篇文章,要進行分句處理。然後對句子進行字符級劃分。最後將數據集構建成如下形式送入模型即可。
在這裏插入圖片描述

from gensim.models import Word2Vec
import numpy as np
import os
word_embedding_dim = 250
x = []  #這個你要按照上面圖片的例子構建數據
# size 表示經過word2vec之後的每個字/詞向量的大小。sg表示具體實現訓練的模型的是哪個(1 for skip-gram; otherwise CBOW.),如果你有了解過我上面給出的論文,你會知道我在說什麼。
model = Word2Vec(x, size = word_embedding_dim , window = 5, min_count = 5, iter = 10, sg = 1)  

#模型訓練完之後(需要一些時間,具體根據數據量和設置參數而定)。可對其進行保存和加載。
#model.save(os.path.join('./', 'w2v_all.model'))
#model =  Word2Vec.load(os.path.join('./', 'w2v_all.model'))

#我們在訓練時並沒設置字典這些,它會根據我們輸入的數據X自動生成字典。下面演示如何獲取字典,和word2vec後的字/詞向量。
word2idx = {}
idx2word = []
embedding_matrix = []  #word2vec後的字/詞向量,最後大小爲len(word2idx) * word_embedding_dim 
for i ,word in enumerate(model.wv.vocab):
	word2idx[word] = len(word2idx)
	idx2word.append(word)
	embedding_matrix.append(model[word])

#在這裏你可能會要問,像<PAD>、<UNK>這些字符並沒在裏面啊。在此我們給他加進去,但不要忘了同時還要加上word2vec後的對應的向量,這個隨機初始化一個就行了。
def add_embedding(word):
    # 把 word 加進embedding,並賦予他一個隨機生成的 representation vector
    # word 只會是 "<PAD>" 或 "<UNK>"
    #np.random.uniform(0, 1, word_embedding_dim) 產生word_embedding_dim個在區間[0,1)中的服從均勻分佈的數據。
    vector = np.random.uniform(0, 1, word_embedding_dim)
    word2idx[word] = len(word2idx)
    idx2word.append(word)
    embedding_matrix.append(vector)
    
#對於其它和上下文有意義的字符不建議這麼做
add_embedding("<PAD>")
add_embedding("<UNK>")
print(word2idx)
print(idx2word)
print(np.array(embedding_matrix).shape)

注意: 在模型初始化過程中,我們設置了一個參數min_count = 5,這個5其實也是默認值。這個參數代表它自動生成的字/詞典中的字/詞在你輸入文本中的出現頻率如果小於min_count,它將忽略這個字/詞。也就是說,如果你僅僅是爲了練習練習代碼,用幾個句子數據測試,很可能報下列錯誤:

RuntimeError: you must first build vocabulary before training the model

也就是說,你的字/詞頻率都小於min_count,全都丟棄了,字/詞典爲空。練練代碼的話將min_count設置小一點就行了。做實驗的話,數據量一般夠多,不會出現這種情況。

總的來說,word2vec不僅降低了字/詞向量表示維度,且提煉出了這些字/詞之間的相關性。如果你的訓練數據夠多,你會發現一些同義詞在向量空間中捱得比較近(利用餘弦相似度計算字/詞的相似性)。你也可以對輸出的所有字/詞的word2vec向量,每個詞向量取其中的任意對應兩維數(比如都取1,3列),畫在x,y軸座標空間裏面,你會發現相關字/詞是挨着的。更準確的,你也可以採用PCA、Tsne對高維數據進行可視化。

Embedding

在很多深度學習框架中,如pytorch,都會採用Embedding層作爲文本數據輸入第一層。如果已經看完我上面所講的Word2vec,其實,embedding表面上和word2vec一樣,將一個字/詞變成更低緯度的向量了。只不過他們具體實現的方式不一樣,word2vec採用的無監督學習(我們只給它輸入了大量文本而已),embedding是有監督的更新權重向量。比如文本情感分類,首先隨機初始化一個embedding字/詞向量,然後根據預測結果和真實結果的損失函數求導結果反向傳播更新embedding字/詞向量。

pytorch中的embedding層使用方式:
前提: 根據所有數據集生成一個字典,<PAD>這些字符根據情況都要考慮進去(也可以自己去下一個)。
一般,embedding層接收的數據集格式是這樣的:
在這裏插入圖片描述
如果上述矩陣爲A,那麼A(i,j)就代表第i行句子中的第j個字/詞在字典中的索引值。

我們進行數據訓練時,都是一批一批數據進行訓練的,這個batch_size大小是我們自定義的,也就是我們每次將batch_size個句子一次性送入模型訓練。 句子長度也是我們統一自定義的。

輸出數據格式是這樣的:
在這裏插入圖片描述

import torch
import torch.nn as nn

#字典
char2id = {'是':0,'我':1,'你':2,'<PAD>':3,'<UNK>':4}
# num_embeddings = 字典長度 , embedding_dim = 生成的每個字/詞向量維度
num_embeddings = 5
embedding_dim = 4
#我們只有一句文本,所以batch_size爲1,句子"我是你"長度sentence_len = 3,所以inputs的shape = (1,3)
#句子"我是你"的字索引表示爲[1,0,2]
inputs = torch.tensor([[1,0,2]])  #inputs.shape = (batch_size, sentence_len)
embedding = torch.nn.Embedding(num_embeddings, embedding_dim)
outputs = embedding(inputs)
print(outputs, outputs.shape)   #outputs.shape = (batch_size, sentence_len, embedding_dim)
tensor([[[-0.3039, -0.7565, -0.2139, -1.3951],
         [ 1.0783,  0.1729, -1.0732,  0.9884],
         [ 0.0366,  0.4481, -1.8975,  2.4956]]], grad_fn=<EmbeddingBackward>) torch.Size([1, 3, 4])

embedding層之後呢,一般都接RNN等類型網絡啦。上面這個outputs輸出會根據後續網絡反向傳播進行迭代訓練更新,第一次只是程序自動隨機初始化的一個權重向量。

在Embedding 中使用預訓練好了Word2vec字/詞向量

那麼怎麼在embedding中使用通過word2vec訓練好了的字/詞向量呢。過程很簡單,通過以下代碼即可實現。
前提: 我們已經通過前面的Word2vec獲取到了word2idx字典,
idx2word列表、embedding_matrix字/詞向量矩陣。這個矩陣的行索引是和列表idx2word是對應的,對於一個字索引值id(id ∈ [0,len(word2idx) ) 且爲整數)。那麼可以通過 idx2word[id] 獲取到此索引值的具體字/詞,可通過 embedding_matrix[id] 可以獲取到此字/詞的對應的word2vec後的向量。

embedding = torch.nn.Embedding(len(embedding_matrix), len(embedding_matrix[0]))  
embedding.weight = torch.nn.Parameter(torch.tensor(embedding_matrix))
embedding.weight.requires_grad = False #設置爲False表明在模型訓練過程中,不更新此權重向量,直接使用我們自己設置的Word2vec預訓練權重向量。當然你也可以設置成True從而進行微調。

總結

在深度學習中,文本數據的最開始的輸入還是字索引形式,也就是one-hot表示,word2vec、embedding這些就是一個文本隱藏特徵抽取過程,一般放在整個網絡的第一層,我們說的這些字詞向量就是這個模型訓練完成之後的權重(參數)向量。當然不同的方式還是差異的,如word2vec之後,這些向量能學習到字詞之間的相關性。以及沒有提到的elmo、bert、gpt等方式,能學習到字詞的上下文語義特徵,比如“蘋果”一詞在不同的文本上下中所代表的的意義不同,公司?or 水果?,但word2vec或許只能獲取其中的一種含義,因爲它是靜態的,它的結果是一個固定了的向量矩陣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章