Caffeで機械学習 -1-  リファレンスモデルを使って画像をカテゴリ分類


※ WIP。

# はじめに

* caffeいれたばかりで使い方よくわからない
* 機械学習で何か実装してみたい
* 論文読むより実装を通じて理解を深めていきたい
* pythonはやったことない。インデントが大事だということぐらい
* プログラミングはそれなりにやったことがある

# ゴール

Caffeのリファレンスモデル（※有名な論文で使われているパラメータチューニングも終わってるモデル）を使って、Caltechの画像を分類すること。具体的に目に見える形として、以下のようなアウトプットを出すことがゴール。

（画像。無事実装が完了したら乗っける）

# おおまかな手順
**STEP1.** 分類する画像データセットをダウンロード
**STEP2.** データセットの画像から特徴を抽出する
**STEP3.** 抽出した特徴を線形SVMで分類するためにSVMを学習させる。
**STEP4.** 学習させたSVMで特徴量からクラス分類

# 1. 分類する画像データセットをダウンロード
以下のページに飛び、Download。
http://www.vision.caltech.edu/Image_Datasets/Caltech101/#Download

# 2. データセットの画像から特徴を抽出する
## 2-1. リファレンスモデルのダウンロード
```
$ scripts/download_model_binary.py models/bvlc_reference_caffenet
```

## 2-2. 画像の特徴を抽出するためのコードの実装
リファレンスモデルを使って画像の特徴データを抽出。
色がRGBという3つの数値で表されるみたいに、今回のモデルでは１つの画像の特徴は **4096個の数値** で表される。
2-2.では入力:jpgデータ、出力:4096の数値データ、な処理を行うスクリプトを作成する。

以下をcaffeのルート・ディレクトリに作成。

```python:feature_extraction.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import sys, os, os.path, numpy as np, caffe

# path to git-cloned caffe dir
CAFFE_DIR  = os.getenv('CAFFE_ROOT')

MEAN_FILE  = os.path.join(CAFFE_DIR, 'python/caffe/imagenet/ilsvrc_2012_mean.npy')
MODEL_FILE = os.path.join(CAFFE_DIR, 'models/bvlc_reference_caffenet/deploy.prototxt')
PRETRAINED = os.path.join(CAFFE_DIR, 'models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel')

LAYER = 'fc7'
INDEX = 4

class FeatureExtraction:

    def __init__(self):
        net = caffe.Classifier(MODEL_FILE, PRETRAINED)
        caffe.set_mode_cpu()
        net.transformer.set_mean('data', np.load(MEAN_FILE))
        net.transformer.set_raw_scale('data', 255)
        net.transformer.set_channel_swap('data', (2,1,0))
        self.net = net

    def extract_features(self):
        imageDirPath = sys.argv[1]
        previousLabelName = ''
        labelIntValue = 0
        for root, dirs, files in os.walk(imageDirPath):
            for filename in files:
                if filename == '.DS_Store': 
                    continue
                fullPath  = os.path.join(root, filename)
                dirname   = os.path.dirname(fullPath)
                labelName = dirname.split("/")[-1]
                if labelName != previousLabelName:
                    labelIntValue += 1
                    previousLabelName = labelName
                image = caffe.io.load_image(fullPath)
                feat = self.extract_features_from_image(image)
                self.print_feature_with_libsvm_format(labelIntValue, feat)

    def build_test_data(self, imagePaths):
        for fullPath in imagePaths:
            image = caffe.io.load_image(fullPath)
            feat = self.extract_features_from_image(image)
            self.print_feature_with_libsvm_format(-1, feat)

    def extract_features_from_image(self, image):
        self.net.predict([image])
        feat = self.net.blobs[LAYER].data[INDEX].flatten().tolist()
        return feat 

    def print_feature_with_libsvm_format(self, labelIntValue, feat):
        formatted_feat_array = [str(index+1)+':'+str(f_i) for index, f_i in enumerate(feat)]
        print str(labelIntValue) + " " + " ".join(formatted_feat_array)

```

## 2-3. STEP1 でダウンロードした全画像の特徴データを一つのファイルに書き出す

上記の実行用のスクリプトを用意しておいて、

```python:exec.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from feature_extraction import FeatureExtraction 
FeatureExtraction().extract_features()
```

以下を実行して、特徴データ（feature.txt）を作成する。

```bash:
$ python exec.py path/to/images_dir > feature.txt
```

手元のマシンでは1行目に

```
(10, 3, 227, 227)
```

という文字列が入ってしまう。
コレは特徴データではなく、別処理でのゴミprintみたいなものなので消しておく。

### 補足: 出力した特徴データのフォーマットについて

STEP3.ではlibsvmによるSVMの学習を行う。libsvmで扱うために、特徴データを以下のような形に書き出す必要がある。

```
...
4 1:0.89 2:0.19 3:0.10 ...  4096:0.77 
1 1:0.01 2:0.99 3:0.11 ...  4096:0.97 
...
```

1データが、

```
(labelの番号) 1:1番目の特徴量の数値 2:2番目の特徴量の数値 ...
```

という形で表されている。feature.txtでは画像枚数分の行数がある。

# 3. 抽出した特徴を線形SVMで分類するためにSVMを学習させる。
SVMには有名なlibsvmというパッケージを使う。
libsvmやsvmの説明はこのあたりが親切。

* [SVMを使いこなす！チェックポイント8つ](http://qiita.com/pika_shi/items/5e59bcf69e85fdd9edb2)
* [10秒で設定可能なlibsvmで機械学習を行う](http://yut.hatenablog.com/entry/20120829/1346197290)
* [Webで見つけたサポートベクターマシンの参考文献] (http://ameblo.jp/economics-teru/theme-10067699498.html)

## 3-1. libsvmのインストール

```
$ brew install libsvm
```

## 3-2. 学習
SVMを学習させる。
以下のコマンドを打つ。

```
$ svm-scale -s scale.txt feature.txt > feature.scaled.txt
$ svm-train -c 0.03 feature.scaled.txt caltech101.model
```

svm-scaleはlibsvmでスケーリングを行うコマンドで、svm-trainが学習を行うコマンド。
各ファイルの意味は以下のとおり。

* scale.txt: どうscaleしたかが保存してあるファイル。
* feature.txt: 特徴データのファイル（STEP2で作成したもの) 
* feature.scaled.txt: スケーリング後の特徴データ
* caltech101.model: 学習させたSVMのパラメータが保存されているファイル

# 4. 学習させたSVMでカテゴリ分類
※ WIP。多分SVMのところ勘違いしている疑惑。
## 4-1. 試しに同じデータで実験

```
$ cp feature.txt feature_test.txt
$ svm-scale -r scale.txt feature_test.txt > feature_test.scaled.txt
$ svm-predict feature_test.scaled.txt caltech101.model result.txt
```

...精度わるっ！ now debugging...

# Next Step 候補

* ファインチューニングする
* 一からモデル作る
* Detection

8月中に3つともできれば。。。

# 参考にしたURLリスト
## 全体の流れにおいて
* [CaffeでDeep Learning つまずきやすいところを中心に](http://qiita.com/uchihashi_k/items/8333f80529bb3498e32f)
  * Caffeの一般的な実装の流れが簡単に触れられている。全体像を知りたいならこれ。
* [Caffeで手軽に画像分類](http://techblog.yahoo.co.jp/programming/caffe-intro/)
  + 今回の元ネタ。ただし2015-08-21現在では手軽にできなくなっているので他の参考URLも見ながら進める。
* [CaffeのImageNetで特徴量抽出器を動かすまで](http://smrmkt.hatenablog.jp/entry/2015/03/08/195625)
  + 元ネタを現在版にアレンジしたもの。
* [”Caffeで手軽に画像分類”の”Caffeを特徴抽出器として使った分類”をやってみた１](http://marsee101.blog19.fc2.com/blog-entry-3086.html)
  + あんまり参考になっていない。
* [『Caffeで手軽に画像分類』が手軽にできない。](http://hogehuga.com/caffe_is_tough/#caffeset_phase_test)
  + 困ったときのFAQ的な資料。ありがとうございます。
* [Caffe, Pylearn2をまとめて試す](http://qiita.com/CORDEA/items/9fad27ae024928b6a7b1)
  + Caffeでリファレンスモデルを利用するときの手順の参考になる。
* [Caffeによる特徴抽出+AROWによる分類を試した](http://kivantium.hateblo.jp/entry/2015/06/28/230551)
  + 特徴抽出の部分のソースコード、参考にさせていただきました。
* [Caffe Pukiwiki](http://wiki.ruka-f.net/index.php?Caffe)
  + libsvm用のフォーマットにするためのコードはここを参考にしました。

## libsvm
+ [10秒で設定可能なlibsvmで機械学習を行う](http://yut.hatenablog.com/entry/20120829/1346197290)
  + libsvmのチュートリアル的な資料。
+ [Homebrew + Virtualenv 環境でlibsvmのインストール](http://salinger.github.io/blog/2013/01/18/1/)
  + pythonでlibsvm。使ってみたい。installはしたが使っておらず。後回し。

# FAQ
## Q1. python/caffe/imagenet/ilsvrc_2012_mean.npyってなんぞ？
A. 平均画像のこと。以下を参照。
> http://qiita.com/uchihashi_k/items/8333f80529bb3498e32f


## Q2. SVMって2値分類器では？
A. 多値分類もできる。libsvmでは入力した教師データのクラスの数をさりげにカウントして、必要であれば多値分類器をつくるように、よしなにやってくれている...なんて甘い話ではなかった。