要开发出一款聊天机器人,首先要理解中文分词、文本的数学表示和文本的相似度计算这几个概念。
中文分词就是将一句句子拆分成独立的词语,Python 提供的 Jieba
分词库可以帮助我们完成这项工作。
使用 Jieba
得到句子分词的示例:
import jieba
s = 'Python是一种面向对象的动态类型语言。'
[ print(c) for c in jieba.cut(s)]
结果:
Python
是
一种
面向对象
的
动态
类型
语言
。
在对这些文本进行处理前,需要将这些文本用数学的方式来表达,然后才能够交给机器去计算。
在数学中,可以用向量来表示一个词,这称之为词向量。
比如有这样一个词典:
Python、 是、 一种、 面向对象、 的、 动态、 类型、 语言
再给定以下三个词:
Python、 面向对象、 人工智能
这三个词的词向量分别为:
可见,如果词典上的某一位置的词与给定的词匹配,则置为 1,否则置为 0。
使用 Python 完成词向量转换:
# 词库
word_vector_list = ["Python", "是", "一种", "面向对象", "的", "动态", "类型", "语言"]
# 要转成词向量的词
word1 = "Python"
word2 = "面向对象"
word3 = "人工智能"
# 定义词向量转换方法
def get_word_vector_result(word):
return [ 1 if(w == word) else 0 for w in word_vector_list]
print(get_word_vector_result(word1))
print(get_word_vector_result(word2))
print(get_word_vector_result(word3))
结果:
[1, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 1, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
词向量如何表示现在已经知道了,那如何将一句句子表示成向量呢?
还是使用刚才的词典:
Python、 是、 一种、 面向对象、 的、 动态、 类型、 语言
再给定以下两句句子:
Python是一种高级语言
我们在学习Python
这两句句子的向量分别为:
在得到句子的向量之前,需要先拆分出每一句句子的分词,再逐一检查词典中的分词是否存在于给定句子的分词中,若存在则置为 1,否则置为 0。
实际上句子向量的转换本质就是词向量的转换,只不过句子是由多个分词构成。
使用 Python 完成句向量转换:
import jieba
# 词库
word_vector_list = ["Python", "是", "一种", "面向对象", "的", "动态", "类型", "语言"]
# 用户输入的语句
s1 = "Python是一种高级语言"
s2 = "我们在学习Python"
# 转化成向量的方法
def get_vector(data):
data_iter = list(jieba.cut(data))
return [ 1 if(w in data_iter) else 0 for w in word_vector_list]
# 打印向量
print(get_vector(s1))
print(get_vector(s2))
Python 提供了 gensim
工具来帮助我们完成向量的转换:
from gensim.models import word2vec
# 从 xxx.txt 读取句子,文件中存的是一个个分词
sentences = word2vec.Text8Corpus('xxx.txt')
# 将句子转换为向量,句子的分词数量较小时,需要加上 min_count=1 参数
model = word2vec.Word2Vec(sentences, min_count=1)
# 将向量结果保存到 word2vec.model 中
model.save('word2vec.model')
# 得到分词的向量
model.wv['分词1', '分词2']
文本的相似度计算涉及到的知识点有:欧氏距离、曼哈顿(街区)距离、余弦相似度。
在直角坐标系中,给定两个点:a(x1, y1)、b(x2, y2),它们的距离计算公式如下:
d = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 d = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2} d=(x1−x2)2+(y1−y2)2
这样得到的距离就是欧式距离。将其扩展到 n 维空间下的距离计算公式如下:
d = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2 + ... + (n_1 - n_2)^2}
那如何通过欧式距离计算两个句子的相似度呢?
给定以下两个句子的句向量:
套用欧式距离计算公式,得:
d = \sqrt{(1 - 1)^2 + (1 - 0)^2 + ... + (1 - 0)^2}
当 d
越接近于 0 时,句子的相似度越高。
曼哈顿距离,也叫作曼哈顿街区距离。直角坐标系中两点之间的曼哈顿距离计算公式如下:
d = |x_1 - x_2| + |y_1 - y_2|
也就是两点连线所在直角三角形的直角边之和。
扩展到 n 维空间下的距离计算公式如下:
d = |x_1 - x_2| + |y_1 - y_2| + ... + |n_1 - n_2|
同样地,d
越接近于 0 ,句子的相似度越高。
余弦相似度的本质就是计算两个向量所成夹角的余弦值,n 维向量的余弦相似度计算公式为:
c o s θ = x 1 y 1 + x 2 y 2 + . . . + x n y n x 1 2 + x 2 2 + . . . + x n 2 ⋅ y 1 2 + y 2 2 + . . . + y n 2 cos\theta = \frac{x_1y_1 + x_2y_2 + ... + x_ny_n}{\sqrt{x_1^2 + x_2^2 + ... + x_n^2} \cdot \sqrt{y_1^2 + y_2^2 + ... + y_n^2}} cosθ=x12+x22+...+xn2⋅y12+y22+...+yn2x1y1+x2y2+...+xnyn
将两个 n 维句子向量的值分别代入上式,得到的值就是这两个句子的余弦相似度。
余弦值的区间是 [-1, 1],当值越趋近 1 时,表示两个句子越相似,越趋近 -1 时,表示两个句子越不相似。
import jieba
from gensim.models import word2vec
# 打开 fenci.txt,内容是一段原始文本
file1 = open('fenci.txt', encoding='utf-8')
# 将分词后的结果保存到 fen_ci.txt,
# open() 第二个参数是 mode,可传入 r/w/x/a
# 'r' -> readonly, 'w' -> truncating the file if it already exists , 'x' -> creating and writing to a new file, 'a' -> append
file2 = open('fenci_result.txt', mode='w', encoding='utf-8')
# 读取 fenci.txt 中的所有行
lines = file1.readlines()
# 将文本的空格、tab缩进、回车换行符都去掉,以便后续对整个文本内容进行分词
for line in lines:
replaced_line = line.replace(' ', '').replace('\t', '').replace('\r', '').replace('\n', '')
seg_list = jieba.cut(replaced_line)
file2.write(' '.join(seg_list))
# 关闭资源
file1.close()
file2.close()
# 加载刚刚生成的语料库,就是已经生成的分词文件
sentences = word2vec.Text8Corpus('fenci_result.txt')
# 使用 word2vec 模型来训练机器(这里就是计算词向量),由于语料库中的分词数较少,需要加上 min_count=1 (最小分词数量=1)参数,否则会报错
model = word2vec.Word2Vec(sentences, min_count=1)
# 将模型命名为 word2vec.model,并保存为本地文件
model.save('word2vec.model')
# 得到词向量,要计算向量的分词必须存在于语料库中,否则会由于找不到分词而报错
word_vec_arr = model.wv['Python', '面向对象']
# 打印词向量
print(word_vec_arr)
# 计算两个分词的相似度
s1 = model.wv.similarity('Python', '面向对象')
s2 = model.wv.similarity('会', '不会')
s3 = model.wv.similarity('会', '会')
# 打印的结果:0.06410548、 -0.069218084、 1.0(完全一致)
print(s1, s2, s3)
持续更新中。。。