🕰️

テキストから日付や時間を抽出するPythonパッケージ ja-timex を作りました

2021/08/03に公開
1

日本語の自然言語で書かれたテキスト中から、日付や時間、期間、頻度といった時間に関する表現を抽出し、Pythonのdatetime/timedelta形式に変換できるPythonパッケージ ja-timexを作りました。この記事では基本的な使い方や動作の仕組み、このパッケージの実装方針を紹介します。

ja-timexとは

皆さんも一度はテキスト中に現れる日付や時間を正規表現でパースした経験があるのではないでしょうか?もしくはdatetime.strptime(str, '%Y/%m/%d %H:%M')といったように、datetimeに変換するパターンを書くこともあるでしょう。こうしたプログラム上で日付や時間を扱うことはよくある作業であるものの、入力されるフォーマットが複雑になったり、日付がテキストの中の一部に含まれたりすると、途端に処理が面倒になります。日付や時間は決まったルールに従っているのだから取得は簡単だろうと最初は思いますが、実際に取り組むとその表現のバリエーションの多さやエッジケースに悩まされることになります。

そこで、時間にまつわる表現を網羅的に集めた抽出ルール群を元にテキスト中から候補となる表現を取得し、プログラミング言語の時間型との変換を行うための時間情報の解析器を作成しました。パッケージとして簡単に利用できるようにしているので、ぜひ試してみてください。

$ pip install ja-timex

https://github.com/yagays/ja-timex/

https://ja-timex.github.io/docs/

どんな動作をするパッケージなのかは、実際に入力と出力を見てもらうとわかりやすいでしょう。

入力

timexes = timex_parser.parse("彼は2008年4月から週に3回ジョギングを1時間行ってきた")

出力

[<TIMEX3 tid="t0" type="DATE" value="2008-04-XX" text="2008年4月">,
 <TIMEX3 tid="t1" type="SET" value="P1W" freq="3X" text="週に3回">,
 <TIMEX3 tid="t2" type="DURATION" value="PT1H" text="1時間">]

このように日付や時間などが書かれたテキストをParserに入力すると、時間表現を持ったTIMEXというクラスとして抽出できます。このクラスには、どのようなタイプの表現なのかというtypeや、規格化された時間情報value、抽出したテキストtextを持っています。

In []: timex = timexes[0] 
# <TIMEX3 tid="t0" type="DATE" value="2008-04-XX" text="2008年4月">

In []: timex.type
Out[]: 'DATE'

In []: timex.value
Out[]: '2008-04-XX'

In []: timex.text
Out[]: '2008年4月'

datetime/timedeltaへの変換

また、時間表現type="DATE"や、期間を表す表現type="DURATION"は、Pythonの時間を扱うライブラリのdatetimetimedelta形式に変換することができます。

In []: timex = timexes[0]
# <TIMEX3 tid="t0" type="DATE" value="2008-04-XX" text="2008年4月">

In []: timex.to_datetime()
Out[]: DateTime(2008, 4, 1, 0, 0, 0, tzinfo=Timezone('Asia/Tokyo'))
In []: timex = timexes[2]
# <TIMEX3 tid="t2" type="DURATION" value="PT1H" text="1時間">

In []: timex.to_duration()
Out[]: Duration(hours=1)

ja-timexがサポートしている機能としては、これで以上です。詳細や具体例はja-timexのドキュメントを参考ください。

具体例 - ja-timex documentation

ja-timexの仕組み

実際の動作例でおおよそのイメージは掴んでいただけたと思います。次にja-timexがどのような表現を対象として、どのように正規化し時間の変換をしているのかをザックリと説明します。

まず、対象となる表現は以下の4つです。

  • 日付表現 (DATE)
    • 2021年7月18日, 2021/07, 令和3年7月18日, 2021年度, 21世紀, etc.
  • 時間表現 (TIME)
    • 12時34分56秒, 9:00 AM, 午前, etc.
  • 持続時間表現 (DURATION)
    • 1年間, 3時間, 1時間30分, etc
  • 頻度集合表現 (SET)
    • 3ヶ月に1回, 3日に1日, 3日おき, 毎月, etc.

それぞれには正規表現による抽出ルールが定められており、テキストに対して全パターンを適用して候補となる表現を抽出します。例えば、日付に関する取得パターンの一部を見てみましょう。Patternというdataclassの単位で扱っており、その中には抽出ルールの正規表現と、抽出後に規格化するための関数、そしてメタ情報の3つのセットで成り立っています。

<Pattern: (西暦)?(?P<calendar_year>[0-9]{1,4})(?P<calendar_month>1[0-2]|0?[1-9])(?P<calendar_day>[12][0-9]|3[01]|0?[1-9])/ parse_func:parse_absdate / option:{}>,
<Pattern: (?P<calendar_month>1[0-2]|0?[1-9])(?P<calendar_day>[12][0-9]|3[01]|0?[1-9])/ parse_func:parse_absdate / option:{}>,
<Pattern: (西暦)?(?P<calendar_year>[0-9]{1,4})(?P<calendar_month>1[0-2]|0?[1-9])/ parse_func:parse_absdate / option:{}>,
<Pattern: (西暦)?(?P<calendar_year>[0-9]{1,4})/ parse_func:parse_absdate / option:{}>,
<Pattern: (?P<calendar_month>1[0-2]|0?[1-9])/ parse_func:parse_absdate / option:{}>,
<Pattern: (?P<calendar_day>[12][0-9]|3[01]|0?[1-9])/ parse_func:parse_absdate / option:{}>,

その後、抽出ルールに紐付いた正規化処理が実行され、日付や時間といった情報を構造化します。この時に用いる規格がTIMEX3と呼ばれるタグ付け基準です。例えば、2021年7月18日2021-07-18というISO-8601の規格に変換され、

<TIMEX3 tid="t0" type="DATE" value="2021-01-21">2021年7月18日</TIMEX3>

といったHTML/XMLで表現されるタグとなり、その中のvalueに時間に関する正規化された情報が入ります。あとは正規化された情報を元に、datetimeやtimedeltaといった形式に変換するメソッドが用意されており、それを利用することでプログラムで利用できる形への変換を行っています。

ja-timexで出来ないことと実装方針

さて、ここまで説明してきたja-timexですが、ありとあらゆる時間や日付を完璧に取得できるかというとそうではありません。人間の言葉による表現能力や認識能力は驚くべきものがあり、それをルールですべて記述することは不可能です。

ja-timexは「ルールベース」の解析器であると強調しているとおり、あくまで決まったルールで出来る範囲のことをなるべく正確にやろうというコンセプトで作成しています。正確に出来ないこととしては、以下の2つがあります。

1. 前後の文脈から類推できない(しない)

ja-timexはあくまで書かれている文字列から分かることのみを解釈するように設計しています。

たとえば、こんな文章があったとします。

12月14日、日本の宇宙航空研究開発機構(JAXA)は、今月6日に地球に帰還した小惑星探査機「はやぶさ2」のカプセルから、リュウグウ由来とみられる砂粒状の粒子を採取することに成功したと発表した

はやぶさ2帰還カプセル 小惑星リュウグウ由来の粒子とガスを確認 - ウィキニュース

この文書の中で今月6日という表現があります。人間が読めばこれは12月6日であることは明らかなのですが、機械がこれを正しく類推するためには、前後の文脈や文章の意味などを解釈する必要があり、一筋縄ではいきません。ja-timexでは、<TIMEX3 tid="t2" type="DATE" value="XXXX-XX-06" text="6日">といったような形で、ただの6日として出力します。

近くにある月を代入すればいいと思われるかもしれませんが、たとえば

4月はあまり暑くなかったが、今月は暑いな

といったときには、この今月は4月ではないことは明らかです(これだけでは何月か特定できません)。

このように単純なルールでは類推することが難しく、現在の自然言語処理の技術を使ってもおそらく100%類推するようなシステムを作ることはかなり難しいでしょう。ja-timexでは、なるべく小さく正確にできることに集中しようという設計思想のもと、こうした類推は行わず、後続の処理で開発者が自身のタスクに合うように処理を加えてもらうようにしています。

2. 日付や時間以外の表現も取得してしまう

ja-timexはなるべく多くの表現を取得するように設計しています。日付や時間をルールで記述している以上、間違ってそのルールを適用してしまうケースも存在します。例えば以下のような場合です。

34試合で打率1割4分7厘

松井秀喜選手が現役引退を表明 - ウィキニュース

これは「分」が時間なのか割合なのかという曖昧性の問題で、前節と同じく前後の文脈を考慮しないといけない問題です。また、以下のようなケースは人間でも判別が難しいです。

一時をお知らせします
一時はどうなることかと

十分なインターバル
十分のインターバル

ルールベースの機械的な処理として、「取りこぼしてしまう時間表現」と「間違って取得してしまう表現」の2つの間違いがあった際にどちらを許容できるかと考えた結果、取りこぼしてしまうものを極力少なくし、間違って取得してしまったものについては何らかの方法で取り除いてもらうという方針にしています。機械学習の用語で言えば、PrecisionよりもRecallを重視するということです。

おわりに

この記事ではja-timexというパッケージを紹介しました。まだまだルールも十分に整備できておらず抜け漏れも多いですが、地道に対応していければと思います。ぜひ色々な方に使っていただき、フィードバックをいただければ幸いです。

https://github.com/yagays/ja-timex/

Discussion

Katsuki InoKatsuki Ino

日付表現を抽出する方法を探していたのですが、「こういうのないかな」と思っていた通りのものなので使わせていただこうと思います!!