sentencePiece 分词原理学习
来源:互联网 发布:苹果cms监控软件手机版 编辑:程序博客网 时间:2024/06/05 03:43
github代码:https://github.com/google/sentencepiece
训练:
spm_train --input=<input> --model_prefix=<model_name> --vocab_size=8000 --model_type=<type>
model_name为保存的模型为model_name.model,词典为model_name.vocab,词典大小可以人为设定vocab
_size.训练模型包括unigram
(default), bpe
, char
, or word
四种类型.
分词:
对于每一句话输入形式为:
echo "我在北京天安门广场" | spm_encode --model=model_2014_bpe.model
▁我 在 北京 天安门 广场
输入为文件形式:
spm_encode --model=model_2014_bpe.model msr_test.utf8
即对文件msr_test.utf8分词处理.
bpe分词原理,python代码参考github:https://github.com/rsennrich/subword-nmt
训练:
对于训练本文输入句子,如, 我 在 北京 天安门 广场,我 是 北京 人,提取全部词,即 我,在,北京,天安门,广场,统计每个词出现的频率,代码如下:
def get_vocabulary(fobj, is_dict=False): """Read text and return dictionary that encodes vocabulary """ vocab = Counter() for line in fobj: if is_dict: word, count = line.strip().split() vocab[word] = int(count) else: for word in line.split(): vocab[word] += 1 return vocab
对词典排序,sorted_vocab = sorted(vocab.items(), key=lambda x: x[1], reverse=True),得到词典和词频为,我2,在1,北京2,天安门1,广场1,是1,人1
对于每个词的相邻字,生成字对,即有,我/w,在/w,北京,京/w,天安,安门,门/w,每个字对(不足两个字的补充结束符/w)的权重为对应的词的频率,这样得到了很多字对及其权重,我/w2,在/w1,北京2,京/w2,天安1,安门1,门/w1,广场1,场/w1,是/w1,人/w1,词典切分对代码如下:
def get_pair_statistics(vocab): """Count frequency of all symbol pairs, and create index""" # data structure of pair frequencies stats = defaultdict(int) #index from pairs to words indices = defaultdict(lambda: defaultdict(int)) for i, (word, freq) in enumerate(vocab): prev_char = word[0] for char in word[1:]: stats[prev_char, char] += freq indices[prev_char, char][i] += 1 prev_char = char return stats, indices
得到字对,stats, indices = get_pair_statistics(sorted_vocab),stats为字对,indices为每个字对应的词的索引,以及该字对在该词中出现的次数.
得到字对后,我们求取stats中最大词频的对pair(词频越大,该字对越可能组成词),保存该对到模型文件model中,代码为:
if stats: most_frequent = max(stats, key=lambda x: (stats[x], x))if stats[most_frequent] < args.min_frequency: sys.stderr.write('no pair has frequency >= {0}. Stopping\n'.format(args.min_frequency)) break
之后需要求pair在那些词中出现过,
changes = replace_pair(most_frequent, sorted_vocab, indices)
并对这些词生成的其他pair的词频减去该词的词频,例如pair在vocab[words2]=3,vocab[words5]=7中出现过,而word2对应的字对有,pair,pair1,pair3,words5对应的字对有,pair,pair6,pair9,那么stats[pair1]-=vocab[words2],stats[pair3]-=vocab[words2],stats[pair6]-=vocab[words5],stats[pair9]-=vocab[words5],代码为
for j, word, old_word, freq in changed: # find all instances of pair, and update frequency/indices around it i = 0 while True: try: i = old_word.index(first, i)#找到该对的第一项在词中对应的索引 except ValueError: break if i < len(old_word)-1 and old_word[i+1] == second: if i: prev = old_word[i-1:i+1] stats[prev] -= freq#对于该对对应的词中的其他对,对应的stats项将去该词的词频 indices[prev][j] -= 1#并且对应的indices减1,j为词的索引,prev为字对 if i < len(old_word)-2: # don't double-count consecutive pairs if old_word[i+2] != first or i >= len(old_word)-3 or old_word[i+3] != second: nex = old_word[i+1:i+3] stats[nex] -= freq indices[nex][j] -= 1 i += 2 else: i += 1
合并该pair对应的词中的pair项,例如pair项为,天安,对应的词为,天,安,门,广,场,则现在词为,天安,门,广,场,由该词得到对,这样又可以得到新的对,天安,门,将其添加进stats,并且pair对应的stats为0,将其删除.
stats[pair] = 0#该对的stats项置0 indices[pair] = defaultdict(int)#该对的indices项置为空 first, second = pair new_pair = first+secondi = 0while True:#对于新合并的字对,例如词,包括,殊,.,/w,现在字对./w词频最高,合并该字对,则得到newword=殊,./w #现在得到新的对殊./w,该新的对对应的stats值为对应的oldword的词频,indices也加1 try: i = word.index(new_pair, i) except ValueError: break if i: prev = word[i-1:i+1] stats[prev] += freq indices[prev][j] += 1 # don't double-count consecutive pairs if i < len(word)-1 and word[i+1] != new_pair: nex = word[i:i+2] stats[nex] += freq indices[nex][j] += 1 i += 1
这部分函数为:
changes = replace_pair(most_frequent, sorted_vocab, indices)# 对于包含该字对的词所生成的对stats减去该字对对应的权重freq,该对应的indices-1,添加新生成的对到statsupdate_pair_statistics(most_frequent, changes, stats, indices)
模型step为args.symbols,但如果stats为空,或者所有的stats项的词频小于最小词频,则停止训练.
big_stats = copy.deepcopy(stats)
# threshold is inspired by Zipfian assumption, but should only affect speed
#stats.values()为字对的词频
threshold = max(stats.values()) / 10
for i in range(args.symbols):
if stats:
most_frequent = max(stats, key=lambda x: (stats[x], x))
# we probably missed the best pair because of pruning; go back to full statistics if not stats or (i and stats[most_frequent] < threshold): prune_stats(stats, big_stats, threshold) stats = copy.deepcopy(big_stats) most_frequent = max(stats, key=lambda x: (stats[x], x)) # threshold is inspired by Zipfian assumption, but should only affect speed threshold = stats[most_frequent] * i/(i+10000.0) prune_stats(stats, big_stats, threshold) if stats[most_frequent] < args.min_frequency: sys.stderr.write('no pair has frequency >= {0}. Stopping\n'.format(args.min_frequency)) break if args.verbose: sys.stderr.write('pair {0}: {1} {2} -> {1}{2} (frequency {3})\n'.format(i, most_frequent[0], most_frequent[1], stats[most_frequent])) args.output.write('{0} {1}\n'.format(*most_frequent)) changes = replace_pair(most_frequent, sorted_vocab, indices) # 对于包含该字对的词所生成的对stats减去该字对对应的权重freq,该对应的indices-1,添加新生成的对到stats update_pair_statistics(most_frequent, changes, stats, indices) stats[most_frequent] = 0#将该字对权重置0 if not i % 100: prune_stats(stats, big_stats, threshold)#从stats中删除权重小于threshold的对
测试:
对于输入句子,如, 我在北京天安门广场,将其相邻字切分成对,即有,我在,在北,北京,京天,天安,安门,门广,广场,场/w,查找每个字对在模型中的权重,若该字对不再模型中,则返回权重为inf,取权重最小的对,合并该对为一个词,将该词最为一个项,重新切分整个句子,再重复之前的过程,知道所有的切分对都不在模型中时,分割结束.
第一次切分,查找最小权重对为,北京,合并北京得到新的句子,我/在/北京/天/安/门/广/场,将其切分,得到:我在,在北京,北京天,天安,安门,门广,广场,在此查找这些对是否在模型中,发现所有对都不在模型中,因此将其作为最后的分词结果,分词结果为: 我 在 北京 天 安 门 广 场
切分代码如下:
def encode(orig, bpe_codes, cache={}): """Encode word based on list of BPE merge operations, which are applied consecutively """ if orig in cache: return cache[orig] word = tuple(orig) + ('</w>',) pairs = get_pairs(word)#得到相邻字的对 while True: #查找pair中的对在bpe_coders中的权重,取权重最小的pair,若pair不再bpe_codes中,则返回权重值为inf pair_min=0 for pair in pairs: pair_min=bpe_codes.get(pair, float('inf')) bigram = min(pairs, key = lambda pair: bpe_codes.get(pair, float('inf'))) # 查找word中相邻的词的对的权重最小,直到最小对不在bpe_codes中停止查找,即所有的对都不在bpe_codes中 if bigram not in bpe_codes: break first, second = bigram new_word = [] i = 0 while i < len(word): try: j = word.index(first, i) new_word.extend(word[i:j]) i = j except: new_word.extend(word[i:]) break #合并该权重最小的对,组成新的词 if word[i] == first and i < len(word)-1 and word[i+1] == second: new_word.append(first+second) i += 2 else: new_word.append(word[i]) i += 1 new_word = tuple(new_word) word = new_word if len(word) == 1: break else: pairs = get_pairs(word) # don't print end-of-word symbols if word[-1] == '</w>': word = word[:-1] elif word[-1].endswith('</w>'): word = word[:-1] + (word[-1].replace('</w>',''),) cache[orig] = word return word
由上可见,对于bpe算法,.训练数据很关键,例如,我在北京天安门广场,实际应该分词为,我 在 北京 天安门广场,而由于训练语料得到的模型中不存在天安门广场对,依次错分.
- sentencePiece 分词原理学习
- sentencepiece分词效果测试
- lucene 分词器的原理和学习
- 跟小刀学习 lucene 分词的原理
- 分词学习
- Lucene 分词原理
- Lucene 分词原理
- Lucene 分词原理
- Lucene 分词原理
- Lucene 分词原理
- lucene分词原理
- Lucene 分词原理
- Lucene 分词原理
- lucene 分词原理
- lucene 分词原理
- 中文分词原理
- lucene3.0分词原理和分词系统
- Lucene3.0分词原理与分词系统
- mysql grant
- Mybatis-Generator自动生成代码——Mybatis 深入浅出(二)
- 深度理解链式前向星(转载于ACdreamer大神)
- MJPG-streamer源码分析-主函数部分
- 【Linux基础系列之】pinctrl系统
- sentencePiece 分词原理学习
- struts2--struts2国际化
- 蓝桥杯_算法训练_动态数组使用
- CListCtrl
- 数据库备份和还原
- php中正则表达式
- SPOJ Count on a tree(树上第K大)
- FIDO UAF Extension
- Android 之 阿里百川 -- 云消息推送服务