关于本文

本文是对【147号】队在武汉2016马拉松的赛场32小时激烈的战斗记录以及产品和技术的分析。前面部分文字比较多,主要讲产品的出发点和自己的一些想法。中间简单的叙述了一些的小波折。最后讲技术上的实现原理。因为不知看官的技术经验背景如何,所以在技术部分讲得比较细(luosuo)一些:从微信公众号的申请和设置;到服务端如何通过微信的验证;到语音消息的接收与消息的发送的微信协议;到自然语言的处理。由于本人的技术水平十分有限,讲得比较疏浅,还望各位大大看后指点一二!m( _ _ )m

产品构思&比赛过程

一些和本作品相关的介绍

今年是我第一次参加马拉松,我们做的产品是一个“基于wechet公众号和自然语言处理的便捷语音备忘录”,我们的产品光从表面上看,似乎没那么创新,毕竟只是一个备忘录嘛,也没那么极客,毕竟还是一个备忘录嘛 ==

好,不开玩笑,这款看似简单的备忘录其实并没有想象中的那么简单,先说说我们这款产品的特点吧:
1. 方便输入。语音输入(后台转文字)先对与传统的文字备忘录要方便输入。
2. 便捷语音搜索。也是使用语音识别进行搜索,只需要说出想要搜索的内容即可过滤出对应的备忘录。
3. 功能专一。现在的siri、小娜这种AI产品的智能程度已经相当高了,可是在对话工程通常过于啰嗦,有时还需要反复确认,并且想找出历史备忘也麻烦,功能过于累赘,缺乏针对性。
4. 对自然语言的处理。当你说出一段较为啰嗦的话或者逻辑上并非那么正常的一句话去搜索备忘录的时候,也许普通的文字匹配就不是那么管用了,针对于这一种情况,我们可以根据语句的语义断词,词性对比,或者对关键字提取等方式匹配出想要的内容。(因为时间关系,当时在赛场只做了高频关键字提取过滤功能,其余功能原理类似)

一点小波折

本来我是先做成安卓的app的,但是当时在赛场上手机出了一些小问题,转向Plan B,使用wechat公众号代替了app。在功能方面和原本计划的有些不一样,比如在录入和找找备忘录的时候,原本想的是在app上,一个按钮录入,一个按钮搜索,各司其职,简单。后来因为wechat的“按住说话”只有一个按钮,所以没有所谓的“各司其职”了,讨论一番,反正我们已经使用了语音输入了,索性就讲语音输入进行到底,直接使用语音控制备忘录了。就这样,“失去”了一个feature,换来了另一个看似更pro的feature。

关于“语音控制备忘录”,目前做法是在操作备忘录前说出关键字(如:“记录”、“查找”、“删除”等),这样其实会出现一个比较常见的问题:控制指令读取失误。在相对吵杂的环境或者因为某些人的语言习惯,通常在读取指令前会有一些“嗯”、“呀”等一些奇怪的字或词被识别出来,如果只是使用简单的startswith(str)过滤,那就挂了。如果使用find(str)字符串匹配的话,则后面的内容部分(非指令部分)出现了关键词则又会导致误判。之后(目前未实现)其实可以高端一些,使用NLP语义断词,提取句子的谓语,如:“麻烦帮我记录一下”或者“帮我记下来”提取“记录”、“记下来”等谓语动词来进行行为判断。

技术部分

公众号准备

  1. 微信公众平台首页 进去注册页面
  2. 选择订阅号
  3. 之后按要求填入你的信息即可
    >注:注册过程需要身份证、已经绑定了银行卡的微信、手机验证码验证等信息,请在注册前考虑是否能提供这些信息

    公众号名字与描述设置

公众号配置

  1. 左侧菜单选择“基本配置”
  2. 填写基本的信息,加密方式选择“明文模式”。
    > tips: 服务端不一定要监听在服务器的80端口,可以利用nginx反代的方式将请求代理到服务端口

到这里,微信端算是已经配置完成了,剩下就是服务端的处理了

服务端验证

在上图的服务端配置中,点击提交,微信服务器就会发送一个url请求到对应的服务器地址,请求内容类似下面这个样子:

GET /?signature=d0772606c432f45946c34a4b1807174755db8407&echostr=5983433196902022263&timestamp=1478434901&nonce=2023366230

按微信官方的文档说明,在明文模式下,只需要将echostr字段原样返回即可(并且返回的status_code需要是200)

note: 需要注意下:
微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。

我使用的是python的flask库,验证部分的代码片段如下:

@app.route("/", methods=["GET", "POST"])
def index():
    if request.method == "GET":
        echostr = request.args.get("echostr")
        return make_response(echostr, 200)

        elif request.method == "POST":
            pass

安全模式可以参考:消息加密接入

消息读取与回复协议

微信的消息都是以POST的方式发送,为XML格式的内容,如下是一条语音消息:

<xml>
<ToUserName><![CDATA[gh_537c9c75xxxx]]></ToUserName>
<FromUserName><![CDATA[odQg3v-kfxHarJHp0h9vypfxxxxx]]></FromUserName>
<CreateTime>1478436434</CreateTime>
<MsgType><![CDATA[voice]]></MsgType>
<MediaId><![CDATA[nCequExsNqJ0EbNdOnJ5q97D2vTpSiILm0UG6miBNC1i8qPwXc0FSxxxxxwe_tl]]></MediaId>
<Format><![CDATA[amr]]></Format>
<MsgId>63498361332448611111</MsgId>
<Recognition><![CDATA[你好啊!]]></Recognition>
</xml>

这些消息字段也比较好理解,我就不一一解析了,如果是语言消息,我们就直接提取Recognition部分,如果是文本消息,则提取Content部分即可。

其他具体的微信消息格式可以参考:微信wiki: https://mp.weixin.qq.com/wiki 的消息管理

xml的解析,其实不用那么手动解析这么麻烦,可以直接利用现成的一些微信解析库来辅助解析,方便很多。这种库在github上一搜一大把,我使用的是doraemonext的wechat-python-sdk

一个使用wechat-python-sdk的消息解析例子:

...
wechat = WechatBasic(token=token)  #  初始化wechat库
# 消息类型判断
if wechat.message.type == "text":
    msg = wechat.message.content
elif wechat.message.type == "voice":
    msg = wechat.message.recognition

user = wechat.message.source
print("%s: %s" % (user, msg))
ret_msg = handle_content(msg, user)  # 逻辑部分处理
xml = wechat.response_text(content=ret_msg)  # 使用wechat库重新将消息包装成xml格式的内容
return make_response(xml, 200)  # 回复,也是200的status_code
...

自然语言处理

对于自然语言我其实也不是行家,理解的比较肤浅,还请大家多多指点。

一个句子的内容各种各样,匹配的时候不能直接将整句话去搜索已有的备忘录,可能会因为一两个虚词,感叹词而影响了匹配的结果。因此,我们需要对句子中极有可能作为搜索关键词的词语提取出来进行配对,这样我们的程序才能聪明地过滤掉一些无关紧要词语。

Boson NLP

关于Boson NLP API

其实这个说来也巧,Boson是我们当时在赛场临时找到的一款自然语言处理的API。记得以前百度有类似的API,但是当时却怎么也找不到了(==)。另外还找了好几个公司的产品,都没有提供API。无奈之下,随意google,居然找到了Boson这款号称“高效准确的中文语义API”。于是注册试用之,哎呦喂,还有免费…(准确地说是每天提供一定量的免费调用次数),于是就使用它了,下面是Boson普通用户的每日调用次数。(还是挺好的,这里给Boson打个广告^_^)

关键词提取匹配

一个句子的内容各种各样,我们匹配的时候不能直接的那我们整句话去匹配已有的备忘录,那样可能会因为一两个虚词,感叹词而影响了匹配的结果,因此,我们需要对句子中极有可能作为搜索关键词的词语提取出来进行配对。这样子,我们的程序就能聪明地过滤掉一些无关紧要词语了。

来一个BosonAPI的调用例子

from __future__ import print_function, unicode_literals
from bosonnlp import BosonNLP

nlp = BosonNLP('tokenxxx')  # token可以在Boson控制台界面获取
s = "去泰国旅游之前记得兑换泰铢"
result = nlp.extract_keywords(s)

for weight, word in result:
    print(weight, word)

输出

0.699895327616 泰铢
0.467608197427 兑换
0.40302458898 泰国
0.341082402936 记得
0.0975725810312 之前
0.0565920485091 去

假设我们输入的是“去泰国旅游之前记得兑换泰铢”,返回的是:泰铢(0.69)、兑换(0.46)、泰国(0.40)、记得(0.34)、之前(0.09)、去(0.56)。这些词后面都带上了他们的权重,我们可以设定了一个权重阀值,比如说是:0.35,那么大于0.35的词才会被入选关键词。假定这条备忘录是已经记录在库了的,那我们使用Boson接口提取一句话查询话语的关键词”去泰国之前需要带什么“,这句话的调用结果是:泰国(0.88),需要(0.34)、之前(0.34)、去(0.12)、什么(0.08),按我们的阀值过滤,只有”泰国“被过滤出来了。于是乎,”泰国” in [“泰铢”, “兑换”, “泰国”],匹配正确。

这个只是其中一个玩法,更进一步的,我们可以对词语进行分类,比如说:游泳,打羽毛球 => 运动,搜索‘运动’的时候能以将对应具体运动事项的备忘录提取出来。

腾讯文智NLP

关于腾讯文智NLP API

关于腾讯文智自然语言处理的说明,先从其官网摘一段过来吧0.0

腾讯文智自然语言处理(Natural Language Processing)基于并行计算、分布式爬虫系统,结合独特的语义分析技术,一站满足NLP、转码、抽取、数据抓取等需求。基于文智API可实现搜索、推荐、舆情、挖掘等功能。文智同时支持定制化语义分析方案。

API功能上还是比较全面的,新用户可以赠送5W次的接口调用,也是灰常不错滴,在这里领取。

腾讯文智NLP API的使用

接口的调用用两种方式:一种是restful的直接调用,另一种是QCloud-SDK的调用(其实也是对restful的包装)

QCloud-SDK支持多种编程语言,包括python、java、php和dotnet,调用起来也是比较方便的。如果直接使用resulful的话需要先使用HMAC-SHA1 算法对上一步中获得的签名原文字符串进行签,然后将生成的签名串使用 Base64 进行编码,获得签名串,最后将签名串作为resulful的其中一个参数来进行调用。

这里以python-sdk为例

# 先克隆sdk仓库
git clone https://github.com/QcloudApi/qcloudapi-sdk-python

# 将QCloudApi复制到你的项目目录
cp -r qcloudapi-sdk-python/src/QcloudApi $你的项目目录

调用:

#!/usr/bin/python
# -*- encoding: utf-8 -*-

from QcloudApi.qcloudapi import QcloudApi

module = 'wenzhi'
action = 'TextKeywords'
config = {
    'Region': 'gz',
    'secretId': '**id**',
    'secretKey': '**key**',
    'method': 'get'
}

s = '去旅游记得带上身份证'

params = {
    'title': s,
    'content': s,
    'channel': 'CHnews_news_ssh'
}

service = QcloudApi(module, config)
service.generateUrl(action, params)
print service.call(action, params)

secretIdsecretKey云 API 密钥处可以获得。

执行:

./main.py | json_pp  # 使用json_pp格式化输出
{
   "code" : 0,
   "keywords" : [
      {
         "keyword" : "旅游",
         "type" : "keyword",
         "score" : 0.28047892451286
      }
   ],
   "codeDesc" : "Success",
   "message" : ""
}

从结果可以看出,旅游这个关键词已经被提取出来了,假如我们对着备忘录说“搜索旅游相关备忘录”,也是用同样的方式提取出这个句子的关键词,然后将两个句子的关键词进行对比即可。

BosonNLP的关键字提取相比文智NLP会好用一些,Boson的比较多,并且能给出一个关键字的“概率”,可以自己调整一个阀值来优化不同应用范畴下的准确性。

最后

非常感谢光谷社区桔子空间联合举办的盛大的黑客马拉松,也感谢腾讯云等知名IT企业提供丰厚的奖品!

赛后总结有三:
1. 赛前要有准备,至少要确定(或和队员事先讨论好)自己要做的是个什么东西(不要打没准备的仗)
2. 平时多积累一些API、工具,能让你少走很多弯路、节省很多时间
3. 评委问最多的一个问题是:你这个技术是怎么实现的?,只要答:调用了xx API,评委:我没问题了(因为再问就是别人的API怎么实现的问题了)。所以核心部分一定要拥有有自己的想法、逻辑甚至自己写的算法在里面,以表示你的作品不仅仅只是API的封装。

(end)

发表评论

电子邮件地址不会被公开。 必填项已用*标注