计算机只能识别和计算数字,我们在处理语言文本时(不仅语言文本,要传入模型计算的数据都是数字或者向量),首要的工作是数据的预处理。最开始是One-Hot Encoder编码,很显然这没有考虑词的含义和词与词的关系。所以根据需求可以有不同的处理方式,最常见的,效果也比较好的就是词向量word2vec
。具体展开请看这里。
很显然,词向量
分为词
和向量
。英文有天然的词分隔符空格
,中文却不是,不同的断句截然不同的意思。具体中文的分词原理请看这篇文章中文分词原理,这里不过多展开。下面来详细的介绍jieba分词的工作方式。
我们的目的很清楚,把语言文本分词,然后制作成向量。
下面一步步来做:
分词,我们要想,我们根据什么来分,什么是标准?所以第一步我们要导入分词的字典,根据我们的业务场景,可以自行定义也可以导入默认的。
step1 导入字典jieba.load_userdict(filename)
在使用的时候,自定义的词典格式和jieba分词器默认的词典格式必须保持一致,即:一个词占一行,每一行分成三部分,一部分为词语,一部分为词频,最后为词性(可以省略),用空格隔开,最重要的一点要用UTF-8!。比如默认词典下:
>>> st = '李大海在上海的人民广场吃炸鸡'
>>> ge1 = jieba.cut(st)
>>> '/'.join(ge1)
'李大海/在/上海/的/人民广场/吃/炸鸡'
自定义词典(不考虑词频):
李大海 1
人民 1
广场 1
导入自定义词典:
>>> jieba.load_userdict('dict.txt')
>>> eg = jieba.cut(st)
>>> '/'.join(eg)
'李大海/在/上海/的/人民广场/吃/炸鸡'
这里有个问题,顺便拓展一下。我们发现人民广场即使是我们添加了自定义的字典也分不开,这是为什么?这是因为自定义的字典只是添加,我只是添加了李大海
、人民
、广场
,并没有把原来字典里的人民广场
拆开。下面介绍几个方法:
- jieba.del_word()----删除原来的词
>>> jieba.del_word("人民广场")
>>> eg = jieba.cut(st)
>>> '/'.join(eg)
'李大海/在/上海/的/人民/广场/吃/炸鸡'
- jieba.del_word()----添加和修改词频
>>> jieba.add_word("人民广场")
>>> eg = jieba.cut(st)
>>> '/'.join(eg)
'李大海/在/上海/的/人民广场/吃/炸鸡'
>>> jieba.add_word("人民广场",0)
>>> eg = jieba.cut(st)
>>> '/'.join(eg)
'李大海/在/上海/的/人民/广场/吃/炸鸡'
- jieba.suggest_ferq()----自行修正(推荐)
>>> jieba.suggest_freq(('人民','广场'),True)
0
>>> eg = jieba.cut(st)
>>> '/'.join(eg)
'李大海/在/上海/的/人民/广场/吃/炸鸡'
>>>
ok导入词典就介绍到这里。第二步就是分词:
step 2 分词jieba.cut()
和jieba.cut_for_search
-
jieba.cut()
>>> st = '李大海在上海的人民广场吃炸鸡'
# 非全模式,默认的
>>> ge = jieba.cut(st,cut_full=False)
>>> '/'.join(ge)
'李大海/在/上海/的/人民/广场/吃/炸鸡'
# 全模式
>>> eg = jieba.cut(st,cut_all=True)
>>> '/'.join(eg)
'李大海/大海/在/上海/的/人民/广场/吃/炸鸡'
>>>
全切模式是把切出来的长词再切分,长词的定义是词里面还有词儿,并不是长得长。。。
-
jieba.cut_for_search()
>>> eg = jieba.cut_for_search(st)
>>> '/'.join(eg)
'大海/李大海/在/上海/的/人民/广场/吃/炸鸡'
这个是搜索模式分词,效果和全模式的cut相似。
最后总结一下jieba的分词模式,一共是两个分词方法,三个分词模式:
- 精确模式
jieba.cut(cut_all=False)
,试图将句子最精确地切开,适合文本分析; - 全模式
jieba.cut(cut_all=True)
,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义; - 搜索引擎模式
jieba.cut_for_search()
,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。 支持繁体分词。
最后介绍几个jieba的关键字提取方法:
- 基于TF-IDF算法:
jieba.analyse.extract_tags()
关于TF-IDF算法,这篇文章介绍的很清楚。
>>> from collections import Counter
>>> import jieba.analyse
>>> import jieba
>>> st
'计算机只能识别和计算数字,我们在处理语言文本时(不仅语言文本,要传入模型计算的数据都是数字或者向量),首要的工作是数据 的预处理。根据需求可以有不同的处理方式,最常见的,效果也比较好的就是词向量。'
>>> eg = jieba.cut(st)
>>> ls = list(eg)
['计算机', '只能', '识别', '和', '计算', '数字', ',', '我们', '在', '处理', '语言', '文本', '时', '(', '不仅', '语言', '文本', ',', '要', '传入', '模型', '计算', '的', '数据', '都', '是', '数字', '或者', '向量', ')', ',', '首要', '的', '工作', '是', '数据', '的', '预处理', '。', '根据', '需求', '可以', '有', '不同', '的', '处理', '方式', ',', '最', '常见', '的', ',', '效果', '也', '比较', '好', '的', '就是', '词', '向量', '。']
>>> Counter(ls)
Counter({'的': 6, ',': 5, '计算': 2, '数字': 2, '处理': 2, '语言': 2, '文本': 2, '数据': 2, '是': 2, '向量': 2, '。': 2, '计算机': 1, '只能': 1, '识别': 1, '和': 1, '我们': 1, '在': 1, '时': 1, '(': 1, '不仅': 1, '要': 1, '传入': 1, '模型': 1, '都': 1, '或者': 1, ')': 1, '首要': 1, '工作': 1, '预处理': 1, '根据': 1, '需求': 1, '可以': 1, '有': 1, '不同': 1, '方式': 1, '最': 1, '常见': 1, '效果': 1, '也': 1, '比较': 1, '好': 1, '就是': 1, '词': 1})
>>> jieba.analyse.extract_tags(st,8)
['向量', '文本', '数字', '语言', '计算', '处理', '预处理', '数据']
可以看到这个方法基本是根据词频来筛选关键字的,但是出去了一些常用的Stop Word(停用词)
可以使用jieba.analyse.set_stop_words(file_name)
这个方法来自定义停用词,自定义文本是每个词独占一行。
- 基于 TextRank 算法:
jieba.analyse.textrank()
对于TextRank算法介绍,这篇博客比较好。
>>> jieba.analyse.textrank(st,8)
['计算', '数据', '数字', '文本', '语言', '传入', '处理', '预处理']
这个方法考虑了词与词的连接,等于是考虑了语意。对于jieba的关键字提取,这篇博客有比较详细实例的介绍。
step 3 分词完成了之后,就要进行词向量的制作
这里使用gensim.models模块中的word2vec模块来简易制作。gensim是一个强大的模块,具体介绍请看这里。
在分词完毕之后,如果数据量大建议写成文件保存,小的话直接可以用来制作词向量(建议都写成文件保存)。话不多说进入正题:
-
首先需要把数据处理成gensim可以处理的格式
- 先说最简单的一种
直接处理成列表嵌套的方式,即:[ [分词好的第一句],[分词好的第二句],.....]。这种形式 -
word2vec.LineSentence(source, max_sentence_length=10000, limit=None)
这个方法读取的是分词处理好的文本,文本格式为:一句话独占一行,且每句好都是分词好,由空格隔开的。 -
word2vec.PathLineSentences(source,max_sentence_length = 10000,limit = None )
与LineSentence类一样,不过这里是处理根目录下的所有文件,每个文件的格式和LineSentence方法中的要求一样,语料库特别大的时候推荐使用这个方法,防止内存过载。 -
word2vec.Text8Corpus(fname,max_sentence_length = 10000 )
这个方法是也是同上,文本容量很大的时候不推荐使用这个方法。
- 先说最简单的一种
以上这些方法都是生成一个可迭代的对象,封装的内容是分词后的单词组成的列表。这几个方法的不同是,LineSentence
是每行封装成一个列表,这个列表的最大长度是max_sentence_length
。而Text8Corpus
是对整个语料库进行分割,每次都是max_sentence_length
这个长度的列表。
-
source
可以接收一个列表(第一个方法中的格式)或者是分此后的文件路径。 -
max_sentence_length
封装的每个列表的最大长度
from gensim.models.word2vec import LineSentence,PathLineSentences,Text8Corpus
s = [ ['你','好','吗'] ,['你','好','吗'],['你','好','吗'],['你','好','吗'] ]
s = LineSentence('\data.txt')
s = PathLineSentences('\data.txt')
s = Text8Corpus('\data.txt')
数据准备好了就可以进行下一步了:
-
训练词向量模型
-
word2vec.Word2Vec(sentences=None, size=100, window=5, min_count=5)
-
sentences
就是上面的准备好的数据,可以是简单的list或这个是其他几个方法生成的可迭代对象。 -
size
生成词向量的大小,也就是特征向量的维数 -
window
一个词上下文的最大关联距离,简单说就是考虑上下文几个词对这个词有影响 -
min_count
忽略词频小于这个值的词
-
-
还有一些学习率等参数,不太需要调节的参数,需要的时候查官方文档
from gensim.models import word2vec
# 训练模型
model = word2vec.Word2Vec(s)
# 保存模型
model.save('lang_model')
对于训练好的模型,有一些基本操作:
# 加载模型
model = word2vec.Word2Vec.load('lang_model')
# 查看某一个词的向量(要在词典内)
print(model['你好'])
# 查看某个词关系密切的几个词
print(model.most_similar(u'你好',topn=10))
# 查看构成的词汇表
print(model.wv.vocab.keys())
到这里,我们等于是把原来的文本数据中的每一个词,制作了一个映射。而不是简单的用一个数字到代表它,更加考虑了词的含义和词与词之间的影响。不过已经可以用这个模型做许多事情了。