2017/07/08

不動産業で使える PDF文書の向き自動補正とTF-IDF×コサイン類似度によるPDF文書の自動仕分け

不動産賃貸業で使える自然言語処理
PDF文書の向き自動補正とTF-IDF×コサイン類似度によるPDF文書の自動仕分け

結論

今までは手動でPDF書類の仕分けをしていましたが、今回、人工知能的な手法と自然言語処理を応用したところ、「仕分け間違いはあるものの、間違いを探すのが簡単ではないレベル」で自動化できました!

不動産業は紙の書類が大量に発生します。当社では、高速スキャナーでPDF化して紙は捨てているので、多くの物件を管理していても書類棚はなくて済んでいます。
さらにVPNで外部からアクセスを許可したり、Redmineなどを駆使して外注先の進捗管理をするなど、極限まで効率化すれば、資産管理会社20社、取引銀行20行、物件300億、1,000部屋くらいまでの賃貸業なら、社長ひとり、もしくは事務員1名で管理できると思います
(なお、当社はそこまでの規模には至りません。また、その規模になれば書類整理の人件費などは金額僅少なので好きなだけ雇えばいいとか、一人に業務集中すると人的依存過多とか事業継続性という別の話にもなってきますが)

さて、ここまでは、よくある書類電子化の序章です。この方法で実際に運用してみると、発生する書類が多いので、スキャンしたPDFの整理が大変です。
そこで、今回のような自動仕分けアプリの出番となりました。

要素技術

・Excel VBAからWindows Script Host(WSH)に引数を渡してPDFファイルを操作
・辞書マッチによりOCR認識単語数のカウント(OCR認識率の判定)
・PDF文書の向きを自動補正
・TF-IDF×コサイン類似度による文書のクラスタリング(自動仕分け)
・多段フィルタにより最適なメタタグをファイル名に付与

具体的に何ができるのか

1.複合機でスキャンしてPDFで出てきた書類は、スキャンする向きにより天地が逆転していたり回転していたりするので、それを正しい向きに自動回転して保存する

2. 公共料金領収書類、賃料入金レポート、不動産関連書類、銀行取引関連書類、税務関連、の5フォルダを基本クラスタ(グループ)として自動的に仕分ける。それ以外に、どれにも属さない、仕分けエラーの計7フォルダに分類する。
それぞれ「賃料入金レポート\物件名」「公共料金領収書類\東京電力\使った場所」のように、自動的に階層を作ってフォルダ分けしていく

3.ファイル名には、物件名、会社名、取引銀行名などを辞書マッチにより自動で付与(タグ付け)していく

4.ネット上のクラウドAPIのようなものは使わずに分類ロジックを自作しているので、業務書類を、知らない誰かが運営しているウェブサービスに提供する必要がなく法人利用できるセキュリティレベルを担保。

5.副産物として、書類の日本語全文検索ができる

このように書類の自動仕分けをしていきます。1つのPDFファイルあたり平均60秒弱の処理時間です。
精度は「間違いを見つけるのが簡単ではないレベル」を実現できました。私がちょっとプログラムを書いたら、この精度なので、おそらく、そう遠くない未来に書類整理という仕事は人工知能に代替されてなくなるでしょう。

用意する材料

ハードウェア

 

・マザーボード ASUSTeK Z10PA-D8 デュアルCPU対応のマザー 5万円
・CPU INTEL Xeon E5-2620v4 (2.1G) LGA2011-3 BOX×2個 11万円
・CPUファン 【HASWELL対応】 虎徹 12cmサイドフロー SCKTT-1000 1万円
・メモリ 160GB = Crucial [Micron製] DDR4 サーバー用メモリー 32GB ECC ( 2133MT/s / PC4-2133 / CL15 / 288pin / DR x4 / Registered DIMM ) 永久保証 CT32G4RFD4213×4個ほか計8本 20万円
→デバッグ環境用に2倍用意しているので、普通に稼働するだけなら64GB~96GBくらいで足ります。2セット並列に走らせれば2倍速くなるという使い方もできます。
・ディスク Crucial [Micron製] 内蔵SSD 2.5インチ MX300 525GB ( 3D TLC NAND /SATA 6Gbps /3年保証 )国内正規品 CT525MX300SSD1/JP 2万円×2個=4万円
・3Uケース、CPUグリス、ほか(3万円)
ハードウェア合計 40万円++

要はVMが12台動かせて、Excelで100万件程度の処理ができるハイスペックなPCがあれば何でも良いです。

ソフトウェア

・Windows OS
Windows 10 LTSB ×13個 今回は、マイクロソフトBizSparkの開発ライセンス提供テーマに適合した企画であろう。ということでBizSpark版を利用してみました。

・仮想化ソフト
VMware Workstation 12.5 Pro  3万円 BizSparkにはWindowsServerも含まれるためHyper-Vを利用してもよいですがHyper-V はいろいろと不具合が多く、VMのほうが調子がよい気がします。ホストOSもWindowsにして、ホストOS上でExcelを動かしました。(VMware Workstation上にExcelを置くとライセンス制限で16論理コアまでしか使えないため)

・OCRソフト
「読取革命Ver.15」(パナソニック)×12個 計12万円(ひとまずは体験版でよい) コマンドラインで動作する法人用のSDKもあるものの50万円前後と高いので家庭用を使い倒すことに。
ABBY Readerもコマンドラインから操作できるようで、良さそうですが、月々の読み取り数にライセンスによる制限があり不自由なので不採用としました。
フリーのOCRソフトtesseract-ocrに期待したものの、どうやら商用OCRに比べて大幅に精度が悪いので、使わないのが無難です。読取革命がなぜ12個も必要かといえば、同ソフトに同梱されている「フォルダウォッチャー」という機能を使うのですが、それがマルチコアに(法人向けのAPIと競合するため意図的に?)対応していないのでシングルコア×複数並列しか高速化の道がないのです。

・大量の日本語単語辞書

今回、どのような単語が出てくるのか見当が付かなかったため、できる限りたくさんの辞書を集めて100万語を構成しました。しかし、実際には20~30万単語(広辞苑ぜんぶくらい)あれば十分なようです。
当社の業務で実際に使われているビジネス文書をOCRして単語をカウントしてみたところ、そのうち、実際にビジネスで使われるユニーク単語は画像のように2万語もない様子です。

WikipediaのEPWING形式をExcelにエクスポートするのが簡単でしょう。一般的なビジネス文書のボキャブラリーをカバーしている辞書であれば何でも良いです。
https://sites.google.com/site/boookends/

・マイクロソフトオフィスExcel 2016 (会社に余っているもの)×1個
Pythonを学ぶよりもExcelのほうが手っ取り早い。というのと、ExcelからPythonを開いて、そこからOpenCVや機械学習ライブラリにつなぐ。という使い方もできるので、普段から慣れているソフトで固めてみました。

処理フローチャート

PDFの向き補正

スキャンされた書類の向きがまちまちなので、その向きを正しく回転させることからスタートです。どの角度で回転させれば正しいのかを判定させるために、0,90,180,270度に回転させた4画像を生成して、それぞれOCRに入力します。

OCRは多少の傾きは補正しますが、上下が完全に逆などは認識してくれませんので、それを逆に利用して、もっとも認識率が高かった角度を正しい角度として採用します。
認識率の判定は、読み込んだOCR結果に対して、いくつの辞書単語がマッチするかで判定すれば、ほぼ100%のに精度で特定できます。

当初は、OCRソフトに認識したいPDFファイルを丸ごとに入れて、逐次処理をして、認識を待っていましたが、それだと非常に時間がかかるのと並列化できないので、最初の3ページのみを1ページずつ切り取って(各4角度=12枚)VM12台で並列処理することにしました。天地の判定は、通常は、最初の3ページだけ見れば十分です。

本来のPDFのあとに、ダミーPDF画像をOCRに入れているのは、フォルダーウォッチャーというアプリがコマンドラインに対応しておらず「OCR認識が終了した」というフラグを返さないためです。
そこで、このアプリがファイルをひとつずつしか処理しないという特性を利用して、ひとつずつ順番に計2つのファイルを入れて、2個目の処理が進んでいたら1個目の処理は完了しているに違いない。という判定をしているためです(処理進行中か否かは状況を外部から判定する方法あり)
ここは、家庭用OCRを無理矢理使い倒しているための処理で、素直に法人用の高いSDKを買えば、このようなことは不要です。

PDFファイルの処理には、cpdfというWindowsで動くコマンドライン型のアプリを使いました。
cpdf.exe -rotateby 270 入力ファイル名.pdf -o 出力ファイル名.pdf のようなコマンドをCreateObject(“WScript.Shell”) → .runで動かします。最初は.execというコマンドでやっていたのですが、これだと途中で原因不明の停止をしたりするので、.runでbatファイルを呼び出すようにしたら安定しました。
このような原因不明の不安定を安定させるのに時間がかかりました。VBAに慣れている人であれば、ここは時間がかからないところなのだと思いますが。

本文OCR認識と単語辞書マッチ

回転すべき角度が確定したら、OCRが稼働しているVMは12台ありますので、PDFの1-12ページを1枚ずつ切り取って、それぞれOCRに並列に投入します(つまり13ページ目以降が存在していても無視していますが、これでも十分です)

TF-IDFとベクトル類似度による書類の分類

認識結果が戻ってきたら、TFとIDFという自然言語処理では最もよく使われているであろう重み付け処理をして、配列をベクトル化してベクトルの類似度を比べていきます。

TFとは、その単語が出てくる回数。IDFとは、単語のレア度(当該文書にはたくさん出てくるが他の文書には出てこない)
これをかけ算すると、レア単語(その文書でしか出てこない特徴語)がたくさん出てくると加点されるようになります。

あらかじめ正しく分類された教師データ群の単語を学習して、先にこのようなクラスタを定義しておきます。クラスタの作り方は、そのクラスタに属している複数の文章をひとまとめにして、大きな長い文章と想定して、その配列をベクトル化して、
それと、新規に入力される文書のベクトルのコサイン類似度を見ています。
とても長い文章と、短い文章の比較。というのはベクトルの正規化をするので問題にはなりません(おそらく?)

a.入力文書の配列×比較対象の配列を掛けて文書間のクロスで内積の和を計算
b.入力文書の内積の和(配列の各値の2乗)
c.比較対象の内積の和(同上)
と計算していき、コサイン類似度=a/(sqrt(b)*sqrt(c))で類似度を判定できますので、最も類似度が高いクラスタに分類すればOKです。

TF-IDFとコサイン類似度については、ここが分かりやすいです。
http://qiita.com/nmbakfm/items/6bb91b89571dd68fcea6
なぜ、配列を2乗してsqrtを取るのかが分からない人は、最小二乗法という統計手法を調べると、なんとなく分かると思います。私も詳しいことは分かりませんが。

全文検索の部分一致によるメタタグの付与

グループ化して仕分けするだけだと中途半端です。「銀行取引関連の書類」というだけでなく、「銀行取引_XX銀行_借入明細\ファイル名_PDFページ枚数.pdf」というような形式で整理したいので、「XX銀行」という単語が出てきたら、それをファイル名にタグとして入れる。というようなテーブルを作り、処理していきます。今回は、100段くらいテーブルを作りました。

それならクラスタ分けなどせず、最初から辞書マッチで「XX銀行」が出てきたら「銀行取引関連の書類」に仕分けすれば、いいじゃないか!と考えがちですが、それでは正しく分類できません。
というのも、不動産関連の書類で「XX銀行が抵当権を設定している」とか公共料金で「XX銀行にお振り込みください」などとあるときに反応してしまうためです。
先に、この書類は「銀行取引関連の書類」である。その中に「XX銀行」と出てきているので、「XX銀行」との取引で間違いないであろう。という2段階の推定をしていくと高精度です。

ファイル名やフォルダ名に付けるタグの計算方法

複数の条件に基づきタグが付けられるようにしており、「XX銀行」と「池袋支店」の両方が出現して、かつ、当社グループの法人名が出てきていない。という3条件を満たしていたら・・というような複雑な指定もできるようにしておきました。
これは、条件1、条件2、条件3と、それぞれ別で成立を判定して(条件未入力は真)、最後に全体をAND条件で見て、全条件が整っていればフィルタで定義したタグを付与。とすればできます。

ここで、三菱東京UFJ銀行と地元信用金庫の2つが出てきてしまったら、どちらをタグ付けするべきか。という問題に直面します。
実際、地元信金の振込明細などの書類には「三菱東京UFJ銀行池袋支店なんたら不動産(株)あての振込」であるとか「三菱東京UFJ銀行の短プラに連動する」などの言葉が出てきて銀行名タグが競合してしまいます。
このようなケースにうまく対応するため「三菱東京UFJ銀行」のように、いろいろなところによく出てくる言葉は加点のウエイトを下げ、地元信金の名称や、地元信金の電話番号などが検出された場合は高得点とするなど、手作業でウエイトの調整が必要です。

意外に大変だった点

じつは、分類ロジックよりも、慣れないExcel VBAの使い方やファイルの入出力で時間をとられました。
100万件を超えるようなビッグデータをExcelで取り扱うときには、いろいろなコツが必要です。
このあたりが最初から分かっていれば、本アプリを業務で実際に使えるレベルに仕上げるまで1週間くらいでできたと思います。

速い処理と遅い処理を熟知する

Excelででかいデータを扱うときは、ソフトウェアの作り方ではなく、ASICのような作り方をすると速いです。
たとえば、8つに分岐する処理があれば、ループを8回するよりも、シートを8枚作って、そこにあらかじめ数式を敷き詰めて、処理を待ち受けると速いです。繰り返したり分岐したりせず、一方通行の流れを作ると速いです。Excelは巨大なピタゴラスイッチだと思って作るとよいでしょう。

処理結果によって、出てくる件数が可変。というような場合でも対応できます。
オートフィル→最後の有効列まで など、毎回の可変処理対応をすると(セルへの書き込みが発生して)遅くなるのでこれはしないほうがよいです。
むしろ、出てくる値の最大限に合わせて、あらかじめ数式を敷き詰めておくと無駄が多いように見えますがじつは速いです(数式がセルに入っている状態で再計算が発生して値が変化する時は速い
数式を並べた数>出てきた結果数 となり、数式部分が余った場合はオートフィルタなどで切り捨てるか、最後の最後にfor nextループで結果の値だけ回収するようにしましょう。

つまり、効率の良さそうなコードを書くよりも、無駄にCPUが酷使される仕事のほうが結局は速いのです。これは、どうやら、シングルスレッドで動くVBAのfor nextループやセル操作は、セルへの書き込みもシングルスレッドでしているようで、演算よりもセルへの書き込みに時間を取られている様子です。
一方、マルチスレッド対応の関数は、計算結果をマルチスレッドでセル(メモリ)に書き込むようで、今回のように32コアあれば、おそらく32倍速くなります。

Excelは最大1024スレッド対応らしいので、うまくやれば、とんでもない規模のビッグデータ処理もできるはずです。なお、GPGPUアドインのようなものは存在しないようですので、論理256コアの構成でも8Way-Xeonで1千万円くらいかかります。あり得ない富裕層構成。と思いきや、金融データの分析で有用な結果が出せるならば、文字通り秒速で回収できますので、1千万円のPCでExcelを動かしても採算が取れるという夢があります。

使ってはいけないコマンドのいくつか

大きなデータのコピー&ペーストを繰り返すVBAを書くと、メモリリークして回復不能となるので避けましょう。
今回のアプリも最終的には200万セル以上を使っているにもかかわらず、500MBほどのメモリ使用量で済みましたが、初期にコピペを繰り返すVBAで書いていたときは15GBものメモリを消費していました。
メモリを浪費するだけなら、メモリを追加すればいいのですが、処理も、動いているのか止まっているのか分からないくらい遅くなります。
100セル以内くらいなら繰り返しコピペしても問題はなさそうです。

セルの読み書きはシングルスレッドになるので激遅です。列や行の挿入もだめです。どうしてもセルの読み書きをしたい場合は、
コピペではなくて、VBAにてrange(“xxx”).value= range(“yyy”).valueとすれば、多少は良い気がします。とにかくVBAでコピペは使わないのが無難です。

高速化で参考になったページ
http://www.ozgrid.com/VBA/SpeedingUpVBACode.htm

データを取ってきたいときは、関数で取得する

たとえば、OCR認識した3,000バイトくらいの文字列のなかにXXという単語が何回出てきたか。をカウントするときは、= IF( (LEN($E$1 ) – LEN(SUBSTITUTE($E$1,A2, “”)))/LEN(A2)=0,””, (LEN($E$1) – LEN(SUBSTITUTE($E$1, A2, “”)))/LEN(A2) ) という関数を使いましたが、これは100万件並べても速いです。

オートフィルタ(フィルタオプションAdvancedFilter)も速いです。
オートフィルタでSQLと全く同じ処理ができるので、100万件以内のバッチ処理ならばSQLやアクセスを使う必要はないでしょう。
遅いExcelの代名詞であるVLOOKUPなどもセル操作よりは高速であり並列処理できるので、マルチコア環境なら使って良いです。

再計算もしていないのに、もたもた遅くなる場合

このような時はシートを細かく分割していくと解決することがあります。シートごとにメモリの管理が別っぽいため。
Excelが遅い原因は99%がセルの読み書きをシングルスレッドで、もたもた処理していることにあると思います。→このあたりの内部処理は10年くらい前とロジックが変わっていない様子?

意外にはまるファイル関連の処理

ファイルが存在しない。存在するけどゼロバイトのファイル。PDFファイルのフォーマットがおかしい(一部壊れている)。他で開いているファイルを消そうとしている。フォルダやファイル名に空白や変な文字が使われている。キューに入れたときに存在したファイルが処理開始時になくなっていた。ファイル作成処理のあとに若干待たないと、ファイルの作成が終わる前に開こうとしてしまう(特に外部との連携の際にはタイミングの管理が大事です)
「なぜ、ここで止まったのか、まったく分からない」というのが多かったです。
ファイル処理は、最悪、入力したファイルがなくなる。という、まずい事態を引き起こすので、神経質な作りにする必要があります。このあたりは「Pythonで作ってみた」的な実験コードとはことなり、コアロジックよりも周辺整備の方が時間がかかる理由です。私があまり慣れていないというのもありますが。