python aiml库的中文支持问题
更新:
aiml库fork的是python-aiml库。于是我也去fork了一个python-aiml,然后添加上中文支持啦!
github地址
下载后如果要安装成library看最后哦:
不安装library,直接使用源码/Debug
安装修改后的源代码为library
注意:不管是安装的是aiml
还是python-aiml
,或者是我们改过的源代码,最后使用的时候都是import aiml
python aiml
库对中文的支持不是很好,总会出现奇怪的问题。比如在打印kernel历史的时候,会发现句子中莫名其妙的多出了许多空格,导致无法成功匹配。这篇文章分享了我的解决办法。
阅读源码后,我们发现问题出在将句子分词和重组的逻辑。因为这个库主要是给英文使用的。我们知道,英文的单词是使用空格将一句话分为一个个单词的,而这也是aiml
库对全英文句子分词的方法。
源代码的具体逻辑是:
在kernel学习.aiml
文件(创建知识库)时会检查每一个<pattern>
标签的text内容是否包含英文,如果包含英文就将text转为大写。
如果text不含英文怎么办呢?aiml
库简单粗暴地用' '.join()
在每个字符之间都插入一个空格。
最后,这些被处理过的句子将在匹配的时候直接被split()
分成一个单词list。(也就是说,如果我们想成功匹配含有非英文的句子,就要在所有相邻字符间都加一个空格。要多难受有多难受)
我们来看几个例子:
"what are you"
:字符串只包含英文,将全部字符大写。结果为:"WHAT ARE YOU"
。 结果符合期望。
"你好"
:字符串不包含英文,字符间插入空格。结果为:"你 好"
。结果符合期望。
"你好hello"
:字符串包含英文,将所有字符大写。结果为:"你好HELLO"
。结果不符合期望。
-
这一整个字符串将只作为一个单词进行匹配,这显然是有问题的。如果我们的pattern是
"* hello"
,则无法成功匹配。 -
我们希望得到的是三个单词:“你”,“好”,“hello”。所以结果应该是
"你 好 HELLO"
。这个结果在调用split()之后才会被分成三个部分。
相似的,在获得机器人回复之前,源代码也会将输入的内容做一次这样的处理。
在匹配通配符时也会出现类似的问题,通配符匹配的内容如果不含英文,则会在字符间插入空格。等我们想获取通配符的匹配内容时,就会发现里面全是不需要的空格。
另外,python aiml库对中文标点也同样不支持。
综上所述,我们需要对源代码进行一些修改。
修改源代码
版本信息:
python 3.7.1
aiml 0.9.2
修改后的两个文件已上传。
下面的路径为相对于aiml源代码根目录的相对路径
我们修改了源代码中的:
aiml/Kernel.py
aiml/PatternMgr.py
尝试解决的具体问题是:
- 中英文混用时的分词预处理不合理
- 中英文混用时,通配符内容在respond中的每一个字符间都有空格
- 不支持中文标点
分词预处理
在aiml/Kernel.py
中添加新的分词预处理函数_split_cn_eng_sentence
,来代替简单的' '.join()
:
def _split_cn_eng_sentence(self, sentence):
# 将中文单字和英文单词用空格隔开
# 连续英文字母和连续的数字算作一个单词
# 特殊字符如"*"也算作一个单词
# 例:
# >>> _split_cn_eng_sentence('*你*好* * *hel*lo')
# * 你 * 好 * * * hel * lo
# >>> _split_cn_eng_sentence('*你*,?!好* ,:* *12*345')
# * 你 * 好 * * * 12 * 345
special_chars = r'\*'
regEx = re.compile(r'[^'+special_chars+r'\w]+') #非 连续单字/数字/特殊字符
chinese = re.compile(r'(['+special_chars+r'\u4e00-\u9fa5])') #中文字符,加()来保留分隔符
sub_sentences = regEx.split(sentence) #获得连续单字/数字/特殊字符
res_list = []
for s in sub_sentences:
res_list += chinese.split(s) #将中文字符作为分隔符,但保留它们,就可以把每个汉字分开了
return ' '.join(r for r in res_list if r) #去掉值为''的部分
修改aiml/Kernel.py
中的learn
函数:
if key and key[0] and key[1] and key[2] and em_ext == '.aiml' and (not self._check_contain_english(key[0])):
new_key = (' '.join(key[0]), key[1], key[2])
elif key and key[0] and key[1] and key[2] and em_ext == '.aiml' and self._check_contain_english(key[0]):
new_key=(key[0].upper(), key[1], key[2])
改为
if key and key[0] and key[1] and key[2] and em_ext == '.aiml':
new_key = (self._split_cn_eng_sentence(key[0]).upper(),
key[1], key[2])
aiml/Kernel.py
中的respond
函数,注释这两行:
if not self._check_contain_english(s):
s = ' '.join(s)
aiml/Kernel.py
中的_respond
函数开头使用_split_cn_eng_sentence
处理输入字符串:
def _respond(self, input_, sessionID):
"""Private version of respond(), does the real work."""
if len(input_) == 0:
return u""
input_ = self._split_cn_eng_sentence(input_) #添加这一行
中文标点
aiml/PatternMgr.py
:在_puncStripRE变量中添加中文标点:
punctuation = "\"`~!@#$%^&*()-_=+[{]}\\|;:',<.>/?"
chinese_punctuation = "、“”;:,《。》?【】·!¥…()—" #添加这一行
self._puncStripRE = re.compile("[" + re.escape(punctuation+chinese_punctuation) + "]") # re.escape(punctuation) 改为 re.escape(punctuation+chinese_punctuation)
通配符内容合并
我们还需要在处理通配符匹配到的内容时,将list
中的单词合理地合并成句子。源代码中同样中是直接使用' '.join()
。
在aiml/PatternMgr.py
中添加新的helper函数_combine_cn_eng_sentence
:
def _combine_cn_eng_sentence(self, word_list):
#MODIFIED
# 将split()后的中文单字和英文单词合并为字符串空格隔开
# 中文和中文用''连接,英文和英文用' '连接,中文和英文用''连接
# 例:
# >>> print(_combine_cn_eng_sentence(['hi', '你', '觉', '得', 'what', '电', '影', '好', '看']))
# hi你觉得what电影好看
def is_chinese_word(word):
# 仅用于Unicode编码
# 用于被_split_cn_eng_sentence(string)+string.split()处理后的单词列表
if len(word) != 1:
return False # 如果单词长度大于1,则不是中文
return u'\u4e00' <= word and word <= u'\u9fa5' #如果不在中文编码范围内,则不是中文
res = ''
is_prev_word_chinese = True # 设为True,开头不加空格
for w in word_list:
is_chinese = is_chinese_word(w)
if is_chinese:
res += w
else:
if is_prev_word_chinese:
res += w
else:
res += ' '+w
is_prev_word_chinese = is_chinese
return res
源代码中aiml/PatternMgr.py
的star
函数底部,
if starType == 'star': return ' '.join(pattern.split()[start:end+1])
elif starType == 'thatstar': return ' '.join(that.split()[start:end+1])
elif starType == 'topicstar': return ' '.join(topic.split()[start:end+1])
改为
if starType == 'star': return self._combine_cn_eng_sentence(pattern.split()[start:end+1]) #MODIFIED
elif starType == 'thatstar': return self._combine_cn_eng_sentence(that.split()[start:end+1]) #MODIFIED
elif starType == 'topicstar': return self._combine_cn_eng_sentence(topic.split()[start:end+1]) #MODIFIED
Debug
如果想debug,可以在根目录下创建一个空的`__init__.py`文件,然后像import一个普通的.py文件一样导入就可以了。比如这样一个目录结构: ``` aaa |--------aiml_0_9_2 |--------test.py ``` 源代码根目录是`aiml_0_9_2`(注意文件夹的名字不要包含`.`)。在test.py里面就可以这样导入 ``` from aiml_0_9_2.aiml import Kernel ```安装修改后的源代码
在源代码根目录中运行
python setup.py install
然后就可以当做正常的python库import啦:
import aiml
aiml库的其他问题
aiml库不完全支持最新的AIML语言。目前仅支持*
作为通配符,_^#
均不支持
_
和*
用法相同,表示一个或多个词 (one+ wildcards)^
和#
用法相同,表示零或多个词 (zero+ wildcards)
python aiml库一些细节上值得注意的地方:
- 标点符号不参与匹配
写在最后
码字不易,觉得有帮助就给我点个赞吧!我会继续努力的!