-
Notifications
You must be signed in to change notification settings - Fork 404
时间语义解析 说明文档
- 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(虚拟时间)
-
时间点描述某一个时间节点,例如“9月15号23点23分”、“前年腊月初八”等。
-
根据人的使用经验,时间点往往限制在日、时、分、秒(不绝对)。
-
时间点重点描述在该时间点发生了某件事,在语言表达中一般都用于修饰一个短暂动词。例如:“18时32分,歹徒袭击了南非议会大楼”。
-
时间点的判断往往要根据文本前后信息进行判断。特例如:“他在9月给菲菲过了个生日”。其中,9月持续时间较长,为时间范围,但在本句中,生日是9月的某一天,即句子的完整表达为“他在9月的某一天给菲菲过了个生日”。因此,此处尽管“9月”持续时间很长,但仍然应该解析为时间点。
-
时间范围描述具体的一段时间,例如“三年前第一个季度”、“2020年4月1号到10月1号”等。
-
时间范围侧重描述该范围内持续发生了某件事,在语言表达中一般都用于修饰一个持续性动词。例如:“麦克从2004年9月到2007年6月在蒙卡私立中学读高中”。
-
同样地,时间范围往往带有模糊性,需要根据前后文进行判定。特例如:“阪神地震持续时间为1995年4月12日2时2分-4分”。本句中,尽管持续时间较短,长度为2分钟,但仍表达一段时间。
-
由上可知,time_point 和 time_span 本质上是相同的,由于人的主观理解而产生了区分,而所修饰的动、介词是短暂性,还是持续性,也由人的主观理解而定。 基于以上考虑,本功能中,由于暂未考虑上下文的影响,对于 time_point 和 time_span 的区分,仅根据时间字符串本身做区分,并不完全准确严格。
-
时间周期描述周期性的时间,例如“每年端午节”、“每隔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
,它说明了时间的起始点和持续时长。
- 时间长度描述抽象的一段时间,例如 “7天时间”、“24个小时零8分钟。”
- 在语言表达中,时间长度往往作为主语、宾语出现,而非时间状语,例如“今年的春节假期共有7天”。
- 时间长度往往表述不确切的时间长度,因此与时间基点无关。但现实使用时,有大量的 time_delta 被转换为 time_span 使用。例如:“过半个小时”、“三十年前”、“两个月以后”、“前俩月”、“未来三天”、“第三周周日”、“90分钟以内”、“五年以来”等等。均需针对性进行解析。
- 询问时间词汇,例如 “现在是几点钟?”、“多少年后才能有自己的房子呢?”。在语言中依然属于时间状语成分,其一般为询问或感叹,即信息量较低,对一般 NLP 任务意义较小。
- 由于无法映射到现实时间轴,暂不支持解析。
- 指无法在现实时间轴上体现的时间,例如 “魔界纪元380年”、“国王在公主失踪后的第二天病倒了。”
- 由于无法映射到现实时间轴,暂不支持解析。
- 本工具提供目前开源领域最优质的时间解析工具,该工具已部署在线版: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 接口。到相应网站上注册使用免费试用接口。
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
,从而得到返回结果。
- 时分秒针对不同 time_base 的转换。
- 超模糊时间,“二十几天”、“40来年前”
- 双时间范围构成时间周期,如“2021年9月到10月的每个工作日上午9点到下午3点”。
-
上下文影响语义解析:汉语属于分析语,即语言语义相对于英语等屈折语,有更强的联系上下文识别语义的特点。放在时间语义解析问题上,主要突出矛盾在于,时间实体在不同的上下文中具有不同的含义。例如,“前两个月”字符串在不同语境中语义不同:
- “2019年全国工业产值创新高,前两个月产值增加4%,年中增加7%。” -> 语义:“2019年的1月和2月”
- “他前两个月离职了,目前仍在失业中。” -> 语义:“此时此刻的大约两个月前的某个时间点,且月数虚指,可能是2个月前,也可能是3个月前”
- “把前两个月的财务报表发给领导。” -> 语义:“从此时此刻往前推两个月”
-
时间基点不确定:某些语言表达中,时间基点不确定,此时工具会默认为当前时刻,造成解析错误。例如:
- “总经理在下班后把他批评了一顿,第二天,他就提交了辞职报告。” -> 第二天所依赖的时间基点不确定,即究竟是哪天“他”被批评。
- 规定租金为4个月一收,乙方应在每期末月的最后15日之前向甲方付清下期租金。 -> “每期月末”具体是哪些月?“最后15日之前”具体是指多长范围?
-
总之,本工具在应对上述困难点时是无法应对操作的,难度过大。若感兴趣,可以试试采用 GPT 模型的训练方式,以对话形式训练模型理解时间语义,我试了一下,对于语言的推理能力是有的,可以打98分,而数学计算能力目前只能打50分,如果进一步优化,效果应该会变得更加优质。