TensorFlowを使った画像認識(その2)

ということで、正解率をあげるためにいろいろと試してみることにしました。

まずはソースコード中の数値をチューニングしてみましたが、あまり効果が(高く)なさそう・・・ということでこれは却下。

それよりも学習用画像を加工する方がよさそうということでこの方向で進めました。

前回書きましたが、まず画像の枚数を増やすために、元画像を90度、180度、270度回転したものを追加しました。これで枚数は4倍になりました。

※画像を回転させるソースコード

 

そして以下の3パターンを試してみました。

 

1:元画像を白黒に色を変えた画像で実行

カラーの画像を白黒に変えるソースコードは以下のとおりです。

 

2:元画像を正方形に切り出した画像で実行

これは実は手作業で実行しました。。(成果があれば自動化しようと思い)

人間の顔等であれば、OpenCVでその部分を切り出すということは可能なようです。今回のような画像の場合はどうなのでしょう・・。

 

3:元画像から緑色を除いた画像で実行

画像の緑色の要素のみ取り除くソースコードは以下のとおりです。

 

以上の方法で各パターンの画像を用意し、その学習用画像を使って実行しました。

実行結果なのですが、残念ながらどのパターンも、画像を加工する前の結果と比べてよい結果は得られませんでした。

 

そこで、

4:画像のサイズを大きくする

を試してみることにしました。

元のソースコードは実行時に画像を28*28にリサイズします。

このサイズを大きくしてみることにしました。

大きくすればするほど実行時間がかかってしまうので、その辺はうまく調整しつつ。

以下が64*64で試した結果です。

おお。だいぶよくなってます。やはり28*28は小さすぎたようです。

 

ではサイズを大きくしたまま、先ほどの画像加工を加えてみます。

1’:元画像を白黒に色を変えた画像で実行(64*64)

2’:元画像を正方形に切り出した画像で実行(64*64)

3’:元画像から緑色を除いた画像で実行(64*64)

ということでこれらの画像加工はサイズを大きくしても特に結果は変わりませんでした。少しくらいよくなるかと思ったのですが。。

 

以上、現状画像のサイズを大きくすることで多少の正解率UPが実現できました。引き続き正解率をあげる方法を探っていこうと思います。

TensorFlowを使った画像認識(その1)

農作物(きゅうり)の画像から病名を判定する機械学習のアプリを作成することになりました。

「TensorFlow」「画像認識」等のキーワードで検索すると、サンプルアプリがいくつかヒットしたので、それほど難しいものではないのかな、と思いつつ、以下の点が心配でした。

1.ラベル(分類)が多い

今回作成するアプリは13種類(見つけたサンプルだと、多くても数種類)

2.画像の数が少ない

今回は手作業でネット上から画像を拾いました。そんなわけで数が少ないです。分類ごとに多くても4、50枚しか集められませんでした。

※アプリを実行する際、元の画像を回転した画像を使って数を増やしました。

結論から言うと、特に2の方(画像数と画像の精度)はかなり結果に影響するのではないかなと思います。

 

まずは環境構築から。

私はWindowsなので、

https://qiita.com/isaac-otao/items/cecfb3efb7d9ccddf922

このページを参照しました。特につまづいたところはありません。

さらに画像を加工するため、OpenCVもインストールしました。

続いて実装です。

http://kivantium.hateblo.jp/entry/2015/11/18/233834

こちらを参照しました。

Pythonのバージョン違いで何か所かエラーが出たのでその部分のみ修正しました。

で、とりあえず動くようになりました。

参照したコードでは、テスト結果は精度を表示するだけなのですが、今回は混同行列(Confusion Matrix)でまとめたかったため、以下のように修正しています。

・テスト画像に対して、各ラベル(分類)の確率を算出する

・確率の高い順にソートする

・一番高いものを採用する

実際のコードはこちらです。

実行すると、

例えばこのような結果が得られます。

あまり正解率は高くないですね。。。

ということで、正解率を上げるために試行錯誤してみるのでした。

続きます。。

PDFBoxを使ってPDFを作成する(その2)

PDFBoxを使ってPDFを作成しようとしたところ・・・という話でした。

5.改行、改ページができない

できない、と書きましたができるんです。
改行は
cos.newLine();
改ページは
page = new PDPage();
document.addPage(page);

でできます。でもそうじゃないんです!
長い文やページを超える文を出力するときに、自動で改行や改ページしてほしいんです!
結論。できません。
そういうものかもしれませんが、なんとなく自動でできるような気がしていました。
ということで、文字数/行数を自分でカウントして、よきところで改行/改ページを入れる方法で解決しました。
コードは汚いので割愛します。。

6.ファイル保存でOutOfMemoryError発生

今回、1000ページ(数百MB)以上のPDFを作成することもあったのですが、
document.save("sample.pdf")
のところでOutOfMemoryが発生することがありました。
調べたところ、ページ数が多いと発生することがあるという記事を見つけました。
(ちなみにJDK1.6限定で、1.7だと発生しないとのことです。今回の環境は1.6でした。)
この記事なんですが、リンク切れで表示できません。一応、URLはこちらです。
http://www.gcgate.jp/engineerblog/2013/12/06/223/
記事によると
・ある一定ページ数になったらPDFをファイルに書き込む
・その書き込んだPDFファイルを読み込みページを追加する
・繰り返す
で解決したということだったので、これを試したのですが解決せず。
いろいろ試して、PDFファイルを分割して保存する方法で解決できました。
これもコードは割愛しますが、ページ数を自分でカウントして、決まった件数のときにファイルを保存する、という流れです。
今回のケースは、ページ数が多いというより、ファイルサイズの大きさによるような気がします。
分割する際ファイルサイズでできればいいのですが、それは厳しいのでページ数で区切りました。

7.作成したファイルで検索ができない

すみません。これはまだ解決できていません。
1つ前の記事で書いた通り、今回日本語を表示するためにIPAゴシックを使いました。
すると、作成したPDFをAdobeのPDFリーダー等で開いて、検索しようとすると
検索できるファイルと検索できないファイルがありました。何か特定の文字が含まれると検索できないようなのですが、原因となる文字は特定できていません。
また、PDFBoxで何かしら対策ができるのか、も今のところ不明です。

他のライブラリは使っていないので比較ができないのですが、PDFBox、便利は便利だけどすごく使いやすくはない、というふわっとした感想で終わりたいと思います。
あ、公式サイトのFAQは結構役立ちました。

PDFBoxを使ってPDFを作成する(その1)

JavaでPDFファイルを作成することになり、いろいろライブラリを見た結果、一番よさげだったApache PDFBoxを使うことにしました。
一応やりたいことはできたものの、困ったことやわからないことがポロポロとあったので、そのあたり書き残しておこうと思います。
ごくごく基本的な使い方(HelloWorld的な)について説明しているページはいくつか見つけたものの、そこから先の細かい使い方を書いてくれているページがあまりなかったので、どこかの誰かの役に立てばいいなと思いつつ。。。

1.ダウンロード

公式サイトのダウンロードページからダウンロードします。

2.HelloWorld

特に難しいことはありません。以下のコードでHelloWorldと出力されます。正確には出力されたPDFファイルが作成されます。ですね。
細かい説明は省きます。
PDDocument document = new PDDocument();
PDPage page = new PDPage();
document.addPage(page);
PDFont font = PDType1Font.HELVETICA_BOLD;
PDPageContentStream cos= new PDPageContentStream(document, page);
cos.beginText();
cos.newLineAtOffset(0f, 0f);
cos.setFont(font, 12);
cos.setLeading(12);
cos.showText("Hello World");
cos.endText();
cos.close();
document.save("helloworld.pdf");
document.close();

※PDPageContentStreamは、1ページにつき1回生成します。
1ページの中に異なるフォントや異なる文字サイズで出力する場合は、
cos.beginText();
から
cos.endText();
までを繰り返します。

3.日本語

必要に応じてフォントをダウンロードしてください。ちなみに今回はIPAフォントを使いました。
そして、
PDFont font = PDType1Font.HELVETICA_BOLD;
の部分を
PDFont font = PDType0Font.load(document, new File("/usr/share/fonts/ipa-gothic/ipag.ttf"));
とすることで、日本語も表示することができます。

4.画像

PDImageXObject ximage = PDImageXObject.createFromFile("photo.jpg", document);
cos.drawImage(ximage, 100, 100, ximage.getWidth(), ximage.getHeight());

で画像が表示できます。

文字の表示にしろ画像の表示にしろ、それぞれ表示位置を指定できるので、1つのページに文章と画像を表示することももちろん可能です。
表示位置は、実際に出力してみて、微調整した方がいいと思います。
また、画像の表示は文字の表示に比べてかなり時間がかかります。(画像のサイズが大きいと特に)

ここまでが基本的な使い方になります。日本語も表示できる、画像も表示できる、よしよし・・・と思ったのも束の間、ちょこちょこと問題が発生するのでした。

続く

SpringでNeo4jを利用する際の注意点

spring-bootからNeo4jを利用する場合、spring-data-neo4j(neo4j-ogm)と言ったライブラリでアクセスする事になります。
その際、バグと思われる現象に遭遇しましたので、忘れないように記しておきます。

概要

ValueObjectの配列またはリストをフィールドにもつEntityを作成します。
Neo4jにValueObjectをNodeとして登録させたくない為、フィールドをコンバータで文字列シリアライズしSaveを行いました。
Saveは正常に行われ、Neo4jへのストアされた内容も想定通りのものでしたが、このエンティティをLoadすると、コンバータの処理で型キャストエラーが発生しLoadに失敗します。

詳細

  • LoadできるEntity
    下記のEntityはSave、Loadが行えます

 

  • LoadできないEntity
    下記のEntityはSaveできるが、Load時にエラーが発生する

ValueObjectのListフィールドを追加する。フィールドをコンバーターにより文字列(Json)に変換する

このエンティティをSave後にLoadを実行すると、下記のような例外が発生し失敗します。スタックトレースから、キャストエラーが確認できます。

ここで発生している例外がListからStringへの変換例外だった為、なぜ発生するのか原因がわかりませんでした。Load時に行う処理は文字列(JSON)からValueObjectへの変換のはずです。なぜListを文字列にCastしようとしているのか、、、

原因

スタックトレースを追っていくと、GraphEntityMapperクラスに問題がありそうです。
当該箇所を確認してみます。

配列、Listの場合の分岐があります。どうやら、デシリアライズ先のフィールドがList(配列)の場合のみここで型の変換を行っています。

Neo4jのNodeは、プロパティに数値、文字列、真偽値またはそれらの配列を持つ事ができます。そのため為のロジックだと思いますが、コンバータの有無を確認せずに型変換を行っています。Writerの中でCallされるコンバータは文字列を期待ていますがListが渡される為、文字列にキャストを行おうとし例外となりました。

対応

色々試行錯誤した結果、当該部分を削除する対応を取りました。
テストして見る限り、動作に変わりは無いようなのですが、

まとめ

spring-data-neo4jでList(配列)フィールドにコンバーターを設定する場合は、本現象が発生します。
また、本現象ですが、キャッシュオブジェクトが利用されると再現しませんのでご注意ください。凄くハマりました(–;