Techブログ - MNTSQ, Ltd.

リーガルテック・カンパニー「MNTSQ(モンテスキュー)」のTechブログです。

ノイズが多いテキストを対象にした正規表現を書きやすくするPythonモジュール regex

MNTSQ Tech Blog TOP > 記事一覧 > ノイズが多いテキストを対象にした正規表現を書きやすくするPythonモジュール regex

MNTSQ(モンテスキュー)という契約書管理のSaaS製品を開発する会社で、
アルゴリズムエンジニアをしている坂本です。
契約書に書かれた情報を自動で抽出する仕組みを作っています。

概要

私も非エンジニア出身であるため、Techブログではあるものの、
幅広い方に読んでいただきたいと思いました。
このブログの内容をざっくり図解すると、こんな内容を扱います。

正規表現とはなにか

テキスト(=文字列)に対して、マッチさせるパターンのことです。
特定の文字を含むテキストを探すときに使います。

例えば、
 テキスト1:「庭には2羽ニワトリがいる」
 パターン1:「ニワトリ」
だった時、このパターン1はテキスト1にマッチします。

ほかにも、
 テキスト2:「庭には2羽鶏がいる」
 パターン2:「鶏」
のように書けば、パターン2はテキスト2にマッチします。

テキスト1とテキスト2のどちらにもマッチするパターンを書きたいときには、
 パターン3:「(ニワトリ|鶏)」
のように「( )」と「|」という記号をつかって、「ニワトリ or 鶏」というパターンを書くことができます。
1つのパターンでテキスト1にも、テキスト2にも、マッチします。

このように、裏技的な記号を使えば、
ある程度のバリエーションを持つテキストに対するパターンが書けます。

正規表現を書きにくい、中ボス的なテキストが存在する

ところが、どうにも正規表現が書きにくいテキストが存在します。
それは、ランダムにノイズが入るタイプのテキストです。
弊社の場合だと、紙の契約書をOCR(光学文字認識)したテキストが該当します。

紙で締結した契約書を解析したい場合、
最初にOCRをして
画像(紙をスキャンしたPDFファイル)からテキストを取り出した後、
取り出したテキストを対象に、いろんな方法で、
契約当事者や、契約締結日を抽出したり、契約類型を推論したりしています。

OCRは、見た目に忠実に認識します。
印字のレイアウトの影響を受けますので、
たとえば、1文字1文字の間にスペースが挟まっていたり、
ほぼ完璧に認識できているのに1文字だけ間違っている、ということがよくあります。

単純な正規表現のパターンを書くと、
このようなエラーを含むテキストにはマッチしません。
そこで、記号を使ってある程度のバリエーションをカバーできるようにしたいのですが、
どこにエラーが出るかがまったくランダムなので、とても難しいのです。

regexモジュールのFuzzy match機能を使って、楽に中ボスを倒す

中ボスを楽に倒すということで、課金アイテm 無料ですが、
別途インストールが必要なregexモジュールを使ってみます。

Pythonをインストールすると標準でついてくるモジュールの中にも、
re という正規表現を扱うためのモジュールがありますが、
regexにはreにはない便利な機能があります。

具体的には、Fuzzy matchと呼ばれる機能をつかって、
ある程度の揺れ幅を許容してパターンとテキストをマッチングさせることができます。
OCR結果のように、多少のノイズが想定されるテキストを扱う際に重宝します。

Fuzzy matchの使用例

regexのFuzzy matchは、通常の正規表現を丸カッコでくくった後、
その直後につけた中括弧の中に細かい指示を書いて使います。

例えば、下記のtext1とtext2にマッチさせるパターンを作るには、
patternAのように書きます

'(?:業務委託契約書){i:\s}'の後半にある {i:\s} がポイントで、
「\s(空白文字)」の[i(挿入)]を許容しています。

import regex

text1 = '業務委託契約書'
text2 = '業 務 委 託 契 約 書'
patternA = '(?:業務委託契約書){i:\s}'

 regex.match(patternA, text1)
<regex.Match object; span=(0, 7), match='業務委託契約書'>

regex.match(patternA, text2)
<regex.Match object; span=(0, 13), match='業 務 委 託 契 約 書', fuzzy_counts=(0, 6, 0)>

また、下記のtext1とtext3にマッチさせるパターンを作るには、
patternBのように書きます。

こんどは、'(?:業務委託契約書){s}'の後半にある {s} がポイントで、
文字の入れ替わり(substitution)を許容しています。

import regex

text1 = '業務委託契約書'<br>
text3 = '業務季託契約書'<br>
patternB = '(?:業務委託契約書){s}'<br>

regex.match(patternB, text3)<br>
<regex.Match object; span=(0, 7), match='業務季託契約書', fuzzy_counts=(1, 0, 0)>

実際には、データの傾向を見ながら、上記のパターンを調整します。
機械学習で解決しても良さそうですが、
データが少ない場合や、試作を作りたいときに、
学習用データを作らなくても動作とデータの相性をざっくり掴むことができます。

裏話

実は、採用ブログも兼ねています

正規表現自然言語処理機械学習、MLOps、検索、Elasticsearch
といった分野に関係のある仕事をお探しの方で、
経営層と近い距離で、
裁量を持って領域横断的に働くことにご興味を持っていただけましたら、
ぜひ右上の採用ページからお問い合わせください。

また、法律分野、もしくは言語学の知識を生かして、
学習用データを作ったり実験サイクルを重ねて製品を作ることにご興味のある方も、
どうぞご連絡ください。
(私も、もともとは言語学でした。)

この記事を書いた人

f:id:mntsq:20201113152310j:plain

坂本明子

MNTSQ, Ltd.でアルゴリズムエンジニアをしています。

入社エントリはこちら
www.wantedly.com