Retty Tech Blog

実名口コミグルメサービスRettyのエンジニアによるTech Blogです。プロダクト開発にまつわるナレッジをアウトプットして、世の中がHappyになっていくようなコンテンツを発信します。

インターンとしてOCR処理の実装および精度上昇に尽力した話

タイトルの通り、8/10-27の3週間Rettyの広告コンテンツチームでインターンをさせていただきました。かなりたくさんのことを行い、考えてきたので思考プロセスって言ったら大げさなんですけど笑備忘録を書かせていただきます。

自己紹介

東京工業大学大学院情報理工学院 数理・計算科学系1年生の森脇と申します。大学院の方では分散学習の研究を行っております。Rettyでインターンをする前から行っていたロングインターンや授業でデータサイエンティストとしての様々なことを学んでおり、新卒でもデータサイエンティストとして就職したいと考えております。Rettyは、逆求人イベントで人事の方とお話させていただき興味を持ちました。その方からインターンを紹介していただき、応募したところ、インターンをさせていただく運びとなりました。

趣味の方で色々とプログラミングでツールやらなにやらを作ることが好きで、そのおかげか人よりも量をこなすことは得意だと思っています。実際、今回行っていたことで趣味が生きた箇所がありました。

インターンのタスク

今回行わせて頂いたタスクはメニュー画像のOCR(光学的文字認識)です。インターンを始める前に様々な課題を用意していただいていたのですが、中でもOCRに関しては今まで行っていなかった機械学習分野であるということがありました。折角インターンをさせていただくので、自分としても何かを吸収して帰りたいと思っていたので非常に丁度良いテーマだなと考えました。

タスクの目的

f:id:rettydev:20210827092115p:plain
FDPが有するデータ量、まさにBIG DATA

RettyにはFood Data Platform(FDP)という膨大なデータベースがあります。これはRettyが保有している飲食店の基本情報や口コミ、画像などのデータであり、民間企業は勿論、内閣府もこのデータを行政に役立てております。すごいと思いました。

FDPの中には「お店会員」(Rettyに登録している店)の店舗のメニューはありますが、登録していない「非お店会員」の店舗メニューはありません。そこで、非お店会員の店舗利用者がRettyにアップロードしてくださったメニュー画像をOCRすることができれば、非お店会員の店舗のメニューを準備することができます。これにより、Retty全社としてデータの拡充を行うことができます。

これとは別に、FDPの利用ユーザー側からRettyが有する縦書きのメニュー画像が欲しいという要望がありました。さらには、

  • 5°以上傾いた画像は遺棄
  • 90°単位で傾いている(=横になったり逆さになったりしている)画像に関しては補正を行う

といった条件も付随しています。後述しますが、OCRを行う際には縦横認識をあらかじめ行っていた方が精度面からみても都合が良いため、事前にメニュー画像の縦横判別用の画像を用いた機械学習を行いました。

具体的な内容

手法の選定

今回OCRをするにあたり、Google Cloud Vision APIを用いることとしました。これは、Google Cloud Platform内にある、画像認識用ツールであり、今回行うOCR以外にも顔検出、ラベル検出など9種類の検出を行うことができます。たまたま趣味の方で存在を知ってはいたのですが、使ったことがなく正直精度の信頼度に関してはインターン開始前は半信半疑って感じでした。

しかし、試しに使ってみようと思い、インターン初日に適当な画像を用いて試してみたところ想像以上に精度が良く、これは使うしかないなという結論になりました。具体的には

f:id:rettydev:20210827093903p:plain

この画像をコントラスト等加工してOCRした結果が

うどん定食
600円
(おにぎり又はいなり、一品付き)
だしまき定食
(780円
(ごはん、一品、ミニうどん付き)
ラーメン定食
800円
カレーラーメン定食
900円
カレーうどん定食
900円
ちゃんぽん定食
600円
900円
(各ごはん、から揚げ2個付き)
から揚げ定食
820円
チキンカツ定食
820円
トンカツ定食
870円
ミックス定食(エビ2尾とから揚げ3ヶ)900円
カツ煮定食
900円
(各ごはん、みそ汁、一品付き)
※ミニうどん付150円増し

これでした。一字一句間違いがなくめちゃくちゃ驚いた記憶があります。

Vision APIの使用について

どちらのタイプの OCR リクエストも 1 つ以上の languageHints をサポートしています。これにより、画像内のテキストの言語を指定します。しかし、ほとんどの場合、値を空にして自動言語検出を有効にしておくことによって最善の結果が得られます。ラテン アルファベット系の言語の場合、languageHints の設定は不要です。まれに画像中のテキストの言語がわかっている場合などに、ヒントを設定すると結果が少し良くなります(逆に、ヒントを間違えると大きな障害となります)。サポートされる言語以外の言語が 1 つでも指定されていると、テキスト検出でエラーが返されます。

https://cloud.google.com/vision/docs/ocr?hl=ja&apix_params=%7B%22resource%22%3A%7B%7D%7D#specify_the_language_optional

とある通り、language_hintsというパラメータを画像と共に付与することができます。例えば、日本語の横書きだったらja、縦書きだったらja_vertといったような感じです。ここで重要なのは、縦書き、横書きでlanguage_hintsを区別する必要があるということです。そのため、OCRを行う前にメニュー画像が縦書き、横書きどちらで書かれているかを判別する必要がありました。なので前述の通り、判別のための機械学習を行っていきます。

機械学習モデルの構築

今回はAmazon Sagemakerを用いて機械学習を行いました。

f:id:rettydev:20210827094951p:plain
Amazon Sagemakerのトップ画面 センスがすごい

これは、AWSで提供されている機械学習プラットフォームであり、機械学習を行う際に必要な煩雑な処理のほとんどを担ってくれます。アウトプットの可視化などの専用ツールがあって実際今回初めて使ってみて非常に便利だったなと思います。

これを読んでいる方で機械学習を行ったことがない方もいるかもしれないので具体的なフローを記述していきます。

学習用画像のアノテーション

機械学習という名前を聞かれたことは多いと思います。イメージとしては、データを投げ入れれば機械がすぐに答えをポンと出してくれるってのを想像されると思うのですが、それを行うためには機械に様々な例を与えなければなりません。そこで必要となってくる処理がアノテーションと呼ばれるものです。これは、一つ一つのデータに人力でラベルを付与していく作業です。機械学習という華美なもの底にはこのような暗い作業があるということをもっとインフルエンサー気取ってる機械学習エンジニアの人は言っていかなきゃいけないだろって常々思ってます。

もっと具体的な実情を知りたい人は

ポルノや斬首画像をタグ付けし続け、月給2万円…「AI」機械学習の闇 | アマゾンやマイクロソフトから受注するインド企業に潜入取材 | クーリエ・ジャポン

を読むと大企業における内容が分かりやすいかも知れません。機械学習における一つのジレンマですね。

さて、この作業を同時期にインターンを行っていた方にも少々手伝っていただいて3000枚行いました。今回は簡単なモデル作成をおこなうということで「縦書きメニュー」「横書きメニュー」「その他(メニュー以外だったり、今回は使用しないような画像)」の3つに分けましたが、これだけでも大分疲れますし集中力を持ってかれます。

なるべくストレスがなくなるようにJupyter Notebookという対話型コンソール上にアノテーション用の即席疑似GUIを作成し、その上で行いました。

f:id:rettydev:20210827100346p:plain
プレビュー

ここのフォームの中にカッコ内のキーを入力すると3値に振り分けられるという感じです。これのおかげでスピーディーに行うことができました。

機械学習の前準備

機械学習では、モデルを作成するためのデータと、作成したモデルをテストするためのデータが必要となってきます。そこで、アノテーションを行った画像を学習用とテスト用に分割しました。また、我々人間にとっては画像は画像ですが、機械にとっては1ピクセルごとに数値が格納されているデータとなります。機械学習で使用するモデルは、統一した分量の数値を入力し、今回だったら最終的に縦書きである可能性は~%、横書きである可能性は~%、その他である可能性は~%といった感じに出力を行い一番可能性が高かったものを結果として採用する、といった形になるため画像のデータ量を統一する必要があります。人間の言葉でいうと、画像のサイズを統一する必要がありました。今回は縦横500pixelにしました。

その画像群から.lstファイルなるものを生成しました。これは、Sagemakerで学習をする際に必要となるファイルで、中を見ると画像のファイル名とラべル情報が紐づけてありました。画像フォルダと.lstファイルをs3というSagemaker用のデータ置き場にデータをアップロードしたら準備完了です。

今回は一からモデルを作成するのではなく、既存の画像認識モデルであるResNet50を本学習用に最適化処理を行いました。

ResNetとは?

Deep Residual Learning for Image Recognition...必読です

2012年にILSVRC2012というコンピュータによる物体認識の精度を競う国際的なコンテストが開催されました。この中で、トロント大学のSuperVisionチームがディープラーニングを用いて物体の分類、および局所化と分類の2タスクで他チームに圧倒的な差をつけて優勝しました。また、同じ2012年にGoogleDeep Learningを用いて構成されたAIにYouTubeのキャプチャされた画像を用いて今でいう教師なし学習を行い学習したモデルを用いて猫を分類できるようになりました。これは「Googleの猫」と言われています。

この2つの出来事の後、急速にディープラーニングについての研究が活発になりました。その流れの中でResNetは2015年に誕生しましたが、「人間が画像を分類する能力を機械学習によって上回った」初めてのシステムとして知られています。具体的には頭の良い人間が画像を長時間学習し、100クラスに分類するタスクを行った時のエラー率が5.1%でしたが、ResNetは3.5%でした。

・特徴

ResNetには、その名の通りショートカット機構が導入されています。 f:id:rettydev:20210827105515p:plain

具体的には、ある層から層への出力F(x)を次の層に渡すのではなく、入力xをカットし、F(x)+xを次の層へと行っています。これが行われると、勾配が直接奥の層まで伝わるため、深いネットワークにおいても精度を維持した学習が行えます。

・従来の問題点およびその克服

ニューラルネットワークは下の図のように入力層、中間層(隠れ層)、出力層の3パーツによって構成されます。

f:id:rettydev:20210827123327p:plain
https://www.tel.co.jp/museum/magazine/communication/160229_report01_02/03.htmlより

入力層はデータを入力するための層、出力層はデータを出力するための層なのでそれぞれ1層で固定ですが、中間層に関しては理論的には何層でも配置することができます。ただ、計算量やモデルの容量が大きくなる等の制約はあります。

ということで、「なら可能なだけ層を増やした方がより正確になって得なんじゃない?」って思いがちなのですが、そこでネックになってくるのが劣化問題および勾配消失問題です。

勾配消失問題については、ResNetの克服点ではないため、省略します。

劣化問題(degradation problem)は層が深いモデルの学習において、誤差の改善が層が浅いモデルよりも早く上昇が止まるという現象です。止まるだけならいいんですけど、そのあとも学習をしていくと性能が下がってしまいます。過学習という性質とは別問題です。

これを克服するために先ほどの機構residual learning frameworkが導入されました。ResNetの中に先ほどの機構が入っており、ある層に入る前の入力をショートカットしてきたもの(要はもう十分に性能がいいならショートカット(=恒等変換)してもまあ精度は下がることはなさそうって考えてそう)と、層に入力されたものの残差(Residual)を学習することによって重みの更新が行われるようになってきます。

これを組み合わせることによって劣化問題を発生しにくくして、結果的に良い精度が取れていると。面白いなあと思いました。

話を戻します。

学習スタート!

パラメータの情報とデータの場所、使用インスタンスの情報等をSagemakerに送ったら後は勝手に学習を行ってくれます。我々はその間にお茶を飲んだり学習し終えたら何しようかなあと考えていれば十分です。

f:id:rettydev:20210827152138p:plain

こんな感じでログを見ることができます。トレーニングの精度がすごいことになってる割にはバリデーションの方が低くて少し不安でした。

推論

スライドからの丸コピーです。

f:id:rettydev:20210827153235p:plain
3つの画像でテストしてみたよ

スライドには3つだけ貼ったのですが、学習に使用していない画像100枚を作製したモデルに投入して結果を見てみました。100個のうちのほぼすべてにおいて正しく分類されていたんですけどこれめちゃくちゃ驚きました。メニュー画像って文字+メニューの構成になってるものが多いのに、ちゃんと分類されるんだ。しかもメニューじゃないものだとか縦横のものだとかその類が全部その他の方に入っていたので一発でこんなにうまくいくのか...って感じでした。これはパラメータの設定がうまかったのか、それともSagemakerが優秀だったのかはわかりませんが、兎角すごいなあって思いました。

Endpoint 作成

最後にエンドポイントを作ります。

f:id:rettydev:20210827153606p:plain

これもSagemakerのすごいところだと思うのですが、学習したモデルをSagemaker内に保管して、APIとして利用することができます。従量課金ではありますが、これによって自分たちでエンドポイントを作成して利用するといったコストがかからないのでエンジニア側としては非常に楽です。ひとまずこれによってあとは画像をエンドポイントにPOSTしたら縦横の判別をしてくれるのでまず1つ目のタスクが終わりました。

f:id:rettydev:20210827153906p:plain

FDPにある画像データの中から3万枚をダウンロードし、このモデルに入力し、判別を行ったところ、縦書き:横書き:その他が1:4:6弱といった感じでした。

OCRを行う

ここまでで判別した縦書き、横書きの画像をそれぞれのlanguage_hintsをつけてVision APIOCRをしました。結果としては、よく判別できている画像とあまり精度が良くない画像が半々程度でした。

精度の良くない結果を精査したところ

  • 白飛び、反射
  • 回転
  • 解像度の悪さ
  • 画像の文字のサイズが小さい
  • 手書き

等の理由が見つかりました。このうち、太字で書かれた項目については、クライアントからの要望とマッチしておりました。つまり、「OCRの精度が悪いメニュー画像を弾けば、自ずとクライアントの要望にそぐわないメニュー画像を弾けるのではないか」と考えました。

そこで、形態素解析を用いてメニュー画像及びテキストデータの篩い分けをしていきます。

形態素解析とは

一言でいうと、自然言語で書かれた文を意味を持つ最小単位(=形態素)に分割し、それぞれの形態素ごとに意味を解析する処理のことです。日本語だと小学生の頃によくやらされた品詞分解ですね。

Pythonで有名なライブラリとしてMeCabがあります。他にもSudachi, Janome, GinZa等Pythonのライブラリがありますが、今回はMeCab + NEologdを用いた形態素解析を行い、未知語が一定以上含まれていたら弾くといった処理を行いました。

MeCab + NEologdを用いた理由は2つあります。

  1. 早い

様々な形態素解析ライブラリの中でMeCabが一番実行時間が早いというものがありました。今回3万というあまり多くはないデータ数でしたが、今後FDPの全画像に対して適応することを考えたときに、高速で終わらせることが可能ならばそれに越したことはないと思いました。

  1. 最新の単語にまで対応している

NEologdはmecabで使用できる辞書ですが、

mecab-ipadic-NEologd は形態素解析エンジン MeCab と共に使う単語分かち書き辞書で、週2回以上更新更新され、新語・固有表現に強く、語彙数が多く、しかもオープンソース・ソフトウェアである という特徴があります。

engineering.linecorp.com

とあるように、かなり最新の単語まで反映されている強力な辞書であり、これだったらほぼほぼ日本語網羅できるだろって思ったので採用しました。

が、

このブログを書きながらTwitterでNEologdについてサーチしたところ、どうやら去年の9月で更新が止まっているらしいです。おいおい...

形態素解析を行った

何はともあれ、出力した全メニューテキストデータについて、形態素解析を行い、「各行で75%以上が辞書登録されているメニュー」の文字数が75%以上であるデータのみを採用するといったダブルフィルターを設定し、フィルタリングを行いました。実行時間は14000ファイルで20秒程度とかなり高速でした。

結果としては、4700ファイルほどに絞れ、それらの中からランダムに100個サンプリングをして精度検証を行ったところ、大幅に精度が向上しました。特に横書きデータに関しては7割ほどがほぼほぼ完ぺきに出力されていたので、個人的には大満足な結果となりました。

今後の方向性

今回使用しなかったデータへの範囲拡大

今回は時間の都合上「縦書き横書き混在した画像」「90°単位で回転している画像」に関してはその他に入れ、使用しませんでしたが、縦横判別モデルを拡張させることによりできるようになると考えました。特に後者に関してはクライアントの要望にあるものなので早急に対応していただきたいと思います。

OCRのパラメータについて

今回は全データ共通で日本語、英語のみをlanguage_hintsとして送りましたが、例えば中華料理屋だったら中国の簡体語が含まれていますしそれ以外の言語が含まれているかもしれません(あるいは英語は全くメニューに記載されていなかったり)。そういうものを一つ一つこちらで見てOCRを行うということは非常に困難です。そこで、お店会員の店舗にまずメニュー画像を蓄積させ、お店側で言語、縦書き横書きを指定するだけでOCRを行いRettyのデータサーバーに画像、メニューデータを送付するようなシステムを作成できれば、より最適化でき精度も向上するのではないかと考えました。これにより店側も、テキストベースで登録していた従来からの効率化も図れるので一石二鳥ではないかと考えました。

インターン中の業務以外に関して

僕が配属された広告コンテンツチームのメンバーの方々みんな優しいし面白いしキャラがめちゃくちゃ濃いです。週に1回プライベートも含めた良かったこと、改善したいこと、その他ToDo事項を共有するのですが、プライベートに入った瞬間からの男子校の部活感がたまらなく好きでした。また、それ以外にも毎日昼にミーティングがあり、そこで各人の進捗報告を行うのですが、皆さん1日1日でしっかり進捗を生み出していたので非常にレベルが高い場に参加しているんだなとこちらも身が引き締まる思いでした。 ご飯にも連れてっていただいていろいろなことを話させていただいたり聞かせていていただいてとても楽しかったです。焼肉めちゃくちゃおいしかった。

感想

今回12日間という短い期間でしたが、僕が普段趣味で開発するペースより何倍も速くいろいろと作ることができ、自分としても成長に驚きました。また、インターンでやりたかった「企業でしかできないツール等を用いる」といった目標も達成でき、非常に満足しています。特にVision APIについては、使用したいと申し出てから許可が下りるまでの時間が非常に短く、Rettyの柔軟性を感じました。12日間毎日が非常に楽しく、有意義な日々を送ることができたと思います。

Rettyの所有するFDPについても膨大な量に触れ、それを使用して分析ができたということで非常に良い経験になりました。一般的に学生が使用するデータは「綺麗」であることが多く、あまり下処理に時間を使わないことが多いのですが、実務で使用するデータということが今回普段では行わないような下処理、選別等を複数行いました。それらの経験ができるのもインターンならではだと思います。

Rettyでインターンをしたい人へ

これを読んでる中でRettyへのインターンを考えている人がいたら、是非エントリーしてほしいです!メンターとして1人の社員の方がついてくださり、わからない箇所に関して的確にアドバイスを下さる(しかもレスポンスがめちゃくちゃ早い)のでかなり不安なくやりたいことを行えます。また、歴代インターン生に伝わるおいしいごはん屋に連れてってもらえます。めちゃくちゃおいしいです。

最後に

メンターをしてくださった本川さんを始めとする広告コンテンツ開発チームの皆様、3週間ありがとうございました。