/LexicalExpression

Primary LanguagePythonApache License 2.0Apache-2.0

简介

词法表达式(lexical expression,以下简称Lexex) 描述了一种文本&标记的匹配模式,可以用来检查一个文本&标记序列是否含有某种子串,并能从序列中取出符合条件的子串等。

例如:

  • 我想听/% + /channel + 新闻/%,可以匹配“我想听(科技/体育/娱乐/财经/……)新闻”等等任何频道所组成的内容,只要括号中的词语可以被打上channel`的标记即可。

  • /person + 爱上了/% + */object = {source:1} + {target:3},可以匹配包括“张三爱上了游泳”、“李四爱上了文玩”等等符合等于号左边模式的内容,并且,可以将相应的内容依照等式右边的形式提取出来,即source:张三;target:游泳source:李四;target:文玩`,只要内容中的人名以及实体名称被正确打上标记即可。

词法表达式 vs 正则表达式

词法表达式是正则表达式在自然语言文本处理这一特定场景下的加强版本。在设计层面上,词法表达式沿用了正则表达式的许多语法以及表达形式,这样设计的目的是让使用者可以在熟悉正则表达式的前提下,轻松了解并掌握词法表达式。

两种表达式之间最大的不同点,在于词法表达式不单单只是对文本字符串序列本身进行匹配,而是同时要对内容的文本序列以及标记序列进行匹配。使用者可以通过对两个序列匹配条件的灵活组合创造出强大而且通用的规则模板,从而从容应对自然语言处理中复杂多样的语言表示形态。


特性

Lexex是专门应用于自然语言处理的规则匹配工具,具备以下特性:

  • 提供文本&标记匹配的规则匹配方式,支持文本或标记任意一项为通配符
  • 支持匹配查询、匹配提取、穷举提取三种查询模式
  • 不限制标记匹配符范围
  • 支持提取合并、分组
  • 支持自定义提取标签

下载与安装

只需要将本项目下的./src/lexex.py文件添加到你的项目中即可。


使用方法

  • 这是一个简单的序列标注词语提取的例子。首先,需要编写简单的规则文件example.rule
*/s = {hit:1}
*/b + #[*/m] + */e = {hit:1,2,3}
  • 接着,使用lexex模块并加载规则模型。
import lexex
lex = lexex.Lexex("example.rule")
  • 这时,你可以在python上任意提取系列标注词语了。
hits = lex.match(["司","马","光","砸","缸"],["b","m","e","o","o"], 0)
for key, value in hits:
    print "key: %s\tvalue: %s" % (key, str(' '.join(value)))
    
## key: hit    value: 司马光

Lexex语法

规则语法

词法表达式中的规则是由等式来描述的,其中,等号“=”作为标识等式的符号用于唯一区分一条规则,单条规则的语法如下:

<匹配规则> = <抽取规则>

在一条词法表达式语句中,位于等式左边的部分被称为“匹配规则”,“匹配规则”用于匹配字符串,只有符合匹配条件的字符串才能够被输出。相对地,位于等式右边的部分被称为“抽取规则”,当词法表达式匹配到符合条件的字符串时,“抽取规则”用于决定匹配结果所输出的内容以及格式。

匹配规则

匹配规则位于整条规则等式的左边,故又称作“规则左部”。通常,匹配规则由若干“规则项”组成,并且,匹配规则通过加号连接符“+”将各个规则项前后串连起来,有且仅有整条被串连起来的规则项与待匹配语句中的词语依次相邻并且全部匹配,才认为整条规则匹配成功。匹配规则的语法如下:

<规则项1> + <规则项2> + <规则项3>

所谓规则项,是匹配规则部分的基本单位,规则项之间无关系,每一个规则项独立地与待匹配语句的词语匹配,并根据成功匹配与否返回相应的结果。规则项根据独立匹配词语个数或方式的不同,又分为“基本项”和“越过项”两种。

词法表达式是一种类似于正则表达式的一种文本模式,包括基本项和越过项。其中越过项又可以由若干个基本项组成。基本项和越过项分别由普通字符和特殊字符构成。通过使用基本项和越过项的任意组合,描述在识别或者抽取文本时要匹配的一个或者多个字符串。

下表包含了基本项和越过项的完整列表以及它们在规则表达式上下文中的作用:

表达式 含义描述
*/% 一个基本项,由“/”分隔的两部分组成:“*”表示文本,“%”表示标签
A+B AB为任意的项,该表达式按顺序连接两项,两者以顺序关系匹配时需同时满足规则要求
(*) 标记子表达式的开始和结束。表示括号中的构成一个子表达式
#[A] 越过项,匹配方括号中的表达式A一次或者多次,其中,A为一个基本项
m:n 标记限定符,限定匹配项出现的次数。表示最低m次,最高n次
A&B and表达式,表示AB需同时满足才算作满足该表达式的规则
x|y or表达式,表示x或y匹配到任意一个则满足规则。例如"z|food",表示匹配"z"或"food"
!(A) not表达式,表示不符合表达式A的项。A可以是字符或者表达式
基本项

基本项是匹配规则的最小单位,一个基本项用于匹配一个词语。词法表达式能够对词语以及词语的标签建立匹配规则的原因就是基于基本项。一个基本项由一个词表达式句柄和一个标记表达式句柄组成,并使用反斜杠符号“/”连接两句柄。例如:

<词表达式>/<标记表达式>

若要匹配一个字符与标签的组合是否满足规则则要制定一个基本项进行匹配如要在下句“我/t 是/s **人/n”,基本项如下:

我/%&^

会输出所有句子中满足“我”位于句首的情况并且不考虑标签。

越过项

越过项用于匹配符合要求的任意数量连续词语。由于需要匹配整条匹配规则才能算作成功,当遇到需要匹配的内容不相邻的情形,需要匹配规则中的匹配项能够越过若干无关的词语进行继续匹配,这时越过项就能够派上用场。

越过项由一个基本项以及固定格式的越过项标识符组成一个标准的越过项语句格式如下:

#<下限>:<上限>[<基本项>]

其中下限和上限分别表示可以越过的与基本项相匹配的词语数边界此外下限和上限数可以省略被省略的情况下下限将以0作为默认值上限将以65000作为默认值省略的越过项语句格式如下:

#<下限>[<基本项>]
#[<基本项>]

如要创建一个表达式,使其满足其中的子表达式都可以出现一次或者多次,请在方括号([ 和 ])内放置一个或多个基本项构成的子表达式,并在括号前加上“#”。基本项子表达式组合括在中括号内时,该列表称为“越过项表达式”。

#1:5[*/t]

该越过项表示最少出现1次最多出现5次且tag标签为t的字符并且输出整个符合该结构的字符串作为输出结果。

特殊标记符

基本项的各个句柄中语句的内容是正式匹配词语的内容,分为普通标记符和特殊标记符。其中,普通标记符由数字、字母或中文汉字组成,在匹配是被当作为字符串进行直接匹配;特殊标记符则由一些符号表示,这些符号并不表示其本身含义,而是提供了额外特殊的标记含义,并且,在实战中需要尽量避免使用特殊标记符的原来意思,以免产生未知错误。具体地,特殊标记符与其含义对照表如下:

特殊字符 含义描述
* 词语通配符,位于“/”之前的词表达式句柄,表示可以是任何的词语
% 标记通配符,位于“/”之后的标记表达式句柄,表示可以是任何的标记
^ 标记句首位置
$ 标记句尾位置
& 和,表示“and”关系,经常用于连接“^”和“$”
| 或,表示“or”关系
! 非,表示“not”关系
() 优先级标记符,跟算术规则类似
优先级顺序

规则解析是从左到右进行计算并遵循优先级顺序,与算术表达式类似,符合人的思维,有助于理解,使用更方便。下表从高都低的介绍规则表达式中各种运算符的优先级顺序:

运算符 含义描述
#[] 越过项标识符
() 括号
{} 限定符
!
&
|
+ 项连接符

字符和标签具有高于替换运算符的优先级,使得“m|food”匹配“m”或“food”。若要匹配“mood”或“food”,请使用括号创建子表达式,从而产生“(m|f)ood”。

抽取规则

抽取规则位于整条规则等式的右边,又称作“规则右部”。抽取规则可以由一项或多项“抽取项”组成,与匹配规则一样,通过加号连接符“+”相连。抽取规则的语法如下:

<抽取项1> + <抽取项2> + <抽取项3> + ……

词法表达式将根据抽取规则对匹配到的内容进行抽取,输出key-value形式的结果。

抽取项

抽取项是抽取规则的单元,项与项之间相互独立。一个抽取项可以从匹配到的内容中指定要抽取的部分,并且为这部分定义一个名称,形成类似key-value的结果。抽取项的语法格式如下:

{<名称>:<匹配id1, 匹配id2, ……>}

其中,抽取项的单元由大括号"{}"进行包裹,并由冒号":"分割名称与要抽取的匹配对象id。

  • 合并提取

匹配id是对应规则左部匹配项的序号,假定规则左部包括n个匹配项,当匹配成功时,词法表达式会对成功匹配的内容对应于各个匹配项进行从1到n的编号。抽取时,一个抽取项可以提取一个或者多个匹配id,当取得多个匹配id时,系统将会直接合并这些id对应匹配内容的文字部分,形成一个整体,并标记抽取项指定的名称。例如:

# input: 我/n 爱/v 北京/city ***/loc

# rule:
*/n + */v + */city + */loc = {location:3,4}

# output: {"location":["北京***"]}
  • 分组提取

可以通过定义多个抽取项来实现分组提取的功能,例如:

# input: 张三/per 借给/v 李四/per 10/m 块/q 钱/n

# rule:
*/per + (借|借给)/v + */per + */m + */q + */n = {source:1} + {target:3} + {object:4,5,6}

# output: {"source":["张三"], "target":["李四"], "object":["10块钱"]}
  • 重名提取

在定义一条抽取规则时,有可能存在同一个名称抽取出多项的情况。例如上面的例子,如果抽取的目的是识别匹配到的人名,那么“张三”和“李四”分别都是需要输出的内容。需要注意到,输出结果中的value项都是数组的形式,正是为了处理这种重名提取的情况。

错误的例子是:

# rule:
*/per + (借|借给)/v + */per + */m + */q + */n = {person:1,3}

# output: {"source":["张三李四"]}

正确的例子是:

# rule:
*/per + (借|借给)/v + */per + */m + */q + */n = {person:1} + {person:3}

# output: {"source":["张三", "李四"]}