只用tf-idf的时候输出的结果基本正确,用了倒排表优化后输出的答案就完全不正确了。不知道哪里出问题了,求大神解答
# 分数(5)
import json
from collections import Counter
import matplotlib.pyplot as plt
from nltk.stem import PorterStemmer
from nltk.tokenize import word_tokenize
from sklearn.feature_extraction.text import TfidfVectorizer
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
import math
from collections import defaultdict
def read_corpus(filepath):
"""
读取给定的语料库,并把问题列表和答案列表分别写入到 qlist, alist 里面。 在此过程中,不用对字符换做任何的处理(这部分需要在 Part 2.3里处理)
qlist = ["问题1", “问题2”, “问题3” ....]
alist = ["答案1", "答案2", "答案3" ....]
务必要让每一个问题和答案对应起来(下标位置一致)
"""
qlist = []
alist = []
with open(filepath) as f:
file_array = json.load(f)['data']
for file in file_array:
paragraph = file['paragraphs']
for paragraphs in paragraph:
paragraph = paragraphs['qas']
for qa in paragraph:
qlist.append(qa['question'])
try:
alist.append(qa['answers'][0]['text'])
except IndexError:
qlist.pop()
assert len(qlist) == len(alist) # 确保长度一样
return qlist, alist
# TODO: 对于qlist, alist做文本预处理操作。 可以考虑以下几种操作:
# 1. 停用词过滤 (去网上搜一下 "english stop words list",会出现很多包含停用词库的网页,或者直接使用NLTK自带的)
# 2. 转换成lower_case: 这是一个基本的操作
# 3. 去掉一些无用的符号: 比如连续的感叹号!!!, 或者一些奇怪的单词。
# 4. 去掉出现频率很低的词:比如出现次数少于10,20....
# 5. 对于数字的处理: 分词完只有有些单词可能就是数字比如44,415,把所有这些数字都看成是一个单词,这个新的单词我们可以定义为 "#number"
# 6. stemming(利用porter stemming): 因为是英文,所以stemming也是可以做的工作
# 7. 其他(如果有的话)
# 请注意,不一定要按照上面的顺序来处理,具体处理的顺序思考一下,然后选择一个合理的顺序
# hint: 停用词用什么数据结构来存储? 不一样的数据结构会带来完全不一样的效率!
def text_preprocessing(text):
# 生成停用词和标准化
stopfile_path = r'C:\Users\Administrator\nltk_data\corpora\stopwords\baidu_stopwords.txt'
with open(stopfile_path, 'r', encoding='UTF-8') as f:
sw = set(f.read())
sw -= {'when', 'who', 'why', 'what', 'how', 'where', 'which'}
ps = PorterStemmer()
seg = list()
#用nltk分词
for word in word_tokenize(text):
#小写化,次干提取
word = ps.stem(word.lower())
#数值归一
word = '#number' if word.isdigit() else word
#去停用词
if len(word)>1 and word not in sw:
seg.append(word)
return seg
def qlist_preprocessing(qlist):
word_cnt = Counter()
qlist_seg = list()
for text in qlist:
seg = text_preprocessing(text)
qlist_seg.append(seg)
word_cnt.update(seg)
value_sort = sorted(word_cnt.values(),reverse=True)
min_tf = value_sort[int(math.exp(0.99*math.log(len(word_cnt))))]
for cur in range(len(qlist_seg)):
qlist_seg[cur] = [word for word in qlist_seg[cur] if word_cnt[word] > min_tf]
return qlist_seg
# 分数(10)
def top5results_invidx(input_q):
"""
给定用户输入的问题 input_q, 返回最有可能的TOP 5问题。这里面需要做到以下几点:
1. 对于用户的输入 input_q 首先做一系列的预处理,然后再转换成tf-idf向量(利用上面的vectorizer)
2. 计算跟每个库里的问题之间的相似度
3. 找出相似度最高的top5问题的答案
"""
qlist, alist = read_corpus(r'C:\Users\Administrator\Desktop\train-v2.0.json')
alist = np.array(alist)
# 分数(10)
# TODO: 把qlist中的每一个问题字符串转换成tf-idf向量, 转换之后的结果存储在X矩阵里。 X的大小是: N* D的矩阵。 这里N是问题的个数(样本个数),
# D是字典库的大小。
vectorizer = TfidfVectorizer() # 定义一个tf-idf的vectorizer
qlist_seg = qlist_preprocessing(qlist) #对qlist进行处理
seg = text_preprocessing(input_q) #对输入的问题进行处理
X = vectorizer.fit_transform([' '.join(se) for se in qlist_seg]) # 结果存放在X矩阵
#定义一个简单的倒排表
inversed_idx = defaultdict(set)
for cur in range(len(qlist_seg)):
for word in qlist_seg[cur]:
inversed_idx[word].add(cur)
candiates = set()
for word in seg:
#取所有包含任意一词的文档的并集
candiates = candiates | inversed_idx[word]
candiates = list(candiates)
input_vec = vectorizer.transform([' '.join(seg)]) #对输入的问题进行分词预处理和转换成tf-idf向量
sim = cosine_similarity(input_vec,X[candiates])[0] #计算余弦相似度
top_idx = np.argsort(sim)[-5:].tolist() #找出相似度最高的top5下标
top_idx.reverse()
print([x for x in sim[top_idx]])
return [alist[i] for i in top_idx] # 返回相似度最高的问题对应的答案,作为TOP5答案
print(top5results_invidx("What government blocked aid after Cyclone Nargis?"))