核心技术与算法学习笔记:词性标注与命名实体识别(日期识别)

结构化数据,可以在数据入库时予以类型约束,在需要时能够通过解析还原读取到对应的日期
非结构化数据日期多与文本结合,日期识别与具体要求有关

背景:
现有一个基于语音问答的酒店预订系统,其根据用户的每句语音进行解析,
识别出用户的酒店预订需求,如房间型号、入住时间等;
用户的语音在发送给后台进行请求时已经转换成中文文本,
然而由于语音转换工具的识别问题,许多日期类的数据并不是严格的数字,
会出现诸如"六月 12" “2016 年八月” “20160812” “后天下午” 等形式。
这里不关注问答系统的具体过程,主要目的是识别出每个请求文本中可能的日期信息,并将其转换成统一的格式进行输出。

主要通过正则表达式和Jieba
分词


import re
from datetime import datetime,timedelta
from dateutil.parser import parse
import jieba.posseg as psg

# 通过Jieba分词将带有时间信息的词进行切分,然后记录连续时间信息的词
# time extract:对句子进行解析,提取其中所有能表示 日期时间的词,并进行上下文拼接
def time_extract(text):
    time_res = []
    word = ''
    keydate = {'今天':0, '明天':1, '后天':2}
    for k,v in psg.cut(text):
        if k in keydate:
            if word != '':
                time_res.append(word)
            word = (datetime.today() + timedelta(days=keydate.get(k,0))).strftime('%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]


# time extract中有个 check time valid 函数,用来对提取的拼接日期串进行进一步处理,以进行有效性判断的。
def check_time_valid(word):
    m = re.match('\d+$',word)
    if m:
        if len(word) <= 6:
            return None
    word1 = re.sub('[号|日]\d+$','日', word)
    if word1 != word:
        return check_time_valid(word1)
    else:
        return word1

#  parse_datetime 函数,用以将每个提取到的文本日期串进行时间转换。 
# 其主要通过正则表达式将日期串进行切割,分为"年" "月" "日" "时" H分""秒"等具体维度,
# 然后针对每个子维度单独再进行识别。
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:
        # re用以处理阿拉伯数字与汉字混杂的日期串的提取,其还加入了 "上中下晚早"的考虑,用以调整最终输出的时间格式。
        m = re.match(
            r'([0-9零一二两三四五六七八九十]+年)?([0-9一二两三四五六七八九十]+月)?'
            r'([0-9一二两三四五六七八九十]+[号日])?([上中下午晚早]+)?([0-9零一二两三四五六七八九十百]+[点:\.时])?'
            r'([0-9零一二三四五六七八九十百]+分?)?([0-9零一二三四五六七八九十百]+秒)?', msg)
        if m.group(0) is not None:
            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 = {}

            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.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)
            return target_date.strftime('%Y-%m-%d %H:%M:%S')
        else:
            return None

# 将常见的中文汉字与对应的阿拉伯数字建立一一对应关系
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 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


text1 = '我要住到明天'
print(text1, time_extract(text1), sep = ':')

text2 = '预定28号的房间'
print(text2, time_extract(text2), sep = ':')

text3 = '我要从26号下午4点住到1月2号'
print(text3, time_extract(text3), sep = ':')

# 采用规则去覆盖所有的语言场景是不太现实的。

text4 = '我要预定今天到29号的房间'
print(text4, time_extract(text4), sep = ':')

text5 = '我今天30号呵呵'
print(text5, time_extract(text5), sep = ':')

输出

我要住到明天:['2019-12-05 00:00:00']
预定28号的房间:['2019-12-28 00:00:00']
我要从13号下午4点住到15号6点:['2019-12-13 16:00:00', '2019-12-15 06:00:00']
我要预定今天到29号的房间:['2019-12-04 00:00:00', '2019-12-29 00:00:00']
我今天30号呵呵:['2030-12-04 00:00:00']
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值