FastText模型简解+THUCNews新闻快速分类实战

文章目录

    • 模型架构:
    • 两个特色:
    • 核心思想:
    • 为什么fasttext会快?
    • 与Word2Vec比较:
    • 实战:
      • 1-数据预处理
      • 2-模型训练
      • 3-评估模型
      • 4-自定义模型评估方法:
    • 参考:

模型架构:

和word2vec中的CBOW很相似, 即模型架构类似但是模型的任务不同。 使用fastText进行文本分类的同时也会产生词的embedding,即embedding是fastText分类的产物。

  • 与word2vec的CBOW类似。不同之处是fastText预测标签,而CBOW预测的是中间词
  • 输入层:词和子词(subword)的n-gram的特征向量
  • 隐藏层:所有词的向量叠加求平均之后经过线性变换到隐藏层
  • 输出层:Hierarchical Softmax输出文本类别

FastText模型简解+THUCNews新闻快速分类实战_第1张图片

  • CBOW输入时目标单词的上下文;fastText的输入是多个单词及其n-gram特征(这些特征表示当个文档)。
  • CBOW输入单词被onehot编码过;fastText输入特征被embedding过。
  • CBOW输出是目标词汇;fastText输出是文档对应的类标。

两个特色:

  • 输入时:字符级的n-gram:除了每个单词的词向量word embedding外,还为每个单词的n-gram字符添加一个向量,作为额外的特征。属于字粒度的n-gram,词粒度的n-gram。

    两点好处:

    1. 对于低频词生成的词向量效果会更好。因为它们的n-gram可以和其它词共享。

    2. 对于训练词库之外的单词,仍然可以构建它们的词向量。我们可以叠加它们的字符级n-gram向量。

      对于单词“apple”,假设n的取值为3,则它的trigram有

      其中,<表示前缀,>表示后缀。于是,我们可以用这些trigram来表示“apple”这个单词,进一步,我们可以用这5个trigram的向量叠加来表示“apple”的词向量。

  • **输出时:分层softmax:**利用哈夫曼树构建,根据目标类别的多少自上而下构建,数目越多的类越在顶部。(与word2vec里类似)

核心思想:

将整篇文档的所有词及n-gram的词向量,叠加平均得到文档向量,然后使用文档向量做softmax线性多分类。

也就是说模型的输入数据==embedding词向量+自设定的n-gram向量的组合!!

这中间涉及到两个技巧:字符级n-gram特征的引入以及分层Softmax分类

为什么fasttext会快?

  • hierarchical softmax 和negative sampling是对普通softmax的极大提速。
  • hs里使用huffman树,做分类一般类别没那么多,叶子节点相对word2vec(每个目标词一个叶子节点)少很多。并且语料每一行只训练一次,word2vec要每个中心词训练一次,训练次数又少了很多。当然fasttext可以设置epoch训练多轮。
  • 各种提速的trick,比如提前算好exp的取值之类的,这点和word2vec是一样的了。

与Word2Vec比较:

  • 都可以无监督学习词向量, fastText训练词向量时会考虑subword
  • fastText还可以进行有监督学习进行文本分类
  • 都利用了hierarchical softmax进行加速
  • Fasttext引入字符级n-gram可以处理长词,未出现过的词,以及低频词

实战:

1-数据预处理

采用THUCNews中体育、娱乐、家居、房产,教育、时尚、时政、游戏、科技、财经10类新闻数据;dir_list =['sports','ent','house','home','edu','fashion','affairs','game','science','economic']

THUCNews中新闻数据如下:标题+新闻文本

体育	金寅�领先LPGA泰国开幕战 曾雅妮首轮落后3杆新浪体育讯 北京时间2月17日消息,美国LPGA新赛季首战泰国本田赛在Siam球场结束首轮比赛。22岁的韩国选手金寅?打出63杆,以3杆优势单独领先。本周新升至世界第一的中国台北小将曾雅妮,以66杆,与名人堂成员朱莉-英科斯特并列第2位。中国内地小将冯珊珊交出72杆,暂时并列第26位。本场比赛总奖金145万美元,60位选手参加,72洞比赛,不设晋级线。“很高兴可以在LPGA的第一场比赛,就打出了好成绩。今天开球很早,也很幸运,天气要更凉爽一些。”金寅?表示道,世界排名第7位的她总共抓到了九只小鸟:其中在5杆洞第1洞就率先开门红,而到了第18洞她险些切进小鸟,差点创造赛事的最低杆数新记录。金寅?坦言第1洞的小鸟给了自己很大的信心,而最终,63杆的成绩也是金寅?职业生涯的最佳单轮成绩记录,此前她在2010年三圣母(Tres Marias)锦标赛中打出过64杆,而该球场的标准杆是73杆。“今天是比赛的第一天,我没有给自己太多压力。现在算是个好的开始,但接下来我也不会去想太多。明天用同样的节奏去打球。”金寅?表示道,她称呼泰国天气很热,所以希望自己首先要吃好、睡好。现在与曾雅妮并列第二位的名人堂老将朱莉-英科斯特,对这周在泰国的生活很习惯,她喜欢泰式按摩,也喜欢吃泰式炒面,对当地热情的球迷也颇有好感。而这位在LPGA上赢过31场的老将,本轮交出66杆,其中在第7号洞,她用沙坑杆切进了98码的老鹰,展现了她不输年轻人的风采。自2006年的萨费维国际赛以来,英科斯特还一直无缘冠军,她最期待的就是在享受比赛的同时可以拿下这场胜利。想赢得这场胜利,并不容易。世界第一曾雅妮,正渴望2011年的四战四胜呢!之前,曾雅妮出战三场,全部都获得胜利,其中一场是在亚洲女子巡回赛,两场属于女子欧巡赛。今天她推杆出色,其中包括第17洞的一个25英尺的小鸟长推。“今年的赛场比去年要长,我的铁杆表现得不是很好,但我打得很有耐心。希望后面三天的比赛,自己表现更好一点。”曾雅妮说。卫冕冠军宫里蓝有个不太圆满的开局,75杆;而2007年的赛事冠军苏珊-佩特森是68杆,暂时排在了第5位。(小钻)

fasttext模型的训练数据格式要求为:文本内容(空格隔开)+“\t__label__类别\n",对新闻文本处理(分词)成如下格式:

金寅 领先 泰国 开幕 战曾 雅妮 首轮 落后 新浪 体育讯 北京 时间 美国 赛季 首战 泰国 田赛 球场 结束 首轮 比赛 韩国 选手 金寅 打出 以杆 优势 单独 领先 本周 升至 世界 第一 中国台北 小将 曾雅妮 以杆 名人堂 成员 朱莉英 科斯特 并列 第位 中国 内地 小将 冯珊珊 交出 暂时 并列 第位 本场 比赛 奖金 万美元 选手 参加 比赛 不设 晋级 第一场 比赛 打出 成绩 开球 很早 幸运 天气 凉爽 金寅 世界 排名第 总共 抓到 九只 小鸟 第洞 率先 开门红 第洞 险些 切进 小鸟 差点 创造 赛事 最低 杆数 记录 金寅 坦言 第洞 小鸟 很大 信心 最终 成绩 金寅 职业生涯 最佳 单轮 成绩 记录 此前 圣母 锦标赛 打出 过杆 球场 标准杆 比赛 第一天 压力 算是 好的开始 明天 节奏 打球 金寅 称呼 泰国 天气 希望 曾雅妮 并列 第二位 名人堂 老将 朱莉英 科斯特 这周 泰国 生活 习惯 喜欢 泰式 按摩 喜欢 泰式 炒面 热情 球迷 好感 这位 过场 老将 本轮 交出 第号 沙坑 切进 老鹰 展现 不输 年轻人 风采 自年 萨费维 国际 科斯特 无缘 冠军 期待 享受 比赛 拿下 这场 胜利 赢得 这场 胜利 世界 第一 曾雅妮 渴望 战四胜 曾雅妮 出战 三场 获得胜利 一场 亚洲 巡回赛 两场 欧巡赛 推杆 出色 包括 第洞 英尺 小鸟 赛场 去年 要长 铁杆 表现 耐心 希望 三天 比赛 表现 更好 一点 曾雅妮 卫冕冠军 宫里 蓝有 圆满 开局 赛事 冠军 苏珊 佩特森 暂时 排在 第位

训练集合:THUnews_10x500_jieba.txt,10个类别,每个类别500条新闻数据
测试集合:THUnews_10x50_jieba.txt,10个类别,每个类别50条新闻数据

通过下面预处理操作,生成最终满足fasttext模型要求的数据格式:

训练数据:news_fasttext_train10x500.txt
测试数据:news_fasttext_train10x50.txt

##生成fastext的训练和测试数据集
# 体育、娱乐、家居、房产,教育、时尚、时政、游戏、科技、财经10类
# 
dir_list = ['sports','ent','house','home','edu','fashion','affairs','game','science','economic']

# text文件不要在colabcolab中打开,colab是LinuxLinux系统,编码不一致
news_train="/content/drive/My Drive/Colab Notebooks/THUCNews/THUnews_10x500_jieba.txt"
train_news_ = open(news_train,encoding="utf-8")
train_lines=train_news_.readlines()
print(len(train_lines))
train_news_.close()

news_test="/content/drive/My Drive/Colab Notebooks/THUCNews/THUnews_10x50_jieba.txt"
test_news_ = open(news_test ,encoding="utf-8")
test_lines=test_news_.readlines()
print(len(test_lines))
test_news_.close()

ftrain ="/content/drive/My Drive/Colab Notebooks/THUCNews/news_fasttext_train10x500.txt"
ftest ="/content/drive/My Drive/Colab Notebooks/THUCNews/news_fasttext_test10x50.txt"

def write_texts(infile, outfile):
  print(infile)
  print(outfile)
  local_in_file = open(infile ,encoding="utf-8")
  local_out_file = open(outfile , "w", encoding="utf-8")
  lines=local_in_file.readlines()
  print(len(lines))

  local_lines=len(lines)
  count=0
  index=0

  for line in lines:
    if(count%(local_lines/10)==0 and count>0):
      print(count)
      index+=1
    text = line.replace("\n",str("\t__label__"+dir_list[index]+"\n"))
    local_out_file.write(text)
    local_out_file.flush()
    count+=1
  print(count)
  local_in_file.close()
  local_out_file.close()

write_texts(news_train, ftrain)
write_texts(news_test, ftest)
print('完成输出数据!')


"""处理后数据:
金寅 领先 泰国 开幕 战曾 雅妮 首轮 落后 新浪 体育讯 北京 时间 美国 赛季 首战 泰国 田赛 球场 结束 首轮 比赛 韩国 选手 金寅 打出 以杆 优势 单独 领先 本周 升至 世界 第一 中国台北 小将 曾雅妮 以杆 名人堂 成员 朱莉英 科斯特 并列 第位 中国 内地 小将 冯珊珊 交出 暂时 并列 第位 本场 比赛 奖金 万美元 选手 参加 比赛 不设 晋级 第一场 比赛 打出 成绩 开球 很早 幸运 天气 凉爽 金寅 世界 排名第 总共 抓到 九只 小鸟 第洞 率先 开门红 第洞 险些 切进 小鸟 差点 创造 赛事 最低 杆数 记录 金寅 坦言 第洞 小鸟 很大 信心 最终 成绩 金寅 职业生涯 最佳 单轮 成绩 记录 此前 圣母 锦标赛 打出 过杆 球场 标准杆 比赛 第一天 压力 算是 好的开始 明天 节奏 打球 金寅 称呼 泰国 天气 希望 曾雅妮 并列 第二位 名人堂 老将 朱莉英 科斯特 这周 泰国 生活 习惯 喜欢 泰式 按摩 喜欢 泰式 炒面 热情 球迷 好感 这位 过场 老将 本轮 交出 第号 沙坑 切进 老鹰 展现 不输 年轻人 风采 自年 萨费维 国际 科斯特 无缘 冠军 期待 享受 比赛 拿下 这场 胜利 赢得 这场 胜利 世界 第一 曾雅妮 渴望 战四胜 曾雅妮 出战 三场 获得胜利 一场 亚洲 巡回赛 两场 欧巡赛 推杆 出色 包括 第洞 英尺 小鸟 赛场 去年 要长 铁杆 表现 耐心 希望 三天 比赛 表现 更好 一点 曾雅妮 卫冕冠军 宫里 蓝有 圆满 开局 赛事 冠军 苏珊 佩特森 暂时 排在 第位	__label__sports
"""

2-模型训练

import logging
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
import fasttext
#训练模型
path = "/content/drive/My Drive/Colab Notebooks/THUCNews/"
os.chdir(path)
# 监督训练模型
model = fasttext.train_supervised(
    input="news_fasttext_train10x500.txt",
    label_prefix="__label__" ,
    lr=0.05,              
    epoch=25, 
    # wordNgrams=2, # 该参数导致准确率降低
    bucket=200000, 
    dim=50, 
    loss="softmax"   # 可选loss='softmax'
    )
model.save_model("model_news_fasttext.bin")
#load训练好的模型
model = fasttext.load_model('model_news_fasttext.bin')
print('训练完成!')

模型训练参数:

# train_supervised parameters
# 比无监督少了model、多了label、pretrainedVectors
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 # pretrained word vectors (.vec file) for supervised learning []

模型方法:


# 返回的模型具备以下方法:
get_dimension           # 获取lookup vector (hidden layer)的数据维数大小。# 等同于dimdim属性
get_input_vector        # Given an index, get the corresponding vector of the Input Matrix.
get_input_matrix        # Get a copy of the full input matrix of a Model.
get_labels              # Get the entire list of labels of the dictionary
						# This is equivalent to `labels` property.
get_line                # 将一行文本分成单词和标签。
get_output_matrix       # Get a copy of the full output matrix of a Model.
get_sentence_vector     # 给定一个字符串,得到向量表示
get_subword_id          # 给定一个子单词,返回它散列到的索引(在输入矩阵中)。
get_subwords            # 给定一个单词,找出它的子单词及其指示词。
get_word_id             # 在字典中获取单词id。
get_word_vector         # 得到单词的向量表示。
get_words               # 获取字典中的全部单词列表------等同于wordswords属性
is_quantized            # 模型是否被量化
predict                 # 给定一个字符串,得到一个标签列表和相应的概率列表。
quantize                # 量化模型,减少模型的大小和内存占用。
save_model              # Save the model to the given path
test                    # 根据测试数据文件求监督模型的值
test_label              # 返回每个标签的精度和召回分数。

随机抽取新闻文本进行测试:

print(model.labels)

ftest = open("news_fasttext_test10x50.txt","r",encoding="utf-8")
lines=ftest.readlines()
import numpy as np
index=np.random.randint(500)
print(len(lines),index,dir_list[index//50],lines[index])
text=lines[index]
# 返回topk的标签概率
result = model.predict(text[:-1],k=3)
print('result:', result)

"""
# 所有标签
['__label__sports', '__label__economic', '__label__house', '__label__game', '__label__ent', '__label__affairs', '__label__science', '__label__home', '__label__edu', '__label__fashion']
500 214 edu 女孩 高考 中科大 少年班 去年 初试 高考 考过 重点 本科 分数 中科大 少年班 录取 高考 考出 高分 参加 严苛 复试 终于 接到 录取 通知书 昨天 重庆 中学 女生 吴优 收到 中国科技大学 少年班 录取 通知书 重庆 直辖 第一位 中科大 少年班 神童 摇篮 录取 学生 高考 优以 高分 中科大 少年班 视线 本月 中旬 合肥 参加 复试 过五关斩六将 方获 录取 吴优 小学同学 刚刚 初中 毕业 汤寒锋 摄影 报道 小学 年级 时连 两级 初中 初中 毕业 参加 招考 多分 在家 自学 高中 学业 去年 初试 高考 考过 重点 本科 分数 中科大 少年班 录取 吴优以 火箭 速度 小学 高中 学习 高考 成绩 一只 跨进 中科大 少年班 门槛 吴优 母亲 陪同 前往 位于 安徽 合肥 中国科技大学 参加 少年班 复试 全国 近名 神童 参加 高考 苛刻 复试 中科大 少年班 学院 一位 负责人 余年 一名 重庆 考生 顺利 复试 该校 少年班 录取 复试 第一轮 传统 笔试 考试 科目 英语 数学 物理 英语 数学 物理 太难 优说 两科 笔试 时间 一个半 小时 题目 一道 听说 奥赛 学过 奥数 奥物 吴优 第一次 碰到 物理 有把握 数学 感觉 棘手 吴优 心头 慌乱 听到 旁边 几名 同学 叹气 握笔 一下子 信心 其他同学 第二轮 复试 要考 学生 知识 现炒现卖 能力 数学 物理 方式 大不一样 考试 中科大 博士生 导师 考生 一堂 新课 一个多 小时 学过 知识 数学 听得懂 物理 吴优 坦白 地说 物理课 好些 地方 飞机 教授 讲完 工作人员 发给 考生 每人 一张 试卷 有道题 刚刚 上课 内容 接受 能力 理解能力 超强 吴优 东西 学得 很快 环节 考试 自我感觉 第一轮 笔试 考得 第轮 复试 吴优 接受 专业 人员 一系列 心理 测试 填写 一大堆 心理 测试 问卷 一轮 复试 面试 名教授 吴优 单独 聊天 小时候 学习 情况 平时 生活 学习 状况 挫折 未来 人生 计划 成功 话题 一名 教授 吴优 要来 少年班 吴优 不假思索 回答 中学 学习 枯燥 辛苦 内容 翻来覆去 重复 题海战术 知识 新鲜感 大学 艰苦 复试 结束 吴优 回到 重庆 静待 昨天 吴优 终于 收到 大红 录取 通知书 终于 梦圆 中科大 少年班 欣喜 父母 拥抱 相关 新闻 自学 高中课程 神童 炼成	__label__edu

# topk标签值及其概率值
result: (('__label__edu', '__label__affairs', '__label__sports'), array([0.97985959, 0.00968821, 0.00318338]))
"""

3-评估模型

# 评估模型

def print_results(N, p, r):
  print("N\t" + str(N))
  print("P@{}\t{:.3f}".format(1, p))
  print("R@{}\t{:.3f}".format(1, r))

print_results(*model.test('news_fasttext_test10x50.txt'))

"""
N	500
P@1	0.906
R@1	0.906
"""

4-自定义模型评估方法:

获取新闻文本数据texts、及其标签labels_right:

labels_right = []
texts = []
# 获取测试集数据,去掉行尾的labellabel标签
with open("news_fasttext_test10x50.txt") as fr:
  for line in fr:
    line = str(line.encode("utf-8"), 'utf-8').rstrip()
    # print(line.split("\t")[1])
    try:
      labels_right.append(line.split("\t")[1].replace("__label__",""))
      texts.append(line.split("\t")[0])
    except:
      print(line)
labels_predict = [term[0] for term in model.predict(texts)[0]] #预测输出结果为二维形式
print(labels_predict)

计算准确率,召回率,F值

# 测试集真实标签
text_labels = list(set(labels_right))
# 测试集预测标签
text_predict_labels = list(set(labels_predict))
print(text_predict_labels)
print(text_labels)
print()
# dict词典统计
A = dict.fromkeys(text_labels,0)      #预测正确的各个类的数目
B = dict.fromkeys(text_labels,0)      #测试数据集中各个类的数目
C = dict.fromkeys(text_predict_labels,0)  #预测结果中各个类的数目

for i in range(0,len(labels_right)):
  # 统计test数据集中各个类的数目
  B[labels_right[i]] += 1   
  # 统计预测结果中各个类别的新闻数目
  C[labels_predict[i]] += 1
  # 真实标签=预测标签!!!
  if labels_right[i] == labels_predict[i].replace('__label__', ''):
    A[labels_right[i]] += 1

print('预测正确:各个类的数目:', A) 
print()
print('test数据集:各个类的数目:', B)
print()
print('预测结果:各个类的数目:', C)
print()

# 计算准确率,召回率,F值!测试数据集各类key遍历
for key in B:
  try:
    # recall:预测正确类/测试集全部类
    recall = float(A[key]) / float(B[key])
    # precision:预测正确类/预测结果
    precision = float(A[key]) / float(C['__label__' + key])
    # F-Measure:
    F_Measure = p * r * 2 / (p + r)
    print("%s:\t\t precision:%f\t\t recall:%f\t\t F_Measure:%f" % (key, precision, recall, F_Measure))
  except:
    print("error:", key, "right:", A.get(key,0), "real:", B.get(key,0), "predict:",C.get(key,0))
    

"""
预测正确:各个类的数目: {'fashion': 48, 'edu': 46, 'ent': 45, 'economic': 44, 'home': 45, 'game': 43, 'affairs': 47, 'science': 43, 'house': 43, 'sports': 49}

测试数据集:各个类的数目: {'fashion': 50, 'edu': 50, 'ent': 50, 'economic': 50, 'home': 50, 'game': 50, 'affairs': 50, 'science': 50, 'house': 50, 'sports': 50}

预测结果:各个类的数目: {'__label__house': 48, '__label__ent': 51, '__label__affairs': 58, '__label__fashion': 54, '__label__sports': 51, '__label__home': 47, '__label__edu': 48, '__label__economic': 45, '__label__science': 54, '__label__game': 44}


fashion:	 precision:0.888889		 recall:0.960000		 F_Measure:0.970297
edu:		 precision:0.958333		 recall:0.920000		 F_Measure:0.970297
ent:		 precision:0.882353		 recall:0.900000		 F_Measure:0.970297
economic:	 precision:0.977778		 recall:0.880000		 F_Measure:0.970297
home:		 precision:0.957447		 recall:0.900000		 F_Measure:0.970297
game:		 precision:0.977273		 recall:0.860000		 F_Measure:0.970297
affairs:	 precision:0.810345		 recall:0.940000		 F_Measure:0.970297
science:	 precision:0.796296		 recall:0.860000		 F_Measure:0.970297
house:		 precision:0.895833		 recall:0.860000		 F_Measure:0.970297
sports:		 precision:0.960784		 recall:0.980000		 F_Measure:0.970297
"""   

随机参数训练得到的模型,测试得到的结果可以看出fasttext的确很强大!

参考:

https://zhuanlan.zhihu.com/p/56382372

你可能感兴趣的:(NLP自然语言处理,fasttext,THUCNews,python,nlp)