Skip to content

时间语义解析 说明文档

dongrixinyu edited this page Mar 15, 2024 · 25 revisions

前言

  • NLP 发展至今,始终都在做复杂规律与特征学习,从未真正做到理解文本的语义(个人认为,即便强大如 ChatGPT,其功能原理更类似于记住了某种联系,而非学会了推理,并且距离人的智能还有一定距离)。想要真正做到语义级别的 NLP 系统,理解现实世界的时空至关重要。
  • jionlp 时间语义解析即针对汉语时间文本,做规则解析,初步实现真正理解时间
  • 目前该工具已部署在线版:JioNLP源站

时间的使用与表达

  • 抛开相对论,人类使用时间几乎都属于线性时间串,所有时间点均匀排列,即现实时间轴。与现实时间轴相对应的是虚拟时间轴,由人类根据现实时间轴想象而来,比如时间飞船的穿越,魔法世界的时间停滞等等,这不在本工具的讨论范围。

  • 几乎所有具体时间的表达,均以某个时间点为基点进行表达。例如:“去年春节期间”,即以当下时间“2021年6月14日1点28分12秒”为基点做时间描述。

  • 距离该时间基点越近,人类描述的时间准确度越高,反之亦然。例如:“9000万年前”、“十多年前”等时间表达,时间距离当下时间基点往往很含糊,人类的感知粒度很粗,“昨天19点28分”则是人类常用的准确、清晰的时间表达。

  • 世界上有很多种描述时间的规则和方法,比如,最常见的即世界公历,除此之外,中国还有农历计时法(jionlp时间解析工具支持),皇帝年号纪元,日本年号纪元,等等,非常多。

  • 在语法中,时间往往作为时间状语出现,用于修饰动词、介词等,即时间的表达与上下文的动、介词有很强的相关关系。此外,时间也往往作为主语、宾语出现,此时往往动、介词的选择范围就很小。

  • 时间类型主要分为time_point(时间点)、time_span(时间范围)、time_period(周期性时间)、time_delta(时间长度)、time_query(时间问词)、time_virtual(虚拟时间)

time_parser

一、time_point 时间点

  • 时间点描述某一个时间节点,例如“9月15号23点23分”、“前年腊月初八”等。

  • 根据人的使用经验,时间点往往限制在日、时、分、秒(不绝对)。

  • 时间点重点描述在该时间点发生了某件事,在语言表达中一般都用于修饰一个短暂动词。例如:“18时32分,歹徒袭击了南非议会大楼”。

  • 时间点的判断往往要根据文本前后信息进行判断。特例如:“他在9月给菲菲过了个生日”。其中,9月持续时间较长,为时间范围,但在本句中,生日是9月的某一天,即句子的完整表达为“他在9月的某一天给菲菲过了个生日”。因此,此处尽管“9月”持续时间很长,但仍然应该解析为时间点。

二、time_span 时间范围

  • 时间范围描述具体的一段时间,例如“三年前第一个季度”、“2020年4月1号到10月1号”等。

  • 时间范围侧重描述该范围内持续发生了某件事,在语言表达中一般都用于修饰一个持续性动词。例如:“麦克从2004年9月到2007年6月在蒙卡私立中学读高中”。

  • 同样地,时间范围往往带有模糊性,需要根据前后文进行判定。特例如:“阪神地震持续时间为1995年4月12日2时2分-4分”。本句中,尽管持续时间较短,长度为2分钟,但仍表达一段时间。

  • 由上可知,time_point 和 time_span 本质上是相同的,由于人的主观理解而产生了区分,而所修饰的动、介词是短暂性,还是持续性,也由人的主观理解而定。 基于以上考虑,本功能中,由于暂未考虑上下文的影响,对于 time_point 和 time_span 的区分,仅根据时间字符串本身做区分,并不完全准确严格。

三、time_period 时间周期

  • 时间周期描述周期性的时间,例如“每年端午节”、“每隔30秒”、“9月6日-16日早上8点-中午12点”等。

  • 时间周期的描述要素有三个,时间周期的频率(freq),时间周期的持续时长(delta),时间周期的起始点(start_point),例如:“每5分钟”,描述时间的出现频率5分钟;而“每年9月至10月”,则在描述了时间频率1年的同时,还描述了持续时长2个月,以及起始点9月1日;“每年中秋节”,描述了时间周期的起始点中秋节的0点0分持续时长1天时间。

  • 频率、持续时长、起始点 在星期日期上难以描述。例如:“每周三下午5点”,起始点为 time_point 类型,但是 time_point 一般格式为 年月日时分秒,不存在

  • 根据以上情况,我们对时间周期的解析,返回结果包括一个 delta指明时间频率,point指明一个具体的time_point,它说明了时间的起始点和持续时长。

四、time_delta 时间长度

  • 时间长度描述抽象的一段时间,例如 “7天时间”、“24个小时零8分钟。”
  • 在语言表达中,时间长度往往作为主语、宾语出现,而非时间状语,例如“今年的春节假期共有7天”。
  • 时间长度往往表述不确切的时间长度,因此与时间基点无关。但现实使用时,有大量的 time_delta 被转换为 time_span 使用。例如:“过半个小时”、“三十年前”、“两个月以后”、“前俩月”、“未来三天”、“第三周周日”、“90分钟以内”、“五年以来”等等。均需针对性进行解析。

五、time_query 时间问词

  • 询问时间词汇,例如 “现在是几点钟?”、“多少年后才能有自己的房子呢?”。在语言中依然属于时间状语成分,其一般为询问或感叹,即信息量较低,对一般 NLP 任务意义较小。
  • 由于无法映射到现实时间轴,暂不支持解析。

六、time_virtual 虚拟时间

  • 指无法在现实时间轴上体现的时间,例如 “魔界纪元380年”、“国王在公主失踪后的第二天病倒了。”
  • 由于无法映射到现实时间轴,暂不支持解析。

时间语义解析

parse_time

  • 本工具提供目前开源领域最优质的时间解析工具,该工具已部署在线版:JioNLP源站
  • 输入时间表达字符串,返回其解析结果,映射到现实时间轴上。包括type(时间类型)definition(准确度)time(详细解析结果)
>>> import time
>>> import jionlp as jio
>>> time_text_list = ['2021年前两个季度', '从2018年12月九号到十五号', '2019年感恩节', '每周六上午9点到11点', '30~90日']
>>> for time_text in time_text_list:
...     print(jio.parse_time(time_text, time_base=time.time()))

# {'type': 'time_span', 'definition': 'accurate', 'time': ['2021-01-01 00:00:00', '2021-06-30 23:59:59']}
# {'type': 'time_span', 'definition': 'accurate', 'time': ['2018-12-09 00:00:00', '2018-12-15 23:59:59']}
# {'type': 'time_point', 'definition': 'accurate', 'time': ['2019-11-28 00:00:00', '2019-11-28 23:59:59']}
# {'type': 'time_period', 'definition': 'accurate',
   'time': {'delta': {'day': 7}, 'point': {'time': ['2021-06-19 09:00:00', '2021-06-19 11:59:59'], 'string': '周六上午9点到11点'}}}
# {'type': 'time_delta', 'definition': 'blur', 'time': [{'day': 30.0}, {'day': 90.0}]}

  • 务必每次调用前,显式指定 time_base:否则会出现 time_base 卡死在初次启动日期的情况

  • 输入参数与说明详见 print(jio.parse_time.__doc__)

  • type 指定了时间的类型,包括上述四种类型

  • definition 表示时间的准确度,取值包括[accurate|blur] 两种

  • 输入参数除时间字符串外,还包括time_base,其指解析时间时指定的时间基点,可支持多种类型,包括time.time类型int时间戳arrow.arrow.Arrow类型float时间戳datetime.datetime类型list类型:[2017, 9, 12, 23, 12, 59]dict类型:{'year':1998, 'month':7, 'day':31, 'hour':6}

  • 当前支持解析的时间字符串类型:(模糊、清晰)年月日、(模糊、清晰)时分秒、星期、世纪、年代、月旬、节日、季节、季度、节气、农历月日、时间周期、时间长度、法律时间、模糊时间代词等。

  • 所有支持的测试用例见测试用例,大约500条测试用例。


使用方法

  • parse_time 工具依赖正则,仅用于解析完整的时间字符串,保证解析的准确率,而不负责召回率。因此使用工具方法,需要使用工具包中的【时间类型实体识别】将所有的文本中的时间实体抽取出来,然后一一进行解析。该时间实体抽取工具不依赖模型,消耗资源非常低,根据测试,抽取F1值达到 93.8%,一般来讲,完全可以替代一般实体识别模型。
  • 当然,也可采用其它实体识别模型进行处理。如百度、阿里、腾讯、讯飞、玻森等AI开放平台提供的 ner 模型 api 接口。到相应网站上注册使用免费试用接口。 image
7月15日               time_point  ['2021-07-15 00:00:00', '2021-07-15 23:59:59']
今年上半年             time_span   ['2021-01-01 00:00:00', '2021-06-30 23:59:59']
两年                  time_delta  {'year': 2.0}
一季度                time_span   ['2021-01-01 00:00:00', '2021-03-31 23:59:59']
二季度                time_span   ['2021-04-01 00:00:00', '2021-06-30 23:59:59']
上半年                time_span   ['2021-01-01 00:00:00', '2021-06-30 23:59:59']
春节                  time_point  ['2021-02-12 00:00:00', '2021-02-12 23:59:59']
五一                  time_point  ['2021-05-01 00:00:00', '2021-05-01 23:59:59']
端午                  time_point  ['2021-06-14 00:00:00', '2021-06-14 23:59:59']
6月份                 time_point  ['2021-06-01 00:00:00', '2021-06-30 23:59:59']
16个月                time_delta  {'month': 16.0}
从2018年至今           time_span   ['2018-01-01 00:00:00', '2021-07-15 09:03:47']
每年                  time_period {'delta': {'year': 1}, 'point': None}
1-5月份               time_span   ['2021-01-01 00:00:00', '2021-05-31 23:59:59']
2020年1-5月份         time_span   ['2020-01-01 00:00:00', '2020-05-31 23:59:59']
2021-07-15 09:03:47  time_point  ['2021-07-15 09:03:47', '2021-07-15 09:03:47']

解析说明

  • 单独解析时间字符串,不依赖上下文,则出现一些歧义情况。如下表:
类型 举例 说明
N日 30日 当N<=31时,可解析为 time_point 与 time_delta 两种,“开幕式定在30日。”,“康复周期为30日。”已支持
N秒 58秒 当N<=60时,可解析为 time_point 与 time_delta 两种,“23秒,32年。”,“23秒时,炸弹爆炸了。”由于单独秒数表达 time_point 情况太少,暂不支持
[前头]time_delta 前七天 不支持解析,可解析为 time_span,但需给定一个具体的长 time_span,“结婚前三年,他俩还很恩爱。”
过去time_delta 过去一年 可解析为 time_span,“过去一年,我们风雨兼程。”已支持;也可表达为一个时间动作,“距离事发已过去三年。”此种情况不予解析,此时“过去三年”并非独立的时间实体,而应当解析的是“三年”。
time_delta[以之]?内 10周之内 可解析为 time_span 与 time_delta,“限你在10周内还款。”,“按规定,接到传票10周内须应诉。”暂不支持**
time_delta[以之]?[前后] 3个月之前 可解析为 time_span,但有两种解法,[-inf, time_base - 3个月] 或者 [time_base - 3个月 - 1, time_base - 3个月 + 1],前者强调3个月以前的整块时间,后者强调3个月前的时间点。部分支持
公元前N世纪 公元前2世纪 此种时间可以被检测到,但由于无法表达为标准时间格式而被丢弃不予解析
昨晚[1-4]点 昨晚1点 该情况受到 time_base 影响,当time_base时间为今天白天到凌晨12点内,昨晚1点存在两种解释,即今天凌晨1点与昨天凌晨1点,当 time_base 时间越过凌晨12点后,仅解析为昨天凌晨1点。这种情况源于人对一日的理解,人们习惯将早上当作一天的开始。“白天上班的时候,小舟给我说她昨晚1点被吵醒了。”暂不支持
time_point[到至-~]time_pointN点 早上9点~下午6点 解析为[time_point, 17:59:59],而非 [time_point, 18:59:59],原因在于,hour 放置 time_span 后,应当不对其作向后扩展,即其内涵为 早上9点0分0秒~下午6点0分0秒已支持
前两[年天] 前两天 在中文中,“前两天”在很多表达中,可等效为“前几天”,具有高度模糊性。“前两天我买了件衣服。” 暂不支持
NN年 35年 可解析为 time_span 与 time_delta,“(19)49年,发生了一件大事。”,“49年是一个人大半生的时间。” 已支持
N[来|几|多少][年月天] 二十来天 具有高度模糊性的时间表达,“三十多年前的事”,“几百个小时的运转”。 暂不支持
  • 针对以上异常与歧义情况,工具可根据参数设置进行不同的解析,如:
import jionlp as jio
text = '30日'
print(jio.parse_time(text, time_type='time_point'))  # 指明按时间点类型进行解析
print(jio.parse_time(text, time_type='time_delta'))

# {'type': 'time_point', 'definition': 'accurate', 'time': ['2021-06-30 00:00:00', '2021-06-30 23:59:59']}
# {'type': 'time_delta', 'definition': 'accurate', 'time': {'day': 30.0}}

“和” 字对解析的影响

  • 有一种类型的字符串,包含字,例如,每周末9点和14点每天9点、14点 等。该类型可归纳为 时间1时间2 类型。
  • 此种类型的字符串,直接使用 jio.parse_time 解析将会报错,原因在于,本工具默认,以 连接为两个时间字符串,即,若使用 NER 抽取时间实体时,很可能上例字符串被抽取为两个 每周末9点14点
  • 针对此类, 首先用该工具包解析 时间1, 然后以 时间1 为 time_base,解析 时间2,从而得到返回结果。

TODO

  • 时分秒针对不同 time_base 的转换。
  • 超模糊时间,“二十几天”、“40来年前”
  • 双时间范围构成时间周期,如“2021年9月到10月的每个工作日上午9点到下午3点”。

问题困难点(本工具难以解决)

  • 上下文影响语义解析:汉语属于分析语,即语言语义相对于英语等屈折语,有更强的联系上下文识别语义的特点。放在时间语义解析问题上,主要突出矛盾在于,时间实体在不同的上下文中具有不同的含义。例如,“前两个月”字符串在不同语境中语义不同:

    • “2019年全国工业产值创新高,前两个月产值增加4%,年中增加7%。” -> 语义:“2019年的1月和2月”
    • “他前两个月离职了,目前仍在失业中。” -> 语义:“此时此刻的大约两个月前的某个时间点,且月数虚指,可能是2个月前,也可能是3个月前”
    • “把前两个月的财务报表发给领导。” -> 语义:“从此时此刻往前推两个月”
  • 时间基点不确定:某些语言表达中,时间基点不确定,此时工具会默认为当前时刻,造成解析错误。例如:

    • “总经理在下班后把他批评了一顿,第二天,他就提交了辞职报告。” -> 第二天所依赖的时间基点不确定,即究竟是哪天“他”被批评。
    • 规定租金为4个月一收,乙方应在每期末月的最后15日之前向甲方付清下期租金。 -> “每期月末”具体是哪些月?“最后15日之前”具体是指多长范围?
  • 总之,本工具在应对上述困难点时是无法应对操作的,难度过大。若感兴趣,可以试试采用 GPT 模型的训练方式,以对话形式训练模型理解时间语义,我试了一下,对于语言的推理能力是有的,可以打98分,而数学计算能力目前只能打50分,如果进一步优化,效果应该会变得更加优质。