MacOS+docker+Caffeで画像分類

#やること
ゴリラの画像とチンパンジーの画像を分類してくれる分類器を作ろうと思います。
ゴリラの画像16枚、チンパンジーの画像16枚で試してみます。

#環境
OS X El Capitan 10.11.6
今回はMacOSにDockerでCaffeを導入しようと思います。Ubuntuで導入した方が色々とよさそうな感じだったんですが、今回はDockerの使い方と軽くCaffeを触りたかっただけなのでMacOSにしました。

#docker
[DockerをMacにインストールする (更新：2017/5/26)](https://qiita.com/kurkuru/items/127fa99ef5b2f0288b81)
dockerが無事インストールできたら上のメニューバーに鯨のマークが出ます。可愛いですね。

```
docker version
Client:
Version:      17.09.1-ce
 API version:  1.32
 Go version:   go1.8.3
 Git commit:   19e2cf6
 Built:        Thu Dec  7 22:22:25 2017
 OS/Arch:      darwin/amd64

Server:
 Version:      17.09.1-ce
 API version:  1.32 (minimum version 1.12)
 Go version:   go1.8.3
 Git commit:   19e2cf6
 Built:        Thu Dec  7 22:28:28 2017
 OS/Arch:      linux/amd64
 Experimental: true
```
無事にdockerがインストールできました

#Caffeの導入
gitでローカルに落としてから、docker buildしていきます。
-tはターミナル起動、-iはインタラクティブだそうです。
[
mac OS Xに、caffe+jupyter notebook環境構築メモ
](http://rikipafe.hatenablog.com/entry/2016/07/01/mac_OS_X%E3%81%AB%E3%80%81caffe%2Bjupyter_notebook%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89%E3%83%A1%E3%83%A2)

```
git clone https://github.com/BVLC/caffe.git
cd Caffe/docker
docker build -t caffe:cpu cpu #dockerfileにしたがってimageを作ります。
docker run -p 8889:8888 -ti caffe:cpu /bin/bash
root@fl23o7yashof:/# cd /opt/caffe
root@fl23o7yashof:/opt/caffe# jupyter notebook --allow-root --ip 0.0.0.0 --no-browser
```
Jupyterが起動します。ない場合は適宜pip installしましょう
表示に接続するURLが出ているので8888を8889に変更すればipythonが開けます

#前準備
早速訓練とかできるのかと思ったら、事前に前準備が色々と必要ですね。
今回はYahoo Blogの中盤に書いてあるCaffeを特徴抽出器として使った分類をやるために準備します。


①get_caffe_reference_imagenet_model.shを実行

```
cd examples/imagenet
wget https://raw.githubusercontent.com/sguada/caffe-public/master/models/get_caffe_reference_imagenet_model.sh
chmod u+x get_caffe_reference_imagenet_model.sh
./get_caffe_reference_imagenet_model.sh
```
②data/ilsvrc12/get_ilsvrc_aux.shを実行

```
cd ~/caffe/data/ilsvrc12/
./get_ilsvrc_aux.sh
```

③画像データを取得

```
cd ~/caffe/
wget http://www.vision.caltech.edu/Image_Datasets/Caltech101/101_ObjectCategories.tar.gz
tar xzvf 101_ObjectCategories.tar.gz
```

④imagenetのモデル定義ファイルをダウンロードしてきて、自分で編集できるようにコピーして編集

```
cd examples/imagenet
wget https://raw.githubusercontent.com/aybassiouny/wincaffe-cmake/master/examples/imagenet/imagenet_deploy.prototxt
cp imagenet_deploy.prototxt imagenet_feature.prototxt
```
imagenet_feature.prototxtの編集

```text:imagenet_feature.prototxt
174   top: "fc6wi" # fc6->fc6wi
175   blobs_lr: 1
176   blobs_lr: 2
177   weight_decay: 1
178   weight_decay: 0
179   inner_product_param {
180     num_output: 4096
181   }
182 }
183 layers {
184   name: "relu6"
185   type: RELU
186   bottom: "fc6wi" # fc6->fc6wi
```
⑤特徴量を取り出せるようにfeature.pyを作成

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

MEAN_FILE = 'python/caffe/imagenet/ilsvrc_2012_mean.npy'
MODEL_FILE = 'examples/imagenet/imagenet_feature.prototxt'
PRETRAINED = 'examples/imagenet/caffe_reference_imagenet_model'
LAYER = 'fc6wi'
INDEX = 4

IMAGE_DIR = 'path/to/imagedir'
image_number = 1

net = caffe.Classifier(MODEL_FILE, PRETRAINED)
#caffe.set_phase_test()
caffe.set_mode_cpu()
net.transformer.set_mean('data', numpy.load(MEAN_FILE))
net.transformer.set_raw_scale('data', 255)
net.transformer.set_channel_swap('data', (2,1,0))

for im in os.listdir(IMAGE_DIR):
    image = caffe.io.load_image(IMAGE_DIR + '/' + im)
    net.predict([ image ])
    feat = net.blobs[LAYER].data[INDEX].flatten().tolist()
    
    print(image_number),
    for i,f in enumerate(feat):
        print(str(i+1) + ":" + str(f)),
    print()
    image_number += 1
```
ディレクトリをいちいち指定するのが面倒だったので,feature.py内でディレクトリごと指定できるようにしました。最終的にSVMに入れる時のデータ形式を以下のようなlibsvm形式にしないといけないのでtxtデータも一つにまとめました。

```
python feature.py  > tmp.txt
```

```text:tmp.txt
0 1:-44.596577 2:-30.565985 3:-26.004364 4:-1.526159 ...
1 1:-10.0806865692 2:-27.590259552 3:-25.8647613525 4:-2.82715797424 ...
```
最初の行に(1,1,277,277)みたいなものがプリントされましたが、今回は不要なので削除しました。

⑥SVMにかける
Ubuntuを触ったのは初めてなのですが、どうやらinstall libsvmではなくinstall libsvm-toolsじゃないとダメみたいです。

```
apt-get install libsvm-tools
```

```
svm-scale -s scale.txt tmp.txt > train_scaled.txt
#scale.txtとtrain_scaled.txtは新しく作られるファイルです。
svm-scale -r scale.txt test.txt > test_scaled.txt
svm-train -c 0.03 train_scaled.txt gorilla_chimp.model
#gorilla_chimp.model　は新しく作られるファイル名です
svm-predict test_scaled.txt gorilla_chimp.model result.txt
```

これでうまくいくやろ！と思ったのですが、どうやらモデルを作成する時にクラスが教師の数だけ(今回はゴリラとチンパンジーを8枚ずつ入れたので、16クラス)出来てしまったので、正答率がめちゃめちゃ低くなってしまいました。そもそも正解値を入れるところなんてどっかにあったか？
調べてもよくわからなかったので、今回はsklearnを使ってcross validationを使うことにしました。
cross　validationならばデータは一緒にまとめちゃっていいので、32枚のデータ全てでtmp.txtを作りスケーリングしたtxtデータをdata.txtとしました。0~15がゴリラ、16~31がチンパンジーの画像です。

```python:learning.py
import sklearn
from sklearn import datasets
from sklearn.model_selection import train_test_split

#txtデータを加工
f = open("data.txt",'r')
string = f.readlines()
this_data = []
for i in range(len(string)):
    x = string[i][string[i].find(" ") +1:].split(" ")
    x.pop(len(x)-1)
    sublist = []
    for j in x:
        j = float(j[j.find(":")+1:])
        sublist.append(j)
    this_data.append(sublist)

#正解値付与
this_target = []
for i in range(16):
    this_target.append(0)
for i in range(16):
    this_target.append(1)

#分類
X_train, X_test, Y_train, Y_test = train_test_split(this_data, this_target, random_state=1)
#random_stateはシード値

#学習
from sklearn.svm import SVC
model = SVC()
model.fit(X_train,Y_train)
from sklearn import metrics
predicted = model.predict(X_test)

#実行
metrics.accuracy_score(Y_test,predicted)
```
sklearn.model_sectionはcross_validationと書いてあるサイトが多かったですが、アップデートで場所が変わったみたいです。accuracy87.5でまあいい精度が出ました。
結局docker(Caffe)で使ったのは画像を4096次元のデータにするだけであとは全部sklearn頼りでした。もう少しdockerについて学ぼうと思います。

##参考資料
[CaffeのImageNetで特徴量抽出器を動かすまで](http://smrmkt.hatenablog.jp/entry/2015/03/08/195625)
[”Caffeで手軽に画像分類”の”Caffeを特徴抽出器として使った分類”をやってみた１](http://marsee101.blog19.fc2.com/blog-date-20150226.html)
[『Caffeで手軽に画像分類』が手軽にできない。](https://hogehuga.com/caffe_is_tough/)
[【Caffe】特徴量抽出を行いSVMで物体識別する](http://punyo-er-met.hateblo.jp/entry/2016/03/04/112450)