本质:对字符串的进行分割,将分割部分对字典集进行匹配。匹配到则被分割部分为一个词,匹配不到则将分割部分的最左(或最右)字符向左移一位。
正向反向都可以使用贪心与分治:
贪心法:
将字符串作为一个整体,从最右向最左扫描,当左部分为一个词时进行分割。再对右部分进行贪心法。
分治法:
先将字符串进行分割n段(n为超参数),从左往右迭代段。在每次迭代中句从右往左进行扫描,当匹配到为词时进行分割,匹配不到将右边字添加到右边段中。(分治法的命中率会高一些)
逆向匹配就是将正相匹配的顺序反一下就行。
分治:
先将字符串分为n段(n为超参数),从右往左迭代段。在每次迭代段中从左往右扫描,当匹配到词时进行分割,匹配不到时将左边字添加到左边段中。
观看开头提供的参考博文可以看到,正相匹配、逆向匹配的分词可能时不同的。
双向最大匹配就是对字符串进行分别进行正相匹配、逆向匹配,然后对两者的结果进行比较决定分词是什么。
比较规则:
在基于传统算法中,jieba是一种代表:jieba是属于概率语言模型的分词。
jieba的三种分词模式:
import jieba
sentence = "我爱自然语言处理"
#默认模式
seg_list = jieba.cut(sentence)
print(seg_list)
#全模式
seg_list = jieba.cut(sentence, cut_all = True)
print(seg_list)
#精确模式
seg_list = jieba.cut(sentence, cut_all = False)
print(seg_list)
#搜索引擎模式
seg_list = jieba.cut_for_search(sentence)
print(seg_list)
jieba自带了一个dict词典,它拥有词以及它出现的频率(因为这是基于人民日报等预料库训练出来的,在训练的同时添加其频率)。在使用jieba的时候,程序运行会将dict.txt中的数据生成一个trie树(单词查找树)放在内存中。
jieba分词的分词依据是:在全切分中得到的所有切分方案中,选择其中一个切分方案S,S在所有的切分方案中P(S)最大。
jieba通过将句子根据trie树以及有向无环图将句子进行分词,如果只根据trie树进行分词会使得出现双重理解组合的问题(所以添加有向无环图算法解决双重理解)。
# jieba的算法:
# * 开始调用jieba时,将词典生成trie树。
# * 在分词时根据有向无环图算法与trie树进行分词,得到所有的分词情况。
# * 对于未登录词,采用了基于汉字成词的HMM模型+Viterbi算法。
# * 采用动态规划查找最大概率路径,找出基于词频的最大切分组合。
import jieba
# jieba的五种分词方式
# 1-精确模式分词
sentence = "我爱自然语言处理"
words = jieba.cut(sentence)
print("/".join(words))
# 2-全模式分词(可以看到全模式分词是一种会将句子中所有词都查出来的一种分词模式。)
words = jieba.cut(sentence, cut_all = True)
print("/".join(words))
# 3-搜索引擎模式分词
words = jieba.cut_for_search(sentence)
print("/".join(words))
# 4-返回list的切词模式lcut、lcut_for_search
list_one = jieba.lcut(sentence)
list_two = jieba.lcut_for_search(sentence)
print(list_one)
print(list_two)
# 5-自定义分词器 jieba.Tokenizer(dictionary=DEFAULT_DICT)
我/爱/自然语言/处理
我/爱/自然/自然语言/语言/处理
我/爱/自然/语言/自然语言/处理
['我', '爱', '自然语言', '处理']
['我', '爱', '自然', '语言', '自然语言', '处理']
# 1-作用:指定自定义字典,以确保dict.txt中没有的词jieba具有识别能力
# 2-用法:jieba.load_userdict(file_name)。file_name是与dict.txt格式相同的txt。
# 3-字典格式:一词占一行;每行三部分:词语、词频、词性(后两者可省略,中间用空格隔开,顺序不可以乱)。文件格式为UTF-8。
jieba.load_userdict("mydict.txt")
sentence = "李喜欢吃笔记本面包并且喜欢喝牛奶咖啡"
words = jieba.cut(sentence)
print("/".join(words))
李/喜欢/吃/笔记本面包/并且/喜欢/喝/牛奶咖啡
# 1-添加词到字典中:add_word(word, freq=None, tag=None)。word词,freq词频;None为字典中的词频最小值,tag词性
# 2-删除词到字典中:del_word(word)
# 3-修改段(词)频,使其能或不能被分出来:suggest_freq(segment, tune=True),且段不为词典树中的词。
# 4-获得词信息:jieba.getFREQ(word):获取word的词频;
# * 自动计算的词频在使用时HMM可能无效
sentence = "李不喜欢吃大白兔奶糖"
words = jieba.cut(sentence)
print("/".join(words))
jieba.add_word("大白兔奶糖", 12)
jieba.add_word("不喜欢")
words = jieba.cut(sentence)
print("/".join(words))
jieba.suggest_freq("吃大白兔奶糖", True)
words = jieba.cut(sentence)
print("/".join(words))
李/不/喜欢/吃/大白兔/奶糖
李/不喜欢/吃/大白兔奶糖
李/不喜欢/吃大白兔奶糖
李/不喜欢/吃大白兔奶糖
# posseg的作用,切分后返回词对象(在普通切分中每个词是字符串而不是对象);词对象拥有字典中词的:词、词性(主要是为了获取其词性)
# 没有词性,使用默认词性
import jieba.posseg as pseg
sentence = "李喜欢吃笔记本面包并且喜欢喝牛奶咖啡"
words = pseg.cut(sentence)
for word in words:
print(word.encode, word.word, word.flag)
李勃颍 qch
喜欢 v
吃 v
笔记本面包 x
并且 c
喜欢 v
喝 vg
牛奶咖啡 milk
# 使用TF-IDF算法进行关键词的提取
# jieba.analyse.extract_tags(sentence, topK=20, withWeight=False, allowPOS=())
# * sentence为待提取文本
# * topK为返回关键词的数量(关键词是根据TF-IDF算法算出来的)
# * withWeight为是否返回关键词的权重值
# * allowPOS为仅包括指定词性的词,即无词性的词不可能作为关键词
import jieba.analyse as jieban
sentence = "李喜欢吃笔记本面包并且喜欢喝牛奶咖啡"
tags = jieban.extract_tags(sentence, topK=2, withWeight=True)
print(tags)
# 关键词提取使用停用词库(自定义词库)
# jieba.analyse.set_stop_words(file_name) file_name为自定义语料库路径
# 添加停用词后就不会将该词作为关键词
[('李', 1.9924612504833332), ('笔记本面包', 1.9924612504833332)]
# 使用TextRank算法进行关键词的提取
占用内存较小的词典:https://github.com/fxsjy/jieba/raw/master/extra_dict/dict.txt.small
支持繁体字分词的词典:https://github.com/fxsjy/jieba/raw/master/extra_dict/dict.txt.big
jieba.set_dictionary是直接覆盖掉原jieba字典。
jieba.load_userdict是添加字典,原jieba字典并没有删除。
TF-IDF算法是用来进行词加权的一种加权技术。(权重就是词的重要性,一个词的重要性与其在文件中出现的频率成正比,与在语料库中出现的频率成反比。)
所以从上述可以看出TF-IDF算法的思想就是:一个单词在一个文件中出现的频率高,在语料库中出现的频率低,那么它就具有很好的分类效果。
TF:词频;一个词的TF = 它在一篇文档中出现的次数 ➗ 文档的总词数。
IDF:逆向文档频率;一个词的IDF = log(文档总数 ➗(含有该词的文档数 + 1))。
TF-IDF = TF * IDF。
TF除以文档总词数是为了归一化,防止词频偏向长文档。
IDF中加1是为了防止分母为0。
从上述公式也可以看出:TF越大,它在文章中的比重就越大。IDF越大,它能作为关键词区分一篇文章的可能性就越大。所以TF-IDF越大,这个词作为关键词区分文章的可能性就越大。但是TF-IDF仍然是不准确的加权算法,因为不一定在一篇文章中出现的越多、在文本库中出现的越少这个词就越重要。
数据集:
[ ['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], # 切分后的词条,一行属于一篇文章
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
['stop', 'posting', 'stupid', 'worthless', 'garbage'],
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
['quit', 'buying', 'worthless', 'dog', 'food', 'stupid'] ]
将句子进行分词后,得到的仍是词列表。想要进行机器学习计算需要将其转化成数字模型。所以有了语言模型。
语言模型是一个非常宽泛的称呼,word2vec是一种语言模型。
语言模型的功能:语言模型的主要目的是为了得到能过判断一个句子是否是人话的模型。而词向量(也就是词的表达)是语言模型训练的副产品。
语言模型:语言模型就是计算词组成的句子符合语言规则的概率。 统计语言模型将词序列看作一个随机事件,并赋予其相应的概率来描述其在某个语言集合中表达符合语言的可能性。也就是说:给定一个语言集合v,S为V中词组成的序列,语言模型赋予S一个概率,这个概率衡量这个序列符合语法的置信度。(以下数学公式是n-gram的数学公式,也就是马尔科夫链)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OgcJTX3f-1611493860715)(G:\MarkDown\图片\深度学习\word2vec\统计语言模型数学公式.PNG)]
一个词的出现与它之前的词的出现相关,这可以使用条件概率来表示(前面i-1个词出现的情况下第i个词出现的概率)。
语言表达的作用:将词向量数字化。(也就是使词向量可计算化)
分布表达就是:一个词的表达与词的分布位置有关。(或者说两个词上下文相似,则它们的语义也相似)
分布假说:一个词的语义由其上下文决定。
除了基于神经网络的分布表达还有:
词嵌入的优势:例如我们使用n-gram对词的上下文进行计算时,当n增加时,矩阵表达的参数增长比神经网络模型参数增长快(因为神经网络模型隐藏层节点个数是一个超参数,并且每个节点是输入的组合这样并不会丢失上下文信息)。
词嵌入会将词降维。(也就是掩藏层维度比输入层维度小)
==word2vec训练后的模型的词向量是多种语义混合起来的词向量。例如在英文中:bank有银行和岸的意思,那么w2v会将bank这个词混合两种词性成为bank的词向量(注意只有训练文本中有作为银行和岸的词义才会得到混合向量)。==但这还是有些缺陷,一个词的词性应该是在不同的语义下是不同的,而不是将它们混合起来。
**word2vec是word embedding的一种,**word embedding还有glove。
CBOW与Skip-Gram都是简单深度神经网络模型(DNN),DNN虽然也有窗口但是窗口内的词并没有时序关系所以出现了RNN。
除了word embedding这种编码方式,还有one-hot编码(词袋模型)、整数编码。不过one-hot编码是将词作为向量,词向量为一个数组(该词所在位置为1其他位置为0),它并没有表示词之间的关系所以这种编码格式并不好。
**而word2vec采用深度学习的模式,使用CBOW(连续词袋模型)、Skip-gram模型训练。CBOW通过上下文来预测当前词,Skip-gram通过当前词来预测上下文。**这样使得词上下文具有相关性来增强准确性。
只要数字化的足够合理,那么两个句向量的夹角越小两个句子就越相似。
CBOW也是统计语言模型的一种。根据前C个词与后C个词计算出某个词出现的概率。
Skip-gram是根据某个词,计算前后出现某几个词的各个概率。
Gensim包含word2vec模型,TF-IDF算法。
Gensim中word2vec模型对语料的要求是,可迭代对象、列表。但是数据量特别大的时候直接使用列表会占据大量内存,所以最好使用可迭代对象作为训练语料。
Word2Vec的可用语料可以使用:
推荐使用LineSentence、Text8Corpus。
例如使用列表:
from gensim.models.word2vec import Word2Vec
sentences = [["我", "喜欢", "李"], ["李", "也", "喜欢", "我"]]
model = Word2Vec(sentence, min_count=1)
构建生成器:
class MySentence(Object):
def __init__(self, dirname):
self.dirname = dirname
def __iter__(self):
for fname in os.listdir(self.dirname):
for line in open(os.path.join(self.dirname, fname)):
yield line.split()
from gensim.models.word2vec import Word2Vec
sentences = "sentence.txt"
model = Word2Vec(MySentence(sentence), min_count=1)
from gensim.models.word2vec import Word2Vec
from gensim.models.Word2Vec import LineSentence
inp = 'sentences.txt'
sentences = LineSentences(inp)
model = Word2Vec(sentences, size=100, window=5, min_count=1)
参数名称 | 默认值 | 用途 |
---|---|---|
sentences | None | 训练的语料,一个可迭代对象。对于从磁盘加载的大型语料最好用gensim.models.word2vec.BrownCorpus ,gensim.models.word2vec.Text8Corpus ,gensim.models.word2vec.LineSentence 去生成sentences |
size | 100 | 生成词向量的维度 |
alpha | 0.025 | 初始学习率 |
window | 5 | 句子中当前和预测单词之间的最大距离,取词窗口大小 |
min_count | 5 | 文档中总频率低于此值的单词忽略 |
max_vocab_size | None | 构建词汇表最大数,词汇大于这个数按照频率排序,去除频率低的词汇 |
sample | 1e-3 | 高频词进行随机下采样的阈值,范围是(0, 1e-5) |
seed | 1 | 向量初始化的随机数种子 |
workers | 3 | 几个CPU进行跑 |
min_alpha | 0.0001 | 随着学习进行,学习率线性下降到这个最小数 |
sg | 0 | 训练时算法选择 0:skip-gram, 1: CBOW |
hs | 0 | 0: 当这个为0 并且negative 参数不为零,用负采样,1:层次 softmax |
negative | 5 | 负采样,大于0是使用负采样,当为负数值就会进行增加噪音词 |
ns_exponent | 0.75 | 负采样指数,确定负采样抽样形式:1.0:完全按比例抽,0.0对所有词均等采样,负值对低频词更多的采样。流行的是0.75 |
cbow_mean | 1 | 0:使用上下文单词向量的总和,1:使用均值; 只适用于cbow |
hashfxn | hash | 希函数用于随机初始化权重,以提高训练的可重复性。 |
iter | 5 | 迭代次数,epoch |
null_word | 0 | 空填充数据 |
trim_rule | None | 词汇修剪规则,指定某些词语是否应保留在词汇表中,默认是 词频小于 min_count则丢弃,可以是自己定义规则 |
sorted_vocab | 1 | 1:按照降序排列,0:不排序;实现方法:gensim.models.word2vec.Word2VecVocab.sort_vocab() |
batch_words | 10000 | 词数量大小,大于10000 cython会进行截断 |
compute_loss | False | 损失(loss)值,如果是True 就会保存 |
callbacks | () | 在训练期间的特定阶段执行的回调序列~gensim.models.callbacks.CallbackAny2Vec |
max_final_vocab | None | 通过自动选择匹配的min_count将词汇限制为目标词汇大小,如果min_count有参数就用给定的数值 |
可继续训练模型就是,下次加载模型后仍可以在模型的基础上进行训练。
持久化为model文件:
model.save(u"我的.model")
激活model文件:
model = word2vec.Word2Vec.load(u"我的.model")
加载后继续训练模型:
model.train(dataset, total_examples=1, epochs=1)
持久化词向量:
word2vec = model.wv
word2vec.save("a.wv")
# 与这个相同 vector_file是txt文件
model.wv.save_word2vec_format(vector_file, binary=False)
激活词向量对象:
wv = KeyedVectors.load("a.wv", mmap="r")
使用词向量对象获取某个词的向量表示:
wordVector = wv["市政委"]
值得注意的是:wv只是词向量对象,并不是模型对象。所以wv不能继续训练,但是model可以继续进行训练。
持久化为c语言可解析的文件:
model.save_word2vec_format(u"我的.model.bin", binary=True)
激活该文件:
model = word2vec.Word2Vec.load_word2vec_format(u"我的.model.bin", binary=True)
model是Word2Vec的实例化对象,wv是Word2Vec的一个属性。wv属性中存储着词向量。
模型大致有三种用途的函数:
wv是词向量对象:wv是KeyedVector的实例化。(这样就将词向量与模型分离了,wv只是词向量并不能训练,而模型可以继续进行训练)
Google提供了多种与训练模型:
atch(words):从一个列表中找出不同类的词。
Google提供了多种与训练模型: