（Part2）PyTorchに学習済みのTorch7モデル(.t7)を読み込んで，画像特徴量を抽出する

# 本記事について
[前回の記事](https://qiita.com/kenmikamin/items/0cd697453827e8f596e0)の続きです。前回の記事では，ネットワークにランダムなテンソルを入力しましたが，今回は実際の画像を入力します。

### 具体的に何をするか

- 画像の前処理。具体的には，正規化とリサイズ。
- 画像のテンソル化➔Variable化
- 画像をニューラルネットワークに入力する！←これが個人的にさしあたりの目標であった

# 画像からの特徴量抽出


下記のリンクを参考に，画像の前処理（正規化➔リサイズ➔Tensor化➔Variable化）を行いました。


**PyTorch quick start: Classifying an image**
http://blog.outcome.io/pytorch-quick-start-classifying-an-image/


必要なパッケージをimportします：

```python
from PIL import Image
from torchvision import models, transforms
from torch.autograd import Variable
```

## 正規化と前処理

画像の正規化には，ネットワークが学習に用いた画像の各チャンネルのピクセル輝度値の平均と標準偏差を用います。
余談ですが，

- $k$番目のチャンネルの$(i,j)$のピクセルを$x_{ijk}$
- $k$番目のチャンネルの正規化後の$(i,j)$のピクセルを$z_{ijk}$
- $k$番目のチャンネルのピクセル輝度値の平均を$\mu_k$
- $k$番目のチャンネルのピクセル輝度値の標準偏差を$\sigma_k$

とすると，

$z_{ij}=\frac{x_{ijk}-\mu_k}{\sigma_k}$

となります。さて，正規化のために平均と標準偏差を知りたいわけですが，私の場合（つまり，'stylenet.t7'の場合）は，つぎのようにして確認できました：

**input**

```python
from torch.utils.serialization import load_lua

model = load_lua("stylenet.t7")
print(model)
```

**output**

```
{'mean': [0.5657177752729754, 0.5381838567195789, 0.4972228365504561],
 'std': [0.29023818639817184, 0.2874722565279285, 0.2933830104791508],
 'stylenet': nn.Sequential {
   [input -> (0) -> (1) -> (2) -> (3) -> (4) -> (5) -> (6) -> (7) -> (8) -> (9) -> (10) -> (11) -> (12) -> (13) -> (14) -> (15) -> (16) -> (17) -> (18) -> (19) -> (20) -> (21) -> (22) -> (23) -> (24) -> output]
   (0): nn.SpatialConvolution(3 -> 64, 3x3, 1, 1, 1, 1)
   (1): nn.ReLU
   (2): nn.SpatialConvolution(64 -> 64, 3x3, 1, 1, 1, 1)
   (3): nn.ReLU
   (4): nn.Dropout(0.2500)
   (5): nn.SpatialMaxPooling(4x4, 4, 4)
   (6): nn.SpatialBatchNormalization
   (7): nn.SpatialConvolution(64 -> 128, 3x3, 1, 1, 1, 1)
   (8): nn.ReLU
   (9): nn.SpatialConvolution(128 -> 128, 3x3, 1, 1, 1, 1)
   (10): nn.ReLU
   (11): nn.Dropout(0.2500)
   (12): nn.SpatialMaxPooling(4x4, 4, 4)
   (13): nn.SpatialBatchNormalization
   (14): nn.SpatialConvolution(128 -> 256, 3x3, 1, 1, 1, 1)
   (15): nn.ReLU
   (16): nn.SpatialConvolution(256 -> 256, 3x3, 1, 1, 1, 1)
   (17): nn.ReLU
   (18): nn.Dropout(0.2500)
   (19): nn.SpatialMaxPooling(4x4, 4, 4)
   (20): nn.SpatialBatchNormalization
   (21): nn.SpatialConvolution(256 -> 128, 1x1)
   (22): nn.ReLU
   (23): nn.Reshape(3072)
   (24): nn.Linear(3072 -> 128)
 }}
```

※結局`load_lua()`使うんかいという感じですが（前回の記事で使わないと明言した），これ以外の方法で平均・標準偏差にアクセスするPyTorch流の方法が間違いなくあると思いますので，時間があったら調べます。

`load_lua()`でモデルを読み込むと，モデルが`dict`型みたいなものに入れられるみたいです。この辞書の最初の方に`mean`と`std`が見えてますね。このキーにアクセスして平均と標準偏差をとってこれますが，一応参考にした文献にならって次のようにコピペして貼り付けます。

```python
# 正規化用の変数
normalize = transforms.Normalize(
   mean=[0.5657177752729754, 0.5381838567195789, 0.4972228365504561],
   std=[0.29023818639817184, 0.2874722565279285, 0.2933830104791508]
)
```

## 前処理

つぎに，前処理用の`transforms.Compose`の値を設定します。

```python
# 前処理
preprocess = transforms.Compose([
   transforms.Scale(256),
   #transforms.CenterCrop(224),  # 画像を正方形で切り取る
   transforms.ToTensor(),
   normalize
])
```
ここで，`transforms.Resize(256)`がちょっとややこしいのですが，公式ドキュメントでは次のようになっています：

>**class torchvision.transforms.Resize(size, interpolation=2)**

>- `size`が非intなら，もとの画像サイズが(h,w)に対し(h*size,w*size)になる。
>- `size`がintなら，短い方の辺の長さが`size`にリサイズされ，長い方の辺は、もとの画像の比を維持するように変更する。
>- `size=[h,w]`のようにすれば、縦横の長さが(h,w)になるようにリサイズする。

私の場合，ネットワークに入力する画像の縦横ピクセルは仕様上(384×256)となっており，また，入力する画像は3:2の縦横比になっているので，`Resize(256)`とすれば画像は(384×256)のピクセルサイズになります。

画像をPILで読み込んで，試しに出力してみます．PILに関しては，[こちら](https://www.lifewithpython.com/2013/09/pil.html)に簡潔にまとめてあってわかりやすいです．

**input**

```python
img_PIL = Image.open("fashion_style.jpg")
img_PIL
```

**output**
![11005.jpg](https://qiita-image-store.s3.amazonaws.com/0/182818/c83d3c32-c903-b88a-b237-e451c5a0969a.jpeg)



ネットワークに画像を入力して特徴量をとっていきたいのですが，ネットワークには`Variable`しか入力できません。そこで，つぎのように画像のTensor化➔Variable化の処理をします。

```python
img_tensor = preprocess(img_PIL)
img_tensor.unsqueeze_(0)
```

`img_tensor`は(3,384,256)のテンソルです。つまり，3チャンネル，ピクセル縦横（384×256）です。`.unsqueeze_(0)`は，バッチ用の次元を追加します。その結果，(1,3,384,256)のテンソルが得られます。

つぎに，テンソル化➔Variable化をして，ついにネットワークに画像を入力してみます。

**input**

```python3
img_variable = Variable(img_tensor)   # Variable化
stylenet.train(False)   # 特徴抽出器として使う
output = stylenet.forward(img_variable)
output
```

**output**

```
Variable containing:

Columns 0 to 9 
 0.5769 -0.8995  1.2549  1.0596 -3.3031  1.8836 -0.7131 -0.2804  0.1950  0.2517
.
.
.
Columns 120 to 127 
 0.9533  0.9206  0.6384 -2.3381  1.6441  1.5416  0.3826 -0.0569
[torch.FloatTensor of size 1x128]
```

上記で`stylenet.train(False)`とするのは，ネットワークを特徴抽出器として使うためです。これをしないと，学習時と同じようにDropoutレイヤーが働く(つまり，層間のノード同士の結合を確率的に決める)ので，同じ画像でも毎回異なった特徴量が出力されることになります。