NLP实体命名识别之时间识别
本程序针对的是酒店的预定系统,已经将语音转换为中文文本的情况下,将时间转换为统一的格式输出。我们可能会遇到如“八月14”,“2018年6月”,“20160502”这样的格式,最后统一转换为Python中‘%Y-%m-%d %H:%M:%S’这种时间格式输出,下面展示的是整个程序最后的效果:
text1 = '我要住到明天下午六点'
print(text1, time_extract(text1), sep=':')
text2 = '预定30号的房间'
print(text2, time_extract(text2), sep=':')
text3 = '我要从27号下午5点住到10月5号'
print(text3, time_extract(text3), sep=':')
我要住到明天下午六点:['2018-08-15 18:00:00']
预定30号的房间:['2018-08-30 00:00:00']
我要从27号下午5点住到10月5号:['2018-08-27 17:00:00', '2018-10-05 00:00:00']
我们将整体的代码分解成三部分来看,先看第一部分,将输入的带有时间标志的文本进行两部处理,一是在时间前加上统一的年月日,方便后期的输出,二是将涉及到两个日期的部分分割出来。
#提取文本中的时间部分
from datetime import datetime,timedelta
import jieba.posseg as psg
def time_extract(text):
time_res = []
word = ''
keyDate = {'今天':0,'明天':1,'后天':2}
for k,v in psg.cut(text):
if k in keyDate.keys():
if word != '':
time_res.append(word)
word = (datetime.today() + timedelta(days=keyDate.get(k,0))).strftime('%Y{}%m{}%d{}').format('年','月','日')
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)
return time_res
测试效果如下:
text1 = '我要从26号下午4点住到11月2号'
print(text1,time_extract(text1),sep=':')
text2 = '我要住到明天下午四点'
print(text2,time_extract(text2),sep=':')
我要从26号下午4点住到11月2号:['26号下午4点', '11月2号']
我要住到明天下午四点:['2018年08月13日下午四点']
最初看这个代码比较困惑,不是很理解第二个elif的具体作用,于是按照自己的理解,改成下面的样子:
#提取文本中的时间部分
from datetime import datetime,timedelta
import jieba.posseg as psg
def time_extract(text):
time_res = []
word = ''
keyDate = {'今天':0,'明天':1,'后天':2}
for k,v in psg.cut(text):
if k in keyDate.keys():
if word != '':
time_res.append(word)
word = (datetime.today() + timedelta(days=keyDate.get(k,0))).strftime('%Y{}%m{}%d{}').format('年','月','日')
else:
if v in ['m','t']:
if word != '':
time_res.append(word)
word = k
if word != '':
time_res.append(word)
return time_res
改动的是中间部分,逻辑变得十分简单,如果不为空,就直接放到列表time_res里面,结果如下:
我要从26号下午4点住到11月2号:['26', '号', '下午', '4', '点', '11', '月', '2', '号']
我要住到明天下午四点:['2018年08月13日', '下午', '四点']
两段代码结果的不同非常明显,由此我们看出第一段代码的整个运行逻辑是先判断文本中有没有今天,明天,后天这三个词,如果有进入第一个if语句,没有的话进入最后一个elif,判断是否包含时间词和数量词。循环判断第二个词语,此时需要加一层判断,word是否为空,对于第一个if语句而言,如果还有词语含有今天,明天,后天这些词语,肯定是属于两个需要单独提取的时间,所以在if内部判断word是否为空,不为空的情况下,要将原来的时间先添加到列表中。对于时间词和量词,我们希望把连在一起的时间词作为一个元素添加到列表中,所以对于这样的词首先判断是否为空,不为空的情况下,判断是否是时间词或者数量词,如果是的话,先把他们合在一起,直到循环到一个不满足的词,才将这一段词语添加到列表。
第二段代码主要是规范年份,如19年,我们要转换为2019年,代码如下,比较简单,主要是注意用int实现地板除这一用法
#进一步处理与规范年份
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.today().year/100)*100 + int(m.group(0))
else:
return int(m.group(0))
else:
return None
print(year2dig('19年'))
最后一部分是主程序,核心在于正则表达式的使用:
from dateutil.parser import parse
import datetime
def parse_datetime(msg):
if msg is None or len(msg) == 0:
return None
try:
dt = parse(msg,fuzzy=True)
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) != 0:
res = {
"year": m.group(1),
"month": m.group(2),
"day": m.group(3),
"hour": m.group(5),
"minute": m.group(6),
"second": m.group(7)
}
params = {}
for name in res:
if res[name] is not None or len(res[name]) != 0:
tmp = None
if name == 'year':
tmp = year2dig(res[name][:-1])
else:
tmp = res[name][:-1]
if tmp is not None:
params[name] = tmp
else:
return None
target_time = datetime.today().replace(**params)
is_pm = m.group(4)
if is_pm is not None:
if is_pm == '中午' or is_pm == '下午' or is_pm == '晚上':
hour = datetime.hour
if hour < 12:
target_time = target_time.replace(hour = hour + 12)
return target_time
else:
return None
本文是跟着《Python自然语言处理实战--核心技术与原理》学习的结果,如有侵权,请联系我处理。
