機械翻訳と訳抜けとConstituency parsing

翻訳エンジンのお試しサイト(http://plant-check.jp:8080/)を更新した。主に下記の機能を追加している。

  • 最大3000文字までの長文対応
  • 訳抜け防止モードの高度化
  • 翻訳結果に対するスコア表示

長文対応は文字数制限を外してnltkのsent_tokenize[1]を使用しているだけである。翻訳結果に対するスコア表示、訳抜け防止モードは以下のように多少工夫した。

訳抜け防止モード

Deep Learningな機械翻訳では訳抜けという現象が発生する。これは訳すべき英文を省略してしまうという現象である。結果、流暢であるが情報が欠けた文章が出力される。Google翻訳やDeepL翻訳などメジャーな翻訳エンジンでも起きることがあり(当然ながら)個人開発の翻訳エンジンではよく発生する。

例えば、下記の英語文を翻訳する例を示す。

Natural language processing (NLP) is a subfield of linguistics, computer science, and artificial intelligence concerned with the interactions between computers and human language, in particular how to program computers to process and analyze large amounts of natural language data.

https://en.wikipedia.org/wiki/Natural_language_processing 11 October 2020, at 18:45 (UTC) の版、Wikipediaより引用

現在の私の翻訳エンジンは上記文章を「 自然言語処理(nlp)は、コンピュータと人間の言語間のインタラクションに関する言語学、コンピュータ科学、人工知能のサブフィールドである。 」と翻訳し、「in particular」以後の情報が抜けている[2]。

訳抜けには様々な理由が考えられるが長い文だと発生しやすい。そこで訳抜け防止モードではconstituency parsing[3]を行ったうえで意味が成立しそうなブロックに分割し翻訳エンジンを適用するフローを採用している。ブロック分割した結果はお試しサイトの一番下に表示される。本件では翻訳対象の文が

Natural language processing ( NLP ) is a subfield of linguistics, computer science, and artificial intelligence concerned with the interactions between computers and human language,

https://en.wikipedia.org/wiki/Natural_language_processing 11 October 2020, at 18:45 (UTC) の版、Wikipediaより引用

in particular how to program computers to process and analyze large amounts of natural language data.

https://en.wikipedia.org/wiki/Natural_language_processing 11 October 2020, at 18:45 (UTC) の版、Wikipediaより引用

に分割された。結果、訳抜け防止モードでは上記の英文を「自然言語処理(nlp)は、コンピュータと人間の言語間の相互作用に関する言語学、コンピュータ科学、人工知能のサブフィールドである。 特に、コンピュータが大量の自然言語データを処理および分析するためのプログラム方法。」と翻訳した。意味としては良くなっている一方で流暢さは損なわれている。 実装した訳抜け防止モードは文を分割して翻訳しているだけであり、現状の機械翻訳エンジンは文脈の考慮もできていない。訳抜け防止モードの翻訳品質は通常モードに比べて低くなる。

翻訳エンジンのお試しサイトでは通常の翻訳×2[4]と訳抜け防止モード×2の結果を文毎に比較し、最も良い結果(スコア算出方法は後述)を採用している。

スコア表示

お試しサイトでは英語文と対応する翻訳文それぞれについてスコアが付与されている。スコアは翻訳文が良いかどうかを表す指標であり、0.0 – 1.0で評価される。概ね0.7以上であればそれなりの訳文になっていることが多く、0.5以下の場合は何かしらの問題が起きていることが多い。特に0.3以下の場合はほぼ確実に訳抜けが発生している。

スコアは「①文の類似度」×「②単語/形態素数の類似度」で計算している。「①文の類似度」はUniversal Sentence Encoder[5] + cos類似度である。LaBSE[6]も試行したがこのタスクではメモリ・計算時間の増加[7]に比べて効果が薄かった。「② 単語/形態素数の類似度 」は英文の単語数と日本語文の形態素数の比率が対訳データの平均(0.85)に近いかを計算している。形態素解析はMeCabを用いた。

所感・その他

お試しサイトの処理フローは以下の通りで機械翻訳エンジンを使う際の対応は大体実施できた気がしている。

  1. 改行が連続した場合は別の文とみなし、処理ブロックを分ける。(途中改行が1つの場合、文は連続しているとみなす。arxivの論文やPPT資料でありがちな改行の入り方に対応している。)
  2. 処理ブロック内の文章をNLTKのsent_tokenizeで文に分割する。
  3. 文に分割されたデータそれぞれに対してconstituency parsingを行い、意味が成立すると思われる一定の長さで文を分割する。
  4. 上記、2.、3.で作成した文のリストを機械翻訳エンジンで和訳する。和訳はハイパーパラメータを変えた2つのエンジンで行う。
  5. 翻訳対象の英語文それぞれについて4つの和訳結果(3.の有無×4.の2つの結果)のスコア(USEのcos類似度×単語/形態素数の平均比)を計算し、一番良いものを採用する。

それなりに複雑な処理になっているがOSSのソフトフェア・モデルをフル活用しているためコードの記述量はそこまで多くない。上記処理もそのうちgithubとかで公開しようと思っている。

(今のところ情熱が残っているので)今後は翻訳エンジン自体の強化を行っていく予定である。

現時点で前回使ったデータに加えて約200万対訳ペアの作成が完了している。加えて50万対訳ペア程度は追加できそうなのでデータ量は1.5倍程度にはなる見込みである。ぼちぼち小文字統一をしなくても良さそうなデータ量になっていることもあり、条件を変えながら深層学習モデルを作って比較するような事もやっていきたい[8]。

文脈が計算可能なデータ(対訳ペアの元となったドキュメント情報が残っているデータ)もそれなりにあるので、文脈パラメータを入れた機械翻訳エンジンの作りたいなーとも思っている。

構築したモデルはCC BY SAくらいのライセンスで公開する予定で自然言語処理分野の英語データセットを和訳する利用方法を想定している。アノテーション構造を保持したい場合の支援機能[9]組み入れも予定しつつ、時間があまりないなーと思っている今日この頃。

脚注

[1] https://www.nltk.org/api/nltk.tokenize.html?highlight=sent_tokenize#nltk.tokenize.sent_tokenize
[2] メジャーな翻訳エンジンは正しく処理する。流石である。
[3] 今回はAllen NLPのhttps://demo.allennlp.org/constituency-parsingを用いた。
[4] 翻訳はハイパーパラメータを変えて2回実行している。複数候補を出して選ぶというのもよく見られる構成だが、本件では行っていない。
[5] https://tfhub.dev/google/universal-sentence-encoder/4 対訳データ作成でもお世話になったモデルである。
[6] https://tfhub.dev/google/LaBSE/1 BERT系のモデルであり、多言語対応のText Embedding用途では最新・最高性能に近いと思われる。
[7] 類似度の妥当性ではUSEに比べてLaBSEがやや良いが、計算時間が数十倍(50倍以上)でありメモリ使用量も増加する。お試しサイトで使っているVPSで動かすのは厳しかった。
[8] AWS課金が凄いことになりそう。。。本当はBack Translationもやりたい・・・。
[9] 英語文→日本語文でタグ構造を維持する程度の機能は入れたい。tokenizer(sentence piece)構築時点でタグを特殊記号扱いし、対訳ペアに正しくタグを扱っている文を追加して学習させる予定である。このあたりは翻訳エンジンそのものに手を入れないと実現しにくく、メジャーな翻訳エンジンで同様の事をやるのは簡単ではないと思っている。

翻訳エンジンの構築(Marian-NMT)

夏休みの宿題(?)としてMarian-NMTを使って翻訳エンジンを構築してみた。構築した翻訳エンジンは「英語→日本語翻訳(http://plant-check.jp:8080/)」から試すことができる。訳すべき単語を飛ばすなど深層学習な機械翻訳特有のミスをすることも多いが、個人が試作したものとしては相応の性能な気がする。割と訳せていて正直驚いた。ただ、入力文の適切な分割など前処理的な事をほとんどやっていないので変な結果になることも多い。

データセットは「WEB クロール + Universal Sentence Encoderで収集したデータセット」+「Free(Creative Commonsライセンスなど)のデータセット」、使用した手法はTransformer + sentence pieceであり、バリバリのDeep Learningで現時点でも本格的である。ただし、環境制約(というか時間制約&予算制約(後述)からBack Translationは使えていない。)

上記エンジンでは300文字以内の英語文(複数文の場合性能は落ちる) [1] を日本語に翻訳することができる。訳抜けを防止するモードもあるが、カンマや記号で文を分けているだけなので訳抜け防止モードの性能はあまりよろしくない。

翻訳エンジンを作った理由

本当は日本語でGPT-2辺りのfine tuningを試そうと思っていて「Faster than training from scratch — Fine-tuning the English GPT-2 in any language with Hugging Face and fastai v2 (practical case with Portuguese)」という素晴らしい記事を読んでいた。その中に「For example, to obtain a Portuguese GPT-2, we could download from the Transformers library of Hugging Face the OpenAI GPT-2 pre-trained in English and the MarianMT translator」という記載があったものの、残念ながら「日本語→英語」のモデルは公開されているが「英語→日本語」 のモデルは公開されていなかった[2]。

自由に使える翻訳エンジンは役に立ちそう[3]なので自分で構築することにした。車輪の再発明ではあるが、色々と良い経験になったと思う。

翻訳エンジンの作り方

翻訳エンジンを作るにはデータセット、学習用ソフトウェア、学習環境が必要である。今回は下記を用いた。

  1. データセット: WEBをクローリングして収集[4]+Freeで公開されているものを追加 [5]
  2. 学習用のソフトウェア: Marian-NMT (transformer) + sentencepiece [6]
  3. 学習環境: AWS p3.2xlarge インスタンス [7]
Read more »

脳からの知識蒸留(Distilling the Knowledge from a Brain) – 結果 –

前回からの続き。脳からの知識蒸留を目指し実験を行った。目的は効率的なハンドラベリングであり、今回のPoCでは生体情報をDeep Learningの蒸留と同じ方法、ソフトターゲットの設定で活用できるか?を検証した。

解いた問題と前提

データセット

脳からの知識蒸留を目指すため、前回作成したツールを用いて約330枚のバラの写真に対するハンドラベリングを行った。ハンドラベリング時に取得したデータは次の通り。

  • 病気の区分(黒星病・うどん粉病・健康)
  • 病気の進行度(軽症・中程度・重症)
  • 脳波(集中度を利用)
  • 分類にかかった時間(集中度の平均化のために使用)

元データはバラの病気診断サイト用に収集したもので、黒星病・うどん粉病・健康が1/3ずつとなるよう調整し、ハンドラベリングを実施した(各クラスのデータ数は同じ)。進行度は軽症が半分、中程度以上が半分な感じだが、データセット内の進行度の割合は調整していない。

モデル・学習の概要

今回は3クラス(黒星病・うどん粉病・健康)分類問題を(Convolution層+Pooling層)×2+分類用の層×2なCNNで解くシンプルな問題設定・モデルとした。転移学習や事前学習は行っていない。
脳からの知識蒸留が有効かを確認するため、下記4つのデータで学習し結果を比較した。

  1. 病気区分のラベルのみを用いた学習(普通の学習)
  2. 病気区分のラベルと進行度を併用した学習。病気進行度が高いほど病気区分ラベルの確信度が高くなるようにした。
  3. 病気区分のラベルと脳波を併用した学習。集中度が低いほど病気区分ラベルの確信度が高くなるようにした。(難しく考えなくても分類できたと言う意図[1])
  4. 病気区分のラベルと進行度と脳波を併用した学習。2.と3.の掛け算。

データセットを学習用75%・評価用25%に分割し、2エポック後の評価用データに対する正解率を比較した。学習データ・評価データに含まれる写真は4条件すべてで同一である(4条件でデータ分割による有利不利は生じていない)。loss関数として1.ではcategorical_crossentropyを、2.-4.ではkullback_leibler_divergenceを用いた。これは、2.-4.の正解データが教師の出力(本件では人間の確信度に相当する分布)でありバイナリ値ではない為である。

結果とまとめ

結果は次の通りであった。驚くべきことに[2]、Distilling the Knowledge from a Brainには効果があった。

  1. 通常の学習:正解率 37%
  2. 進行度の併用:正解率 37%
  3. 脳波の併用:正解率 41%
  4. 進行度+脳波の併用:正解率 49%

結果の解釈は難しいが、正解ラベル以外の情報(特に脳波)にも意味がありそうな感じである。データ数が少なく、そもそもの正解率が低いので何ともいえない感もあるので、今後データ数を増やして再度実験を行ってみたいところ。以下、硬い感じのまとめ。
AIが流行るにつれてハンドラベリングの重要性も上がっている[3]。本PoCではハンドラベリング時に脳波を測定し、それをモデル学習時に使用することで学習の効率化が出来る事がわかった。今後のラベリング作業では脳波を測定することがスタンダードになるだろう[4]。分類時の脳波付きデータセットが広く公開されることを期待する[5]。そのようなデータセットのもと、Distilling the Knowledge from a Brainの活用や脳波予測タスクをマルチタスクの1つとして解く学習によって、他のタスクの精度が上がっていくと推測される[6]。
(硬いまとめはここまで。個人的な思い的な考察はその他に続く。)

脚注

[1] この仮定は相当怪しい。
[2] こんな雑な問題設定・解き方で差が出るとは思わなかったが、複数回実行しても結果がほぼ同じであった。同じモデルにtrainを繰り返していないか確認したり、1.-4.の学習順番を変えてみたりもしたが同じ結果だった。びっくり。観測者効果的なもので脳波が変わったのだろうか?それはそれでびっくりだが。
[3] これはたぶん本当。実務では大きな課題。
[4] 脳波計測がスタンダードにはならないだろうが、取りやすい生体データが併用される可能性は感じた。特に心拍とか視線とか。
[5] 欲しい人がいれば今回のデータを公開してもよいかなーと思いつつ、雑にやったところを綺麗にするのが面倒なので、お蔵入りになりそうな予感がしている。
[6] 個人的にマルチタスクへの適用に可能性を感じている(参考論文「One Model To Learn Them All」)が、良い感じのデータが無いので試せていない。暇があったらやるかも。
Read more »

脳からの知識蒸留(Distilling the Knowledge from a Brain) – 準備-

知識の蒸留(Knowledge Distillation)とは?

Deep learningの世界では知識の蒸留(Knowledge Distillation)が行われている。蒸留というと非常にかっこよい響きなのだが、やっているのは「大きなモデル」を用いて「小さなモデル」を「効率的に学習・構築」することである。
バラの病気診断モデルは以前紹介したように次の手順で構築した。

  • データ(写真)を集めて、ラベル(病気の有無など)を人が設定する(ハンドラベリング)。
  • ハンドラベリングしたデータを用いて、教師あり学習を利用し、植物の葉が病気か否かを判別する多値分類モデルを構築する。

上記で構築したバラの病気診断モデルはInception V3をベースにしておりネットワークの規模が大きい(ネットワーク規模の情報)。すなわち、高精度だが低速度である。バラの病気診断モデルをスマホに展開したい場合、若干精度を落としてでもネットワーク規模が小さく高速なモデル(例えばMobileNet)を使いたくなる。普通はデータだけ再利用してモデルは学習しなおすというプロセスが必要となるが、蒸留を用いると効率的な構築が可能となる。ざっくりとした仕組みは次の通りである。

  1. 病気診断モデル(Inception V3 / 先生)に学習用画像データを入力し、その画像に対する「健康・黒星病・うどん粉病・その他カビ系の病気」の4カテゴリの確率を得る。
  2. 病気診断モデル(Inception V3 / 先生)が出した4カテゴリの確率情報も用いて病気診断モデル(MobileNet / 生徒)を学習する。

普通は健康か否かという0/1の情報で学習するが、高精度モデルが診断した確率にも有用な情報が含まれているので、それを利用しようというアイデアである。実際に蒸留は効果があり、モデル圧縮をする場合によく用いられている。詳細はIntelの記事が良くまとまっていて、kerasを用いた実装例も載っている。

脳からの知識の蒸留(Distilling the Knowledge from a Brain)

バラの病気診断サイトを作ったときに時間がかかったのは、病気か否かのラベリングである。一般的に人間によるラベリングは高コストであり、しかも、間違いが含まれている。ハンドラベリングをしたことがある人ならわかると思うが、現実のデータには判定に困る画像も多い。加えて判定基準は人によって異なっている。同症状の画像に異なるラベルが貼られていることは少なくない。
ハンドラベリングは教師である人間から、生徒であるDeep Learningな各モデルへの情報伝達に他ならない。ということは蒸留の仕組みも利用できるはずである。人間が下した0/1の判断だけではなく、それに付随する判断確率の情報を用いれば高速・高精度なモデルが構築できるに違いない。問題は判断確率の分布情報をどうやって得るかであるが、近年のテクノロジーによって解決できる。すなわち脳波を計れば良い[1]。

脳波の測定とラベリング

今回、脳波の測定はNeuroSkyのMindWave Mobile 2で行った。このデバイスを用いてハンドラベリング時の脳波をはかり、その値をラベルの確度の一つとして利用する。MindWave Mobile2は1.5万円くらいで購入でき、python+thinkgearを用いてデータの取得が可能である(serialではなくpyserialが必要なことに注意)。なお、本件はMacで実施したのでWindowsではやり方が異なる可能性がある。
ライブラリはpipから導入できる。

pip install thinkgear
pip install pyserial

ライブラリ導入後、pythonを用いて

device = '/dev/tty.MindWaveMobile-SerialPo'
tg = ThinkGearProtocol(device)
for pkt in tg.get_packets():
  for d in pkt:
    if isinstance(d, ThinkGearAttentionData):
      val = d.value
      now = datetime.datetime.now()
      print("{},{}".format(now, val))

と言う感じでデータが取得できる(上記の例では脳波というよりは集中度をとっている)
今回は下画像のようなラベリング用簡易WEBアプリに脳波取得ロジックを組み込み、ラベリング実施時の脳波(特に集中度)を計測・保存した。ハンドラベリング時には0/1な病気判定だけでなく、その病気の進行度も選べるようになっており、迷いが生じると脳波に現れる[2]。モデル構築の学習時に脳波情報を併用することで、Deep Learning部の収束が早くなれば、脳からの知識蒸留ができた[3]と言えるのではないか。

実験の結果は次ページで報告。

脚注

[1] 別に脳波である必要は無い。むしろ、脳波は計測が難しく、このような用途には適していない。
[2] 実際のところ現れたと言えば現れたが本当に脳波なのかはかなり疑問である。前述の通り、心拍とか血圧とか目線とか判断までの時間とか脳波よりも測りやすくて効果がありそうな指標は多数存在する。
[3] 本件が誇大広告であることは認識している。が、Deep Learning関係の研究論文から著しく外れた言葉使いはしていない(と思う)ので激しい突っ込みは勘弁してください。

AI診断の信頼性:XAI(Explainable Artificial Intelligence)とLIME

これまで3回にわたって、AIで植物の病気を診断するサイトを作った時の話をまとめた。「その他」の所に「作ったモデルにイマイチ自信ないわー」と書き続けた気がするので、(正しく)Deep Learningな人工知能(AI)を構築する難しさと、その対応について記事にしてみた。

Deep Learningを使用したモデルはイマイチ信頼できない

ラノベのタイトルみたいだなーと思いつつ・・・。色々な場所で言われている通り、Deep Learningを使用した判別モデルが実際のところ何を行っているかを知るのは簡単ではない。AIに限らずだが、モデル作成を長くやっているとクロスバリデーションで高精度なモデルが現実世界では全く使えない状況に遭遇する。特に、ドメイン知識から考えてモデルに納得感が無い場合、「評価設計が間違えている」「リーク情報(leakage) を拾っている」「データのバイアスが影響している」事が原因で、現実問題に対して有効でない事が多い。納得感はまっとうなモデルを作る上でとても重要である。私は何をやっているかわからない(説明可能性がない、納得感のない)モデルを用いることに恐怖感を感じるし、それを信頼して使うことは出来ない。(そーいうこともあり、でぃーぷらーにんぐは、しょうじき、しごとではあんまつかいたくない。(画像が対象ならいろいろ確認しつつ使うけど・・・。))

Deep Learningを説明する取り組み

Deep Learningのような複雑なモデルを説明する研究が進んでいる。アプローチとしては、Deep Learningの各層がどのような入力に強く反応するかを調べる方法が有名で、Feature Visualizationに詳しい。その他の手法として、入力を変化させながらモデル出力の変化を見る方法がある。前々回紹介した「“Why Should I Trust You?”Explaining the Predictions of Any Classifier」で提案されたLIME(Local Interpretable Model-Agnostic Explanations)は、入力近傍の動きを探ることでモデルの説明を行う方法である。ブラックボックスなモデルであっても、妥当な動きが見られれば信頼できる、少なくとも、信頼を得る材料にはなるという考え方である。

LIMEを試してみた

LIMEを行うソフトウェアは

pip install lime

でインストールできる。LIMEはモデル構築手法を選ばず適用できるが、scikit-learnやkerasを使っていると利用しやすい。植物の病気診断サイトはkerasを用いたモデル構築を行っており、limeのチュートリアルに沿って診断根拠を把握することができる。
下の画像は典型的な黒星病の葉であり、植物の病気診断サイトでは99%の確率で黒星病であると判定される。

これは正しい判断であるのだが、正しく画像を判定して黒星病と診断しているのか、たまたま黒星病と判定しているのか、何らかのリーク情報を拾っているのかはパッと見はわからない。LIMEを利用すると、AIがどこに反応しているかある程度把握できる。LIMEを利用した結果は下記のようなものとなる。(画像の大きさはモデルの前提にあわせて変更している。)

上の画像では黒星病と判定するにあたって注目した部分を黄色で囲んでいる。画像中、左部分と中央部分は黒星病診断で重要となる黒の斑点を診断根拠して提示しており、納得感がある。一方で右上のバラのトゲ部分を診断根拠している点には違和感がある。これは「黒星病の写真にトゲが写っている画像が多く、診断にあたりトゲに注目するモデルができている」「黒星病を(トゲがある)バラ特有の病気だと認識した」などと解釈できる。LIMEの結果は人が解釈する必要があり、それにはドメイン知識とモデル(データセット含む)の知識が必要となる。本件ではデータセットのほとんどがバラの写真であることから、前者の疑いが強い。

上記は「画像は健康な葉か?」を診断した結果であり、緑の部分は「この葉が健康であると診断する場合、ポジティブな要素として用いる部分」、赤の部分は「この葉が健康であると診断する場合、ネガティブな要素として用いる部分」を示す。画像上部の緑一色の部分が「健康そう」とした根拠、黒星病の特徴である黒の斑点が「健康でなさそう」という判断根拠とされている。この点は納得感があるが、地面部分も「健康でなさそう」という判断根拠とされていて、違和感がある。「地面の画像とともに健康な葉が提示されている画像が少なく、黒の斑点以外に注目してしまう」「茶色の地面を”枯れている”と誤認識した」などの理由が考えられる。
LIMEを利用することで、AIがどのように診断しているかをある程度可視化することができ、納得感が無い場合はどのように修正するかを判断できる。本件ではおそらくデータセットに問題がある。一応気をつかって撮影したはずのデータセットを用いていても、このような問題を内包している。きちんとしたAI構築は簡単ではない。(コードを書いて実行するだけなら簡単だけど・・・。)

XAIの重要性

説明可能なAIをXAI(Explainable Artificial Intelligence)と呼ぶ。前述のLIMEはXAIを実現するための1手法としても位置づけられる(完璧とは言いがたいが)。XAIは、AIが実務で使用されるにつれ重要となっていくテクノロジーであり、私は2つの側面があると考えている。

  1. モデル構築者の不安を解消するテクノロジー
  2. 社会にAIが受け入れられるためのテクノロジー

1.の観点は前述の通りで、ある程度説明性がないと、構築したAIが現実でうまく動くのか不安で仕方ない私のような人を手助けするモノである。(が、この点を重視する人はほぼいない)
2.の観点は重要である(というか本来的な意味はこっちの方である)。これに関連し、モデルの判断について「説明を受ける権利(right to explanation)」があるとする考え方もあり、GDPR2018でも話題になった。GDPRにおいて「説明を受ける権利」が存在しているかは議論がある(左記が整理された論文その日本語の解説)が、この手の権利が今後重視されることは間違いない。

公平性、FADM(Fairness Aware Data Mining)

公平性の観点からも判断理由の説明は重要である。例えば、「この家を買うと、収入に比べて、借入額が多すぎます。そのため、あなたにはお金を貸しません。」という判断には納得感がある。ある意味、相手のことを思い「無理な返済計画の後、破産しないよう」判断しているとも言える。一方で「あなたの名前は怪しいので、お金を貸しません。」という判断には納得できないだろう。不利に判断される名前が、ある地域に特有のものであれば、差別に繋がる判断として大問題となる可能性すらある。
Deep Learningだ、AIだ、人工知能だ、とテクノロジーを使って何らかの判断をするのは良いのだが、データセットや手法に依存して、差別的なモデルが作成される可能性がある。このようなモデルが運用されると、差別を助長する方向で判断がなされ、その拡大につながりかねない。クロスバリデーションで確認した、統計分析した、など理由があっても、現実世界で「差別的な判断」は受け入れられないだろう。
前述のお金を貸す例でいうと、差別は下記のように広がる。

  1. 特定の名前の人(複数)がお金を返さなかったデータが存在する状況下で
  2. 何も考えずに、名前も判断材料とする「AI」を作り、運用すると
  3. 「AI」は特定の名前の人に不利な結果を返すため、その名前の人に提示される金利は上がり
  4. 特定の名前の人の破産率は上がって、1.に戻る

名前には地域や生まれ年(=年齢)が反映されており年収と関連する。「名前」を入れることで精度が上がる「AI」は作成可能だが(現時点でも)実務屋としては「これはダメです」と言わないといけないんだろーなーと思っている。書いていても思うが、実際の関連性も怪しいし。
現時点では微妙としても、GDPRのような規制が進んでいくと、差別的な判断が法的・社会的に問題となるだろう。現在、持てるデータをすべてつっこみ作られた作成者すら何が起きているかよくわからない「AI」が増えている。知らず知らずのうちに差別的な「AI」を運用し、その結果大炎上する可能性は高い。公平性・説明可能性は今後重要性を増していく。
公平性はFADM(Fairness Aware Data Mining)といったキーワードで研究が進んでいるが決定打となるテクノロジーは存在しない。いくつかの手法が提案されているが、差別の定義が難しかったり、計算コストが高かったりと運用が難しい。一方で、今でも(人間の判断でも)差別が行われている可能性はある。差別とは何か?が定式化され、数学的に差別がないと保証された方法ができれば、世の中が良くなるかもしれない。人工知能ブームでいろんな議論が盛り上がっているし、企業によってはお金もあるしで、この手の研究が進めばよいなーと思う。(結局、結論は前回と同じ)

WEBアプリ構築(keras+uwsgi+bottle)編+まとめ&AI構築の悩み(3/3)

前回、前々回から引き続き、植物の写真から病気を判別するサイト(http://www.plant-check.jp/)を作ったときのまとめ。今回はWEBサイトを構築したときのまとめ。

WEBサイトの概要

リンクにあるとおり、WEBサイトはシンプルな作りとなっている。大きなユースケースは下記の2つ。そしてやることはほとんど同じ。

  1. ユーザがサイトの「写真を撮影」ボタンを押すと、自分のスマホから写真を撮ることが出来る。次に「病気をチェック」ボタンを押すと、取った写真の植物が病気か判定される。
  2. ユーザがサイトの「写真を選択」ボタンを押すと、自分のPC/スマホから写真を選択することが出来る。次に「病気をチェック」ボタンを押すと、選んだ写真の植物が病気か判定される。

HTML部分

スマホ経由で写真を取らせるにはcapture=”camera”を使えばOK。

 <input type="file" accept="image/*" capture="camera" id="camera" style="display:none;" name="upload" />

それ以外の部分として、labelタグを使ってボタンを隠したり、若干JavaScriptでボタンの活性・非活性を切り替えたりしているが特殊なことはやっていない。

サーバ部分

サーバ部分の構成は次の通りで、こちらも特殊な構成ではない。

  • sakuraのVPS(ubuntu 16.04 LTS)
  • Docker
  • nginx
  • uwsgi
  • Pythonのアプリ
    • bottleを利用してモデル部分(keras + tensorflow)とWEBアプリ部分をつなげた。

uwsgiとnginxのつなぎはSocketを使用した。(nginx側 uwsgi_pass unix:/path_to/uwsgi.sock、uwsgi側 socket = /path_to/uwsgi.sock )良くわからない場合は、公式サイトのサンプルを見ながら書くのが一番だった記憶がある。
Pythonのアプリ部分はbottleを用いて書いた。毎回毎回モデルをロードするアプローチはさすがに富豪過ぎるので、起動時に判別モデル(植物か否か、病気か否か)をロードしてそれを使いまわすようにしている。
一点はまった点としてuwsgi.confでthreads = 1、enable-threads = falseとしないと動作しなかった。当時forumを確認した感じではkerasのpredict処理がマルチスレッドに対応していないのが原因だったと記憶している(が、モデル読み込み時にフリーズしたような記憶もある)。それと、bottleの制約のようだが、巨大ファイルをアップロードされた場合の対策(chunkごとに読みこんで、一定以上のサイズの場合は処理を停止)を自分で書かないといけないのが面倒だった。

まとめ

モデル構築部分のコード行数は約100行(×2)、サーバ部分のコード行数は約200行だった。「AI(人工知能)が病気を診断!」というサイトがこの程度の行数で書けてしまうのは、便利なライブラリが整備されたおかげで、作者に感謝である。もっとも、モデル構築部分はノウハウ含めて自動化されていくはずで、大手各社もAutoML、Azure Machine Learning、・・・と言った名前でそのような環境をリリースしている。
AIやら人工知能やら何やらの主戦場は、結局何するの?という方向か、ほんまに使えるの?という方向になる気がしていて、クロスバリデーションベースのモデル精度を競っても無意味な時代になりつつあるんだろうなーと思う。(ということで私は自動化大歓迎、その他の思いはその他へ)
今後、情熱が復活すれば他の病気への対応を行う予定。もう少し実験したいことがあるので、コードを公開するのはその後になる見込み。
Read more »

モデル構築(keras/tensorflow+InceptionV3+Data augmentation)編(2/3)

前回から引き続き、植物の写真から病気を判別するサイト(http://www.plant-check.jp/)を作ったときのまとめ。

モデル構築の流れ

前回書いたとおり、今回、(1)植物の葉かそれ以外かを判別する2値分類モデル+(2)植物の葉が病気か否かを判別する多値分類モデルを作成した。構築方法は(1)、(2)ともに同様でkeras, tensorflowを用いて転移学習を行った。モデル構築の流れは下記の通り。バリバリのDeep Learningなので、人工知能(AI)を利用し、植物の病気判定を行った!と言っていいはず(大事なことなので2回目)。

  1. 写真をラベルごと((1)は「植物の葉/それ以外」、(2)は「健康な葉/黒星病/うどん粉病/その他カビ系の病気」)ごとに別のディレクトリに格納する。
  2. kerasを使ってImageNetを学習したInception V3をロードする。
  3. ロードしたモデルの一部(今回は250層以降)を学習可能に設定する。
  4. 問題(2値 or 多値)に応じて、Inecption V3の後段のネットワークを設定する。
  5. 写真データを学習用と検証に分ける。
  6. 写真データをdata augmentationするよう設定する。
  7. 学習、検証する。

モデル構築のコード

上記1.~7.のフローはkeras (+ tensorflow)を用いると簡単に実装できる。2値分類モデルにおける、2.~4.は下記のように実装可能。多値の場合は最終層を「predictions = Dense(クラス数, activation=”softmax”)(x)」てな感じに変えて「loss = “categorical_crossentropy”」とすればよい。ざっくりいうとニューラルネットワークの前段として学習済みのネットワークを用い特徴量抽出等を再利用(=転移学習)し、本件で必要な分類を行うネットワークを追加している。

base_model = InceptionV3(weights='imagenet', include_top=False)
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(1024, activation="relu")(x)
x = Dropout(0.5)(x)
x = Dense(256, activation="relu")(x)
predictions = Dense(1, activation="sigmoid")(x)
model = Model(inputs=base_model.input, outputs=predictions)
for layer in model.layers[:249]:
   layer.trainable = False
for layer in model.layers[249:]:
   layer.trainable = True
model.compile(loss = "binary_crossentropy", optimizer = optimizers.SGD(lr=0.0001, momentum=0.9), metrics=["accuracy"])

Data augmentationは画像を変形(回転、移動、縮小、拡大などなど)させながらデータを増やして学習する方法である。画像を変形させることで、1つの画像から複数のパターンを生み出す。日本語だとデータ拡張とか呼ばれている(はず)。写真の撮られ方に依存する差異等が吸収できるので、未知データに対する性能の向上に効果がある(と私は思っている)。モデル適用時にも変形させながら何パターンか適用し、その平均を取ると性能向上効果がある(こともある)が、こっちをデータ拡張と言うかはよくわからない。
kerasだと「ImageDataGenerator」を使って簡単に書ける。多値の場合は「class_mode = “categorical”」に変更すればよい。学習データと検証データのsplitを事前に行っていれば、同じようにtest_generatorをかける。

train_datagen = ImageDataGenerator(
    rescale = 1./255,
    horizontal_flip = True,
    fill_mode = "nearest",
    zoom_range = 0.3,
    width_shift_range = 0.3,
    height_shift_range=0.3,
    rotation_range=90)
train_generator = train_datagen.flow_from_directory(
    train_data_dir,
    target_size = (img_height, img_width),
    batch_size = batch_size,
    class_mode = "binary")

学習は下記のように行えばよく、チェックポイントごとにモデルが保存され、精度に応じて自動でストップされる。nvidia-dockerのコンテナでGPU版のtensorflowを入れていれば、GPUを用いた学習が行われる。とても便利。

checkpoint = ModelCheckpoint("/保存用ディレクトリ/モデル名_{epoch:02d}.h5", monitor='val_acc', verbose=1, save_best_only=True, save_weights_only=False, mode='auto', period=1)
early = EarlyStopping(monitor='val_acc', min_delta=0, patience=10, verbose=1, mode='auto')
model.fit_generator(
    train_generator,
    samples_per_epoch = nb_train_samples,
    epochs = epochs,
    validation_data = validation_generator,
    nb_val_samples = nb_validation_samples,
    callbacks = [checkpoint, early])

今まで紹介したコードは、だいたいkerasのsampleコードをベースにしている。自分でやってみたい方はkerasのチュートリアル(https://keras.io/ja/内のリンクから飛べる)を読むのがお勧めである。

モデル構築のポイント

コード自体は簡単に書け、実行してみると検証データでの精度も良いと見える結果が得られる。が、実際に重要なのは「未知データに対する予測性能」で、思ったとおりの結果にならないことがある。これは学習・検証データの関連が強すぎるから(=独立じゃないから)で、学習結果を評価する上で重要なポイントとなる。この手の問題への対応はとっても難しい。私は人工知能やらAIやらと呼ばれるモノの中には、正しく評価されていない、過大評価なヤツも多いと思っている。
Deep Learningを使った場合(特に本件のような非常に複雑なネットワークを組んでいる場合)、「黒星病の特徴である黒の斑点を見て、黒星病と判断している」など「AIの判定に納得感があるか」はわかりにくい。検証すると、「AIはバラが植わっている鉢の色に注目して病気だと判別する」場合もある(詳しくは後述*1)。
このような動きはモデルの説明可能性の文脈でよく話題になっていて、たとえばKDD 2016の「“Why Should I Trust You?”Explaining the Predictions of Any Classifier」に詳しい。論文中の対応案は、まずまず良く動くが、速度面など使い勝手はイマイチという印象を受けた。説明可能性は重要な分野だが、現時点で決定打となる対応策は存在しない。
本件では写真を撮ったのが自分自身ということもあり、背景や鉢が注目点とならないよう気を使っている。具体的には病気の葉と健康な葉それぞれを同じ木・背景で撮影する、様々なパターンを混ぜるなど、変な場所に注目されないようにデータを作成している。
モデルの学習時には完全に未知のデータで検証がされるようにし、また、手動でAIが間違いやすいパターンの解析・チェックを行っている。割と丁寧に判別モデルを作っているが、それでも、「未知データに対する判別能力」は「検証結果として数学的に求められた能力」ほど高くはないんだろうなーと思っている。
Read more »

植物の病気をDeep Learningで判別  概要+準備編(1/3)

植物の写真から病気を判別するサイト(http://www.plant-check.jp/)を作ってみた。
サイトは2017年8月くらいに作ったのだが、そのメモがてら、やったことをまとめてみる。
使用した技術は最近の流行をふまえ、わりと本格的。バリバリのDeep Learningなので、人工知能(AI)を利用し、植物の病気判定を行った!と言っていいはず。

  • 病気判定に使用してたのは(1)植物の葉かそれ以外かを判別する2値分類モデル+(2)植物の葉が病気か否かを判別する多値分類モデル。
  • 2つのモデルともにDeep Learningを活用。具体的にはInception V3(ImageNet)を転移学習させる形で作成。
  • ソフトウェアはPythonで作成。Deep Learning部分はkeras + tensorflow、WEB部分はnginx + uwsgi + bottleで構成。
  • インフラ部分にnvidia-docker(keras + tensorflowでの学習部分)とdocker(WEB部分と判別モデル利用)を使用。サーバはsakuraのVPS(ubuntu 16.04 LTS)。
  • 学習データにはa) 自分で集めたバラの葉っぱ画像(健康な葉と病気の葉をハンドラベリング)とb)SUN Database(http://groups.csail.mit.edu/vision/SUN/)を使用。
    • (1)植物の葉かそれ以外かを判別するモデルにはa) + b)を利用
    • (2)植物の葉が病気か否かを判別するモデルにはa)のハンドラベリング結果を利用

開発環境の準備

開発環境としてDockerを利用したコンテナを2つ+リリース用のコンテナを1つ作成した。

  1. GPUを用いた学習用としてtensorflow + kerasが動作するコンテナ
    • ホストとなるUbuntu 16.04にnVidia公式ドライバをインストールした後、nvidia-dockerを導入して構築。
    • 安定版のgcr.io/tensorflow/tensorflow:latest-gpuをベースにしてkerasを追加で入れた。
    • (困ったときは公式サイトを見るのが一番だった。)
  2. 学習したモデルを用いたWEBアプリを開発するためのコンテナ
    • ホストは1.と同じ。pipからkeras, tensorflow, bottle, uwsgiを入れた。
    • 開発用なのでjupyterも導入。
  3. WEBアプリをリリースする環境のためのコンテナ
    • 2.との違いはjupyterなど余計なソフトウェアを省いている点だけ。

データの準備

本件で作成したモデルの概要とモデル構築に利用するデータは次のとおり。

  1. 植物の葉かそれ以外かを判別する2値分類モデル
    • バラの葉の画像データを正例、SUN Database(http://groups.csail.mit.edu/vision/SUN/)の植物以外のカテゴリを負例とする2値分類モデル。
    • Imagenetで学習したInception V3(keras付属)を利用。
    • 前半250層を固定。Inception V3の後に判別層を追加する形でネットワークを構成(詳細は次回記載)、学習させた。
    • いわゆる転移学習とかfine tuningとか呼ばれる手法。
    • 使ったデータは約16000枚。(data augmentation前)
  2. 植物の葉が病気か否かを判別する多値分類モデル
    • バラの葉の画像データを「健康」「黒星病」「うどん粉病」「その他カビ系の病気」の4カテゴリに分類する多値分類モデル。カテゴリ分けは自分でとったバラの写真・動画(画像に一定フレームごとに画像として切り出し)をハンドラベリングした。
    • 利用した学習手法は1.と同様。
    • 使ったデータは約1800枚。(data augmentation前)

バラの葉の画像データは夏休みに都内の公園をめぐって自分で撮影した。モデルに投入していないデータ(バラ以外、対象病気以外)も含めて3000枚以上撮影。機械学習のコード書くより大変だった(が良い経験になった(詳しくは次回))
Read more »

ドキュメントとソースコードの対応2(word2vecの利用)

先週に引き続きドキュメントとソースコードの対応付けについて実験してみた。
手順は下記の通り。前回からの改良(?)としてword2vecで日本語→英語の変換を行ってみた。

  1. 英語と日本語のマニュアルからword2vecで使用できる形式で単語を抜き出す。
    • 形態素解析のライブラリにはMecabを利用。
    • 2.のword2vecの処理で使うためのデータ。
  2. PostgreSQLのソース内にあるhtml、c、hファイルを対象にファイル内に存在する単語を抜き出す。
    1. 単語が日本語であった場合はword2vec的に類似度の高い単語TOP10から距離に応じて英単語をランダムに選ぶ。
      • ライブラリにはgensim(http://radimrehurek.com/gensim/index.html)を利用。
  3. 2.で抜き出したデータに対し、TF-IDF用の辞書(単語と出現頻度)を作る。
  4. 2.で抜き出したデータと3.を使って、それぞれのファイルに含まれる単語に対してTF-IDFで重み付けを行う。
  5. データ内(PostgreSQLのソース内)にあるhtmlファイルに対してコサイン距離の近いc、hファイルを一覧化する。

結果は↓の通り。前回に比べてコサイン類似度の値自体は上がっているものの適切なソースファイルを引けているかというとやや疑問。とはいえ何もやらないよりはマシかも(?)
詳細な結果とソースは以下のURLをご参照。

--../data/postgresql-9.3.4/doc/src/sgml/html/app-createuser.html--
[   (   '../data/postgresql-9.3.4/src/bin/scripts/createuser.c',
        0.5312760078128166),
    (   '../data/postgresql-9.3.4/src/backend/commands/user.c',
        0.49654701095797743),
    (   '../data/postgresql-9.3.4/src/bin/pg_dump/pg_dumpall.c',
        0.4521924409520057),
    (   '../data/postgresql-9.3.4/src/include/commands/user.h',
        0.4458741678013434),
    (   '../data/postgresql-9.3.4/src/include/catalog/pg_authid.h',
        0.4366612559172716)]
--../data/postgresql-9.3.4/html-ja/app-createuser.html--
[   (   '../data/postgresql-9.3.4/src/bin/scripts/createuser.c',
        0.27458665377082037),
    (   '../data/postgresql-9.3.4/src/bin/psql/tab-complete.c',
        0.2411847340696424),
    (   '../data/postgresql-9.3.4/src/backend/commands/user.c',
        0.20269999574142247),
    ('../data/postgresql-9.3.4/src/include/utils/acl.h', 0.16197452745649446),
    (   '../data/postgresql-9.3.4/src/bin/pg_dump/pg_backup.h',
        0.16130232695577076)]
--../data/postgresql-9.3.4/doc/src/sgml/html/indexes-intro.html--
[   (   '../data/postgresql-9.3.4/src/backend/commands/indexcmds.c',
        0.5887593627491802),
    ('../data/postgresql-9.3.4/src/include/utils/rel.h', 0.5847988052787634),
    (   '../data/postgresql-9.3.4/src/backend/optimizer/path/indxpath.c',
        0.5736868945937168),
    (   '../data/postgresql-9.3.4/src/backend/catalog/index.c',
        0.5695396670614502),
    (   '../data/postgresql-9.3.4/src/backend/optimizer/util/plancat.c',
        0.5669650556082131)]
--../data/postgresql-9.3.4/html-ja/indexes-intro.html--
[   (   '../data/postgresql-9.3.4/src/backend/access/rmgrdesc/spgdesc.c',
        0.6065874266691939),
    (   '../data/postgresql-9.3.4/src/backend/access/transam/rmgr.c',
        0.6055142588080052),
    (   '../data/postgresql-9.3.4/contrib/pg_xlogdump/rmgrdesc.c',
        0.6055142588080052),
    (   '../data/postgresql-9.3.4/src/backend/access/spgist/spgquadtreeproc.c',
        0.6054292723362433),
    (   '../data/postgresql-9.3.4/src/include/access/rmgrlist.h',
        0.6054292723362433)]

ドキュメントとソースコードの対応

ソフトウェア開発においてドキュメントとソースコードの対応を取り続けることは想像以上に難しい。開発当初は綺麗に対応していたドキュメントとソースが年を追うごとに乖離していき、それぞれ別物として成長していくことはよくある光景である。そのような中、ドキュメントに対応するソースが何であるか一覧化できるそりゅ~しょんが強く求められている・・・ような気がする。
ざっくり言うとドキュメントとソースコードの紐付けが自動でできるかどうか気になったのでやってみた。(ここにはひさしぶりの投稿)
条件は下記の通り。

  • Postgresql9.3.4のマニュアルに対応するソースを表示する。
  • マニュアル、ソースそれぞれについて出てくる単語を抽出し、TF-IDFで重み付けを行い、コサイン類似度の大きなものを関連しているとみなす。

結果、英語版のマニュアルとソースの対応はそこそこの精度で取れるが、日本語版のマニュアルに対する対応はイマイチだった。ソース中に日本語が入っているとは思えない(=キーとなる情報が少ない)ので納得のいく結果ではある。
下記はcreateuserとindex-introの例。[]内には–・・・.html(マニュアル)–に対して類似度が大きなソース上位5つが入っている。()内の”の中がソースコードの名称でその後の数字がコサイン類似度。なお、htmlディレクトリ以下にあるのが英語版で、html-ja以下にあるのが日本語版となっている。

--../data/postgresql-9.3.4/doc/src/sgml/html/app-createuser.html--
[   (   '../data/postgresql-9.3.4/src/bin/scripts/createuser.c',
        0.5315036131957114),
    (   '../data/postgresql-9.3.4/src/backend/commands/user.c',
        0.49634873213501424),
    (   '../data/postgresql-9.3.4/src/bin/pg_dump/pg_dumpall.c',
        0.45199746892518794),
    (   '../data/postgresql-9.3.4/src/include/commands/user.h',
        0.4456889634461926),
    (   '../data/postgresql-9.3.4/src/include/catalog/pg_authid.h',
        0.436479878362433)]
--../data/postgresql-9.3.4/html-ja/app-createuser.html--
[   (   '../data/postgresql-9.3.4/src/bin/scripts/createuser.c',
        0.24660267524744783),
    (   '../data/postgresql-9.3.4/src/bin/psql/tab-complete.c',
        0.21638658446826328),
    (   '../data/postgresql-9.3.4/src/backend/commands/user.c',
        0.18185439320172517),
    (   '../data/postgresql-9.3.4/src/backend/catalog/objectaddress.c',
        0.17654887868713476),
    (   '../data/postgresql-9.3.4/src/backend/parser/gram.c',
        0.15892817112702087)]
--../data/postgresql-9.3.4/doc/src/sgml/html/indexes-intro.html--
[   (   '../data/postgresql-9.3.4/src/backend/commands/indexcmds.c',
        0.5887593627491802),
    ('../data/postgresql-9.3.4/src/include/utils/rel.h', 0.5847988052787634),
    (   '../data/postgresql-9.3.4/src/backend/optimizer/path/indxpath.c',
        0.5736868945937168),
    (   '../data/postgresql-9.3.4/src/backend/catalog/index.c',
        0.5695396670614502),
    (   '../data/postgresql-9.3.4/src/backend/optimizer/util/plancat.c',
        0.5669650556082131)]
--../data/postgresql-9.3.4/html-ja/indexes-intro.html--
[   (   '../data/postgresql-9.3.4/src/backend/lib/stringinfo.c',
        0.09184457364491616),
    (   '../data/postgresql-9.3.4/src/interfaces/ecpg/test/expected/sql-insupd.c',
        0.0886416204237675),
    (   '../data/postgresql-9.3.4/src/test/examples/testlibpq3.c',
        0.08837346944289427),
    (   '../data/postgresql-9.3.4/src/backend/access/heap/visibilitymap.c',
        0.08815864541352804),
    (   '../data/postgresql-9.3.4/src/interfaces/ecpg/test/expected/sql-indicators.c',
        0.08800994862677734)]

詳細な結果はhttp://staka.jp/doc-src/result.txtの通り。なんとなくだが、英語版マニュアルとソースコードはそれなりに紐付いているように見える。日本語のコメントが豊富にあるソースとであれば日本語版もうまく行くような気がしないでもない。
ソースコードはhttps://github.com/s-taka/doc-srcにおいておいた。基本的にPythonで書いていて、日本語マニュアルの形態素解析はMeCabで行っている。最近流行のword2vecの利用は今後の課題。

補記

前回同様、前段の補記。ドキュメントとソースが乖離していくのがよくある光景なのは事実だが、ドキュメントとソースが(ざっくりと)紐付いてうれしい人がどれだけいるかは謎。ドキュメント→ソース→コミットログ→作成者とたどっていけるのはある程度便利かもしれないが。本当は、ドキュメントとソースの乖離が分かるとうれしい人は多そうだけど、自然言語処理のアプローチでそれを見つけるのは至難の技だろーなーと思う。