FastText-实践检测
前言
**fasttext**是**facebook**开源的一个词向量与文本分类工具,fastText结合了自然语言处理和机器学习中最成功的理念。这些包括了使用词袋以及n-gram袋表征语句,还有使用子字(subword)信息,并通过隐藏表征在类别间共享信息;在2016年开源,典型应用场景是“带监督的文本分类问题”。提供简单而高效的文本分类和表征学习的方法,性能比肩深度学习而且速度更快。
一、fasttext是什么?
抛开那些不是很讨⼈喜欢的公式推导,来想⼀想fastText⽂本分类的核⼼思想是什么?
仔细观察模型的后半部分,即从隐含层输出到输出层输出,会发现它就是⼀个softmax线性多类别分类器,分类器的输⼊是⼀个⽤来表征当前⽂档的向量;模型的前半部分,即从输⼊层输⼊到隐含层输出部分,主要在做⼀件事情:⽣成⽤来表征⽂档的向量。那么它是如何做的呢?叠加构成这篇⽂档的所有词及n-gram的词向量,然后取平均。叠加词向量背后的思想就是传统的词袋法,即将⽂档看成⼀个由词构成的集合。于是fastText的核⼼思想就是:将整篇⽂档的词及n-gram向量叠加平均得到⽂档向量,然后使⽤⽂档向量做softmax多分类。
这中间涉及到两个技巧:字符级n-gram特征的引⼊以及分层Softmax分类。
二、使用步骤
1.引入库
代码如下(示例):
import re
import os
import pandas as pd
PATH_BASE = os.path.dirname(os.getcwd())+'\class_dataset'
2.读入数据
代码如下(示例):
def save_file():
# 保存到一个文件
raw_list = []
for classes_dataset in os.listdir(PATH_BASE):
words_path = os.path.join(PATH_BASE, classes_dataset)
for i in open(words_path, 'r', encoding='utf-8'):
if len(re.findall('名称|爱心东东|京东超市|"京东国际|"\n',i)) == 1:
continue
laybel = words_path.split('\\')[-1].split('-')[0]
data = [i.replace(' ', '').replace(' ', '|').replace('\n', '').split('\t')[-1] +'\t'+laybel.replace('.csv','')]
raw_list.append(data)
pd.DataFrame(raw_list).to_csv('../updata_dataset/all_data.csv',header=None,index=0)
原始数据
先了解一下:模型搭建遵循以下步骤:
- 添加输⼊层(embedding层)。Embedding层的输⼊是⼀批⽂档,每个⽂档由⼀个词汇索引序列构成。例如:[10, 30, 80, 1000] 可
能表⽰“我 昨天 来到 达观数据”这个短⽂本,其中“我”、“昨天”、“来到”、“达观数据”在词汇表中的索引分别是10、30、80、
1000;Embedding层将每个单词映射成EMBEDDING_DIM维的向量。于是:input_shape=(BATCH_SIZE, MAX_WORDS),
output_shape=(BATCH_SIZE,
MAX_WORDS, EMBEDDING_DIM); - 添加隐含层(投影层)。投影层对⼀个⽂档中所有单词的向量进⾏叠加平均。keras提供的GlobalAveragePooling1D类可以帮我们实现
这个功能。这层的input_shape是Embedding层的output_shape,这层的output_shape=( BATCH_SIZE, EMBEDDING_DIM); - 添加输出层(softmax层)。真实的fastText这层是Hierarchical Softmax,因为keras原⽣并没有⽀持Hierarchical Softmax,所以
这⾥⽤Softmax代替。这层指定了CLASS_NUM,对于⼀篇⽂档,输出层会产⽣CLASS_NUM个概率值,分别表⽰此⽂档属于当前类的可
能性。这层的output_shape=(BATCH_SIZE, CLASS_NUM) - 指定损失函数、优化器类型、评价指标,编译模型。损失函数我们设置为categorical_crossentropy,它就是我们上⾯所说的softmax
回归的损失函数;优化器我们设置为SGD,表⽰随机梯度下降优化器;评价指标选择accuracy,表⽰精度。
3.fasttext_demo
数据预处理:
"""
函数说明:去停用词
参数:
content_line:文本数据
sentences:存储的数据
category:文本类别
"""
def preprocess_text(content_line, stopwords):
sentences = []
for line in content_line:
label = line.split('\t')[0] # label_words
words = line.split('\t')[1:] # label_words
try:
# segs = jieba.lcut(words) # 利用结巴分词进行中文分词 label_words
segs = jieba.lcut(words[0]) # 利用结巴分词进行中文分词
segs = filter(lambda x: len(x) > 1, segs) # 去掉长度小于1的词
segs = filter(lambda x: x not in stopwords, segs) # 去掉停用词
# sentences.append(label+'\t'+" ".join(segs)) # label_words
sentences.append("__label__" +str(label) + "\t" + " ".join(segs)+','+words[0].replace('\n',''))
# sentences.append("__label__" +str(label) + "\t" + words[0].replace('\n','')) # 不行太低
except Exception as e:
print(words)
continue
pd.DataFrame(sentences).to_csv('./dataset/train_words.txt', header=None, index=0)
model的构建
def train_model(ipt=None, opt=None, model='', dim=100, epoch=100, lr=0.1, loss='softmax'):
# suppress: bool, 科学记数法启用
# True用固定点打印浮点数符号,当前精度中的数字等于零将打印为零。
# False用科学记数法;最小数绝对值是<1e-4或比率最大绝对值> 1e3。默认值False
np.set_printoptions(suppress=True)
if os.path.isfile(model):
classifier = fasttext.load_model(model)
else:
classifier = fasttext.train_supervised(ipt, label='"__label__', dim=dim, epoch=epoch,
lr=lr, wordNgrams=2, loss=loss)
"""
训练一个监督模型, 返回一个模型对象
@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
"""
classifier.save_model(opt)
return classifier
总的练习代码
import os
import numpy as np
import fasttext
from sklearn.metrics import recall_score, precision_score, f1_score, confusion_matrix, multilabel_confusion_matrix, \
classification_report
def train_model(ipt=None, opt=None, model='', dim=100, epoch=100, lr=0.1, loss='softmax'):
# suppress: bool, 科学记数法启用
# True用固定点打印浮点数符号,当前精度中的数字等于零将打印为零。
# False用科学记数法;最小数绝对值是<1e-4或比率最大绝对值> 1e3。默认值False
np.set_printoptions(suppress=True)
if os.path.isfile(model):
classifier = fasttext.load_model(model)
else:
classifier = fasttext.train_supervised(ipt, label='"__label__', dim=dim, epoch=epoch,
lr=lr, wordNgrams=2, loss=loss)
"""
训练一个监督模型, 返回一个模型对象
@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
"""
classifier.save_model(opt)
return classifier
def unified_format(data_lable, predict_lable): # 多余
lable_to_cate = {'萌宠': 0, '园艺': 1, '运动_户外': 2, '星座': 3, '影视': 4, '摄影': 5, '穿搭': 6, '游戏': 7, '数码_科技': 8, '家居': 9, '汽车': 10,
'户外旅游': 11, '美食': 12, '母婴': 13, '美容妆发': 14, '二次元_动漫': 15}
list_uni = []
list_pre = []
for i, y in zip(iter(data_lable), iter(predict_lable)):
unified = [lable_to_cate[i.split('__label__')[-1]] for node_i in lable_to_cate.keys() if node_i in i]
predict = [lable_to_cate[y[0].split('__label__')[-1]] for node_i in lable_to_cate.keys() if node_i in y[0]]
list_pre.append(predict)
list_uni.append(unified)
return list_uni, list_pre
import jieba
import pandas as pd
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
def preprocess_lcut(content_line, stopwords):
sentences = []
for line in content_line:
words = line.split('\t')[0] # label_words
try:
# segs = jieba.lcut(words) # 利用结巴分词进行中文分词 label_words
segs = jieba.lcut(line) # 利用结巴分词进行中文分词
segs = filter(lambda x: len(x) > 1, segs) # 去掉长度小于1的词
segs = filter(lambda x: x not in stopwords, segs) # 去掉停用词
# sentences.append(label+'\t'+" ".join(segs)) # label_words
sentences.append(" ".join(segs) + words)
except Exception as e:
print(line)
continue
return sentences
if __name__ == '__main__':
dim = 300
lr = 1e-3
epoch = 1000
# 模型存储路径
# f'string' 相当于 format() 函数
model = f'model/data_dim{str(dim)}_lr0{str(lr)}_iter{str(epoch)}.model'
train_path = 'dataset/train_words.txt' # 数据增强后的训练集,文章最后有提到
val_path = 'dataset/val_label_sim.txt'
test_path = 'dataset/test_label_sim.txt'
# 输出原始标签与模型标签不匹配的文本
unmatch_path = f'unmatch_classification/unmatch_classification_dim{str(dim)}_lr0{str(lr)}_iter{str(epoch)}.txt'
# 模型训练
classifier = train_model(ipt=train_path,
opt=model,
model=model,
dim=dim, epoch=epoch, lr=0.5
)
print(classifier.words) # list of words in dictionary
print(classifier.labels)
# # 模型测试
result = classifier.test(val_path)
print(result)
data_words = [i.replace('\n', '').split('\t')[-1] for i in
open('./dataset/val_label_sim.txt', 'r', encoding='utf-8')]
data_lable = [i.replace('\n', '').replace('"', '').split('\t')[0] for i in
open('./dataset/val_label_sim.txt', 'r', encoding='utf-8')]
predict_lable = classifier.predict(data_words)[0]
# 进行数据的编码
list_uni, list_pre = unified_format(data_lable, predict_lable)
# 降低维度
list_pre = [i[0] for i in list_pre]
list_uni = [i[0] for i in list_uni]
print(recall_score(list_pre, list_uni, average='micro'))
print(precision_score(list_uni, list_pre, average='micro'))
'''
average参数定义了该指标的计算方法,二分类时average参数默认是binary;多分类时,可选参数有micro、macro、weighted和samples。
None:返回每个班级的分数。否则,这将确定对数据执行的平均类型。
binary:仅报告由指定的类的结果pos_label。仅当targets(y_{true,pred})是二进制时才适用。
micro:通过计算总真阳性,假阴性和误报来全球计算指标。也就是把所有的类放在一起算(具体到precision),然后把所有类的TP加和,再除以所有类的TP和FN的加和。因此micro方法下的precision和recall都等于accuracy。
macro:计算每个标签的指标,找出它们的未加权平均值。这不会考虑标签不平衡。也就是先分别求出每个类的precision再求其算术平均。
weighted:计算每个标签的指标,并找到它们的平均值,按支持加权(每个标签的真实实例数)。这会改变“宏观”以解决标签不平衡问题; 它可能导致F分数不在精确度和召回之间。
samples:计算每个实例的指标,并找出它们的平均值(仅对于不同的多标记分类有意义 accuracy_score)。
————————————————
'''
print(confusion_matrix(list_uni, list_pre))
# print(multilabel_confusion_matrix(list_uni, list_pre))
lable_to_cate = {'萌宠': 0, '园艺': 1, '运动_户外': 2, '星座': 3, '影视': 4, '摄影': 5, '穿搭': 6, '游戏': 7, '数码_科技': 8, '家居': 9, '汽车': 10,
'户外旅游': 11, '美食': 12, '母婴': 13, '美容妆发': 14, '二次元_动漫': 15}
categories = [i for i in lable_to_cate.keys()]
print(classification_report(list_uni, list_pre, target_names=categories))
test_demo = ['比比赞爆浆曲奇小丸子200g夹心饼干巧克力球网红小零食小吃休闲食品整箱',
'索讯科 GULIAN 实木床现代简约主卧双人出租房床架加宽床床经济型简易单人床 实木床40厘米高【满铺】 1.35米*2米',
'书架简约落地简易现代客厅置物架柜家用学生卧室储物收纳 【特惠款】53×150cm-暖白-无背板',
'【12期免息+碎屏险】vivo手机X80新品5G手机旗舰芯片蔡司闪充旗舰机vivox80 12GB+256GB',
'石砾水洗米石子胶粘石头水刷石水磨石砾石黑白灰色砾石米庭院透水路面 白色磨圆 5斤',
'红色高跟鞋']
stopwordsFile = r"./data/stopwords.txt"
stopwords = getStopWords(stopwordsFile)
sentence = preprocess_lcut(test_demo, stopwords)
predict_lable = classifier.predict(sentence)
print(predict_lable)
效果测试
数据不多 可能效果有些误差,但整体效果还是不错的,如果打不到要求建议使用ALbert
停用词网上就有:hit_stop_哈工大
———
》),
)÷(1-
”,
)、
=(
:
→
℃
&
*
一一
~~~~
’
.
『
.一
./
--
』
=″
【
[*]
}>
[⑤]]
[①D]
c]
ng昉
*
//
[
]
[②e]
[②g]
={
}
,也
‘
A
[①⑥]
[②B]
[①a]
[④a]
[①③]
[③h]
③]
1.
--
[②b]
’‘
×××
[①⑧]
0:2
=[
[⑤b]
[②c]
[④b]
[②③]
[③a]
[④c]
[①⑤]
[①⑦]
[①g]
∈[
[①⑨]
[①④]
[①c]
[②f]
[②⑧]
[②①]
[①C]
[③c]
[③g]
[②⑤]
[②②]
一.
[①h]
.数
[]
[①B]
数/
[①i]
[③e]
[①①]
[④d]
[④e]
[③b]
[⑤a]
[①A]
[②⑧]
[②⑦]
[①d]
[②j]
〕〔
][
://
′∈
[②④
[⑤e]
12%
b]
...
...................
…………………………………………………③
ZXFITL
[③F]
」
[①o]
]∧′=[
∪φ∈
′|
{-
②c
}
[③①]
R.L.
[①E]
Ψ
-[*]-
↑
.日
[②d]
[②
[②⑦]
[②②]
[③e]
[①i]
[①B]
[①h]
[①d]
[①g]
[①②]
[②a]
f]
[⑩]
a]
[①e]
[②h]
[②⑥]
[③d]
[②⑩]
e]
〉
】
元/吨
[②⑩]
2.3%
5:0
[①]
::
[②]
[③]
[④]
[⑤]
[⑥]
[⑦]
[⑧]
[⑨]
……
——
?
、
。
“
”
《
》
!
,
:
;
?
.
,
.
'
?
·
———
──
?
—
<
>
(
)
〔
〕
[
]
(
)
-
+
~
×
/
/
①
②
③
④
⑤
⑥
⑦
⑧
⑨
⑩
Ⅲ
В
"
;
#
@
γ
μ
φ
φ.
×
Δ
■
▲
sub
exp
sup
sub
Lex
#
%
&
'
+
+ξ
++
-
-β
<
<±
<Δ
<λ
<φ
<<
=
=
=☆
=-
>
>λ
_
~±
~+
[⑤f]
[⑤d]
[②i]
≈
[②G]
[①f]
LI
㈧
[-
......
〉
[③⑩]
第二
一番
一直
一个
一些
许多
种
有的是
也就是说
末##末
啊
阿
哎
哎呀
哎哟
唉
俺
俺们
按
按照
吧
吧哒
把
罢了
被
本
本着
比
比方
比如
鄙人
彼
彼此
边
别
别的
别说
并
并且
不比
不成
不单
不但
不独
不管
不光
不过
不仅
不拘
不论
不怕
不然
不如
不特
不惟
不问
不只
朝
朝着
趁
趁着
乘
冲
除
除此之外
除非
除了
此
此间
此外
从
从而
打
待
但
但是
当
当着
到
得
的
的话
等
等等
地
第
叮咚
对
对于
多
多少
而
而况
而且
而是
而外
而言
而已
尔后
反过来
反过来说
反之
非但
非徒
否则
嘎
嘎登
该
赶
个
各
各个
各位
各种
各自
给
根据
跟
故
故此
固然
关于
管
归
果然
果真
过
哈
哈哈
呵
和
何
何处
何况
何时
嘿
哼
哼唷
呼哧
乎
哗
还是
还有
换句话说
换言之
或
或是
或者
极了
及
及其
及至
即
即便
即或
即令
即若
即使
几
几时
己
既
既然
既是
继而
加之
假如
假若
假使
鉴于
将
较
较之
叫
接着
结果
借
紧接着
进而
尽
尽管
经
经过
就
就是
就是说
据
具体地说
具体说来
开始
开外
靠
咳
可
可见
可是
可以
况且
啦
来
来着
离
例如
哩
连
连同
两者
了
临
另
另外
另一方面
论
嘛
吗
慢说
漫说
冒
么
每
每当
们
莫若
某
某个
某些
拿
哪
哪边
哪儿
哪个
哪里
哪年
哪怕
哪天
哪些
哪样
那
那边
那儿
那个
那会儿
那里
那么
那么些
那么样
那时
那些
那样
乃
乃至
呢
能
你
你们
您
宁
宁可
宁肯
宁愿
哦
呕
啪达
旁人
呸
凭
凭借
其
其次
其二
其他
其它
其一
其余
其中
起
起见
起见
岂但
恰恰相反
前后
前者
且
然而
然后
然则
让
人家
任
任何
任凭
如
如此
如果
如何
如其
如若
如上所述
若
若非
若是
啥
上下
尚且
设若
设使
甚而
甚么
甚至
省得
时候
什么
什么样
使得
是
是的
首先
谁
谁知
顺
顺着
似的
虽
虽然
虽说
虽则
随
随着
所
所以
他
他们
他人
它
它们
她
她们
倘
倘或
倘然
倘若
倘使
腾
替
通过
同
同时
哇
万一
往
望
为
为何
为了
为什么
为着
喂
嗡嗡
我
我们
呜
呜呼
乌乎
无论
无宁
毋宁
嘻
吓
相对而言
像
向
向着
嘘
呀
焉
沿
沿着
要
要不
要不然
要不是
要么
要是
也
也罢
也好
一
一般
一旦
一方面
一来
一切
一样
一则
依
依照
矣
以
以便
以及
以免
以至
以至于
以致
抑或
因
因此
因而
因为
哟
用
由
由此可见
由于
有
有的
有关
有些
又
于
于是
于是乎
与
与此同时
与否
与其
越是
云云
哉
再说
再者
在
在下
咱
咱们
则
怎
怎么
怎么办
怎么样
怎样
咋
照
照着
者
这
这边
这儿
这个
这会儿
这就是说
这里
这么
这么点儿
这么些
这么样
这时
这些
这样
正如
吱
之
之类
之所以
之一
只是
只限
只要
只有
至
至于
诸位
着
着呢
自
自从
自个儿
自各儿
自己
自家
自身
综上所述
总的来看
总的来说
总的说来
总而言之
总之
纵
纵令
纵然
纵使
遵照
作为
兮
呃
呗
咚
咦
喏
啐
喔唷
嗬
嗯
嗳
总结
提示:以上是了解tasttext做的小验证