一、命名实体识别
首先,我们来认识一下命名实体识别的概念。命名实体识别(Named Entities Recognition, NER)研究的命名实体一般分为3大类(实体类、时间类和数字类)和7小类(人名、地名、组织机构名、时间、日期、货币和百分比),研究的目的是将语料中的这些命名实体识别出来。主要有三种方式:
1)基于规则的命名实体识别:依赖于手工规则的系统,结合命名实体库,对每条规则进行权重辅助,然后通过实体与规则的相符情况来进行类型判断。大多数时候,规则往往依赖具体语言领域和文本风格,难以覆盖所有的语言现象。
2)基于统计的命名实体识别:与分词类似,目前流行的基于统计的命名实体识别方法有:隐马尔科夫模型、条件随机场模型等。主要思想是基于人工标注的语料,将命名实体识别任务作为序列标注问题来解决。该方法依赖于语料库。
3)混合方法:自然语言处理并不完全是一个随机过程,单独使用基于统计的方法使状态搜索空间非常大,必须借助规则知识提前进行过滤修剪处理,很多情况下,命名实体识别都是使用混合方法,结合规则和统计方法。
二、基于规则的命名实体识别
这里我们以日期识别为例,详细讲述。假设数据需求背景为:抽取酒店预订系统中日期类的数据,这些数据是非结构化数据,会出现各种形式的日期形式。我们的目的是识别出每条数据中可能的日期信息,并转换为统一的格式输出。
首先通过jieba分词将带有时间信息的词进行切分,然后记录连续时间信息的词,提取词性为‘m’(数字)、‘t’(时间)的词。
然后检查日期的有效性,最后将提取到的日期转换为统一格式。具体代码如下:
# coding=utf-8
import datetime
import re
import jieba.posseg as psg
from dateutil import parser #就是与日期相关库里的一个日期解析器 能够将字符串 转换为日期格式
UTIL_CN_NUM = {
'零': 0, '一': 1, '二': 2, '两': 2, '三': 3, '四': 4,
'五': 5, '六': 6, '七': 7, '八': 8, '九': 9,
'0': 0, '1': 1, '2': 2, '3': 3, '4': 4,
'5': 5, '6': 6, '7': 7, '8': 8, '9': 9
}
UTIL_CN_UNIT = {'十': 10, '百': 100, '千': 1000, '万': 10000}
#检查抽取的日期有效性
def check_time_valid(word):
m = re.match("\d+$", word)
if m:
if len(word) <= 6:
print(word)
return None
word1 = re.sub('[号|日]\d+$', '日', word)
if word1 != word:
return check_time_valid(word1)
else:
return word1
def cn2dig(src):
if src == "":
return None
m = re.match("\d+", src)
if m:
return int(m.group(0))
rsl = 0
unit = 1
for item in src[::-1]:
if item in UTIL_CN_UNIT.keys():
unit = UTIL_CN_UNIT[item]
elif item in UTIL_CN_NUM.keys():
num = UTIL_CN_NUM[item]
rsl += num * unit
else:
return None
if rsl < unit:
rsl += unit
return rsl
def year2dig(year):
res = ''
for item in year:
if item in UTIL_CN_NUM.keys():
res = res + str(UTIL_CN_NUM[item])
else:
res = res + item
m = re.match("\d+", res)
if m:
if len(m.group(0)) == 2:
return int(datetime.datetime.today().year/100)*100 + int(m.group(0))
else:
return int(m.group(0))
else:
return None
def parse_datetime(msg):
if msg is None or len(msg) == 0:
return None
try:
dt = parser.parse(msg)
return dt.strftime('%Y-%m-%d %H:%M:%S')
except Exception as e:
m = re.match(
r"([0-9零一二两三四五六七八九十]+年)?([0-9一二两三四五六七八九十]+月)?([0-9一二两三四五六七八九十]+[号日])?([上中下午晚早]+)?([0-9零一二两三四五六七八九十百]+[点:\.时])?([0-9零一二三四五六七八九十百]+分)?([0-9零一二三四五六七八九十百]+秒)?",
msg)
if m.group(0):
res = {
"year": m.group(1),
"month": m.group(2),
"day": m.group(3),
"hour": m.group(5) if m.group(5) is not None else '00',
"minute": m.group(6) if m.group(6) is not None else '00',
"second": m.group(7) if m.group(7) is not None else '00',
}
params = {}
#print(m.group())
for name in res:
if res[name] is not None and len(res[name]) != 0:
tmp = None
if name == 'year':
tmp = year2dig(res[name][:-1])
else:
tmp = cn2dig(res[name][:-1])
if tmp is not None:
params[name] = int(tmp)
target_date = datetime .datetime.today().replace(**params) #替换给定日期,但不改变原日期
is_pm = m.group(4)
if is_pm is not None:
if is_pm == u'下午' or is_pm == u'晚上' or is_pm == '中午':
hour = target_date.time().hour
if hour < 12:
target_date = target_date.replace(hour=hour + 12)
#print(target_date)
return target_date.strftime('%Y-%m-%d %H:%M:%S')
#return m.group(0) #group() 同group(0)就是匹配正则表达式整体结果, group(1) 列出第一个括号匹配部分,group(2) 列出第二个括号匹配部分,group(3) 列出第三个括号匹配部分
else:
return None
def time_extract(text):
time_res = []
word = ''
keyDate = {'今天': 0, '明天':1, '后天': 2}
for k, v in psg.cut(text): #k是切分的词,v是对应词的词性
if k in keyDate:
if word != '':
time_res.append(word)
word = (datetime.datetime.today() + datetime.timedelta(days=keyDate.get(k, 0))).strftime('%Y{y}%m{m}%d{d}') .format(y='年',m='月',d='日')
elif word != '':
if v in ['m', 't']:
word = word + k
else:
time_res.append(word)
word = ''
elif v in ['m', 't']:
word = k
if word != '':
time_res.append(word)
result = list(filter(lambda x: x is not None, [check_time_valid(w) for w in time_res]))
final_res = [parse_datetime(w) for w in result]
return [x for x in final_res if x is not None]
if __name__ =='__main__':
text1='我要预定明天下午3点到后天下午的6个房间。'
text2='我要预定5月1号至五月5号的房间'
time_deal=time_extract(text1)
print(text1,':',time_deal )
time_deal2 = time_extract(text2)
print(text2, ':', time_deal2)
最后输出结果为:
我要预定明天下午3点到后天下午的6个房间。 : ['2020-04-10 15:00:00', '2020-04-11 12:00:00']
我要预定5月1号至五月5号的房间 : ['2020-05-01 00:00:00', '2020-05-05 00:00:00']