1. FastText原理
fastText是一種簡單高效的文本表徵方法,性能與深度學習比肩。fastText的核心思想就是:將整篇文檔的詞及n-gram向量疊加平均得到文檔向量,然後使用文檔向量做softmax多分類。這中間涉及到兩個技巧:字符級n-gram特徵的引入以及分層Softmax分類。主要功能在於:
- 文本分類:有監督學習
- 詞向量表徵:無監督學習
1.1 模型框架(Model architecture)
fastText的結構與word2vec的CBOW模型架構相似(fastText的開源工具不僅可以文本分類,還可以訓練詞向量,與word2vec相似)。word2vec有兩種模型:skip-gram 模型和CBOW模型。兩者的區別概括區別是:skip-gram,用當前詞來預測上下文;CBOW,用上下文來預測當前詞。
CBOW及fastText的模型框架對比如下:
CBOW模型框架
|
fastText模型框架
|
1.2 層次softmax(Hierarchical softmax)
1.2.1 標準softmax函數
softmax函數又稱爲歸一化指數函數,常在神經網絡輸出層充當激活函數,它是二分類函數sigmoid在多分類上的推廣,目的是將多分類的結果以概率的形式展現出來。下圖爲softma x的計算過程:
softmax的作用:
- 將預測結果轉化爲非負數;
- 各種預測結果概率之和等於1;
1.2.2 層次softmax函數
標準的softmax中,計算一個類別的softmax概率時,需要對所有的類別概率做歸一化,這在類別數量很大時會很耗時;分層softmax(Hierarchical Softmax)的目的就是提高計算效率,方法是構造霍夫曼樹來代替標準softmax,只需計算一條路徑上的所有節點的概率值,無需在意其它的節點。通過分層softmax可以將複雜度從N降低到logN。
霍夫曼樹:
給定n個權值作爲n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹爲最優二叉樹,也稱爲霍夫曼樹(Huffman Tree)。
如左圖所示兩棵二叉樹,葉子結點爲A、B、C、D,對應權值分別爲7、5、2、4。樹的帶權路徑長度規定爲所有葉子結點的帶權路徑長度之和,記爲WPL。
葉子結點爲A、B、C、D,對應權值分別爲7、5、2、4。
- 左樹的WPL = 7 * 2 + 5 * 2 + 2 * 2 + 4 * 2 = 36
- 右樹的WPL = 7 * 1 + 5 * 2 + 2 * 3 + 4 * 3 = 35
由ABCD構成葉子結點的二叉樹形態有許多種,但是WPL最小的樹只有右樹所示的形態。則右樹爲一棵霍夫曼樹。
下圖是一個層次softmax:
樹的結構是根據類別標記的頻數構造的霍夫曼樹。K個不同的類標組成所有的葉子節點,從根節點到某個葉子節點經過的節點和邊形成一條路徑,路徑長度爲。需要計算目標詞的概率,這個概率的具體含義,是指從根結點開始隨機走,走到目標詞的概率,非葉子結點處需要分別知道往左走和往右走的概率。例如到達非葉子節點n的時候往左邊走和往右邊走的概率分別是:
圖中標記的路徑是從根節點到葉子節點的路徑,路徑長度,節點的概率可以表示爲:
從根節點走到葉子節點 ,實際上是在做了3次二分類的邏輯迴歸;通過分層的softmax,計算複雜度從|K|降低到log|K|。
1.3 N-gram特徵(N-gram features)
原始文本是一個單詞序列,一般的詞袋錶示中沒有任何序列,它只記錄每個單詞在文本中出現的次數。因此 fastText 還加入了 N-gram 特徵,基本思想是將文本內容按照字節順序進行大小爲N的滑動窗口操作,最終形成長度爲N的字節片段序列。n-gram可以是字粒度,也可以是詞粒度的。n-gram產生的特徵只是作爲文本特徵的候選集,後面可能會採用信息熵、卡方統計、IDF等文本特徵選擇方式篩選出比較重要特徵。
bigram特徵示例:我來到北京旅遊
- 字粒度:我來 來到 到北 北京 京旅 旅遊
- 詞粒度:我/來到 來到/北京 北京/旅遊
n-gram有如下優點:
- 保持詞序信息:n-gram可以讓模型學習到局部單詞順序的部分信息;
- 處理低頻詞:字符級別的n-gram,即使這個單詞出現的次數很少,但是組成單詞的字符和其他單詞有共享的部分,可以優化生成的單詞向量;
- 處理未出現過的詞:字符級n-gram,即使單詞沒有出現在訓練語料庫中,仍然可以從字符級n-gram中構造單詞的詞向量;
2.fastText文本分類實踐
fasttext官網:https://fasttext.cc/
中文社區:http://fasttext.apachecn.org/#/doc/zh/support
fastText 支持的不同用例:
The commands supported by fasttext are:
supervised 訓練一個監督分類器
quantize 量化模型以減少內存使用量
test 評估一個監督分類器
predict 預測最有可能的標籤
predict-prob 用概率預測最可能的標籤
skipgram 訓練一個 skipgram 模型
cbow 訓練一個 cbow 模型
print-word-vectors 給定一個訓練好的模型,打印出所有的單詞向量
print-sentence-vectors 給定一個訓練好的模型,打印出所有的句子向量
nn 查詢最近鄰居
analogies 查找所有同類詞
fasttext.supervised 參數如下
input_file 訓練文件路徑(必須)
output 輸出文件路徑(必須)
label_prefix 標籤前綴 default __label__
lr 學習率 default 0.1
lr_update_rate 學習率更新速率 default 100
dim 詞向量維度 default 100
ws 上下文窗口大小 default 5
epoch epochs 數量 default 5
min_count 最低詞頻 default 5
word_ngrams n-gram 設置 default 1
loss 損失函數 {ns,hs,softmax} default softmax
minn 最小字符長度 default 0
maxn 最大字符長度 default 0
thread 線程數量 default 12
t 採樣閾值 default 0.0001
silent 禁用 c++ 擴展日誌輸出 default 1
encoding 指定 input_file 編碼 default utf-8
pretrained_vectors 指定使用已有的詞向量 .vec 文件 default None
先貼出其他博客不錯的代碼,後面給出實例:
# -*- coding:utf-8 -*-
import pandas as pd
import random
import fasttext
import jieba
from sklearn.model_selection import train_test_split
cate_dic = {'technology': 1, 'car': 2, 'entertainment': 3, 'military': 4, 'sports': 5}
"""
函數說明:加載數據
"""
def loadData():
#利用pandas把數據讀進來
df_technology = pd.read_csv("./data/technology_news.csv",encoding ="utf-8")
df_technology=df_technology.dropna() #去空行處理
df_car = pd.read_csv("./data/car_news.csv",encoding ="utf-8")
df_car=df_car.dropna()
df_entertainment = pd.read_csv("./data/entertainment_news.csv",encoding ="utf-8")
df_entertainment=df_entertainment.dropna()
df_military = pd.read_csv("./data/military_news.csv",encoding ="utf-8")
df_military=df_military.dropna()
df_sports = pd.read_csv("./data/sports_news.csv",encoding ="utf-8")
df_sports=df_sports.dropna()
technology=df_technology.content.values.tolist()[1000:21000]
car=df_car.content.values.tolist()[1000:21000]
entertainment=df_entertainment.content.values.tolist()[:20000]
military=df_military.content.values.tolist()[:20000]
sports=df_sports.content.values.tolist()[:20000]
return technology,car,entertainment,military,sports
"""
函數說明:停用詞
參數說明:
datapath:停用詞路徑
返回值:
stopwords:停用詞
"""
def getStopWords(datapath):
stopwords=pd.read_csv(datapath,index_col=False,quoting=3,sep="\t",names=['stopword'], encoding='utf-8')
stopwords=stopwords["stopword"].values
return stopwords
"""
函數說明:去停用詞
參數:
content_line:文本數據
sentences:存儲的數據
category:文本類別
"""
def preprocess_text(content_line,sentences,category,stopwords):
for line in content_line:
try:
segs=jieba.lcut(line) #利用結巴分詞進行中文分詞
segs=filter(lambda x:len(x)>1,segs) #去掉長度小於1的詞
segs=filter(lambda x:x not in stopwords,segs) #去掉停用詞
sentences.append("__lable__"+str(category)+" , "+" ".join(segs)) #把當前的文本和對應的類別拼接起來,組合成fasttext的文本格式
except Exception as e:
print (line)
continue
"""
函數說明:把處理好的寫入到文件中,備用
參數說明:
"""
def writeData(sentences,fileName):
print("writing data to fasttext format...")
out=open(fileName,'w')
for sentence in sentences:
out.write(sentence.encode('utf8')+"\n")
print("done!")
"""
函數說明:數據處理
"""
def preprocessData(stopwords,saveDataFile):
technology,car,entertainment,military,sports=loadData()
#去停用詞,生成數據集
sentences=[]
preprocess_text(technology,sentences,cate_dic["technology"],stopwords)
preprocess_text(car,sentences,cate_dic["car"],stopwords)
preprocess_text(entertainment,sentences,cate_dic["entertainment"],stopwords)
preprocess_text(military,sentences,cate_dic["military"],stopwords)
preprocess_text(sports,sentences,cate_dic["sports"],stopwords)
random.shuffle(sentences) #做亂序處理,使得同類別的樣本不至於扎堆
writeData(sentences,saveDataFile)
if __name__=="__main__":
stopwordsFile=r"./data/stopwords.txt"
stopwords=getStopWords(stopwordsFile)
saveDataFile=r'train_data.txt'
preprocessData(stopwords,saveDataFile)
#fasttext.supervised():有監督的學習
classifier=fasttext.supervised(saveDataFile,'classifier.model',lable_prefix='__lable__')
result = classifier.test(saveDataFile)
print("P@1:",result.precision) #準確率
print("R@2:",result.recall) #召回率
print("Number of examples:",result.nexamples) #預測錯的例子
#實際預測
lable_to_cate={1:'technology'.1:'car',3:'entertainment',4:'military',5:'sports'}
texts=['中新網 日電 2018 預賽 亞洲區 強賽 中國隊 韓國隊 較量 比賽 上半場 分鐘 主場 作戰 中國隊 率先 打破 場上 僵局 利用 角球 機會 大寶 前點 攻門 得手 中國隊 領先']
lables=classifier.predict(texts)
print(lables)
print(lable_to_cate[int(lables[0][0])])
#還可以得到類別+概率
lables=classifier.predict_proba(texts)
print(lables)
#還可以得到前k個類別
lables=classifier.predict(texts,k=3)
print(lables)
#還可以得到前k個類別+概率
lables=classifier.predict_proba(texts,k=3)
print(lables)