技術研究所の (あ) です。
前回書いたとおり、ちょいと機械学習を用いた画像分類を試してみる機会がありました。
そうすると、せっかくなので自分の手持ちの写真で試してみたくなります。
手持ちの写真で、判りやすく分類できて、そこそこ数があって、すぐに用意できるものというと…僕の場合こんなのになります。

虹と環天頂アークと、環水平アーク、後者二つは聞いたことがない人も多いかもしれませんが、太陽の周りにできる暈の仲間です。似てるような似てないような、これらの自然現象の写真の自動分類をやってみます。

そもそも機械学習による分類って?

まずは、そもそも機械学習による画像分類ってどういうこと? というのをざっくりイメージだけ解説しておきます。「ざっくりイメージ」なので正確さはちょっと置いておきます。

画像の中にはいろいろな「特徴」があります。図で描くとこんな感じで、うまく「特徴」を選ぶと、持っている特徴の度合いで、画像がそれぞれの「分類」に分けられます。

機械学習は、学習データから、このうまい「特徴」を見つけ、さらに分類の間の境界線を見つけます。そして、与えられた新たな画像が、どのエリアに入るか、で分類を判定します。

たとえば下の図の★印の位置だったとすると、「イ」と判定されるわけです。学習の仕方や、学習させたデータの組み合わせによっては境界線の位置が変わり、同じ★印の位置が 「ア」や 「カ」 と判定されるかもしれません。

この学習には多層のニューラルネットワークがよく用いられるわけですが、この場合は中間の層のどこかのノードが何らかの特徴に対応する感じになります。下の図のような感じで、この特徴とこの特徴がこれくらいあるから、きっとこれは「ア」だろう、となるわけです。

かなりアバウトな説明ですが、イメージは掴めたでしょうか?

IBM Bluemix の Visual Recognition

今回、使ったのは IBM の Bluemix の Visual Recognition です。これまで、Bluemix のアカウントがあればお試し的に無料で使えたのですが、ちょうどこの 7月から有料化されてしまいました(;_;) (この記事に書いている内容は 6月に試したものです)。[追記: 8月からに変更されているようです]

Visual Recognition は Web API を通して利用します。Java, Node.js, Python, Swift など向けの SDK が用意されていますが、curl を使ってコマンドラインから使うようなこともできます。デフォルトの分類器が用意されているので、自分で学習データを用意せずとも、分類したい写真を指定してその分類結果を得ることができます。

試しに虹の写真 (test_rb1.jpg) を分類してみます。

curl -X POST -F "images_file=@test_rb1.jpg" "https://gateway-a.watsonplatform.net/visual-recognition/api/v3/classify?api_key={API_KEY}&version=2016-05-20"

こんな感じで、ファイル名や URL を指定してコマンドを実行します。API_KEY のところには、Bluemix のサービス設定のページで得られる文字列を入れます。結果は、

{
    "images": [
        {
            "classifiers": [
                {
                    "classes": [
                        {
                            "class": "cloud",
                            "score": 0.475021,
                            "type_hierarchy": "/elements/textures/cloud"
                        },
                        {
                            "class": "beach",
                            "score": 0.450166,
                            "type_hierarchy": "/activities/attractions/beach"
                        },
                        {
                            "class": "sunset",
                            "score": 0.310026,
                            "type_hierarchy": "/shooting modes/scene modes/sunset"
                        }
                    ],
                    "classifier_id": "default",
                    "name": "default"
                }
            ],
            "image": "test_rb1.jpg"
        }
    ],
    "images_processed": 1
}

こんな感じで返ってきます。cloud (雲)、beach (浜辺)、sunset (夕暮れ) がそれぞれ 0.475, 0.450, 0.310 くらいの確信度、という意味です。虹は判別されていませんが、出てきた分類はまあ、だいたい納得はいく、というところでしょうか。

複数の画像を .zip でまとめて一気に渡すこともできます (最大20枚)。

curl -X POST -F "images_file=@test.zip" "https://gateway-a.watsonplatform.net/visual-recognition/api/v3/classify?api_key={API_KEY}&version=2016-05-20"

カスタムの分類器を作る

手持ちの写真で新たな分類を学習させるには、その新たな分類に分類されるべき写真と、そうでない写真をたくさん用意する必要があります。本来なら100枚、1000枚と用意せねばならないところですが、とっさにそんなにはなかなか集められません。でも、Bluemix の Visual Recognition の場合、少ない学習データからでもそこそこの精度を出してくれるようです (10枚以下だと分類器が一見作れたように見えても、分類してくれないようです)。
今回は、虹の写真 17枚、環天頂アークの写真 27枚、環水平アークの写真 17枚を用意しました。また、対照用の「それ以外」のデータとして、これらの現象が写っていない写真を、53枚用意しました。

Visual Recognition の API のバージョンが 2 だったころは、ある分類か否か、の分類器しか作れなかったのですが、2016年5月にバージョン3 になって、一つの分類器で複数の種類 (クラス) を識別できるようになりました。それぞれのクラスのデータは、zip でまとめた形で渡せます。
虹のデータを rb.zip、環天頂アークのデータを cz.zip、環水平アークのデータを ch.zip、それ以外のデータを other.zip として、

curl -X POST -F "circumzenithal_arc_positive_examples=@cz.zip" -F "circumhorizontal_arc_positive_examples=@ch.zip" -F "rainbow_positive_examples=@rb.zip" -F "negative_examples=@other.zip" -F "name=ao1" "https://gateway-a.watsonplatform.net/visual-recognition/api/v3/classifiers?api_key={API_KEY}&version=2016-05-20"

こんな感じでコマンドを叩きます。*_positive_example の * の部分がクラスの名前になります。”name” はわかりやすいように適当につけます。

{
    "classifier_id": "ao1_012321230",
    "name": "ao1",
    "owner": "(略)",
    "status": "training",
    "created": "2016-06-02T08:27:54.163Z",
    "classes": [
        {"class": "rainbow"},
        {"class": "circumzenithal_arc"},
        {"class": "circumhorizontal_arc"}
    ]
}

こんな感じで結果が返ってくれば成功です。status が training ですが、これくらいの枚数であれば、ほとんど待たずに使えるようになります。渡すデータが 1000枚とかになると、20分以上待たされたりします。この分類器を使うには、分類器の ID を指定した json ファイルを作っておく必要があります。

{
    "classifier_ids": [
            "ao1_012321230"
        ],
}

分類器の ID はカンマで区切って複数指定できますが、とりあえず今は一つだけ。このファイル名を ao.json とすると、何か画像を分類するには以下のような感じでファイル名をパラメータとして渡します。
curl -X POST -F "images_file=@test.zip" -F "parameters=@ao.json" "https://gateway-a.watsonplatform.net/visual-recognition/api/v3/classify?api_key={API_KEY}&version=2016-05-20"

デフォルトだと、0.5未満の確信度のものは表示されないので、全部表示させるためには URL 部分に threshold というオプションを追加します (分類器を列挙した json ファイルで指定する方法もあります)。

curl -X POST -F "images_file=@test.zip" -F "parameters=@ao.json" "https://gateway-a.watsonplatform.net/visual-recognition/api/v3/classify?api_key={API_KEY}&version=2016-05-20&threshold=0.0"

結果は、デフォルトの分類器のときと同じように返ってきます。

{
    "images": [
        {
            "classifiers": [
                {
                    "classes": [
                        {
                            "class": "circumhorizontal_arc",
                            "score": 0.85903
                        },
                        {
                            "class": "circumzenithal_arc",
                            "score": 0.631123
                        },
                        {
                            "class": "rainbow",
                            "score": 0.0749748
                        }
                    ],
                    "classifier_id": "ao1_012321230",
                    "name": "ao1"
                },
            ],
            "image": "test.zip/test1.jpg"
        },
        (略)
    ],
    "images_processed": 0
}

そして、虹と環天頂アークと環水平アークは識別できたのか?

テスト用の画像として、虹、環天頂アーク、環水平アークそれぞれ 2枚、何の現象も写っていない空の風景を 1枚の 7枚を与えてみた結果は、以下のようになりました。

  • テスト1_虹.jpg
    虹: 0.995
    環水平アーク: 0.516
    環天頂アーク: 0.067
  • テスト2_虹.jpg
    虹: 0.996
    環水平アーク: 0.125
    環天頂アーク: 0.069
  • テスト3_環水平.jpg
    虹: 0.117
    環水平アーク: 0.831
    環天頂アーク: 0.726
  • テスト4_環水平.jpg
    虹: 0.208
    環水平アーク: 0.320
    環天頂アーク: 0.931
  • テスト5_環天頂.jpg
    虹: 0.032
    環水平アーク: 0.890
    環天頂アーク: 0.480
  • テスト6_環天頂.jpg
    虹: 0.074
    環水平アーク: 0.739
    環天頂アーク: 0.570
  • テスト7_なし.jpg
    虹: 0.034
    環水平アーク: 0.429
    環天頂アーク: 0.884

確信度の一番高いものを「答え」と見ると、環天頂アークと環水平アークを逆に答えているものが多いですが、虹とこの二つはきちんと区別ができています。今回はデータ数がさすがに少なすぎますので、これでも上々の結果と言えるでしょう。もうちょっと写真をかき集めてまた試してみたいと思います。
こうやって、お手軽に試せるのはなかなかおもしろいですね。
他にもいろいろとマニアックな分類を試してみたくなります。