基于词向量的 f a q faq faq问答
step 1
读取将
J
S
O
N
JSON
JSON文件中的问答卷,将其中的
q
u
e
s
t
i
o
n
question
question和
a
n
s
w
e
r
answer
answer分离sentences_list
记录
q
u
e
s
t
i
o
n
question
question
with open('train.json', 'r', encoding='utf-8') as f:
for line in f:
# 表示将字符串line解析为JSON格式的对象
json_obj = json.loads(line)
question[cnt] = json_obj
sentences_list.append(question[cnt]['question'])
cnt = cnt + 1
with open('train.json', 'r', encoding='utf-8') as f:
以r
的形式,以'utf-8'的格式
打开'train.json'
文件。
sentences_list
是一个列表,里面存储了所有的question
。
step 2
加载停用词表
def stopwordslist():
#strip()方法只会去除字符串开头和结尾的空白字符,返回一个list
stopwords = [line.strip() for line in open('stopwords.txt', 'r', encoding='utf-8').readlines()]
return stopwords
把'stopwords.txt'
里面的停用词加载出来,并存储到列表stopwords
中
step 3
使用 j i e b a jieba jieba分词和停用词表进行分词
# 加载停用词表
stopwords = stopwordslist()
# print(f"stopwords===========>{stopwords}")
sentences_cut = []
# 结巴分词
for ele in sentences_list:
cuts = jieba.cut(ele, cut_all=False)
new_cuts = []
for cut in cuts:
if cut not in stopwords:
new_cuts.append(cut)
sentences_cut.append(new_cuts)
使用jieba
分词,把sentences_list
里面的每一个question分词,并且过滤掉停用词,并把过滤之后的词放到 sentences_cut
中。
例如:
在
J
S
O
N
JSON
JSON文件中question
是这样的::
"question": "我是在工地开车把左手小手指粉碎骨折,但老板为了报保险给我用别人的名字那我还可以和老板要误工费吗"
经过 j i e b a jieba jieba分词和过滤停用词之后得到
['工地', '开车', '左手', '小手指', '粉碎', '骨折', '老板', '报', '名字', '老板', '误工费']
step 4
把列表sentences_cut
写入到文件'data1.txt'
中
with open('data1.txt', 'w', encoding='utf-8') as f:
for ele in sentences_cut:
# print(ele)
ele = ele + list('\n')
elestr = ' '.join(ele)
f.write(elestr)
step 5
训练模型
可以用
B
r
o
w
n
C
o
r
p
u
s
BrownCorpus
BrownCorpus,
T
e
x
t
8
C
o
r
p
u
s
Text8Corpus
Text8Corpus或
l
i
n
e
S
e
n
t
e
n
c
e
lineSentence
lineSentence来构建sentences
# 可以用BrownCorpus,Text8Corpus或lineSentence来构建sentences,一般大语料使用这个
# word2vec.LineSentence()函数返回的可迭代对象赋值给变量sentences,sentences是一个二维list
sentences = list(word2vec.LineSentence('data1.txt'))
# sentences = list(word2vec.Text8Corpus('data.txt'))
##训练方式1
model = Word2Vec(sentences, vector_size=300,min_count=1, window=5, sg=1, workers=multiprocessing.cpu_count())
# 训练方式2
# 加载一个空模型
model2 = Word2Vec(min_count=1)
# 加载词表
model2.build_vocab(sentences)
# 训练
model2.train(sentences, total_examples=model2.corpus_count, epochs=10)
其中第一种方式中Word2Vec各参数的意义为:
step 6
模型的保存与加载
模型保存可以有很多种格式,根据格式的不同可以分为2种,一种是保存为.model的文件,一种是非.model文件的保存。我常用的保存格式是.model和.vector直接上代码和结果:
#方式一:
model.save('word2vec.model')
#方式二:
model.wv.save_word2vec_format('word2vec.vector')
model.wv.save_word2vec_format('word2vec.bin')
这两种方式的加载在获取词向量的时候应该是差别不大,区别就是.model可以继续训练, . v e c t o r .vector .vector的文件不能继续训练。加载速度也可以见,前者比后者快很多。前者时间为0.0020秒后者0.03秒,相差十多倍。 . v e c t o r .vector .vector和 . b i n .bin .bin文件直接可以用 . t x t .txt .txt打开可视,而 . m o d e l .model .model并不可视,它们的内存占用要少一些,加载的时间要多一点。
结果如下:
step 7
检验结果
ask = '被车撞,对方负全责,我该找车主还是保险公司来买单?'
new_cuts_first = []
cuts = jieba.cut(ask, cut_all=False)
for cut in cuts:
if cut not in stopwords:
new_cuts_first.append(cut)
print(new_cuts_first)
ask
相当于用户提出的问题,按照上面的方式进行分词,new_cuts_first
就是分词之后的结果。
#similar_max 记录最大相似度
similar_max = 0
#flag 记录所在位置
flag = -1
length = len(sentences_list)
for i in range(length):
sen = sentences_list[i]
cuts = jieba.cut(sen, cut_all=False)
new_cuts_sceond = []
for cut in cuts:
if cut not in stopwords:
new_cuts_sceond.append(cut)
if len(new_cuts_first) != 0 and len(new_cuts_sceond) != 0:
利用相同的方法对sentences_list
里面的米每一个问题都分词。并记录到new_cuts_sceond
当中。
if len(new_cuts_first) != 0 and len(new_cuts_sceond) != 0:
# 计算相似度
similar = model.wv.n_similarity(new_cuts_first, new_cuts_sceond)
# print(similar)
if similar >= similar_max:
similar_max = similar
flag = i
print(question[flag]['answer'])
print(flag)
计算相似度那里,model.wv.n_similarity(new_cuts_first, new_cuts_sceond)
具体来说,n_similarity
函数会接受两个参数:一个是词语列表,另一个是另一个词语列表。它会计算并返回这两个词语列表中的各个词语的平均余弦相似度(cosine similarity)。
得到的最终结果为:
第一行的输出为
a
s
k
ask
ask的
j
i
e
b
a
jieba
jieba分词的结果new_cuts_first
。
第二行的输出为 a s k ask ask的回答。
第三行的输出为在原 J S O N JSON JSON文件中,所在位置。(从0开始,也就是原文件中第11998行)。
总的代码
import multiprocessing
import jieba
from gensim.models import Word2Vec, word2vec
import json
cnt = 0
question = {}
#将问题添加到sentences_list里面
sentences_list = []
with open('train.json', 'r', encoding='utf-8') as f:
for line in f:
# 表示将字符串line解析为JSON格式的对象
json_obj = json.loads(line)
question[cnt] = json_obj
sentences_list.append(question[cnt]['question'])
cnt = cnt + 1
#停用词
def stopwordslist():
#strip()方法只会去除字符串开头和结尾的空白字符,返回一个list
stopwords = [line.strip() for line in open('stopwords.txt', 'r', encoding='utf-8').readlines()]
return stopwords
if __name__ == '__main__':
# 加载停用词表
stopwords = stopwordslist()
# print(f"stopwords===========>{stopwords}")
sentences_cut = []
# 结巴分词
for ele in sentences_list:
#cut_all=False分词采用精确模式,cut_all=False=True分词采用全模式
cuts = jieba.cut(ele, cut_all=False)
new_cuts = []
for cut in cuts:
if cut not in stopwords:
new_cuts.append(cut)
sentences_cut.append(new_cuts)
# print(f"sentences_cut===========>{sentences_cut}")
# 分词后的文本保存在data.txt中
with open('data1.txt', 'w', encoding='utf-8') as f:
for ele in sentences_cut:
ele = ele + list('\n')
elestr = ' '.join(ele)
f.write(elestr)
# 可以用BrownCorpus,Text8Corpus或lineSentence来构建sentences,一般大语料使用这个
# word2vec.LineSentence()函数返回的可迭代对象赋值给变量sentences,sentences是一个二维list
sentences = list(word2vec.LineSentence('data1.txt'))
# sentences = list(word2vec.Text8Corpus('data.txt'))
## 训练方式1
model = Word2Vec(sentences, vector_size=300,min_count=1, window=5, sg=1, workers=multiprocessing.cpu_count())
# # 训练方式2
# # 加载一个空模型
# model2 = Word2Vec(min_count=1)
# # 加载词表
# model2.build_vocab(sentences)
# # 训练
# model2.train(sentences, total_examples=model2.corpus_count, epochs=10)
# print(f"model2==========>{model2}")
## 保存
#方式一:
model.save('word3vec.model')
#方式二:
model.wv.save_word2vec_format('word3vec.vector')
model.wv.save_word2vec_format('word3vec.bin')
# 分词
ask = '我被车撞到,对方负全责,保险公司垫付交强险X万元费用,但是还差医院X万多,差的钱该找车主还是保险公司来买单?'
new_cuts_first = []
cuts = jieba.cut(ask, cut_all=False)
for cut in cuts:
if cut not in stopwords:
new_cuts_first.append(cut)
print(new_cuts_first)
#similar_max 记录最大相似度
similar_max = 0
#flag 记录所在位置
flag = -1
length = len(sentences_list)
for i in range(length):
sen = sentences_list[i]
cuts = jieba.cut(sen, cut_all=False)
new_cuts_sceond = []
for cut in cuts:
if cut not in stopwords:
new_cuts_sceond.append(cut)
if len(new_cuts_first) != 0 and len(new_cuts_sceond) != 0:
# 计算相似度
similar = model.wv.n_similarity(new_cuts_first, new_cuts_sceond)
# print(similar)
if similar >= similar_max:
similar_max = similar
flag = i
print(question[flag]['answer'])
print(flag)
至此一个完整的 f a q faq faq问答 d e m o demo demo已经完成。
但是考虑到存在这样的场景,模型训练以后,会有新的语料,也就存在新词,这个时候新词用 w o r d 2 v e c word2vec word2vec就得不到词向量,会报 o v o ovo ovo(貌似这样的,反正就是 o u t v a c b u a r y out vacbuary outvacbuary)的错误。那么就需要重新训练模型, g e n s i m gensim gensim就提供了一个很好的机制,就是增量训练,新词不用和旧词全部一起再重新训练。
增量训练
model = Word2Vec.load('word2vec.model')
print(model)
new_sentence = [
'我喜欢吃苹果',
'大话西游手游很好玩',
'人工智能包含机器视觉和自然语言处理'
]
stopwords = stopwordslist()
sentences_cut = []
#结巴分词
for ele in new_sentence:
cuts = jieba.cut(ele,cut_all=False)
new_cuts = []
for cut in cuts:
if cut not in stopwords:
new_cuts.append(cut)
sentences_cut.append(new_cuts)
#增量训练word2vec
model.build_vocab(sentences_cut,update=True) #注意update = True 这个参数很重要
model.train(sentences_cut,total_examples=model.corpus_count,epochs=10)
print(model)
new_sentence
就相当于新的语料。
model.build_vocab(sentences_cut,update=True)
#新增词汇训练模型,得到新的模型。旧模型Word2Vec(vocab=122, size=256, alpha=0.025),新模型Word2Vec(vocab=149, size=256, alpha=0.025)。