FastText:快速的文字分類器
一、簡介
fasttext是facebook開源的一個詞向量與文字分類工具,在2016年開源,典型應用場景是“帶監督的文字分類問題”。提供簡單而高效的文字分類和表徵學習的方法,效能比肩深度學習而且速度更快。
fastText結合了自然語言處理和機器學習中最成功的理念。這些包括了使用詞袋以及n-gram袋錶徵語句,還有使用子字(subword)資訊,並通過隱藏表徵在類別間共享資訊。我們另外採用了一個softmax層級(利用了類別不均衡分佈的優勢)來加速運算過程。
這些不同概念被用於兩個不同任務:
- 有效文字分類 :有監督學習
- 學習詞向量表徵:無監督學習
舉例來說:fastText能夠學會“男孩”、“女孩”、“男人”、“女人”指代的是特定的性別,並且能夠將這些數值存在相關文件中。然後,當某個程式在提出一個使用者請求(假設是“我女友現在在兒?”),它能夠馬上在fastText生成的文件中進行查詢並且理解使用者想要問的是有關女性的問題。
二、FastText原理
fastText方法包含三部分,模型架構,層次SoftMax和N-gram特徵。
2.1 模型架構
fastText的架構和word2vec中的CBOW的架構類似,因為它們的作者都是Facebook的科學家Tomas Mikolov,而且確實fastText也算是words2vec所衍生出來的。
Continuous Bog-Of-Words:
fastText
fastText 模型輸入一個詞的序列(一段文字或者一句話),輸出這個詞序列屬於不同類別的概率。
序列中的詞和片語組成特徵向量,特徵向量通過線性變換對映到中間層,中間層再對映到標籤。
fastText 在預測標籤時使用了非線性啟用函式,但在中間層不使用非線性啟用函式。
fastText 模型架構和 Word2Vec 中的 CBOW 模型很類似。不同之處在於,fastText 預測標籤,而 CBOW 模型預測中間詞。
2.2 層次SoftMax
對於有大量類別的資料集,fastText使用了一個分層分類器(而非扁平式架構)。不同的類別被整合進樹形結構中(想象下二叉樹而非 list)。在某些文字分類任務中類別很多,計算線性分類器的複雜度高。為了改善執行時間,fastText 模型使用了層次 Softmax 技巧。層次 Softmax 技巧建立在哈弗曼編碼的基礎上,對標籤進行編碼,能夠極大地縮小模型預測目標的數量。
fastText 也利用了類別(class)不均衡這個事實(一些類別出現次數比其他的更多),通過使用 Huffman 演算法建立用於表徵類別的樹形結構。因此,頻繁出現類別的樹形結構的深度要比不頻繁出現類別的樹形結構的深度要小,這也使得進一步的計算效率更高。
2.3 N-gram特徵
fastText 可以用於文字分類和句子分類。不管是文字分類還是句子分類,我們常用的特徵是詞袋模型。但詞袋模型不能考慮詞之間的順序,因此 fastText 還加入了 N-gram 特徵。“我 愛 她” 這句話中的詞袋模型特徵是 “我”,“愛”, “她”。這些特徵和句子 “她 愛 我” 的特徵是一樣的。如果加入 2-Ngram,第一句話的特徵還有 “我-愛” 和 “愛-她”,這兩句話 “我 愛 她” 和 “她 愛 我” 就能區別開來了。當然啦,為了提高效率,我們需要過濾掉低頻的 N-gram。
三、 基於fastText實現文字分類
3.1 fastText有監督學習分類
fastText做文字分類要求文字是如下的儲存形式:
__label__2 , birchas chaim , yeshiva birchas chaim is a orthodox jewish mesivta high school in lakewood township new jersey . it was founded by rabbi shmuel zalmen stein in 2001 after his father rabbi chaim stein asked him to open a branch of telshe yeshiva in lakewood . as of the 2009-10 school year the school had an enrollment of 76 students and 6 . 6 classroom teachers ( on a fte basis ) for a student–teacher ratio of 11 . 5 1 .
__label__6 , motor torpedo boat pt-41 , motor torpedo boat pt-41 was a pt-20-class motor torpedo boat of the united states navy built by the electric launch company of bayonne new jersey . the boat was laid down as motor boat submarine chaser ptc-21 but was reclassified as pt-41 prior to its launch on 8 july 1941 and was completed on 23 july 1941 .
__label__11 , passiflora picturata , passiflora picturata is a species of passion flower in the passifloraceae family .
__label__13 , naya din nai raat , naya din nai raat is a 1974 bollywood drama film directed by a . bhimsingh . the film is famous as sanjeev kumar reprised the nine-role epic performance by sivaji ganesan in navarathri ( 1964 ) which was also previously reprised by akkineni nageswara rao in navarathri ( telugu 1966 ) . this film had enhanced his status and reputation as an actor in hindi cinema .
其中前面的label是字首,也可以自己定義,label後接的為類別。
具體程式碼:
# -*- 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("[email protected]:",result.precision) #準確率
print("[email protected]:",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)
3.2 fastText有監督學習分類
# -*- 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,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(" ".join(segs))
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,stopwords)
preprocess_text(car,sentences,stopwords)
preprocess_text(entertainment,sentences,stopwords)
preprocess_text(military,sentences,stopwords)
preprocess_text(sports,sentences,stopwords)
random.shuffle(sentences) #做亂序處理,使得同類別的樣本不至於扎堆
writeData(sentences,saveDataFile)
if __name__=="__main__":
stopwordsFile=r"./data/stopwords.txt"
stopwords=getStopWords(stopwordsFile)
saveDataFile=r'unsupervised_train_data.txt'
preprocessData(stopwords,saveDataFile)
#fasttext.load_model:不管是有監督還是無監督的,都是載入一個模型
#fasttext.skipgram(),fasttext.cbow()都是無監督的,用來訓練詞向量的
model=fasttext.skipgram('unsupervised_train_data.txt','model')
print(model.words) #列印詞向量
#cbow model
model=fasttext.cbow('unsupervised_train_data.txt','model')
print(model.words) #列印詞向量
三、總結
3.1 fastText和word2vec的區別
相似處:
- 圖模型結構很像,都是採用embedding向量的形式,得到word的隱向量表達。
- 都採用很多相似的優化方法,比如使用Hierarchical softmax優化訓練和預測中的打分速度。
不同處:
- 模型的輸出層:word2vec的輸出層,對應的是每一個term,計算某term的概率最大;而fasttext的輸出層對應的是 分類的label。不過不管輸出層對應的是什麼內容,起對應的vector都不會被保留和使用;
- 模型的輸入層:word2vec的輸出層,是 context window 內的term;而fasttext 對應的整個sentence的內容,包括term,也包括 n-gram的內容;
兩者本質的不同,體現在 h-softmax的使用:
- Wordvec的目的是得到詞向量,該詞向量 最終是在輸入層得到,輸出層對應的 h-softmax
也會生成一系列的向量,但最終都被拋棄,不會使用。 - fasttext則充分利用了h-softmax的分類功能,遍歷分類樹的所有葉節點,找到概率最大的label(一個或者N個)
3.2 小結
總的來說,fastText的學習速度比較快,效果還不錯。fastText適用與分類類別非常大而且資料集足夠多的情況,當分類類別比較小或者資料集比較少的話,很容易過擬合。
可以完成無監督的詞向量的學習,可以學習出來詞向量,來保持住詞和詞之間,相關詞之間是一個距離比較近的情況;
也可以用於有監督學習的文字分類任務,(新聞文字分類,垃圾郵件分類、情感分析中文字情感分析,電商中使用者評論的褒貶分析)