一. 前言
前一天已经说明N-gram的一些计算方式了,这篇会以实作'预测词'来作为N-gram的范例,就是利用前面的词来预测後面该接哪个词较好,这是参与某堂课里面的其中一项作业,因觉得应用不错,故分享~但我有做一些小修改,让我比较好讲解XD~
二. 资料集
资料集是利用维基百科的资料的中文资料做处理,该课程提供的资料集较小,猜测应该是有缩小资料集,但来源是维基的没有错
三. 演算法
四. 实作
import 套件:
import re
from collections import Counter
简单的前处理,只保留中文,并断句:
def prepocess_text(line: str) -> list:
# 只保留中文字元,并且断开不连续的中文字
chinese = r'[\u4E00-\u9FFF]+'
segments = re.findall(chinese, line)
return segments
读取data,用prepocess_text来做前处理:
word_list = []
with open('./wiki_zh_small.txt', encoding="utf-8") as file:
for line in file.readlines():
word_list += prepocess_text(line)
prepocess_text('“今天”雨会下非常大,大到你受不了')
# output: ['今天', '雨会下非常大', '大到你受不了']
建立counter的class,用来记录1个字出现几次,2个字出现几次:
class etWordCounters:
def __init__(self, n):
self.n = n
self.counters = [Counter() for _ in range(n + 1)]
def generate_gram(self, segments):
# 若 n=1->unigram n=2-> bigram
for i in range(1, 1 + self.n):
for segment in segments:
self.counters[i] += Counter(self._skip(segment, i))
# 用 self.counters[0] 来记录总共有几个字,这边用'eating'当key XD
self.counters[0] = Counter({'eating': sum(dict(self.counters[1]).values())})
def __getitem__(self, k):
return self.counters[k]
def _skip(self, segment, n):
if len(segment) < n:
return []
shift = n - 1
for i in range(len(segment) - shift):
yield segment[i : i + shift + 1]
建立N-gram 的class:
class Ngram:
def __init__(self, n: int, counters: list):
"""
n: n-gram's n
counters: etWordCounters object
"""
self.n = n
self.major_counter = counters[n]
self.minor_counter = counters[n-1]
def predict_next_word(self, prefix: str = '', top_k: int = 5):
"""
explain:
if prefix is empty string, using 1-gram predict next word
elif prefix is greater than one words use, using 2-gram
"""
# 表示前无词可预测
if self.n <= 1:
prefix = 'eating'
else:
prefix = prefix[-(self.n - 1):] # 取得str後面个字
count_prefix = self.minor_counter[prefix]
probs = []
# get word and probablity
for key, count in dict(self.major_counter).items():
# transfer eating to ''
prefix = '' if prefix == 'eating' else prefix
if key.startswith(prefix):
prob = count / count_prefix
probs.append((prob, key[-1]))
sorted_probs = sorted(probs, reverse=True)
return sorted_probs[:top_k] if top_k > 0 else sorted_probs
def get_word_dict(self, prefix=''):
"""
explain:
get predict_next_word result and transfer to dict
"""
return {word: prob for prob, word in self.predict_next_word(prefix, top_k=-1)}
计算字的次数:
counters = etWordCounters(n=2)
counters.generate_gram(word_list)
来看共有一个字:
# 全部有多少字
counters[0]
# output: Counter({'eating': 371373}) 总共有371373个字
建立 uni-gram 与 bi-gram model
unigram = Ngram(1, counters)
bigram = Ngram(2, counters)
预测建立预测下个字model的class,若前面无词用uni-gram,有一个字以上用bi-gram:
class ChineseWordPredict:
def __init__(self, unigram, bigram):
self.unigram = unigram
self.bigram = bigram
def predict_proba(self, prefix='', top_k=5):
# 使用Ngram来建立选字系统
if len(prefix) == 0:
return self.unigram.predict_next_word(prefix, top_k)
elif len(prefix) >= 1:
return self.bigram.predict_next_word(prefix, top_k)
model = ChineseWordPredict(unigram, bigram)
预测'我思'的下个字:
probs = model.predict_proba('我思', top_k=4)
probs
# output:
# [(0.3370165745856354, '想'),
# (0.12154696132596685, '考'),
# (0.09944751381215469, '维'),
# (0.04419889502762431, '是')]
这次介绍怎麽建立N-gram 的模型,下一篇会开始介绍POS(part of speech) 以及如何用python实作演算法,POS的主题还蛮长的,应该会花个几天写这个主题~
接下来我们来看看在SwiftUI 怎麽使用WebView 网页的元件,SwiftUI 框架 有一个缺...
变数 JS目前有三种宣告变数的方法。 在ES5以前都用var,ES6之後推出let与const。 新...
ZK 元件的配色与设计都有经过设计师制定,但我想你仍有你自己特定的需要,当有需要改变元件外观时,有几...
延续昨天的文章,今天来做PHP新增资料表的内容(不影响资料表结构)。 注意! 跟资料库有关的动作 都...
在正式开发前,开发者都需要安装许多软件,来建立开发的环境。但是安装的方式会因开发所在的机器环境而有所...