FastText的精髓在于將整篇檔案的詞及n-gram向量疊加平均得到檔案向量,然后使用檔案向量做softmax多分類,
上面是業界大佬們對FastText模型的高度總結,看起來是不是特別簡單?然而,模型的輸入到底是什么?為什么要引入n-gram向量?檔案的詞以及n-gram向量是怎么疊加的?為什么它訓練速度快并且性能很好?這些問題一直困擾著我,今天就來深扒一下,
一、理論基礎
FastText是facebook開源的一個快速文本分類器,在提供簡單高效的文本分類和表征學習方法的同時,性能比肩深度學習并且訓練速度非常快,往往可以作為文本分類場景下的baseline,其模型非常簡單,和word2vec的cbow模型很相似,不同點在于cbow預測的是中心詞,而fasttext預測的是文本標簽,

上圖是論文中模型的架構,其模型輸入 = 句子本身 + n-gram額外特征,舉個例子:我喜歡她,我們對這句話分詞后得到:我,喜歡,她,其對應的bi-gram特征為:我喜歡,喜歡他,那么模型的輸入變為:我,喜歡,她, 我喜歡,喜歡她,這樣做一方面引入了更多字符特征,另一方面解決了詞順序的問題,畢竟我喜歡她和她喜歡我還不是同一個意思,Tri-gram以此類推,
由于每個句子的長度不同,為了便于建模,需要把每個句子填充到相同的長度,如果只使用常見的10000的詞(資料預處理后),我們需要把它映射到2-10002之間的整數索引,其中1是留給未登錄詞,0是用來填充長度,此外,如果資料量很大且句子較長,會引起n-gram資料組合爆炸的問題,原論文中通過采用hash映射到1到K之間來解決這個問題(具體見下面的模型優化章節),同時為了避免和前面的漢字索引出現沖突,哈希映射值一般會加上最大長度的值,即映射到10003-10003+k之間,所以模型的輸入長度 = 句子填充長度 + 哈希映射值 = 10003 + k,
資料轉換索引后,模型會經過Embedding層,將索引映射為稠密向量,那么模型是如何求平均的呢?這里參考Keras官方實作的fasttext的文本分類檔案:
model = Sequential()
# 我們從有效的嵌入層開始,該層將 vocab 索引映射到 embedding_dims 維度
model.add(Embedding(max_features,
embedding_dims,
input_length=maxlen))
# 我們添加了 GlobalAveragePooling1D,它將對檔案中所有單詞執行平均嵌入
model.add(GlobalAveragePooling1D())
# 我們投影到單個單位輸出層上,并用 sigmoid 壓扁它:
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy'])
這里采用了GlobalAveragePooling1D()來對所有檔案執行嵌入,它會把所有詞的Embedding的向量求平均得到一個向量,即把我,喜歡,她,我喜歡,喜歡她這5個詞的Embedding求和再除以5,得到均值向量,然后跟上輸出層,
在模型優化上,作者主要采用了兩種加速訓練的方法:
1、層次softmax: 這個并不是新穎的技術,主要是用來解決文本類別比較多時,使用softmax計算會使計算復雜度很高,大概是O(kh),其中k是文本類別樹,h是embedding維度,而層次softmax采用哈夫曼樹的方式來把復雜度降低到O(hlog2k),大大加快了訓練速度,具體細節不鋪開討論,
2、其次,為了節省記憶體和n-gram組合爆炸的問題,fasttext把n-gram資料構成一個詞典,并通過哈希函式映成整數(索引)至1到K,理論上哈希到同一個位置的不同n-gram應該共享索引和Embedding(待驗證)

二、代碼實戰
1、安裝fasttext:
git clone https://github.com/facebookresearch/fastText.git
cd fastText
pip install .
如果安裝不上,可以用gensim包中的fasttext,
2、資料簡介:
以天池新聞文本分類資料集為資料集,其中訓練集有20萬條樣本,測驗集有AB兩個版本各5萬條樣本,為避免選手自行打標,對資料按字符進行了匿名處理,可以理解為把漢字轉化成了索引,樣本如下圖所示:

資料匿名處理其實幫我們省略了很多資料預處理的作業,比如說去除標點符號、去除停用詞等等,但是可能也難以達到特別好的精度,
資料統計總共有14個類別,是個典型的文本多分類問題,評估指標為f1-score ,
3、模型引數:
input # training file path (required)
lr # learning rate [0.1]
dim # size of word vectors [100]
ws # size of the context window [5]
epoch # number of epochs [5]
minCount # minimal number of word occurences [1]
minCountLabel # minimal number of label occurences [1]
minn # min length of char ngram [0]
maxn # max length of char ngram [0]
neg # number of negatives sampled [5]
wordNgrams # max length of word ngram [1]
loss # loss function {ns, hs, softmax, ova} [softmax]
bucket # number of buckets [2000000]
thread # number of threads [number of cpus]
lrUpdateRate # change the rate of updates for the learning rate [100]
t # sampling threshold [0.0001]
label # label prefix ['__label__']
verbose # verbose [2]
pretrainedVectors
4、完整代碼
import fasttext
import pandas as pd
from sklearn.utils import shuffle
class DataProcess(object):
def load_data(self):
df_train = pd.read_csv('train_set.csv', sep='\t')
# 對類別加上 "__label__"前綴
df_train['label_ft'] = '__label__' + df_train['label'].astype(str)
df_train[['text', 'label_ft']].iloc[:195000].to_csv('train.csv', index=None, header=None, sep='\t')
return df_train
def split_data(self, df_train):
# 打亂資料集
df_train = shuffle(df_train)
# 訓練集
train_data = df_train[['text', 'label_ft']].iloc[:195000]
train_data.to_csv('train.csv', index=None, header=None, sep='\t')
# 挑選5000條資料作為驗證集
validate_data = df_train[['text', 'label_ft']].iloc[-5000:]
validate_data.to_csv('validate.csv', index=None, header=None, sep='\t')
class FastTextModel(object):
def __init__(self, ):
pass
def train(self):
model = fasttext.train_supervised(input='train.csv',
label_prefix="__label__",
epoch=30,
dim=32,
lr=0.1,
loss='softmax',
word_ngrams=3,
min_count=2,
bucket=1000000)
return model
def save_model(self, model):
model.save_model("fasttext.bin")
def load_model(self):
model = fasttext.load_model("fasttext.bin")
return model
# 預測驗證集結果
def test(self):
model = self.load_model()
score = model.test("validate.csv")
precision = score[1]
recall = score[2]
f1_score = round(2 * (precision * recall) / (precision + recall), 2)
print("驗證集評測結果:Precision:{}, Recall:{}, F1-score:{}".format(precision, recall, f1_score))
# 預測5萬條測驗集A的結果,或者測驗集B的結果提交
def predict_testA(self):
df_testA = pd.read_csv("test_a.csv")
test_data = df_testA["text"].values.tolist()
model = self.load_model()
res = model.predict(test_data)
predict_res = [y_[0].replace("__label__", "") for y_ in res[0]]
print(predict_res)
predict_label = pd.Series(predict_res, name="label")
predict_label.to_csv("predict_label.csv", index=False)
if __name__ == '__main__':
data_process = DataProcess()
fasttext_model = FastTextModel()
df_train = data_process.load_data()
data_process.split_data(df_train)
model = fasttext_model.train()
fasttext_model.save_model(model)
fasttext_model.test()
fasttext_model.predict_testA()
模型沒怎么調參,F1-score達到了0.95:
驗證集評測結果:Precision:0.9466, Recall:0.9466, F1-score:0.95
由于資料量比較大,可以移步公眾號:一路向AI,回復文本分類獲取,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/169130.html
標籤:其他
