RubyでYARD定義を使って実行時にメソッド引数と戻り値の型チェックを試みる

Ruby のカンファレンスの度に、毎度話題になる?) Ruby に型が欲しい件ですが、個人的な見解を書いておこうと思います。ちなみに私は RubyKaigi 2018 に参加しておりません。Twitterのタイムラインでの賑わいを見ていただけです。

最近、Swift を触っててそのコンパイルの重さにうんざりしているので、 型推論 は現代のマシンスペックでは基本的に辛いと思っています(カフェでノマドコーディングしたいので)。またメタプログラミングし放題の Ruby に導入するのは困難という認識です。

では、どういうときに 型定義 が欲しくなるのか考えてみます。

  1. コードを書いて実行して確認というトライアンドエラーを減らしたい、実行前にエラーをなるべく洗い出したい。
  2. 型定義起因による補完を効かせながらコーディングしたい。これはエディタやIDEのサポートも必要です。

1 について、Rubyの場合テストでカバレッジを稼いで、なるべくエラーの芽を潰しておこうとします。しかしユニットテストにおいて end-to-end テストが存在しない場合、単純なユーティリティ関数でなければ、モックやスタブに頼るようになりチェックが甘くなります。そのためテストは通過するものの、実際通しで動かしたとき想定とは異なる型のデータ受渡しが発生してしまうことがあります。

2 について、メソッド名や引数名から型をコードの読解で推測することは可能ですが、それなりの規模のアプリケーションやライブラリではコードコメントでドキュメント定義していないと(昔の自分や)他人の書いたコードを扱うのが困難になることが多いと思います。 そして例えば YARD で引数や戻り値の型をコメントに定義して、ドキュメントを生成したりIDEでコード補完に用いることが多いでしょう。

さてここから本題ですが、1 で型チェックしないで検証から漏れてしまう問題は、テストでメソッド呼び出しの引数と戻り値をよりチェックするアサーションを明示的に書いていく必要があります。一方 2 でYARDによるドキュメントの型定義は必ずしも実際のソースコードで走る処理と一致してるか保証されない問題もあります。

そこで『YARD定義によるメソッド引数と戻り値の型チェック』を実行時に行ってみたらどうかと考えてみました。

YARDはテンプレートで書きだす直前の解析データを、YARD::Registry から参照する機能を提供しています。またあらゆるメソッド呼び出し(:call)と戻り(:return)は、TracePoint 機構を使ってフックすることができます。これを組み合わせれば、割と簡単に、実行時に定義どおりに適切な型の引数が指定されてメソッド呼び出され、適切な戻り値が返っているかをチェックすることできると思います。

ということで、試してみましょう。

lib/dog.rb

これが YARD 定義を書いた検証対象のクラスになります。

module Animal
  #
  # This class is Dog
  #
  class Dog
    # @param name [String] a name
    # @param weight [Numeric] weight
    def initialize(name, weight)
      @name = name
      @weight = weight
      @children = []
    end

    # Add a child dog
    # 
    # @param dog [Animal::Dog] a child dog
    def add_child(dog)
      @children.push(dog)
    end

    # Run.
    # 
    # @param distance [Integer]
    # @return [String] message
    def run(distance)
      "#{@name} runs #{distance}."
    end

    # dummy method returns wrong type value
    # 
    # @return [Integer]
    def dummy
      "a string"
    end
  end
end

definition.rb

YARD::Registry から ClassObject と属する MethodObject を解析してチェックしやすい定義クラス MethodDefinition の集合 DefinitionStore に変換します。

require 'rubygems'
require 'yard'

class MethodDefinition
  def initialize(method_obj)
    @name = method_obj.name(true)
    @args = []
    @ret = nil

    load_docstr(method_obj.docstring)
  end

  def validate_arguments(args)
    errors = []

    args.each_with_index do |arg, i|
      arg_def = @args[i]

      ts = arg_def.types
      result = ts.find { |t|
          klass = Object.const_get(t)
          arg.is_a?(klass)
        }
      
      if result.nil?
        if ts.count > 1
          errors.push "#{arg_def.name}: #{arg.inspect} isn't any of " + ts.join(',')
        else
          errors.push "#{arg_def.name}: #{arg.inspect} isn't #{ts[0]}"
        end
      end
    end

    errors.empty? ? nil : "#{@name}(" + errors.join(', ') + ")"
  end

  def validate_return(ret_val)
    return nil if @ret.nil?

    ts = @ret.types
    result = ts.find { |t|
        klass = Object.const_get(t)
        ret_val.is_a?(klass)
      }

    if result.nil?
      if ts.count > 1
        "#{@name} returned #{ret_val.inspect} isn't any of " + ts.join(',') + ")"
      else
        "#{@name} returned #{ret_val.inspect} isn't #{ts[0]})"
      end
    else
      nil
    end
  end

  def load_docstr(docstr)
    docstr.tags.each do |tag|
      tag_name = tag.tag_name

      if tag_name == 'param'
        @args.push OpenStruct.new({
            name: tag.name,
            types: tag.types            
          })
      elsif tag_name == 'return'
        @ret = OpenStruct.new({ types: tag.types })
      end
    end
  end
end

class DefinitionStore
  def initialize
    @store = {}

    registry = YARD::Registry.load!
    registry.all(:class).each do |class_obj|
      add(class_obj)
    end
  end

  def add(class_obj)
    method_map = @store[class_obj.path] = {}
    class_obj.meths.each do |mt|
      method_map[mt.name] = MethodDefinition.new(mt)
    end
  end

  def get(klass_name, method_name)
    klass_def = @store[klass_name]
    klass_def[method_name]
  end
end

checker.rb

TracePoint でメソッド呼び出しと戻りをトレースして、引数または戻り値がYARD定義と適合してるかチェックします。

require 'tracer'
require_relative 'definition'

definition_store = DefinitionStore.new

TracePoint.trace(:call, :return) do |tp|
  klass_name = tp.defined_class.name
  method_def = definition_store.get(klass_name, tp.method_id)
  
  if tp.event == :call
    args = tp.binding.local_variables.map do |name|
             tp.binding.local_variable_get(name)
           end

    if err = method_def.validate_arguments(args)
      puts "Invalid call #{klass_name}#{err} on #{tp.path}:#{tp.lineno}"
    end
  elsif tp.event == :return && tp.method_id != :initialize
    if err = method_def.validate_return(tp.return_value)
      puts "Invalid return #{klass_name}#{err} on #{tp.path}:#{tp.lineno}"
    end
  end
end

# 試行
require_relative 'lib/dog'

dog1 = Animal::Dog.new(nil, "4.5")
p dog1.run(20)
dog1.dummy

dog2 = Animal::Dog.new("Taro", 6.5)
dog2.add_child(dog1)
dog2.add_child(nil)
p dog2.run(nil)

実行例

yard で解析データを生成してから、下記のようにチェッカーを実行してみます。

$ ruby cheker.rb
Invalid call Animal::Dog#initialize(name: nil isn't String, weight: "4.5" isn't Numeric) on ./lib/dog.rb:8
" runs 20."
Invalid return Animal::Dog#dummy returned "a string" isn't Integer) on ./lib/dog.rb:34
Invalid call Animal::Dog#add_child(dog: nil isn't Animal::Dog) on ./lib/dog.rb:17
Invalid call Animal::Dog#run(distance: nil isn't Integer) on ./lib/dog.rb:25
"Taro runs ."

とりあえずこの簡単な例で試すことは成功しました。もちろん引数が特定のモジュールを mix-in してるかといったチェックなど全然足りてはいません。ただこのような機構を、テストや開発向けデプロイ環境で動かすことで型違いを起因とするバグを減らせる手法の1つとして、提示できたんではないかと思います。

構造化ログのススメとRuby向けロガーOugaiを作った理由

構造化ログ

構造化ログ とは、機械的に処理しやすいログのことであり、その機構(ロギング)である。 英語圏では、 Structured Logging と表記される。たとえば Google Cloud の Stackdriver のドキュメントには下記の説明ページがあります。(残念ながら執筆時点で、これの日本語ページがまだできてないので、Google がどう訳すか興味深い)

Structured Logging  |  Stackdriver Logging  |  Google Cloud

普通のログと構造化ログの比較

普通のログは、基本的に タイムスタンプレベル 、そして メッセージ の文字列だけである。ログとして残す事象(イベント)のコンテキストになる情報はメッセージに適当に埋め込む。コンソール等で人が読みやすいものである。

構造化ログは、メッセージに埋め込んでいたコンテキストになる情報をそれぞれログ構造のフィールドに独立して持たせる。そのため後から解析がしやすい。そして出力するログはテキストベースで JSON にすることが多い。

では、見比べてみましょう。通常のRubyのLoggerと自作のOugaiでのログは次のようになります。『ユーザが記事を作成した』というログです(冠詞、削ってます)。

logger.info "User created article  (user_id=#{user.id} article_id=#{article.id}"
I, [2018-05-13T17:51:08.772542 #6253]  INFO -- : User created article  (user_id=123 article_id=45)
logger.info "User created article", user_id: user.id, article_id: article.id
{"pid":6253,"level":20,"time":"2018-05-13T17:52:25.147+09:00","msg":"User created article","user_id":123,"article_id":45}"

※デフォルトフォーマットとは異なります

見てわかる通り、普通のログは埋め込んで付帯情報を文字列化しつつ書かなくてはなりません。一方、構造化ログの方が JSON にした影響で長くなるのでコンソールでは読みづらいです。しかし読みづらいことはフォーマッタの動作環境での切替やparse機構を持つログビューワを使えば問題になりません。

構造化ログの方が解析しやすいというのは、例えば普通のログでは「あるユーザのログだけ抽出したいとき」に単に grep "user_id=10" とすると user_id が 101 など他のものまで引っかけてしまいます。構造化ログでは(主に JSONPath を使って)フィルタが $.user_id = 10 のように簡単に確実に絞り込めます。

ログ管理サービスと構造化ログ

AWS CloudWatch Logs, Google Cloud の Stackdriver LoggingLoggly, Logentries といったログ管理や解析向け SaaS はどれも構造化ログをサポートしていますし、むしろそうしないとそれらのサービスの機能を活かし切れません。ただ各サービスが提供するライブラリ自体は、構造化出力に対応してなかったりするので、Fluentd で中継させて送るのが現状ベストと思います。

Ougai を作った理由

元々 フロントAPIサーバを Node.js で書いて、バックエンドの管理サービスを Ruby/Rails で作るというプロジェクトによく参加してました。Node.js には Bunyan という有名な JSON ロガーがあり、これを好んで使っていました。しかしRubyにはいくつかの構造化ログを扱うライブラリはあるものの、各々のライブラリがフレームワークになっていました。Node.js 自体に console.info のようなコンソール出力向け機能しかないのですが、Ruby にはそれ自体に Logger があるのにも関わらず。もう一つマイクロサービスにおいて複数の言語で各サービスを実装することは多いですが、できれば横断的にログを解析したいためにそのフォーマットも統一したいと思いました。

これらの背景もあり、Ruby のオリジナル Logger ベースで、前述の Bunyan 互換でログ出力するフォーマットを持つロガーを自作しました。それが Ougai です。(Bunyan がイギリスの文学者名が由来だそうなので日本の文学者から名付けました)

github.com

Ruby のオリジナルの Logger クラスの拡張なのでいきなり導入しても、メッセージフィールドに出るだけでクラッシュすることはありません。徐々にコンテキストを独立したフィールドに移行することができます。フォーマットは標準で、JSON 出力用は Node.js の Bunyan と Pino 互換のもの、awesome_print を利用してターミナルでカラフルに見やすい Readable が入っています。JSON 用のはそれぞれ Bunyan と Pino が持つ専用ログビューワコマンドでもちろん閲覧できます。フックによって共通情報を入れやすくしたりする機能もあります。

Home · tilfin/ougai Wiki · GitHub

Wiki には Rails, Sidekiq, Fluentd 等と組み合わせた使うための設定例も載せてあります。

ougai | RubyGems.org | your community gem host

なお既にリリースしたから1年以上経っているので、知ってる範囲でも、知らないところでもそこそこ使ってもらえています。

Transgate という Node.js 製エージェントベースのタスクフローフレームワーク

Transgate というNode.js製のエージェントベースのタスクフロフレームワークを作った。

どうして作ったのか?

自宅の家電を操作するためのプログラムを書いていたら、色々なフローがごちゃごちゃになったから。 Dysonのファンから定期的に温度湿度を取得してデータベースに保存したり、Google Home/Assistant + IFTTT から来るメッセージを処理してIRKit を操作する。そのうち温度に従って自動的に IRKit 経由でエアコンを操作したくもなった、さてどう書こうかと?

どんなもの?

突然だけど空港などの荷物の仕分けをイメージしてください。エージェントは、ゲートから出てくるアイテムを受け取り、処理して別のゲートに送る。ゲートの向こう側がどうなっているかは、エージェントは何も知らない。エージェントは空のアイテムを来たら作業を終える。アーキテクチャのイメージはこんな感じです。

エージェントはゲートからアイテムを受け取ることと、別のゲートに新たにアイテムを送ることができる。アイテムはシンプルなオブジェクトだ。エージェントは自身のタスクに集中できる。だから前工程や次工程が増えても減ってもアイテムの構成が変わらなければ問題なく動く。そして入出力がシンプルなためユニットテストも簡単にかける。エージェントはゲートの実体を知らないので、入力元ゲートをスタブに、出力先ゲートをモックに、簡単に置き換えられる。

フレームワークに出てくる概念のまとめ

  • ゲート(Gate)はファイルストレージやデータベース、キュー、APIサービスといった入出力のエンドポイントです。
  • エージェント(Agent)はゲート間でアイテムを処理するタスクワーカーです。ゲートが何に通じているかは関知しません。
  • アイテム(Item)はゲート間を流れるシンプルなオブジェクトでエージェントが処理する対象です。

使用例

今回のフレームワークを作るきっかけになったホームコントロールプログラムを通じて説明してみます。 ちなみにこのプログラムは靴箱の中の Raspberry PI 上でデーモンとして動いています。

構成図

f:id:tilfin:20171123144501p:plain

メインプログラム (main.js)

const {
  Agent,
  HttpClientGate,
  HttpServerGate,
  IntervalGate,
  JointGate,
  StdoutGate,
  duplicator,
  mixer,
} = require('transgate');

const pino = require('pino')();
const config = require('konfig-yaml')();

const MongoGate = require('./lib/mongo_gate');
const IRKitGate = require('./lib/irkit_gate');

// Agent
const AnalysisCommander = require('./lib/analysis_commander');
const DysonCoolLinkRecorder = require('./lib/dyson/cool_link_recorder');
const EnvironmentalAnalyzer = require('./lib/environmental_analyzer');

// Gate
const slackGate = new HttpClientGate({ endpoint: config.slack.webhook_url });
const iftttGate = new HttpServerGate({ port: config.port });
const irkitGate = new IRKitGate(config.irkit.endpoint);
const intervalGate = new IntervalGate(60);
const mongoGate = new MongoGate(config.mongodb.endpoint, config.mongodb.collection);
const drToEaGate = new JointGate();

(async () => {
  try {
    await Agent.all(
      new AnalysisCommander(iftttGate, { irkitGate, slackGate }),
      new DysonCoolLinkRecorder(intervalGate, duplicator(mongoGate, drToEaGate)),
      new EnvironmentalAnalyzer(drToEaGate, { irkitGate, slackGate }),
    );
  } catch(err) {
    pino.error(err);  
    await iftttGate.close();
    await mongoGate.close();
  }

  intervalGate.clear();
})()
.catch(err => {
  pino.error(err);
});

7つのゲート

  • slackGate は slack にテキストメッセージをポストします。HttpClientGate のインスタンスで、アイテムとなるJSON{ "text": "<text message>" } です。
  • iftttGate は IFTTT の webhook から受け取った JSON をアイテムとして利用します。アイテムとなるJSON{ "target": "TV", "text": "<speaking words>" } です。
  • irkitGate はHTTPインターフェイスを備える赤外線送信器に命令します。アイテムとなるJSON{ "command": "celling_light_off" } です。
  • intervalGate は一定の間隔でアイテムを生成します。アイテムは { "time": <Date instance> } です。この場合は 1 分おきにエージェントの処理を走らせます。
  • mongoGate は MongoDB の指定のコレクションに送信されたアイテムを登録します。
  • drToEaGate は後述の DysonCoolLinkRecorder から EnvironmentalAnalyzer にアイテムの流すジョイントです。

3つのエージェント

  • AnalysisCommander は IFTTT の webhook から来た JSON をアイテムとして受け取り、操作対象とテキストから IRKit に対して送信すべき赤外線信号を指定します。slack には文言が解釈できなかったときにポストします。
  • DysonCoolLinkRecorder は Dyson PureCoolLink ファンから1分おきに温度と湿度を取得して、duplicator という複製機を挟んで MongoDB への書き込みとジョイントとなるゲートに送ります。
  • EnvironmentalAnalyzer はそのジョイントを通じて来た温度から閾値を超えていたらエアコンの操作を IRKit に要求します。自動的に操作をしたときは slack に記録します。

エージェントの実装

Agentのサブクラスを作ります。main メソッドで受け取ったアイテムを処理して指定先のゲートに新たなアイテムを送る処理を書きます。before/after のフックメソッドを使って、初期化処理や別に利用するプロセス(例えば headless chrome) をここで制御(起動・停止)します。

下記は EnvironmentalAnalyzer の実装例でです。室温が摂氏17度以下になったらエアコンをオンにします。

const { Agent } = require('transgate');

module.exports = 
class EnvironmentalAnalyzer extends Agent {
  async before() {
    this._preTemp = null;
    this._airconAlive = false;
  }

  async main(item, { irkitGate, slackGate }) {
    const curTemp = item.temp;

    if (this._preTemp && this._preTemp > 17 && curTemp <= 17) {
      if (!this._airconAlive) {
        await irkitGate.sendAll({ command: 'aircon_on' });
        this._airconAlive = true;
        await slackGate.send({ text: `Turn on aircon because temp is down to ${curTemp}` });          
      }
    }

    this._preTemp = curTemp;
  }
}

コンストラクタとアイテムの入力元ゲートが隠蔽されているのは、 null を受け取ると次のゲートに送り、自身は終了するという仕様の実装を意識させないためです。

特徴のまとめ

  • 複雑なデーモンやバッチプログラムに向いている。
  • 同じエージェントを並列で動かすようなことは想定していないので、大量に捌く処理には向いてない。
  • メインプログラムで登場するゲートとエージェントと、アイテムのタスクフローが定義できる。そのためこれだけで全体が把握できる。
  • エージェントの処理は async/await で擬似的に同期に書けつつ、エージェントが多くても Node.js なのでスレッドベースのように重くならない。
  • ゲートの置き換えが容易なので、エージェントのユニットテストが書きやすく、部分的な実行の確認もしやすい。

予想される疑問と答え

参照先サービスは全部ゲートになるのか? 

Noです。ゲート間は一方通行に限定されます。エージェントはその先を知らない。つまりリクエストを投げて、それに対するレスポンスを得ることはできません。往復ではなく、ループにすることは可能ですが、ステートレスなのでどの送り出したアイテム(リクエスト)に対してのレスポンスかはわからないのです。ゲートは、エージェントにとってトリガーとなるものを出す部分と成果を送る部分になります。

一連のフローが終わったら時にキッカーに完了を通知するには?

キューシステムはタスクが完了したら完了通知を送る必要が往往にあります。こういった場合は、アイテムにそのコンテキストを持たせてフローに流して、最後のゲートが完了通知を送る役割を担うようにします。

ロガーはゲートにすべきか?

ログがアウトプットそのものになるならゲートにすべきです。そうすれば後からゲートをさらに Agent にジョイントするものに置き換えて、そこからログ解析サービスに投げるといった修正も容易にできます。

ゲートにどこまでロジックを含めていいのか?

ゲートはできる限りシンプルな方が良いです。エージェントはテストしやすいように設計しますが、ゲートそのものにロジックを入れてしまうと入出力先を付け替えてテストできなくなります。ただプロジェクト共通のロジックでそれがフォーマット程度であれば、ゲートに実装してもいいでしょう。複雑ならばそれ用のエージェントを作ってゲートの前に置き、ジョイントで繋げるだけです。

Transgate に興味を持っていただけたら幸いです。

English version Transgate is Agent-based taskflow framework for Node.js

Windows 上に Python 環境を構築して Tensorflow GPU + Keras で日本古典籍字形の文字認識を試すまで

tilfin.hatenablog.com

こちらの記事で書いたように Windows で Tensorflow を GPU で試してみました。この中で Python をオフィシャルのインストーラから入れて virtualenv で動作環境を作成したのですが、後々 matplotlib や OpenCV を入れようとしたところ Windows のために諸々嵌ることがあったので、より簡単に All-in-One 導入できる Anaconda から入れてみることにしました。

www.procrasist.com

またこちらの記事で見た Keras が、今後 Tensorflow で色々と試す上で非常分かりやすそうだったので、導入してみることにしました。

Anaconda で Python 3.5 をセットアップ

既にインストールしていた Python は「プログラムと機能」からアンインストールしておきました。

Download Anaconda Now! | Continuum から [Python 3.5 version 64-BIT INSTALLER] をダウンロードして実行します。インストール ウィザードで環境変数を通しておきます。(デフォルトのインストール場所ではなく D:\Anaconda35 に自分は入れました。)

Tensorflow + Keras をセットアップ

環境構築

ここから先は PowerShell で行います。

conda で work という名前の環境を作ります。

PS D:\> conda create -n work python=3.5 anaconda

…省略…

Extracting packages ...
[      COMPLETE      ]|##################################################| 100%
Linking packages ...
        1 個のファイルをコピーしました。##################               |  71%
[      COMPLETE      ]|##################################################| 100%
#
# To activate this environment, use:
# > activate work
#
# To deactivate this environment, use:
# > deactivate work
#
# * for power-users using bash, you must source
#

Tensorflow の導入

work 環境にして pip で tensorflow と tensorflow-gpu をインストールします。

PS D:\> activate work

PS D:\> pip install tensorflow
Collecting tensorflow
  Using cached tensorflow-0.12.1-cp35-cp35m-win_amd64.whl
Requirement already satisfied (use --upgrade to upgrade): wheel>=0.26 in d:\anaconda3\lib\site-packages (from tensorflow)
Collecting protobuf>=3.1.0 (from tensorflow)
  Using cached protobuf-3.1.0.post1-py2.py3-none-any.whl
Installing collected packages: protobuf, tensorflow
Successfully installed protobuf-3.1.0.post1 tensorflow-0.12.1

PS D:\> pip install tensorflow-gpu
Collecting tensorflow-gpu
  Using cached tensorflow_gpu-0.12.1-cp35-cp35m-win_amd64.whl
Installing collected packages: tensorflow-gpu
Successfully installed tensorflow-gpu-0.12.1

Keras をセットアップ

Keras Documentation は numpy, scipy, pyyaml OpenCV, HDF5, hdf5 に依存しています。この中で OpenCV は anaconda で入らないので別途インストール必要があります。

OpenCV 3.2 をインストール

hikuichi.hatenablog.com 単純に pip でインストールしても Windows では動かないので、こちらを参考にビルド済みパッケージを使って、OpenCV をインストールします。 http://www.lfd.uci.edu/~gohlke/pythonlibs/ から opencv_python‑3.2.0+contrib‑cp35‑cp35m‑win_amd64.whl をダウンロードして、 pip で絶対パスを指定してインストールします。

PS D:\> pip install D:\opencv_python-3.2.0+contrib-cp35-cp35m-win_amd64.whl
Processing d:\opencv_python-3.2.0+contrib-cp35-cp35m-win_amd64.whl
Installing collected packages: opencv-python
Successfully installed opencv-python-3.2.0+contrib

Keras をインストール

PS D:\> pip install keras
Collecting keras
  Downloading Keras-1.2.0.tar.gz (167kB)
    100% |################################| 174kB 911kB/s
Collecting theano (from keras)
  Downloading Theano-0.8.2.tar.gz (2.9MB)
    100% |################################| 2.9MB 471kB/s
Building wheels for collected packages: keras, theano
  Running setup.py bdist_wheel for keras ... done
  Stored in directory: C:\Users\someone\AppData\Local\pip\Cache\wheels\f8\2c\8e\ffffff128220b1acf6fcd5e692269c6e8d607
44c15fd160c2c
  Running setup.py bdist_wheel for theano ... done
  Stored in directory: C:\Users\someone\AppData\Local\pip\Cache\wheels\96\2b\3d\ffffff24a7171a4afb7144d1e944a7be643b4
48b23a35b9937
Successfully built keras theano
Installing collected packages: theano, keras
Successfully installed keras-1.2.0 theano-0.8.2

Matplotlib, OpenCV, Keras を試す

Matplotlib を試す

REPL からシグモイド関数をグラフ描画して表示します。

PS D:\> python
Python 3.5.2 |Anaconda 4.2.0 (64-bit)| (default, Jul  5 2016, 11:41:13) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> import matplotlib.pylab as plt
>>> def sigmoid(x):
...   return 1 / (1 + np.exp(-x))
...
>>> x = np.arange(-5.0, 5.0, 0.1)
>>> y = sigmoid(x)
>>> plt.plot(x, y)
SetProcessDpiAwareness(2) failed: COM error 0xffffffff80070005  (Unknown error 0x0ffffffff80070005), using 2
[<matplotlib.lines.Line2D object at 0x000002370B81EA20>]
>>> plt.ylim(-0.1,1.1)
(-0.1, 1.1)
>>> plt.show()
>>>
>>> ^Z

f:id:tilfin:20170108213321p:plain

OpenCV を試す

REPL から画像ファイルを開いて表示してみます。

PS D:\> python
Python 3.5.2 |Anaconda 4.2.0 (64-bit)| (default, Jul  5 2016, 11:41:13) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> cv2.imread('pic.jpg')
>>> img = cv2.imread('./pic.jpg')
>>> cv2.imshow('win1', img)
>>> cv2.waitKey(0)
13
>>>
>>> cv2.destroyWindow('win1')
>>> ^Z

f:id:tilfin:20170108213343p:plain

cv2.waitKey(0) の後、ウィンドウに対して Enter を入力すると表示されます。

日本古典籍字形の文字認識で Keras を試す

www.procrasist.com Keras を実際に試してみます。こちらの記事を参考に日本古典籍字形の文字認識を試してみました。

書名一覧 | 日本古典籍字形データセット の「機械学習による文字認識」から サンプルコード(TAR+GZ 24.57 MB) をダウンロードします。 D:\pmjt_sample_20161116 に展開しました。

PS D:\pmjt_sample_20161116> python .\run.py
Using TensorFlow backend.
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library cublas64_80.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library cudnn64_5.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library cufft64_80.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library nvcuda.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library curand64_80.dll locally
X_train shape: (19909, 28, 28, 1)
19909 train samples
3514 test samples
Train on 19909 samples, validate on 3514 samples
Epoch 1/12
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:885] Found device 0 with properties:
name: GeForce GTX 960
major: 5 minor: 2 memoryClockRate (GHz) 1.253
pciBusID 0000:01:00.0
Total memory: 2.00GiB
Free memory: 1.64GiB
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:906] DMA: 0
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:916] 0:   Y
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:975] Creating TensorFlow device (/gpu:0) -> (device: 0, name: GeForce GTX 960, pci bus id: 0000:01:00.0)
19909/19909 [==============================] - 5s - loss: 1.4426 - acc: 0.5135 - val_loss: 0.7607 - val_acc: 0.7803
Epoch 2/12
19909/19909 [==============================] - 3s - loss: 0.7044 - acc: 0.7817 - val_loss: 0.4843 - val_acc: 0.8677
Epoch 3/12
19909/19909 [==============================] - 3s - loss: 0.5310 - acc: 0.8401 - val_loss: 0.3561 - val_acc: 0.8973
Epoch 4/12
19909/19909 [==============================] - 2s - loss: 0.4451 - acc: 0.8646 - val_loss: 0.3038 - val_acc: 0.9124
Epoch 5/12
19909/19909 [==============================] - 2s - loss: 0.3969 - acc: 0.8815 - val_loss: 0.2888 - val_acc: 0.9121
Epoch 6/12
19909/19909 [==============================] - 2s - loss: 0.3606 - acc: 0.8928 - val_loss: 0.2586 - val_acc: 0.9266
Epoch 7/12
19909/19909 [==============================] - 2s - loss: 0.3389 - acc: 0.9012 - val_loss: 0.2390 - val_acc: 0.9306
Epoch 8/12
19909/19909 [==============================] - 2s - loss: 0.3208 - acc: 0.9065 - val_loss: 0.2283 - val_acc: 0.9308
Epoch 9/12
19909/19909 [==============================] - 2s - loss: 0.2999 - acc: 0.9116 - val_loss: 0.2147 - val_acc: 0.9351
Epoch 10/12
19909/19909 [==============================] - 2s - loss: 0.2819 - acc: 0.9176 - val_loss: 0.2038 - val_acc: 0.9397
Epoch 11/12
19909/19909 [==============================] - 2s - loss: 0.2677 - acc: 0.9181 - val_loss: 0.1995 - val_acc: 0.9394
Epoch 12/12
19909/19909 [==============================] - 2s - loss: 0.2605 - acc: 0.9219 - val_loss: 0.1968 - val_acc: 0.9405
Test score: 0.196767529901
Test accuracy: 0.940523619806

Ubuntu に OpenCV をインストールして Python で画像から顔をクロップするまで

以前に Tensorflow のセットアップをして、付属するサンプルを試すところまでして終わっていましたが、 実際に何か試してみないとということで、よくある顔判別をやるためにデータを用意する仕組みを作ろうとしてます。

Python 3 と OpenCV 3 を使って画像から顔検出してそれを正方形でクロップして同一サイズにするというスクリプトを作ります。

セットアップ

ほぼ下記のサイトの通りやったところうまくいきました。 www.pyimagesearch.com 以前にcudaのライブラリを VirtualBox 上の仮想環境にに入れていたせいでコンパイルエラーになりましたが、 それも下記のオプションを指定して進められました。 codeyarns.com

スクリプト

Python はほとんど書いたことないのですが、色々と調べてなんとかそれらしいものができました。

import cv2
import sys
import os


class FaceCropper(object):
    CASCADE_PATH = "data/haarcascades/haarcascade_frontalface_default.xml"

    def __init__(self):
        self.face_cascade = cv2.CascadeClassifier(self.CASCADE_PATH)

    def generate(self, image_path, show_result):
        img = cv2.imread(image_path)
        if (img is None):
            print("Can't open image file")
            return 0

        #img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        faces = self.face_cascade.detectMultiScale(img, 1.1, 3, minSize=(100, 100))
        if (faces is None):
            print('Failed to detect face')
            return 0

        if (show_result):
            for (x, y, w, h) in faces:
                cv2.rectangle(img, (x,y), (x+w, y+h), (255,0,0), 2)
            cv2.imshow('img', img)
            cv2.waitKey(0)
            cv2.destroyAllWindows()

        facecnt = len(faces)
        print("Detected faces: %d" % facecnt)
        i = 0
        #height, width = img.shape[:2]

        for (x, y, w, h) in faces:
            r = max(w, h) / 2
            centerx = x + w / 2
            centery = y + h / 2
            nx = int(centerx - r)
            ny = int(centery - r)
            nr = int(r * 2)

            faceimg = img[ny:ny+nr, nx:nx+nr]
            lastimg = cv2.resize(faceimg, (32, 32))
            i += 1
            cv2.imwrite("image%d.jpg" % i, lastimg)


if __name__ == '__main__':
    args = sys.argv
    argc = len(args)

    if (argc != 2):
        print('Usage: %s [image file]' % args[0])
        quit()

    detecter = FaceCropper()
    detecter.generate(args[1], True)

CASCADE_PATH は OpenCV に付属している顔検出の XML 定義のファイルパスを指定します。 顔検出は face_cascade.detectMultiScale の部分で最低の検出サイズを 100x100 として、scaleFactor=1.1, minNeighbors=3 を色々試行して良さそうな値としました。 また下記のサイトを参考にさせてもらいました。 物体検出(detectMultiScale)をパラメータを変えて試してみる(minNeighbors編) | Workpiles

スクリプトJPEGファイルパスを指定すると、サイズ 32x32 の imageナンバー.jpg というファイルが検出された数だけ保存されます。 generateメソッドの第2引数を True にすると検出状態をウィンドウで表示します。

実装例によっては一旦グレースケールに変換しているものを見ましたが(上記ではコメントアウトしてます)、手元の画像で試した限り結果に差はなさそうでした。データセットとしてDeep Learningに用いることまで考慮するとモノクロの方が情報削減にはなりそうですが。

TensorFlow が正式に Windows サポートして GPU が使えたので試してみた

Google から正式に Tensorflow が Windows 対応して GPU が使えるとのアナウンスがありました。
https://developers.googleblog.com/2016/11/tensorflow-0-12-adds-support-for-windows.html

セットアップ環境

この環境で TensorFlow を試しみたいと思います。なるべくDドライブにセットアップするようにしています。

CUDA と cuDNN をインストール

CUDA Toolkit 8.0

https://developer.nvidia.com/cuda-downloads

  • Operating System: Windows
  • Architecture: x86_64
  • Version: 10
  • Installer Type: exe (network)

普通にインストーラを実行しました。

cuDNN v5.1

開発者アカウントを登録して利用規約に同意してダウンロードしました。

https://developer.nvidia.com/rdp/cudnn-download cuDNN v5.1 Library for Windows 10 を落として zip 内の cuda フォルダ内を C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0 にコピー展開します。

Python 3.5 のセットアップ

Windows の場合 Anaconda を使った方が後々楽なのでこちらを奨めます。 (2017/01/08 追記)
http://tilfin.hatenablog.com/entry/2017/01/08/220556 内に記載してます。

普通にトップページからダウンロードすると 32bit 版なので、https://www.python.org/downloads/windows/ から Download Windows x86-64 executable installer をダウンロードしてインストールします。 ※ なお、 D:\Python35 にインストールしました。

3.5系は環境変数の追加や pip も同時に入れてくれました。 PowerShell から一応 pip のアップグレードもしました。

virtualenv をインストール

実行環境用の作業フォルダを作れるモジュールを入れます。

PS D:\> pip install --upgrade virtualenv
Collecting virtualenv
  Downloading virtualenv-15.1.0-py2.py3-none-any.whl (1.8MB)
    100% |################################| 1.8MB 646kB/s
Installing collected packages: virtualenv
Successfully installed virtualenv-15.1.0

TensorFlow のパッケージをインストール

PowerShell から pip で tensorflow と tensorflow-gpu を入れます。 https://pypi.python.org/pypi/tensorflow

tensorflow

PS D:\> pip install tensorflow
Processing y:\tensorflow-0.12.0rc0-cp35-cp35m-win_amd64.whl
Collecting six>=1.10.0 (from tensorflow==0.12.0rc0)
  Using cached six-1.10.0-py2.py3-none-any.whl
Collecting protobuf==3.1.0 (from tensorflow==0.12.0rc0)
  Downloading protobuf-3.1.0-py2.py3-none-any.whl (339kB)
    100% |################################| 348kB 2.2MB/s
Collecting wheel>=0.26 (from tensorflow==0.12.0rc0)
  Using cached wheel-0.29.0-py2.py3-none-any.whl
Collecting numpy>=1.11.0 (from tensorflow==0.12.0rc0)
  Downloading numpy-1.11.2-cp35-none-win_amd64.whl (7.6MB)
    100% |################################| 7.6MB 179kB/s
Requirement already satisfied (use --upgrade to upgrade): setuptools in d:\python35\lib\site-packages (from protobuf==3
1.0->tensorflow==0.12.0rc0)
Installing collected packages: six, protobuf, wheel, numpy, tensorflow
Successfully installed numpy-1.11.2 protobuf-3.1.0 six-1.10.0 tensorflow-0.12.0rc0 wheel-0.29.0

tensorflow-gpu

PS D:\> pip install tensorflow-gpu
Collecting tensorflow-gpu
  Downloading tensorflow_gpu-0.12.0rc0-cp35-cp35m-win_amd64.whl (32.5MB)
    100% |################################| 32.5MB 40kB/s
Requirement already satisfied: wheel>=0.26 in d:\python35\lib\site-packages (from tensorflow-gpu)
Requirement already satisfied: numpy>=1.11.0 in d:\python35\lib\site-packages (from tensorflow-gpu)
Requirement already satisfied: six>=1.10.0 in d:\python35\lib\site-packages (from tensorflow-gpu)
Requirement already satisfied: protobuf==3.1.0 in d:\python35\lib\site-packages (from tensorflow-gpu)
Requirement already satisfied: setuptools in d:\python35\lib\site-packages (from protobuf==3.1.0->tensorflow-gpu)
Installing collected packages: tensorflow-gpu
Successfully installed tensorflow-gpu-0.12.0rc0

実行開始

virtualenv で D:\tensorflow に作業フォルダを作ります。

PS D:\> virtualenv --system-site-packages D:\tensorflow
Using base prefix 'd:\\python35'
New python executable in D:\tensorflow\Scripts\python.exe
Installing setuptools, pip, wheel...done.

学習解析サンプルを用意する

適当なところで git clone --recurse-submodules https://github.com/tensorflow/tensorflow します。自分は普段 VirtualBoxLinux を動かしていて SMB でファイル共有するのでそちらでクローンしました。 tensorflow/tensorflow/modelsD:\tensorflow\models となるようにコピーします。

MNIST を試す

手書き数字の解析プログラムを試してみます。

PS D:\> cd tensorflow\tensorflow\models\image\mnist
PS D:\tensorflow\models\image\mnist> python convolutional.py
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfu
lly opened CUDA library cublas64_80.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfu
lly opened CUDA library cudnn64_5.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfu
lly opened CUDA library cufft64_80.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfu
lly opened CUDA library nvcuda.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfu
lly opened CUDA library curand64_80.dll locally
Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.
Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes.
Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes.
Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes.
Extracting data\train-images-idx3-ubyte.gz
Extracting data\train-labels-idx1-ubyte.gz
Extracting data\t10k-images-idx3-ubyte.gz
Extracting data\t10k-labels-idx1-ubyte.gz
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:885] F
ound device 0 with properties:
name: GeForce GTX 960
major: 5 minor: 2 memoryClockRate (GHz) 1.253
pciBusID 0000:01:00.0
Total memory: 2.00GiB
Free memory: 1.64GiB
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:906] D
MA: 0
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:916] 0
:   Y
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:975] C
reating TensorFlow device (/gpu:0) -> (device: 0, name: GeForce GTX 960, pci bus id: 0000:01:00.0)
E c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:586] C
ould not identify NUMA node of /job:localhost/replica:0/task:0/gpu:0, defaulting to 0.  Your kernel may not have been bu
ilt with NUMA support.
Initialized!
Step 0 (epoch 0.00), 50.9 ms
Minibatch loss: 8.334, learning rate: 0.010000
Minibatch error: 85.9%
Validation error: 84.6%
Step 100 (epoch 0.12), 12.1 ms
Minibatch loss: 3.226, learning rate: 0.010000
Minibatch error: 4.7%
Validation error: 7.3%
Step 200 (epoch 0.23), 12.0 ms
Minibatch loss: 3.404, learning rate: 0.010000
Minibatch error: 10.9%
(省略)
Minibatch loss: 1.609, learning rate: 0.006302
Minibatch error: 0.0%
Validation error: 1.0%
Test error: 0.8%

割と早く終わったので GPU が効いているのでしょう。上手く動いたことは確認できましたが、わかりやすい ImageNet を次に試します。

ImageNet を試す

画像を解析して何の画かを当てる ImageNet です。

PS D:> cd \tensorflow\models\imagenet

まず準備です。 python .\classify_image.py を実行します。

PS D:\tensorflow\models\image\imagenet> python .\classify_image.py
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library cublas64_8
0.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library cudnn64_5.
dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library cufft64_80
.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library nvcuda.dll
 locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library curand64_8
0.dll locally
>> Downloading inception-2015-12-05.tgz 100.0%
Successfully downloaded inception-2015-12-05.tgz 88931400 bytes.
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:885] Found device 0 with properties:
name: GeForce GTX 960
major: 5 minor: 2 memoryClockRate (GHz) 1.253
pciBusID 0000:01:00.0
Total memory: 2.00GiB
Free memory: 1.64GiB
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:906] DMA: 0
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:916] 0:   Y
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:975] Creating TensorFlow device (/gpu:0)
 -> (device: 0, name: GeForce GTX 960, pci bus id: 0000:01:00.0)
E c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:586] Could not identify NUMA node of /jo
b:localhost/replica:0/task:0/gpu:0, defaulting to 0.  Your kernel may not have been built with NUMA support.
W c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\framework\op_def_util.cc:332] Op BatchNormWithGlobalNormalization is depr
ecated. It will cease to work in GraphDef version 9. Use tf.nn.batch_normalization().
W c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\bfc_allocator.cc:217] Ran out of memory trying to allocate
 1.91GiB. The caller indicates that this is not a failure, but may mean that there could be performance gains if more memory is available.
giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca (score = 0.89233)
indri, indris, Indri indri, Indri brevicaudatus (score = 0.00859)
lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens (score = 0.00264)
custard apple (score = 0.00141)
earthstar (score = 0.00107)

適当に M:\fuji.jpg に富士山の写真をおきました。 python .\classify_image.py --image_file M:\fuji.jpg で解析させます。

PS D:\tensorflow\models\image\imagenet> python .\classify_image.py --image_file M:\fuji.jpg
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library cublas64_8
0.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library cudnn64_5.
dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library cufft64_80
.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library nvcuda.dll
 locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library curand64_8
0.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:885] Found device 0 with properties:
name: GeForce GTX 960
major: 5 minor: 2 memoryClockRate (GHz) 1.253
pciBusID 0000:01:00.0
Total memory: 2.00GiB
Free memory: 1.64GiB
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:906] DMA: 0
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:916] 0:   Y
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:975] Creating TensorFlow device (/gpu:0)
 -> (device: 0, name: GeForce GTX 960, pci bus id: 0000:01:00.0)
E c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:586] Could not identify NUMA node of /jo
b:localhost/replica:0/task:0/gpu:0, defaulting to 0.  Your kernel may not have been built with NUMA support.
W c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\framework\op_def_util.cc:332] Op BatchNormWithGlobalNormalization is depr
ecated. It will cease to work in GraphDef version 9. Use tf.nn.batch_normalization().
W c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\bfc_allocator.cc:217] Ran out of memory trying to allocate
 1.91GiB. The caller indicates that this is not a failure, but may mean that there could be performance gains if more memory is available.
volcano (score = 0.91087)
fire screen, fireguard (score = 0.00192)
alp (score = 0.00162)
lakeside, lakeshore (score = 0.00130)
geyser (score = 0.00077)

volcano (score = 0.91087) 火山と認識されましたね。ちなみに写真は雪化粧してる富士山でした。

W c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\bfc_allocator.cc:217] Ran out of memory trying to allocate
 1.91GiB. The caller indicates that this is not a failure, but may mean that there could be performance gains if more memory is available.

と警告が出ていたのでもっとメモリがあるといいのでしょう。

とりあえず特に嵌らずに動いたのでみなさんもお試しください。