0301030ace20709b33cac7702a392bf4.png

本篇通过文本相似度匹配算法,从0到1搭建一个简单的对话系统chatbot。

具体代码参见git:https://github.com/EdisonChen0816/chatbot

chatbot有三部分:
1,意图识别
2,faq标准问
3,闲聊

三者采用同样的技术,都采用文本相似度匹配,只是返回结果的形式略有不同而已。

以意图识别举例。

1,确定实体,将能抽象出来的词语,确定为实体,其他的为Term,创建实体词典。比如:

上海天气怎么样? 杭州天气怎么样? 南京天气怎么样?

上海、杭州和南京都能抽象出来城市实体,<City>,最终三句话可以变成一句话,<City>天气怎么样?

实体格式如下:共有term、label、value三列

包头市 City   包头
乌海市    City   乌海
赤峰市    City   赤峰
通辽市    City   通辽
鄂尔多斯市  City   鄂尔多斯
呼伦贝尔市  City   呼伦贝尔
巴彦淖尔市  City   巴彦淖尔
乌兰察布市  City   乌兰察布
霍林郭勒市  City   霍林郭勒

2,数据准备。

通过nltk提供的语法生成器,按照模板格式,自己生成语料。比如生成天气意图数据。

模板:weather.cfg

S -> CITY WEATHER HOW | DATE WEATHER HOW | CITY DATE WEATHER HOW | DATE CITY WEATHER HOW | WANT DATE WEATHER HOW  WANT CITY WEATHER HOW
DATE -> '<RelDay>'
CITY -> '<City>'
WEATHER -> '天气' | '气候'
HOW -> '怎么样' | '如何'
WANT -> '我要查询一下' | '我想查一下' | '帮我查一下'

代码:genbygram.py

grammar = nltk.data.load('file:weather.cfg')
rd_parser = nltk.RecursiveDescentParser(grammar, trace=0)
for n, sent in enumerate(generate(grammar), 1):
     print(''.join(sent) + 'tWeather')

生成的语料格式如下:

<City>天气怎么样 Weather
<City>天气如何 Weather
<City>气候怎么样    Weather
<City>气候如何 Weather
<RelDay>天气怎么样  Weather
<RelDay>天气如何   Weather
<RelDay>气候怎么样  Weather
<RelDay>气候如何   Weather

3,实体识别:

基于第一步的实体词典,采用ac自动机匹配。

4,token化操作

将输入的句子token化操作。

token类如下:

class Token(object):

    def __init__(self, info):  # info: (term, label, vlaue, weight)
        self.term = info[0]
        self.label = info[1]
        self.value = info[2]
        self.weight = info[3]

比如输入:上海天气怎么样?

具体过程如下:

(1)分词,替换实体, 上海 天气 怎么 样。

(2)每一个词是一个封存成一个token,比如上海是城市实体,term=上海,label=City,value=上海,权重通过tf-idf计算。天气是非实体,term=天气,label=Term,value=天气,权重通过tf-idf计算。

(3)将每个token,append到tokens列表中。

5,召回

采用倒排索引或者向量召回,本案例采用的是annoy向量召回,每次召回30条,句向量生成方式,通过每个词的词向量,乘以权重,然后取平均。

def q2v(self, tws):
    v = np.zeros([1, 300]).astype('float32')
    for k in tws:
        if k in self.w2v:
            v += np.array(self.w2v[k]) * tws[k]
    if len(tws) > 0:
        v /= float(len(tws))
    return v

6,文本相似度匹配:

召回之后,要对输入数据和召回的30条结果进行文本相似度匹配,并按分值排序,本案例,采用比较简单传统的文本匹配算法,bm25、wmd、cqr、ctr、余弦距离。

def ctr(q, t):
    s1 = 0.0
    s2 = 0.0
    for qt in q:
        s1 += q[qt]
        if qt in t:
            s2 += t[qt]
    if s1 == 0:
        return 0.0
    else:
        s = s2 / s1
        if s > 0.5:
            s = 0.5 + (s - 0.5) * 0.5
        return s
def cqr(q, t):
    s1 = 0.0
    s2 = 0.0
    for qt in t:
        s1 += t[qt]
        if qt in q:
            s2 += q[qt]
    if s1 == 0:
        return 0.0
    else:
        s = s2 / s1
        if len(q) - len(t) > 0:
            s /= math.sqrt(1 + (len(q) - len(t)))
        if s > 0.5:
            s = 0.5 + (s - 0.5) * 0.4
    return s

匹配过程中,还要对长度进行一定的惩罚,因为短的句子更容易匹配上。

def query_len_penalty(q):
    return math.log(len(q), 2) - 1.5


def title_len_penalty(t):
    return math.log(len(t), 2) - 1.3

使用单一的文本相似度算法,结果很容易“飘”,而且每个算法都有局限性,有的是基于关键词的,有的是基于语义的,因此将多个算法的结果乘以不同的权重,然后相加,得到最终结果。权重调参可以选择手动调参,都跑几批数据,根据每个算法的结果情况进行调参,也可以自己实现反向传播,自动调参。

最终的参数结果如下:

def get_score(qts, tts, qws, tws, w2v, qv):
    return bm25(qws, tws) * 2 + ctr(qws, tws) + cqr(qws, tws) - wmd(qts, tts, w2v) + se_sim(qv, tts, w2v)[0] + query_len_penalty(qts) + title_len_penalty(tts) - 2.0

最后的分数,还要设定一个阈值,超过阈值的才返回意图。

以上就是大致的过程。后续会有很多改进的地方:

(1)文本相似度算法可采用深度学习,如Representation-based模型(代表模型:sentence-bert)、Interaction-based 模型(代表模型:k-nrm、MatchPyramid)。本人之前在公司的智能客服项目采用的就是k-nrm,召回结果达到了90%+。

(2)向量召回的句向量,可以尝试用bert语义向量。

(3)增加对话管理功能,增加多轮对话。

(4)实体识别可以采用bilstm_crf、bert_crf、bert_bilistm_crf模型。

(5)尝试用生成模型,解决某些的问题。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐