本文目錄:
- 一、one-hot編碼
- 二、word2vec模型
- (一)概念
- 1.CBOW(Continuous bag of words)模式
- 2.skipgram模式:
- 3.詞向量的檢索獲取
- (二)word2vec的訓練和使用
- 1. 獲取訓練數據
- 2.查看原始數據
- 3.原始數據處理,并查看處理后的數據
- 4.詞向量訓練與保存
- 5.查看單詞對應的詞向量
- 6.模型效果檢驗
- 7.模型超參數檢驗
- 三、詞嵌入word embedding
- (一)概念
- (二)代碼實現
- (三)tensorboard可視化
前言:前文分享了NLP的數據處理的幾種方式,本文講解文本張量的表示方法。
張量表示方法主要有三種:one-hot編碼、Word2vec、Word Embedding,本文也主要介紹這三種方法。
一、one-hot編碼
又稱獨熱編碼,將每個詞表示成具有n個元素的向量,這個詞向量中只有一個元素是1,其他元素都是0,不同詞匯元素為0的位置不同,其中n的大小是整個語料中不同詞匯的總數。
舉個例子:
["改變", "要", "如何", "起手"]`
==>[[1, 0, 0, 0],[0, 1, 0, 0],[0, 0, 1, 0],[0, 0, 0, 1]]
onehot編碼實現前,需要先講解一下Tokenizer,簡單來說,它是一個詞匯映射器,內部可生成index_word 、word_index等,原理展示如下:
import joblibclass Tokenizer:def __init__(self, max_len=100):self.max_len = max_lenself.word_idx = {}self.idx_word = {}def encode(self,word):if word not in self.word_idx:idx_=len(self.word_idx)+1self.word_idx[word] = idx_ #形式為:{word:idx}self.idx_word[idx_] = word #形式為:{idx:word}def fit_on_text(self,text):for word in text:self.encode(word)def decode(self,idx):word=self.idx_word[idx]return worddef indexs_to_words(self,idxs):words=[]for idx in idxs:words.append(self.decode(idx))return wordsdef __len__(self):return len(self.word_idx)def __str__(self):return f'{self.idx_word}\n{self.word_idx}'if __name__ == '__main__':tokenizer = Tokenizer()words=['花','世界','草','樹','動物']tokenizer.fit_on_text(words)print(tokenizer)# print(len(tokenizer))word2=tokenizer.indexs_to_words([1,2,3])print(word2)
運行結果:
{1: '花', 2: '世界', 3: '草', 4: '樹', 5: '動物'}
{'花': 1, '世界': 2, '草': 3, '樹': 4, '動物': 5}
['花', '世界', '草', '樹', '動物']
講完Tokenizer,下面是運用Tokenizer實現了onehot編碼:
import jieba
# 導入keras中的詞匯映射器Tokenizer
from tensorflow.keras.preprocessing.text import Tokenizer
# 導入用于對象保存與加載的joblib
from sklearn.externals import joblib# 思路分析 生成onehot
# 1 準備語料 vocabs
# 2 實例化詞匯映射器Tokenizer, 使用映射器擬合現有文本數據 (內部生成 index_word 、word_index)
# 2-1 注意idx序號-1:在Keras的Tokenizer類中,fit_on_texts()方法生成的詞索引(word index)默認是從1開始的,而不是從0開始。
# 3 查詢單詞idx 賦值 zero_list,生成onehot
# 4 使用joblib工具保存映射器 joblib.dump()
def dm_onehot_gen():# 1 準備語料 vocabsvocabs = {"周杰倫", "陳奕迅", "王力宏", "李宗盛", "小剛", "鹿晗"}# 2 實例化詞匯映射器Tokenizer, 使用映射器擬合現有文本數據 (內部生成 index_word、word_index)# 2-1 注意idx序號-1mytokenizer = Tokenizer()mytokenizer.fit_on_texts(vocabs)# 3 查詢單詞idx 賦值 zero_list,生成onehotfor vocab in vocabs:zero_list = [0] * len(vocabs)idx = mytokenizer.word_index[vocab] - 1zero_list[idx] = 1print(vocab, '的onehot編碼是', zero_list)# 4 使用joblib工具保存映射器 joblib.dump()mypath = './mytokenizer'joblib.dump(mytokenizer, mypath)print('保存mytokenizer End')# 注意5-1 字典沒有順序 onehot編碼沒有順序 []-有序 {}-無序 區別# 注意5-2 字典有的單詞才有idx,idx從1開始# 注意5-3 查詢沒有注冊的詞會有異常 eg: 狗蛋print(mytokenizer.word_index)print(mytokenizer.index_word)
運行結果:
陳奕迅 的onehot編碼是 [1, 0, 0, 0, 0, 0]
王力宏 的onehot編碼是 [0, 1, 0, 0, 0, 0]
鹿晗 的onehot編碼是 [0, 0, 1, 0, 0, 0]
周杰倫 的onehot編碼是 [0, 0, 0, 1, 0, 0]
李宗盛 的onehot編碼是 [0, 0, 0, 0, 1, 0]
吳亦凡 的onehot編碼是 [0, 0, 0, 0, 0, 1]保存mytokenizer End{'陳奕迅': 1, '王力宏': 2, '鹿晗': 3, '周杰倫': 4, '李宗盛': 5, '小剛': 6}
{1: '陳奕迅', 2: '王力宏', 3: '鹿晗', 4: '周杰倫', 5: '李宗盛', 6: '小剛'}
二、word2vec模型
(一)概念
Word2Vec 是一種用于詞嵌入(Word Embedding)的經典模型,由 Google 團隊于 2013 年提出(Mikolov et al.)。它通過神經網絡將單詞映射到低維稠密向量空間,使得語義相似的詞在向量空間中距離相近。
它包含CBOW和skipgram兩種訓練模式。
1.CBOW(Continuous bag of words)模式
給定一段用于訓練的文本語料, 再選定某段長度(窗口)作為研究對象, 使用上下文詞匯預測目標詞匯。
上圖分析:
窗口大小為9, 使用前后4個詞匯對目標詞匯進行預測。
CBOW模式下的word2vec過程說明:
假設我們給定的訓練語料只有一句話: Hope can set you free (愿你自由成長),窗口大小為3,因此模型的第一個訓練樣本來自Hope can set,因為是CBOW模式,所以將使用Hope和set作為輸入,can作為輸出,在模型訓練時, Hope,can,set等詞匯都使用它們的one-hot編碼. 如圖所示: 每個one-hot編碼的單詞與各自的變換矩陣(即參數矩陣3x5, 這里的3是指最后得到的詞向量維度)相乘之后再相加, 得到上下文表示矩陣(3x1)。
接著, 將上下文表示矩陣與變換矩陣(參數矩陣5x3, 所有的變換矩陣共享參數)相乘, 得到5x1的結果矩陣, 它將與我們真正的目標矩陣即can的one-hot編碼矩陣(5x1)進行損失的計算, 然后更新網絡參數完成一次模型迭代。
最后窗口按序向后移動,重新更新參數,直到所有語料被遍歷完成,得到最終的變換矩陣(3x5),這個變換矩陣與每個詞匯的one-hot編碼(5x1)相乘,得到的3x1的矩陣就是該詞匯的word2vec張量表示。
2.skipgram模式:
給定一段用于訓練的文本語料, 再選定某段長度(窗口)作為研究對象, 使用目標詞匯預測上下文詞匯。
上圖分析:
窗口大小為9, 使用目標詞匯對前后四個詞匯進行預測。
skipgram模式下的word2vec過程說明:
假設我們給定的訓練語料只有一句話: Hope can set you free (愿你自由成長),窗口大小為3,因此模型的第一個訓練樣本來自Hope can set,因為是skipgram模式,所以將使用can作為輸入 ,Hope和set作為輸出,在模型訓練時, Hope,can,set等詞匯都使用它們的one-hot編碼. 如圖所示: 將can的one-hot編碼與變換矩陣(即參數矩陣3x5, 這里的3是指最后得到的詞向量維度)相乘, 得到目標詞匯表示矩陣(3x1)。
接著, 將目標詞匯表示矩陣與多個變換矩陣(參數矩陣5x3)相乘, 得到多個5x1的結果矩陣, 它將與我們Hope和set對應的one-hot編碼矩陣(5x1)進行損失的計算, 然后更新網絡參數完成一次模型迭代。
最后窗口按序向后移動,重新更新參數,直到所有語料被遍歷完成,得到最終的變換矩陣即參數矩陣(3x5),這個變換矩陣與每個詞匯的one-hot編碼(5x1)相乘,得到的3x1的矩陣就是該詞匯的word2vec張量表示。
3.詞向量的檢索獲取
神經網絡訓練完畢后,神經網絡的參數矩陣w就我們的想要詞向量。如何檢索某1個單詞的向量呢?以CBOW方式舉例說明如何檢索a單詞的詞向量。
如下圖所示:a的onehot編碼[10000],用參數矩陣[3,5] * a的onehot編碼[10000],可以把參數矩陣的第1列參數給取出來,這個[3,1]的值就是a的詞向量。
(二)word2vec的訓練和使用
第一步: 獲取訓練數據
第二步: 訓練詞向量
第三步: 模型超參數設定
第四步: 模型效果檢驗
第五步: 模型的保存與重加載
1. 獲取訓練數據
數據來源:http://mattmahoney.net/dc/enwik9.zip
在這里, 我們將研究英語維基百科的部分網頁信息, 它的大小在300M左右。這些語料已經被準備好, 我們可以通過Matt Mahoney的網站下載。
2.查看原始數據
$ head -10 data/enwik9# 原始數據將輸出很多包含XML/HTML格式的內容, 這些內容并不是我們需要的
<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.3/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.3/ http://www.mediawiki.org/xml/export-0.3.xsd" version="0.3" xml:lang="en"><siteinfo><sitename>Wikipedia</sitename><base>http://en.wikipedia.org/wiki/Main_Page</base><generator>MediaWiki 1.6alpha</generator><case>first-letter</case><namespaces><namespace key="-2">Media</namespace><namespace key="-1">Special</namespace><namespace key="0" />
3.原始數據處理,并查看處理后的數據
# 使用wikifil.pl文件處理腳本來清除XML/HTML格式的內容
# perl wikifil.pl data/enwik9 > data/fil9 #該命令已經執行# 查看前80個字符
head -c 80 data/fil9# 輸出結果為由空格分割的單詞anarchism originated as a term of abuse first used against early working class
4.詞向量訓練與保存
# 1.訓練詞向量工具庫的安裝
# 方法1 簡潔版
pip install fasttext
# 方法2:源碼安裝(推薦)
# 以linux安裝為例: 目錄切換到虛擬開發環境目錄下,再執行git clone 操作
git clone https://github.com/facebookresearch/fastText.git
cd fastText
sudo pip install . (從當前目錄安裝,即從源碼安裝)
#sudo表示以管理員權限運行
#pip是Python的包管理工具
#install是pip的安裝命令2.訓練詞向量
# 導入fasttext
import fasttextdef dm_fasttext_train_save_load():# 1 使用train_unsupervised(無監督訓練方法) 訓練詞向量mymodel = fasttext.train_unsupervised('./data/fil9')print('訓練詞向量 ok')# 2 save_model()保存已經訓練好詞向量 # 注意,該行代碼執行耗時很長 mymodel.save_model("./data/fil9.bin")print('保存詞向量 ok')# 3 模型加載mymodel = fasttext.load_model('./data/fil9.bin')print('加載詞向量 ok')# 上述代碼運行效果如下:
有效訓練詞匯量為124M, 共218316個單詞
Read 124M words
Number of words: 218316
Number of labels: 0
Progress: 100.0% words/sec/thread: 53996 lr: 0.000000 loss: 0.734999 ETA: 0h 0m
5.查看單詞對應的詞向量
# 通過get_word_vector方法來獲得指定詞匯的詞向量, 默認詞向量訓練出來是1個單詞100特征
def dm_fasttext_get_word_vector():mymodel = fasttext.load_model('./data/fil9.bin')myvector = mymodel.get_word_vector('the')print(myvector)# 運行效果如下:
array([-0.03087516, 0.09221972, 0.17660329, 0.17308897, 0.12863874,0.13912526, -0.09851588, 0.00739991, 0.37038437, -0.00845221,...-0.21184735, -0.05048715, -0.34571868, 0.23765688, 0.23726143],dtype=float32)
6.模型效果檢驗
# 檢查單詞向量質量的一種簡單方法就是查看其鄰近單詞, 通過我們主觀來判斷這些鄰近單詞是否與目標單詞相關來粗略評定模型效果好壞.# 查找"運動"的鄰近單詞, 我們可以發現"體育網", "運動汽車", "運動服"等.
>>> model.get_nearest_neighbors('sports')[(0.8414610624313354, 'sportsnet'), (0.8134572505950928, 'sport'), (0.8100415468215942, 'sportscars'), (0.8021156787872314, 'sportsground'), (0.7889881134033203, 'sportswomen'), (0.7863013744354248, 'sportsplex'), (0.7786710262298584, 'sporty'), (0.7696356177330017, 'sportscar'), (0.7619683146476746, 'sportswear'), (0.7600985765457153, 'sportin')]# 查找"音樂"的鄰近單詞, 我們可以發現與音樂有關的詞匯.
>>> model.get_nearest_neighbors('music')[(0.8908010125160217, 'emusic'), (0.8464668393135071, 'musicmoz'), (0.8444250822067261, 'musics'), (0.8113634586334229, 'allmusic'), (0.8106718063354492, 'musices'), (0.8049437999725342, 'musicam'), (0.8004694581031799, 'musicom'), (0.7952923774719238, 'muchmusic'), (0.7852965593338013, 'musicweb'), (0.7767147421836853, 'musico')]# 查找"小狗"的鄰近單詞, 我們可以發現與小狗有關的詞匯.
>>> model.get_nearest_neighbors('dog')[(0.8456876873970032, 'catdog'), (0.7480780482292175, 'dogcow'), (0.7289096117019653, 'sleddog'), (0.7269964218139648, 'hotdog'), (0.7114801406860352, 'sheepdog'), (0.6947550773620605, 'dogo'), (0.6897546648979187, 'bodog'), (0.6621081829071045, 'maddog'), (0.6605004072189331, 'dogs'), (0.6398137211799622, 'dogpile')]
7.模型超參數檢驗
# 在訓練詞向量過程中, 我們可以設定很多常用超參數來調節我們的模型效果, 如:
# 無監督訓練模式: 'skipgram' 或者 'cbow', 默認為'skipgram', 在實踐中,skipgram模式在利用子詞方面比cbow更好.
# 詞嵌入維度dim: 默認為100, 但隨著語料庫的增大, 詞嵌入的維度往往也要更大.
# 數據循環次數epoch: 默認為5, 但當你的數據集足夠大, 可能不需要那么多次.
# 學習率lr: 默認為0.05, 根據經驗, 建議選擇[0.01,1]范圍內.
# 使用的線程數thread: 默認為12個線程, 一般建議和你的cpu核數相同.>>> model = fasttext.train_unsupervised('data/fil9', "cbow", dim=300, epoch=1, lr=0.1, thread=8)Read 124M words
Number of words: 218316
Number of labels: 0
Progress: 100.0% words/sec/thread: 49523 lr: 0.000000 avg.loss: 1.777205 ETA: 0h 0m 0s
三、詞嵌入word embedding
(一)概念
通過一定的方式將詞匯映射到指定維度(一般是更高維度)的空間。
廣義的word embedding包括所有密集詞匯向量的表示方法,如之前學習的word2vec, 即可認為是word embedding的一種。
狹義的word embedding是指在神經網絡中加入的embedding層, 對整個網絡進行訓練的同時產生的embedding矩陣(embedding層的參數), 這個embedding矩陣就是訓練過程中所有輸入詞匯的向量表示組成的矩陣。
(二)代碼實現
可通過使用tensorboard可視化嵌入的詞向量。
import torch
from tensorflow.keras.preprocessing.text import Tokenizer
from torch.utils.tensorboard import SummaryWriter
import jieba
import torch.nn as nn# 注意:
# fs = tf.io.gfile.get_filesystem(save_path)
# AttributeError: module 'tensorflow._api.v2.io.gfile' has no attribute 'get_filesystem'
# 錯誤原因分析:
# 1 from tensorboard.compat import tf 使用了tf 如果安裝tensorflow,默認會調用它tf的api函數import os
import tensorboard.compat.tensorflow_stub.io.gfile as gfilegfile.join = os.path.join# 實驗:nn.Embedding層詞向量可視化分析
# 1 對句子分詞 word_list
# 2 對句子word2id求my_token_list,對句子文本數值化sentence2id
# 3 創建nn.Embedding層,查看每個token的詞向量數據
# 4 創建SummaryWriter對象, 可視化詞向量
# 詞向量矩陣embd.weight.data 和 詞向量單詞列表my_token_list添加到SummaryWriter對象中
# summarywriter.add_embedding(embd.weight.data, my_token_list)
# 5 通過tensorboard觀察詞向量相似性
# 6 也可通過程序,從nn.Embedding層中根據idx拿詞向量def dm02_nnembeding_show():# 1 對句子分詞 word_listsentence1 = '日月教育是一家上市公司,旗下有孟子撰稿人品牌。我是在孟子這里學習文本編輯'sentence2 = "我愛自然語言處理"sentences = [sentence1, sentence2]word_list = []for s in sentences:word_list.append(jieba.lcut(s))# print('word_list--->', word_list)# 2 對句子word2id求my_token_list,對句子文本數值化sentence2idmytokenizer = Tokenizer()mytokenizer.fit_on_texts(word_list)# print(mytokenizer.index_word, mytokenizer.word_index)# 打印my_token_listmy_token_list = mytokenizer.index_word.values()print('my_token_list-->', my_token_list)# 打印文本數值化以后的句子sentence2id = mytokenizer.texts_to_sequences(word_list)print('sentence2id--->', sentence2id, len(sentence2id))# 3 創建nn.Embedding層embd = nn.Embedding(num_embeddings=len(my_token_list), embedding_dim=8)# print("embd--->", embd)# print('nn.Embedding層詞向量矩陣-->', embd.weight.data, embd.weight.data.shape, type(embd.weight.data))# 4 創建SummaryWriter對象 詞向量矩陣embd.weight.data 和 詞向量單詞列表my_token_listsummarywriter = SummaryWriter()summarywriter.add_embedding(embd.weight.data, my_token_list)summarywriter.close()# 5 通過tensorboard觀察詞向量相似性# cd 程序的當前目錄下執行下面的命令# 啟動tensorboard服務 tensorboard --logdir=runs# 6 從nn.Embedding層中根據idx拿詞向量for idx in range(len(mytokenizer.index_word)):tmpvec = embd(torch.tensor(idx))print(f'索引:{idx}, 單詞:{mytokenizer.index_word[idx+1]}, 詞向量:{tmpvec.detach().numpy()}')
運行結果:
my_token_list--> dict_values(['是', '孟子', '我', '日月', '教育', '一家', '上市公司', ',', '旗下', '有', '撰稿人', '品牌', '。', '在', '這里', '學習', '文本編輯', '愛', '自然語言', '處理'])
sentence2id---> [[4, 5, 1, 6, 7, 8, 9, 10, 2, 11, 12, 13, 3, 1, 14, 2, 15, 16, 17], [3, 18, 19, 20]] 2
索引:0, 單詞:是, 詞向量:[ 0.69119644 1.558375 1.6096373 0.98399407 2.19424 0.728914-1.4884706 0.74123204]
索引:1, 單詞:孟子, 詞向量:[ 0.54096115 0.554475 -0.36367872 1.0417004 -0.08993819 0.363280861.0169278 -2.0248666 ]
索引:2, 單詞:我, 詞向量:[-0.28916082 1.991956 -1.0531932 2.2981353 -0.12863453 -0.43955870.37317175 -1.9240963 ]
索引:3, 單詞:日月, 詞向量:[-0.3404994 0.64136696 -0.41038752 0.4643729 -1.4025296 -0.72162175-0.9584457 -0.5583694 ]
索引:4, 單詞:教育, 詞向量:[ 0.6051798 0.5324559 1.3707999 -0.9619674 -0.4099177 1.07640221.169339 -0.5462842]
索引:5, 單詞:一家, 詞向量:[ 1.0526828e+00 1.2929035e+00 -5.1964927e-01 -3.8134031e-02-1.1102496e+00 -7.6204950e-01 -4.0794726e-04 -8.9704728e-01]
索引:6, 單詞:上市公司, 詞向量:[-0.58729947 0.6140487 -0.7034455 -0.20555277 -1.0994186 -0.153647840.78021735 0.21966906]
索引:7, 單詞:,, 詞向量:[ 0.49117938 -0.35445458 0.51375175 -0.21544148 0.27322227 -0.59599570.4328904 -0.19425716]
索引:8, 單詞:旗下, 詞向量:[-0.47452 -1.5148909 0.08764656 -0.23282683 0.1024209 -0.2980003-0.40261132 0.08614016]
索引:9, 單詞:有, 詞向量:[ 0.23161238 -0.2524453 0.387037 2.454544 1.2310503 0.400453930.30937108 0.822938 ]
索引:10, 單詞:撰稿人, 詞向量:[-0.92806697 1.5328188 -1.8695339 0.51484096 1.1806546 -1.51092030.00246807 0.04051867]
索引:11, 單詞:品牌, 詞向量:[-0.5824984 -0.2912002 0.0206483 -0.09639109 0.28824955 -0.52154961.0424472 0.9492429 ]
索引:12, 單詞:。, 詞向量:[-0.09745022 -1.2284819 1.1367943 0.54926044 -0.95832205 2.6447535-0.47664887 0.86957765]
索引:13, 單詞:在, 詞向量:[ 1.097833 1.8800806 0.08421371 0.18953332 0.33601582 0.715077940.13900037 -0.08530565]
索引:14, 單詞:這里, 詞向量:[ 0.19942084 0.7394484 0.3601424 0.04815122 -0.53084886 -0.63848720.26165137 -0.4921349 ]
索引:15, 單詞:學習, 詞向量:[ 0.6652479 0.29150504 -1.5092025 -1.6463886 0.18891443 1.2385565-0.9700842 0.56979525]
索引:16, 單詞:文本編輯, 詞向量:[ 0.8814708 0.95923316 -0.86357886 1.7589295 1.7440987 -0.19017713-2.1311624 -0.75222325]
索引:17, 單詞:愛, 詞向量:[ 0.41096488 0.4740911 0.21957813 -0.56212014 -0.8156858 -0.30315447-1.0360459 0.16603687]
索引:18, 單詞:自然語言, 詞向量:[-1.1221373 0.3029554 0.5329474 -0.65763694 0.2559778 -0.560264650.19609775 0.25071865]
索引:19, 單詞:處理, 詞向量:[ 1.83175 -0.57898027 -0.07835663 1.0467906 0.22850354 -0.2075311-0.08430544 -1.1057415 ]
(三)tensorboard可視化
#首先終端輸入以下命令:
$ cd ~
$ tensorboard --logdir=runs
運行結果:
今天的分享到此結束。