赞
踩
list.copy()
,二维字典传入使用copy.deepcopy(dict)
bi-gram
阶段copy
函数。尤其是在进行bi-gram
过程时,需要使用deepcopy
函数,非常慢。之后直接对整个test
文本进行一次平滑操作。前后结果基本没有发生变化bi-gram
的概率计算方式和平滑处理方式不对,导致困惑度偏大P(abc) = P(a)P(b|a)P(c|b)
{a: {b: 1, c: 2}}
,当进行平滑处理时,令b
,c
都加一,也就是{a: {b: 2, c: 3}}
,这样的话,a
出现的次数b + c
就从1+2
变成2+3
了,对单个a
来说就不是加一平滑了{a: {a: 3, b: 1, c: 2}}
,将a
存入a
的子项中。进行平滑处理时,令a
,b
,c
都加一,也就是{a: {a: 4, b: 2, c: 3}}
,这样的话a = b + c
就不成立了abc
变成begin abc end
。这样的话,我们统计的时候,就可以仅统计 P(a|b)
形式的词频就好了,因为P(begin abc end) = P(begin)P(a|begin)P(b|a)P(c|b)P(end|c)
。一句话的开头,P(begin) = 1
。所以可以写成P(begin abc end) = P(a|begin)P(b|a)P(c|b)P(end|c)
。全是P(a|b)
形式,容易平滑和处理因为train
和test
都是以文本的形式给出的,而我们利用语言模型生成句子是以词项为基本单位的。因此,我们需要从文本中提取词项,以构建语言模型和测试语言模型
needless_words = ['!', ',', '.', '?', ':', ';', '<', '>'] # 常见标点符号
读取训练数据
def read_train_file(file_path): # 返回的是所有的词,格式是二维列表,每句的词组成一个列表,
res_list = []
with open(file_path, 'r', encoding='utf-8') as f:
reader = f.readlines()
for line in reader:
split_line = line.strip().split('__eou__') # 分句
words = [nltk.word_tokenize(each_line) for each_line in split_line] # 分词,每句的词都是一个列表
for i in words:
need_word = [word.lower() for word in i if word not in needless_words] # 删除常见标点,并将所有词进行小写处理
if len(need_word) > 0:
res_list.append(need_word)
return res_list
读取测试数据
def read_test_file(file_path): # 返回的是所有的句子,格式是二维列表,每个句子都是一个列表
res_list = []
with open(file_path, 'r', encoding='utf-8') as f:
reader = f.readlines()
for line in reader:
split_line = line.strip().split('__eou__') # 分句
for i in split_line:
if len(i) > 0:
res_list.append(i)
return res_list
词项统计(train)
read_train_file
函数的输出内容{a: 10}
,含义是:a
在训练数据中出现了 10 次total_words
。因为后面要算的是一个词出现的概率,total_words
可作为分母def uni_gram(word_list): # 计算词频,返回的是一个保留词频的字典,word_list格式是二维列表
global total_words
uni_dict = defaultdict(float)
for line in word_list:
for word in line:
total_words += 1 # 计算总的词的个数
uni_dict[word] += 1 # 计算词频
return uni_dict
加一平滑 + 困惑度计算
输入:word_dict
字典,就是uni_gram
函数的输出内容。sens
二维列表,就是read_test_file
函数的输出内容
输出:列表,存储了测试数据中每个句子的困惑度
加一平滑:
为防止测试数据中出现了训练数据中从未出现的词,而导致一句话出现的概率为 0。我们在遍历训练数据时,当遇到字典中没有出现过的词时,我们将其添加到字典中,令其出现的次数为 0。
之后我们对字典中的所有词的词项都加 1。
计算每个词出现的概率:
C
(
w
i
)
C(w_i)
C(wi) 为原词频,
N
N
N 为训练数据总词数,
V
V
V 是新增加的 1 的个数
计算句子出现的概率: P ( a b c d ) = P ( a ) P ( b ) P ( c ) P ( d ) P(abcd) = P(a)P(b)P(c)P(d) P(abcd)=P(a)P(b)P(c)P(d),这里为了减小误差,我们将累乘变成 l o g log log 累加的形式
计算困惑度
def ppl_compute(word_dict, sens): # word_dict是存储词频的字典, sen是没经过分词的一个 test 句子 temp = [] for sen in sens: words = nltk.word_tokenize(sen) need_words = [word.lower() for word in words if word not in needless_words] # 提取出句子中所有的词项 temp.append(need_words) for word in need_words: # test语句中未在 train 时出现过的词,新加入 if word not in word_dict: word_dict[word] = 0 for word in word_dict: # 所有词项的词频都加 1,进行平滑处理 word_dict[word] += 1 word_dict[word] /= len(word_dict) + total_words # 每个词都加一后的增加量 + 原有的词的总数 for need_words in temp: res_ppl = 1 for word in need_words: res_ppl += log(word_dict[word], 2) # 防止累乘出现 res_ppl = 0 的情况 uni_ppl.append(pow(2, -(res_ppl / len(need_words))))
词项统计(train)
read_train_file
函数的输出内容{a: {b: 1, c: 2}}
,含义:在a
出现的情况下,b
出现 1 次,c
出现 2 次遇到和解决的问题中阐述
。def bi_gram(word_list): # 统计 bi_gram 的词频,返回一个二维字典
bi_dict = defaultdict(dict)
for words in word_list:
words.insert(0, 'nsy6666') # 每行的词加个开头
words.append('nsy6666///') # 每行的词加个结尾
for index in range(len(words) - 1):
if words[index + 1] not in bi_dict[words[index]]: # 其他词作为子项
bi_dict[words[index]][words[index + 1]] = 1
else:
bi_dict[words[index]][words[index + 1]] += 1
return bi_dict
加一平滑 + 困惑度计算
bi_word
字典,就是bi_gram
函数的输出内容。sens
二维列表,就是read_test_file
函数的输出内容b
出现的情况下,a
出现的概率。就是bi_word[b][a] / b出现的次数
。uni-gram
一样计算困惑度def ppl_compute_bi(bi_word, sens): temp = [] for sen in sens: # 遍历每个句子 words = nltk.word_tokenize(sen) need_words = [word.lower() for word in words if word not in needless_words] # 提取出句子中所有的词项 need_words.insert(0, 'nsy6666') # 每行的词加个开头 need_words.append('nsy6666///') # 每行的词加个结尾 temp.append(need_words) for index in range(len(need_words) - 1): # 添加 test 句子中同时出现的 bi_gram,但未在 train 中同时出现的 bi_gram if need_words[index + 1] not in bi_word[need_words[index]]: bi_word[need_words[index]][need_words[index + 1]] = 0 for first_word in bi_word: # 对 bi_gram 词项进行平滑处理 for second_word in bi_word[first_word]: bi_word[first_word][second_word] += 1 for first_word in bi_word: # 对 bi_gram 词项进行平滑处理。不能只使用 need_words,因为中间有很多重复的词,会进行不该进行的除法 tt = sum(bi_word[first_word].values()) # 需要提前定义在这里,否则后面进行除法之后,这个值就发生改变了 for second_word in bi_word[first_word]: bi_word[first_word][second_word] /= tt for need_words in temp: res_ppl = 0 for index in range(len(need_words) - 1): res_ppl += log(bi_word[need_words[index]][need_words[index + 1]], 2) bi_ppl.append(pow(2, -(res_ppl / (len(need_words) - 1))))
uni-gram
print(sum(uni_ppl) / len(uni_ppl)) # 取所有句子困惑度的平均值
>>723.2634736604283
bi-gram
print(sum(bi_ppl) / len(bi_ppl)) # 取所有句子困惑度的平均值
>>51.02679427126319
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。