❝We can train fastText on more than one billion words in less than ten minutes using a standard multicore CPU, and classify half a million sentences among 312K classes in less than a minute.
首先引用论文中的一段话来看看作者们是怎么评价fasttext模型的表现的。
这篇论文的模型非常之简单,之前了解过word2vec的同学可以发现这跟CBOW的模型框架非常相似。
对应上面这个模型,比如输入是一句话,到就是这句话的单词或者是n-gram。每一个都对应一个向量,然后对这些向量取平均就得到了文本向量,然后用这个平均向量取预测标签。当类别不多的时候,就是最简单的softmax;当标签数量巨大的时候,就要用到「hierarchical softmax」了。
模型真的很简单,也没什么可以说的了。下面提一下论文中的两个tricks:
这篇博客将会简要记录使用python版本的fastText对不同类别新闻进行分类,中间会使用结巴分词,pandas的数据处理。新闻数据可以使用清华的新闻数据。
Python版本:3.6 安装结巴分词以及fasttext
pip install jieba
pip install fasttext
分词过程中会删除一些常用的停用词,停用词可以使用https://github.com/dongxiexidian/Chinese/tree/master/dict
segmentation.py
import jieba
import pandas as pd
import codecs
import math
import random
stopwords_set = set()
basedir = '/Users/derry/Desktop/Data/'
# 分词结果文件
train_file = codecs.open(basedir + "news.data.seg.train", 'w', 'utf-8')
test_file = codecs.open(basedir + "news.data.seg.test", 'w', 'utf-8')
# 停用词文件
with open(basedir + 'stop_text.txt', 'r', encoding='utf-8') as infile:
for line in infile:
stopwords_set.add(line.strip())
train_data = pd.read_table(basedir + 'News_info_train.txt', header=None, error_bad_lines=False)
label_data = pd.read_table(basedir + 'News_pic_label_train.txt', header=None, error_bad_lines=False)
train_data.drop([2], axis=1, inplace=True)
train_data.columns = ['id', 'text']
label_data.drop([2, 3], axis=1, inplace=True)
label_data.columns = ['id', 'class']
train_data = pd.merge(train_data, label_data, on='id', how='outer')
for index, row in train_data.iterrows():
# 结巴分词
seg_text = jieba.cut(row['text'].replace("\t", " ").replace("\n", " "))
outline = " ".join(seg_text)
outline = " ".join(outline.split())
# 去停用词与HTML标签
outline_list = outline.split(" ")
outline_list_filter = [item for item in outline_list if item not in stopwords_set]
outline = " ".join(outline_list_filter)
# 写入
if not math.isnan(row['class']):
outline = outline + "\t__label__" + str(int(row['class'])) + "\n"
train_file.write(outline)
train_file.flush()
# 划分数据集
# if random.random() > 0.7:
# test_file.write(outline)
# test_file.flush()
# else:
# train_file.write(outline)
# train_file.flush()
train_file.close()
test_file.close()
这里使用fasttext进行训练的时候调整了一下参数word_ngrams,原本默认值为1,效果可能会好一点。不过要在后面加上bucket=2000000(默认值) ,不然会出错,在issue里面查了一下,好像是Python版本的fasttext版本比较旧,使用官方C++版就不会出现这个问题了。
classification.py
import logging
import fasttext
import pandas as pd
import codecs
basedir = '/Users/derry/Desktop/Data/'
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
# 训练
classifier = fasttext.supervised(basedir + "news.data.seg.train", basedir + "news.dat.seg.model", label_prefix="__label__", word_ngrams=3, bucket=2000000)
# 测试并输出 F-score
result = classifier.test(basedir + "news.data.seg.test")
print(result.precision * result.recall * 2 / (result.recall + result.precision))
# 读取验证集
validate_texts = []
with open(basedir + 'news.data.seg.validate', 'r', encoding='utf-8') as infile:
for line in infile:
validate_texts += [line]
# 预测结果
labels = classifier.predict(validate_texts)
# 结果文件
result_file = codecs.open(basedir + "result.txt", 'w', 'utf-8')
validate_data = pd.read_table(basedir + 'News_info_validate.txt', header=None, error_bad_lines=False)
validate_data.drop([2], axis=1, inplace=True)
validate_data.columns = ['id', 'text']
# 写入
for index, row in validate_data.iterrows():
outline = row['id'] + '\t' + labels[index][0] + '\tNULL\tNULL\n'
result_file.write(outline)
result_file.flush()
result_file.close()
最后附上GitHub地址:https://github.com/DerryChan/CSIT6000/tree/master/Derry
训练参数
def train_supervised(input, lr=0.1, dim=100,
ws=5, epoch=5, minCount=1,
minCountLabel=0, minn=0,
maxn=0, neg=5, wordNgrams=1,
loss="softmax", bucket=2000000,
thread=12, lrUpdateRate=100,
t=1e-4, label="__label__",
verbose=2, pretrainedVectors=""):
"""
训练一个监督模型, 返回一个模型对象
@param input: 训练数据文件路径
@param lr: 学习率
@param dim: 向量维度
@param ws: cbow模型时使用
@param epoch: 次数
@param minCount: 词频阈值, 小于该值在初始化时会过滤掉
@param minCountLabel: 类别阈值,类别小于该值初始化时会过滤掉
@param minn: 构造subword时最小char个数
@param maxn: 构造subword时最大char个数
@param neg: 负采样
@param wordNgrams: n-gram个数
@param loss: 损失函数类型, softmax, ns: 负采样, hs: 分层softmax
@param bucket: 词扩充大小, [A, B]: A语料中包含的词向量, B不在语料中的词向量
@param thread: 线程个数, 每个线程处理输入数据的一段, 0号线程负责loss输出
@param lrUpdateRate: 学习率更新
@param t: 负采样阈值
@param label: 类别前缀
@param verbose: ??
@param pretrainedVectors: 预训练的词向量文件路径, 如果word出现在文件夹中初始化不再随机
@return model object
"""
模型保存与加载
# 保存模型
model.save_model("fasttext.model.bin")
# 压缩模型
model.quantize(input=train_data, qnorm=True, retrain=True, cutoff=100000)
print_results(*model.test(valid_data))
model.save_model("fasttext.model.ftz") # 保存压缩后的模型
# 加载模型
model= fasttext.load_model("fasttext.model.bin",label_prefix = "__label__")
词向量训练
def train_unsupervised(input, model="skipgram", lr=0.05, dim=100,
ws=5, epoch=5, minCount=5,
minCountLabel=0, minn=3,
maxn=6, neg=5, wordNgrams=1,
loss="ns", bucket=2000000,
thread=12, lrUpdateRate=100,
t=1e-4, label="__label__",
verbose=2, pretrainedVectors=""):
"""
训练词向量,返回模型对象
输入数据不要包含任何标签和使用标签前缀
@param model: 模型类型, cbow/skipgram两种
其他参数参考train_supervised()方法
@return model
"""