Tomas Mikolov2013年在ICLR提出用于獲取word vector的論文《Efficient estimation of word representations in vector space》,文中簡(jiǎn)單介紹了兩種訓(xùn)練模型CBOW
、Skip-gram
,以及兩種加速方法Hierarchical Softmax
、Negative Sampling
。除了word2vec之外,還有其他的文本向量化的方法,因此在這里做個(gè)總結(jié)。
文本向量化(又稱“詞向量模型”、“向量空間模型”)即將文本表示成計(jì)算機(jī)可識(shí)別的實(shí)數(shù)向量,根據(jù)粒度大小不同可將文本特征表示分為字、詞、句子或篇章幾個(gè)層次。文本向量化的方法主要分為離散表示和分布式表示。
1.離散表示
一種基于規(guī)則和統(tǒng)計(jì)的向量化方式,常用的方法包括詞集模型
和詞袋模型
,都是基于詞之間保持獨(dú)立性、沒(méi)有關(guān)聯(lián)為前提,將所有文本中單詞形成一個(gè)字典,然后根據(jù)字典來(lái)統(tǒng)計(jì)單詞出現(xiàn)頻數(shù),不同的是:
- 詞集模型:例如One-Hot Representation,只要單個(gè)文本中單詞出現(xiàn)在字典中,就將其置為1,不管出現(xiàn)多少次
- 詞袋模型:只要單個(gè)文本中單詞出現(xiàn)在字典中,就將其向量值加1,出現(xiàn)多少次就加多少次
其基本的特點(diǎn)是忽略了文本信息中的語(yǔ)序信息和語(yǔ)境信息,僅將其反映為若干維度的獨(dú)立概念,這種情況有著因?yàn)槟P捅旧碓蚨鵁o(wú)法解決的問(wèn)題,比如主語(yǔ)和賓語(yǔ)的順序問(wèn)題,詞袋模型天然無(wú)法理解諸如“我為你鼓掌”和“你為我鼓掌”兩個(gè)語(yǔ)句之間的區(qū)別。
One-Hot Representation
將每個(gè)詞都表示成一個(gè)長(zhǎng)向量,向量的維度是詞表的大小,詞的當(dāng)前位置用1表示,其他位置用0表示。
import numpy as np
import pandas as pd
import jieba
def doc2onthot_matrix(file_path):
# 讀取待編碼的文件
with open(file_path, encoding="utf-8") as f:
docs = f.readlines()
with open(file_path1, encoding="utf-8") as f:
docs1 = f.readlines()
# 將文件每行分詞,分詞后的詞語(yǔ)放入words中
words=[]
for i in range(len(docs)):
docs[i] = jieba.cut(docs[i].strip("\n"))
words += docs[i]
# 找出分詞后不重復(fù)的詞語(yǔ),作為詞袋,是后續(xù)onehot編碼的維度
vocab = sorted(set(words), key=words.index)
# 建立一個(gè)M行V列的全0矩陣,M是文檔樣本數(shù),這里是行數(shù),V為不重復(fù)詞語(yǔ)數(shù),即編碼維度
V = len(vocab)
M = len(docs)
onehot = np.zeros((M,V))
for i,doc in enumerate(docs1):
words = ""
for word in doc:
if word != " ":
words = words + word
continue
if words in vocab:
pos = vocab.index(words)
onehot[i][pos] = 1
words = ""
else:
words = ""
continue
onehot=pd.DataFrame(onehot, columns=vocab)
return onehot
file_path = "D:/Pythonworkspace/test.txt"
file_path1 = "D:/Pythonworkspace/word.txt"
onehot = doc2onthot_matrix(file_path)
onehot
One-Hot編碼的優(yōu)點(diǎn)是簡(jiǎn)單快捷,缺點(diǎn)是數(shù)據(jù)稀疏、耗時(shí)耗空間、不能很好地展示詞與詞之間的相似關(guān)系,且還未考慮到詞出現(xiàn)的頻率,因而無(wú)法區(qū)別詞的重要性。
詞袋模型
對(duì)于句子或篇章而言,常用的離散表示方法是詞袋模型。詞袋模型以O(shè)ne-Hot為基礎(chǔ),忽略詞表中詞的順序和語(yǔ)法關(guān)系,通過(guò)記錄詞表中的每一個(gè)詞在該文本中出現(xiàn)的頻次來(lái)表示該詞在文本中的重要程度,解決了 One-Hot 未能考慮詞頻的問(wèn)題。
詞袋模型的優(yōu)點(diǎn)是方法簡(jiǎn)單,當(dāng)語(yǔ)料充足時(shí),處理簡(jiǎn)單的問(wèn)題如文本分類,其效果比較好。詞袋模型的缺點(diǎn)是數(shù)據(jù)稀疏、維度大,且不能很好地展示詞與詞之間的相似關(guān)系。
TF-IDF
TF-IDF(詞頻-逆文檔頻率法,Term Frequency–Inverse Document Frequency)作為一種加權(quán)方法,在詞袋模型的基礎(chǔ)上對(duì)詞出現(xiàn)的頻次賦予TF-IDF權(quán)值,對(duì)詞袋模型進(jìn)行修正,進(jìn)而表示該詞在文檔集合中的重要程度。
import numpy as np
import pandas as pd
import math
import jieba
import jieba.analyse
import re
# 中文分詞
def word_segment(file_path_before, file_path_after, stop_word_file,dic_file):
# 讀取待編碼的文件
with open(file_path_before,encoding="utf-8") as f:
docs = f.readlines()
# 加載停用詞
stopwords=[]
for word in open(stop_word_file, 'r'): # 這里加載停用詞的路徑
stopwords.append(word.strip("\n"))
words_list = []
for i,text in enumerate(docs):
words = []
final_word = []
p = re.compile(r"\n|:|;|,|、|(|)|\.|。|,|/|(\|)", re.S)
text = p.sub('', text)
jieba.load_userdict(dic_file)
word = jieba.cut(text)
words += word
for i,word in enumerate(words):
if word not in stopwords:
final_word.append(word)
words_list.append(final_word)
for i in range(0,len(words_list)):
with open(file_path_after, 'a') as f1:
f1.write(str(words_list[i]))
f1.write("\n")
return words_list
# 生成TD-IDF矩陣
def doc2tfidf_matrix(file_path_before, words_list):
# 找出分詞后不重復(fù)的詞語(yǔ),作為詞袋
words = []
for i,word in enumerate(words_list):
words += word
vocab = sorted(set(words),key=words.index)
# print(vocab)
# 建立一個(gè)M行V列的全0矩陣,M是文檔樣本數(shù),這里是行數(shù),V為不重復(fù)詞語(yǔ)數(shù),即編碼維度
V = len(vocab)
M = len(words_list)
onehot = np.zeros((M,V)) # 二維矩陣要使用雙括號(hào)
tf = np.zeros((M,V))
for i,doc in enumerate(words_list):
for word in doc:
if word in vocab:
pos = vocab.index(word)
# print(pos,word)
onehot[i][pos] = 1
tf[i][pos] += 1 # tf,統(tǒng)計(jì)某詞語(yǔ)在一條樣本中出現(xiàn)的次數(shù)
else:
print(word)
row_sum = tf.sum(axis=1) # 行相加,得到每個(gè)樣本出現(xiàn)的詞語(yǔ)數(shù)
# 計(jì)算TF(t,d)
tf = tf/row_sum[:, np.newaxis] #分母表示各樣本出現(xiàn)的詞語(yǔ)數(shù),tf為單詞在樣本中出現(xiàn)的次數(shù),[:,np.newaxis]作用類似于行列轉(zhuǎn)置
# 計(jì)算DF(t,D),IDF
df = onehot.sum(axis=0) # 列相加,表示有多少樣本包含詞袋某詞
idf = list(map(lambda x:math.log10((M+1)/(x+1)),df))
# 計(jì)算TFIDF
tfidf = tf*np.array(idf)
tfidf = pd.DataFrame(tfidf,columns=vocab)
return tfidf
file_path_before = "C:/Users/asus/Desktop/goodat.txt"
file_path_after = "C:/Users/asus/Desktop/word segment.txt"
stop_word_file = "C:/Users/asus/Desktop/stop_word.txt"
dic_file = "C:/Users/asus/Desktop/dic.txt"
words_list = word_segment(file_path_before, file_path_after,stop_word_file,dic_file)
tfidf = doc2tfidf_matrix(file_path_after, words_list)
for i in range(0,50):
goodat = ""
row = tfidf.iloc[i].sort_values(ascending=False)
for i in range(0,10):
goodat = goodat + row.index[i] + "/"
print(goodat)
在利用TF-IDF進(jìn)行特征提取時(shí),若詞α在某篇文檔中出現(xiàn)頻率較高且在其他文檔中出現(xiàn)頻率較低時(shí),則認(rèn)為α可以代表該文檔的特征,具有較好的分類能力,那么α作為特征被提取出來(lái)。
CountVectorizer
CountVectorizer 根據(jù)文本構(gòu)建出一個(gè)詞表,詞表中包含了所有文本中的單詞,每一個(gè)詞匯對(duì)應(yīng)其出現(xiàn)的順序,構(gòu)建出的詞向量的每一維都代表這一維對(duì)應(yīng)單詞出現(xiàn)的頻次,這些詞向量組成的矩陣稱為頻次矩陣。但 CountVectorizer 只能表達(dá)詞在當(dāng)前文本中的重要性,無(wú)法表示該詞在整個(gè)文檔集合中的重要程度。
TF-idfVectorizer
TF-idfVectorizer 則在CountVectorizer 的基礎(chǔ)上計(jì)算了詞的詞頻和逆文檔頻率,將詞的 TF-IDF 權(quán)值作為該詞對(duì)應(yīng)維度的值以區(qū)分詞的重要性。相比之下,數(shù)據(jù)集越大,利用 TF-idfVectorizer 進(jìn)行文本向量化就更有優(yōu)勢(shì)。
潛語(yǔ)義分析模型(Latent Semantic Analysis)
文檔的詞向量空間轉(zhuǎn)化為語(yǔ)義級(jí)別的向量空間,使用主成分分析方法降維,抽取向量空間內(nèi)分布方差最大的若干個(gè)正交方向來(lái)作為最后的表示方向,并對(duì)其余方向的內(nèi)容進(jìn)行丟棄。
SVD奇異值分解
PPMI
2.分布式表示
每個(gè)詞根據(jù)上下文從高維映射到一個(gè)低維度、稠密的向量上,向量的維度需要指定。在構(gòu)成的向量空間中,每個(gè)詞的含義都可以用周邊的詞來(lái)表示,優(yōu)點(diǎn)是考慮到了詞之間存在的相似關(guān)系,減小了詞向量的維度。常用的方法包括:
- 基于矩陣的分布表示
- 基于聚類的分布表示
- 基于神經(jīng)網(wǎng)絡(luò)的分布表示,其特點(diǎn)是:①利用了激活函數(shù)及softmax函數(shù)中的非線性特點(diǎn) ②保留了語(yǔ)序信息
Word2vec
Word2vec是Google的開(kāi)源項(xiàng)目,其特點(diǎn)是將所有的詞向量化,這樣詞與詞之間就可以定量度量。Word2vec以詞嵌入為基礎(chǔ),利用深度學(xué)習(xí)的思想,對(duì)出現(xiàn)在上下文環(huán)境中的詞進(jìn)行預(yù)測(cè),經(jīng)過(guò)Word2vec訓(xùn)練后的詞向量可以很好度量詞與詞之間的相似性,將所有詞語(yǔ)投影到k維的向量空間,每個(gè)詞語(yǔ)都可以用一個(gè)k維向量表示,進(jìn)而將文本內(nèi)容的處理簡(jiǎn)化為K維向量空間中的向量運(yùn)算。
已有的預(yù)訓(xùn)練詞向量:騰訊AI實(shí)驗(yàn)室 Embedding Dataset,該語(yǔ)料庫(kù)為超過(guò)800萬(wàn)個(gè)中文單詞和短語(yǔ)提供了200維矢量表示,這些單詞和短語(yǔ)是在大規(guī)模高質(zhì)量數(shù)據(jù)上預(yù)先訓(xùn)練的。
Word2vec有Continuous Bag-of-Words(CBOW)
和Skip-gram
兩種訓(xùn)練模型,這兩種模型可以看做簡(jiǎn)化的三層神經(jīng)網(wǎng)絡(luò),主要包括輸入層、隱藏層以及輸出層。CBOW對(duì)小型數(shù)據(jù)庫(kù)比較合適,而Skip-Gram在大型語(yǔ)料中表現(xiàn)更好。
- CBOW:訓(xùn)練輸入是某一個(gè)特征詞上下文相關(guān)的詞對(duì)應(yīng)的詞向量,輸出就是這特定的一個(gè)詞的詞向量。
- Skip-gram:和CBOW模型相反,輸入是中心詞,輸出是上下文。
import jieba
from gensim.models import word2vec
# 去掉標(biāo)點(diǎn)符號(hào)
def clearSen(comment):
comment = ' '.join(comment).replace(',', '').replace('。', '').replace('?', '').replace('“', '').replace('”', '').replace('.', '').replace('(', '').replace(':', '').replace('…', '').replace('...', '').replace('、', '')
return comment
# 讀取數(shù)據(jù)并分詞
file_path = "D:/Pythonworkspace/test.txt"
with open(file_path,encoding="utf-8") as f:
docs = f.readlines()
docs = clearSen(docs)
words = ' '.join(jieba.cut(docs))
with open("D:/Pythonworkspace/word.txt",'w',encoding='utf-8') as f:
f.write(words)
sentences = word2vec.Text8Corpus(r'D:/Pythonworkspace/word.txt')
# 基于當(dāng)前語(yǔ)料集訓(xùn)練模型
model = word2vec.Word2Vec(sentences, size=50, window=5, workers=4, min_count=3)
# 獲取詞向量
print(model['骨科'])
# 查看向量化之后的距離
for i in model.most_similar(u"骨科"):
print(i[0],i[1])
用gensim
函數(shù)庫(kù)訓(xùn)練Word2Vec
模型有很多配置參數(shù),這里進(jìn)行簡(jiǎn)單說(shuō)明:
class gensim.models.word2vec.Word2Vec(sentences=None,size=100,alpha=0.025,window=5, min_count=5, max_vocab_size=None, sample=0.001,seed=1, workers=3,min_alpha=0.0001, sg=0, hs=0, negative=5, cbow_mean=1, hashfxn=<built-in function hash>,iter=5,null_word=0, trim_rule=None, sorted_vocab=1, batch_words=10000)
參數(shù):
-
sentences
:可以是一個(gè)list,對(duì)于大語(yǔ)料集,建議使用BrownCorpus,Text8Corpus或LineSentence構(gòu)建。 -
sg
: 用于設(shè)置訓(xùn)練算法,默認(rèn)為0,對(duì)應(yīng)CBOW算法;sg=1則采用skip-gram算法。 -
size
:是指特征向量的維度,默認(rèn)為100。值太小會(huì)導(dǎo)致詞映射因?yàn)闆_突而影響結(jié)果,值太大則會(huì)耗內(nèi)存并使算法計(jì)算變慢,一般值取為100到200之間 -
window
:句子中當(dāng)前詞與目標(biāo)詞之間的最大距離,3表示在目標(biāo)詞前看3-b個(gè)詞,后面看b個(gè)詞(b在0-3之間隨機(jī)) -
alpha
: 學(xué)習(xí)速率 -
seed
:用于隨機(jī)數(shù)發(fā)生器,與初始化詞向量有關(guān)。 -
min_count
: 可以對(duì)字典做截?cái)?,詞頻少于min_count次數(shù)的單詞會(huì)被丟棄掉,默認(rèn)值為5 -
max_vocab_size
: 設(shè)置詞向量構(gòu)建期間的RAM限制。如果所有獨(dú)立單詞個(gè)數(shù)超過(guò)這個(gè),則就消除掉其中最不頻繁的一個(gè)。每一千萬(wàn)個(gè)單詞需要大約1GB的RAM。設(shè)置成None則沒(méi)有限制。 -
sample
: 高頻詞匯的隨機(jī)降采樣的配置閾值,默認(rèn)為1e-3,范圍是(0,1e-5) -
workers
: 參數(shù)控制訓(xùn)練的并行數(shù)。 -
hs
: 如果為1則會(huì)采用hierarchica·softmax技巧。如果設(shè)置為0(defau·t),則negative sampling會(huì)被使用。 -
negative
: 如果>0,則會(huì)采用negativesampling,用于設(shè)置多少個(gè)noise words -
cbow_mean
: 如果為0,則采用上下文詞向量的和,如果為1(default)則采用均值。只有使用CBOW的時(shí)候才起作用。 -
hashfxn
: hash函數(shù)來(lái)初始化權(quán)重。默認(rèn)使用python的hash函數(shù) -
iter
: 迭代次數(shù),默認(rèn)為5 -
trim_rule
: 用于設(shè)置詞匯表的整理規(guī)則,指定那些單詞要留下,哪些要被刪除??梢栽O(shè)置為None(min_count會(huì)被使用)或者一個(gè)接受()并返回RU·E_DISCARD,uti·http://s.RU·E_KEEP或者uti·http://s.RU·E_DEFAU·T的函數(shù)。 -
sorted_vocab
:如果為1(defau·t),則在分配word index 的時(shí)候會(huì)先對(duì)單詞基于頻率降序排序。 -
batch_words
:每一批的傳遞給線程的單詞的數(shù)量,默認(rèn)為10000
在上面的代碼中,使用了gensim
庫(kù)中的word2vec
,對(duì)分好詞后的語(yǔ)料集進(jìn)行向量化,并可以計(jì)算出詞與詞之間的相似性,例如在該語(yǔ)料集中,與“骨科”最相似的詞有:
doc2vec
PV-DM(Distributed Memory version of Paragraph Vector)
沿用了word2vec中CBOW的方法,通過(guò)一個(gè)單層的簡(jiǎn)單神經(jīng)網(wǎng)絡(luò)來(lái)構(gòu)建模型,在構(gòu)建隱藏層時(shí)將段落向量也加入隱藏層,并且與其他的詞向量一起獲得反向傳播的梯度
PV-DBOW(Distributed Bags of Words version of Paragraph Vector)
沿用了word2vec中Skip-gram的方法,以文檔向量作為輸入,以最大化輸出文檔中的詞作為目標(biāo)進(jìn)行訓(xùn)練。
GloVec
附:通過(guò)已保存的訓(xùn)練好的詞向量模型,得到word embedding
# load the pre-trained word-embedding vectors
embeddings_index = {}
for i, line in enumerate(open('wiki-news-300d-1M.vec', 'r', encoding='UTF-8')):
values= line.split()
embeddings_index[values[0]] = np.asarray(values[1:], dtype='float32')
len(embeddings_index) # 999995
# create atokenizer
token = text.Tokenizer()
token.fit_on_texts(自己的語(yǔ)料)
word_index = token.word_index # 單詞對(duì)應(yīng)的index
index_docs = token.index_docs # index對(duì)應(yīng)單詞出現(xiàn)的文檔的數(shù)量
word_docs = token.word_docs # 單詞出現(xiàn)的文檔的數(shù)量
word_counts = token.word_counts # 單詞在所有文檔中出現(xiàn)的總次數(shù)
# convert text tosequence of tokens andpad them toensure equal length vectors
train_seq_x = sequence.pad_sequences(token.texts_to_sequences(Train_X_abs))
test_seq_x = sequence.pad_sequences(token.texts_to_sequences(Test_X_abs))
# create token-embedding mapping
# embedding_matrix = np.zeros((len(word_index) + 1, 300))
embedding_matrix = pd.DataFrame()
for word, i in word_index.items():
word = word.replace("'","")
embedding_vector = embeddings_index.get(word)
if embedding_vector is not None:
embedding_matrix[word] = embedding_vector
print(embedding_matrix['system'])