问题描述
在Q群里聊到关于规则生成的问题,有个这样的题目:
在基于自然语言的人机交互系统中,通常会定义一些语义模板来训练NLU (自然语言理解)模型,比如下面的模板可以支持用户通过语音控制机器播放音乐:
- 放几首@{singer}的歌
- 播放一首@{singer}的歌
- 来一曲@{singer}的歌曲
- 来首@{singer}的音乐
- 来个@{singer}的流行音乐
其中"@{singer}"是个参数, 代表歌手,比如第一个模板可以匹配这样的用户query:“放几首刘德华的歌”。可以看到,同样是放歌,有很多种不同但相似的说法,但把他们一条一条单独列出来,编辑的成本会比较高, 而且会漏掉一些说法, 不严谨。实际上,上面的5个模板,可以用下面的语义模板表达式来表示:
<[播]放|来>[一|几]<首|曲|个>@{singer}的<歌[曲]|[流行]音乐>
其中包含中括号("[]") 、尖括号("<>") 和竖线("|")三种元素:
- 中括号代表其中的内容是可选的,比如"[歌]曲",能匹配"歌"和"歌曲";
- 尖括号代表其中的内容是必选的,比如"<播>放", 能匹配"播放";
- 括号是可以嵌套的;
- 竖线代表或的关系,即竖线分隔的内容是可替换的,
- 比如"<播放|来首>歌曲"能匹配"播放歌曲"和"来首歌曲",再如"[播放|来首]歌曲"能匹配"播放歌曲",“来首歌曲"和"歌曲”(因为中括号里面是可选的,所以可以匹配歌曲) ;
- 竖线在其所属的括号内,优先级大于括号中的其他括号,比如"<[播]放|来>首歌曲",能匹配"播放首歌曲",“放首歌曲"和"来首歌曲";
- 竖线可以脱离括号独立存在,比如"在哪里|哪里有",可以匹配"在哪里"和"哪里有";
那么,给一个上述的语义模板表达式和用户的query,你能判断用户的quey是否能匹配这个表达式吗?
思路
一看这规则,我就想到了正则,真TMD像啊。有正则这么好的东西,难道还要再造个轮子么,肯定不要了。直接把规则“转换”成一个正则表达式,不就可以了么?
题目中提供的规则已经写得这么规整了,基本上只需要替换一下就可以了:
#根据文本规则生成正则表达式
#parm:
# txt 用户的规则
#返回:
# 字符串,生成的正则表达式;
#测试用例:
# genRegex('<[播]放|来>[一|几]<首|曲|个>@{singer}的<歌[曲]|[流行]音乐>')
#return:
# ^((播)?放|来)(一|几)?(首|曲|个)@\{singer\}的(歌(曲)?|(流行)?音乐)$
def genRegex (txt):
pass
ret = txt
ret = ret.replace('{','\{')
ret = ret.replace('}','\}')
ret = ret.replace('[','(')
ret = ret.replace(']',')?')
ret = ret.replace('<','(')
ret = ret.replace('>',')')
ret = '^' + ret + '$'
return ret
不要怀疑,就是这么简单!
根据题目的意思,这里我把 <>
替换成了正则中的 ()
,而 []
替换成了正则中的 ()?
生成了正则表达式后,就可以用正则表达式来判断了,代码也很简单:
#对单条文本判断是否匹配规则
#参数:
# strRegex string,正则表达式
# strTxt string,待匹配的单条文本
#返回:
# 匹配返回True ,否则返回False
def testRule (strRegex,strTxt):
pass
try:
#编译正则对象,参数:单行,多行,大小写
reObj = re.compile(strRegex,re.S|re.U|re.M)
searchObj = re.match(reObj,strTxt)
#print(searchObj)
return searchObj != None
except Exception as e :
return False
测试
最后,找几个句子来测试一下:
def doctest ():
txt = [
'放几首@{singer}的歌',
'播放一首@{singer}的歌',
'来一曲@{singer}的歌曲',
'来首@{singer}的音乐',
'来个@{singer}的流行音乐',
'放首@{singer}的歌',
'播放@{singer}的歌',
'在哪里可以找到@{singer}的音乐',
'哪里有@{singer}的歌曲',
]
print('批量测试'.center(30,'-') )
ruleTxt = '<[播]放|来>[一|几]<首|曲|个>@{singer}的<歌[曲]|[流行]音乐>'
ruleRegex = genRegex(ruleTxt)
print('规则文本:%s \n生成正则表达式:%s ' % ( ruleTxt,ruleRegex))
print('-'*30)
print('规则测试:')
for x in txt:
pass
print('测试句子: %s 判断结果:%s' %(x , testRule(ruleRegex,x)) )
运行结果
调用下方法,测试一下结果:
if __name__ == '__main__':
pass
ruleTxt = '<[播]放|来>[一|几]<首|曲|个>@{singer}的<歌[曲]|[流行]音乐>'
ruleRegex = genRegex(ruleTxt)
print(ruleRegex)
print(testRule (ruleRegex, '放几首@{singer}的歌'))
doctest()
运行结果:
---------- python36 ----------
^((播)?放|来)(一|几)?(首|曲|个)@\{singer\}的(歌(曲)?|(流行)?音乐)$
True
-------------批量测试-------------
规则文本:<[播]放|来>[一|几]<首|曲|个>@{singer}的<歌[曲]|[流行]音乐>
生成正则表达式:^((播)?放|来)(一|几)?(首|曲|个)@\{singer\}的(歌(曲)?|(流行)?音乐)$
------------------------------
规则测试:
测试句子: 放几首@{singer}的歌 判断结果:True
测试句子: 播放一首@{singer}的歌 判断结果:True
测试句子: 来一曲@{singer}的歌曲 判断结果:True
测试句子: 来首@{singer}的音乐 判断结果:True
测试句子: 来个@{singer}的流行音乐 判断结果:True
测试句子: 放首@{singer}的歌 判断结果:True
测试句子: 播放@{singer}的歌 判断结果:False
测试句子: 在哪里可以找到@{singer}的音乐 判断结果:False
测试句子: 哪里有@{singer}的歌曲 判断结果:False
输出完成 (耗时: 0 秒)
完整源码
“不管白猫黑猫,能抓老鼠就是好猫”,思路比较土,但能解决问题就好。
不足之处,还请指正。
完整代码放在这里:
代码下载