提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
提示:以下是本篇文章正文内容,下面案例可供参考
一、问答机器人的实现逻辑
主要实现逻辑:从现有的问答对中,选择出和问题最相似的问题,并且获取其相似度(一个数值),如果相似度大于阈值,则返回这个最相似问题对应的答案。
问答机器人实现可以分为三个步骤:
- 对问题的处理
- 对问题进行的机器学习召回
- 对召回的结果进行排序
1.1 对问题的处理
对问题的处理过程中,我们可以考虑以下问题:
- 对问题进行基础的清洗,去除特殊符号等
- 问题主语的识别,判断问题中是否包含特定的主语,比如python等,提取出来之后,方便后续对问题进行过滤。
- 可以看出,不仅需要对用户的输入问题进行处理,获取主语,还需要对现有问答对进行处理。
- 获取问题的词向量,可以考虑使用词频,tfidf等,方便召回的时候使用。
1.2 问题的召回
召回:可以理解为是一个海选的操作,就是从现有的问答对中选择可能相似的前k个问题。
为什么要进行召回?
主要目的是为了后续进行排序的时候,减少需要计算的数据量,比如有10万个问答对,直接通过深度学习肯定是可以获取相似度,但是速度慢
如何实现召回?
召回就是选择前K个最相似的问题,所以召回的实现就是想办法通过机器学习的手段计算其相似度。
可以思考的方法:
- 使用词袋模型,获取词频矩阵,计算相似度
- 使用tfidf,获取tfidf矩阵,计算相似度
上述的方法理论上都可行,只是当候选计算的词语数量太多的时候,需要挨个计算相似度,非常耗时,所以要考了一下两点:
- 通过前面获取的主语,对问题进行过滤
- 使用聚类的方法,对数据先聚类,再计算某几类别的相似度,而不用去计算全部。
但是此时获取的结果是没有考虑文字顺序的,效果不一定是最好的,此时我们可以使用fastText或者n-gram来实现。
1.3 问题的排序
排序的过程,使用了召回的结果作为输入,同时输出的是最相似的那一个。
整个过程使用深度学习实现,深度学习虽然训练的速度慢,但是整体效果肯定比机器学习好(机器学习受限于特征工程,数据量等因素,没有办法深入的学会不同问题之间的内在相似度),所以通过自建的模型,获取最后的相似度。
使用深度学习的模型这样一个黑匣子,在训练数据最够多的时候,能够学习到用户的各种不同输入的问题,当我们把目标值(相似的问题)给定的情况下,让模型自己去找到这些训练数据目标值和特征值之间相似的表示方法。
那么此时有以下两个问题:
- 使用什么数据,来训练模型,最后返回模型的相似度?
训练数据的来源:可以考虑根据现有的问答对去手动构造,但是构造的数据不一定能够覆盖后续用户提问的全部问题,所以可以考虑通过程序去采集网站上相似的问题,比如百度知道的搜索结果。 - 模型该如何构建?
模型可以有两个输入,输出为一个数值,两个输入的处理方法肯定是一样的。这种网络结构我们通常把他称作孪生神经网络
很明显,我们对输入数据需要进行编码的操作,比如word embedding+LSTM/GRU/BiGRU等两个编码后的结果进行组合,然后通过一个多层的神经网络,输出一个数字,把这个数值定义为我们的相似度。
当然我们的深层神经网络在最开始的时候也并不是计算的相似度,但是我们的训练数据的目标值是相似度。在N多次的训练之后,确定了输入和输出的表示方法之后,那么最后的模型输出就是相似度了。
二、QA机器人的召回
流程如下:
- 准备数据,问答对的数据等
- 问题转化为向量
- 计算相似度
1.对现有问答对的准备
这里说的问答对,是带有标准答案的问题,为了后续方便,可以把现有的问答对处理成如下格式,并存入数据库或本地文件中。
2.把问题转化为向量
把问答对中的问题,和用户输出的问题转化为向量,为后续计算相似度做准备。
这里我们使用tfidf对问答对中的问题进行处理,转化为向量矩阵。
如果使用单字,使用n-gram,使用word2vec,效果会更加准确
3.计算相似度
思路很简单,对用户输入的问题使用tfidf_vectorizer进行处理,然后和features_vec中的每一个结果进行计算,获取相似度。但是由于好事可能很久,所以考虑使用其他方法来实现
3.1 pysparnn的介绍
官方地址:https://github.com/facebookresearch/pysparnn
pysparnn 是一个对稀疏数据的近似最近邻搜索的python库,这个库是用来实现高维空间中寻找最相似的数据。
3.2 使用pysparnn完成召回的过程
这个过程中,需要注意,提前把cp,tfidf_vec等内容提前准备好,而不应该在每次接收到用户的问题之后重新生成一遍,否贼效率会很低。
3.3 pysparnn的原理介绍
参考地址:https://nlp.stanford.edu/IR-book/html/htmledition/cluster-pruning-1.html
前面我们使用的pysparnn使用的是一种cluster pruning(簇修剪)
的技术,即:开始的时候对数据进行聚类,后续再有限个类别中进行数据的搜索,根据计算的余弦相似度返回结果。
4. 召回过程优化
前面的学习中,我们能够返回相似的召回结果,但是,如何让这些结果更加准确呐?可以从下面的角度出发:
- tfidf使用的是词频和整个文档的词语,如果用户问题的某个词语没有出现过,那么此时,计算出来的相似度可能就不准确,该问题的解决思路:
- 对用户输入的问题进行文本对齐,比如,使用训练好的word2vec,往句子中填充非主语的其他词语的相似词语。例如
python 好学 嘛
–>填充后是:python 好学 嘛 简单 难 吗
,这里假设word2vec学会了好学,简单,难
他们之间是相似的。 - 使用word2vec对齐的好处除了应对未出现的词语,还能够提高主语的重要程度,让主语位置的tfidf的值更大,从而让相似度更加准确
- 对用户输入的问题进行文本对齐,比如,使用训练好的word2vec,往句子中填充非主语的其他词语的相似词语。例如
- tfidf是一个词袋模型,没有考虑词和词之间的顺序
- 使用n-gram和词一起作为特征,转化为特征向量
- 不去使用tfidf处理句子得到向量
- 使用BM25算法
- 或者使用fastText、word2vec,把句子转化为向量
4.1 通过BM25算法替代TFIDF
4.2 BM25算法的实现
4.3 使用Fasttext实现获取句子向量
这里我们可以使用Fasttext,word2vec等方式实现获取词向量,然后对一个句子中的所有词语的词向量进行平均,获取整个句子的向量表示,即sentence vector,该实现方法在fasttext和word2vec中均有实现,而且通过参数的控制,实现N-gram的效果。那么我们可以实现获取句子向量的方法如下:
from fastText import FastText
# 训练模型,设置n-gram=2
model = FastText.train_unsupervised(input="./a.txt",minCount=1,wordNgrams=2)
# 获取句子向量,是对词向量的平均
model.get_sentence_vector("我 是 谁")
4.4 训练模型和封装代码
步骤如下:
- 进行分词之后写入文件中
- 进行模型的训练
- 使用模型获取句子向量。并且封装代码
三、QA机器人的排序
1、排序模型的介绍和实现思路
介绍:通过排序模型挑选出最相似的哪一个问题,返回相应的答案
实现思路:我们需要实现的模型是两个输入,即两个问题,输出的是一个相似度,所以和之前深度学习模型一样,需要实现的步骤如下:
- 准备数据
- 构建模型
- 模型评估
- 对外提供接口并返回结果
1.1、准备数据
这里的数据,我们使用的是之前采集的百度问答的相似问题和手动构造的数据,那么我们需要将他格式化为最终模型需要的格式,即两个输入和输出的相似度。
1.1.1、两个输入
这里的输入,我们可以使用单个字作为特征,也可以使用一个分词之后的词语作为特征。所以在实现准备输入数据方法的过程,可以提前准备。
1.1.2、相似度准备
这里我们使用每个问题搜索结果的前两页认为他们是相似的,相似度为1,最后两页的结果是不相似的,相似度为0。
1.2、构建模型
首先先介绍一下孪生神经网络(Siamese Network),他是有两个共享权值的网络组成,或者只用实现一个,另一个直接调用,有两个输入,一个输出。
孪生神经网络通过两个输入,被DNN进行编码,得到向量的表示之后,根据实际的用途来制定损失函数。比如我们需要计算相似度的时候,可以使用余弦相似度。
孪生神经网络被用于有多个输入和一个输出的场景,比如手写数字体识别、文本相似度检验、人脸识别等。
在计算相似度之前,我们可以考虑在传统的孪生神经网络的基础上,在计算相似度之前,把我们的编码之后的向量通过多层神经网络进行非线性的变化,结果往往会更好,那么此时其网络结构大致为:
其中Network1和Network2位权重参数共享的两个形状相同的网络,用来对输入的数据进行编码,包括(word-embedding,GRU,BiGru等),Network3部分是一个深层的神经网络,包括(batchnorm,dropout,relu,Linear等)
2、模型搭建
attention的计算:
实现思路:
- 先获取attention_weight
- 再使用attention_weight 和 encoder_output进行相乘
Pooling的实现
池化的过程有一个窗口的概念在其中,所以max或者是average指的是窗口中的值取最大值还是取平均值,整个过程可以理解为拿着窗口在源数据上取值。
窗口有窗口大小和步长两个概念。
相似度计算部分
相似度的计算我们可以使用一个传统的距离计算公式,或者是exp的方法来实现,但是其效果不一定好,所以这里我们使用一个深层的神经网络来实现,使用pytorch中的Sequential对象来实现非常简单。
在上述过程中,我们使用了激活函数ELU,而没有使用RELU,因为在有噪声的数据中ELU的效果往往会更好。
损失函数部分
四、实现对聊天记录的保存
不同的用户,连续10分钟内的对话认为是一轮对话,如果10分钟还没有下一次对话,认为该轮对话结束,如果10分钟后开始对话,认为是下一轮对话。要是为了保存不同轮中的聊天主题,后续可以实现基本的对话管理。比如用户刚问了python的相关问题,后续如果问题中不带主体,那么就把redis中的python作为其主体。
主要实现逻辑为:
- 使用redis存储用户基本的数据
- 使用mongodb存储对话记录
具体思路如下:
- 根据用户id,获取对话id,根据对话id判断当前的对话是否存在
- 如果对话id存在:
- 更新对话的entity,上一次对话的时间,设置对话的过期时间
- 保存数据到mongodb
- 如果对话id不存在:
- 创建用户的基础信息(user_id,entity,对话时间)
- 把用户的基础信息存入redis,同时设置对话id和过期时间
- 保存数据到mongodb中
具体代码如下: