【fastai2ベースライン】PETS、単一ラベル多クラス分類、転移学習、混同行列、モデル保存・復元、運用時の推論

# はじめに
fastai2でディープラーニングするにあたり、テンプレート・ベースラインになるようなNotebookを作成しました。

参考：公式チュートリアル　[Computer vision | fastai](https://docs.fast.ai/tutorial.vision.html)

🪐JupyterNotebook・ソースコード：[【fastai2ベースライン】PETS、単一ラベル多クラス分類、転移学習、混同行列、モデル保存・復元、運用時の推論.ipynb - GitHub](https://github.com/lilacs2039/ColabNotebooks/blob/master/fastai/%E3%80%90fastai2%E3%83%99%E3%83%BC%E3%82%B9%E3%83%A9%E3%82%A4%E3%83%B3%E3%80%91PETS%E3%80%81%E5%8D%98%E4%B8%80%E3%83%A9%E3%83%99%E3%83%AB%E5%A4%9A%E3%82%AF%E3%83%A9%E3%82%B9%E5%88%86%E9%A1%9E%E3%80%81%E8%BB%A2%E7%A7%BB%E5%AD%A6%E7%BF%92%E3%80%81%E6%B7%B7%E5%90%8C%E8%A1%8C%E5%88%97%E3%80%81%E3%83%A2%E3%83%87%E3%83%AB%E4%BF%9D%E5%AD%98%E3%83%BB%E5%BE%A9%E5%85%83%E3%80%81%E9%81%8B%E7%94%A8%E6%99%82%E3%81%AE%E6%8E%A8%E8%AB%96.ipynb)

# 環境

本スクリプトは、以下の環境で動作確認した。

- OS : Windows-10
- python : 3.9.10
- GPU : RTX3070Ti
- cuda : 11.3

# 本スクリプトでやること

1. 📁データ読み込み：PETSデータセット
1. 🔪前処理：256x256へ変換
1. 📝学習の準備：fastaiの学習オブジェクト組立て
1. 👨‍🎓学習：犬・猫の品種を分類
1. 📊評価：混同行列
1. 💾モデル保存・復元
1. 📈運用：１画像だけ推論

# テクニック
## 混合精度(Mixed Precision)
- 半精度浮動小数点数 (FP16) を使って、精度を落とさずに学習を高速化
- GPUメモリの消費が減る分、計算時のバッチサイズを大きくできる。

## 累積勾配(Accumulated Gradient)

> [Google Brainの論文「学習率を落とすな、バッチサイズを増やせ」を読む - Qiita](https://qiita.com/koshian2/items/8d8f0197aab1779e096b)  
> バッチサイズを大きくしても同様に学習率も大きくすればだいたい同じ精度でかつ高速化のメリットだけ得られる

- 計算時のバッチサイズはGPUメモリの制限で上限がある。
- 勾配を累積することで実効バッチサイズを大きくすることで、学習率が高めでも学習を安定させる。
- 例）パラメータ更新を64バッチ・1024バッチに変えたときのAccuracyの変動
    - 1024バッチの方がAccuracyの収束が速く、学習が安定していることが分かる。
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186338/61fa6271-5f11-39b9-7fc3-2ff877ecb5db.png)

## 早期打ち切り(Early Stopping)
- モデルトレーニング中に指標が改善しなくなった時点で学習を打ち切ることで、モデルが過学習するのを防ぐ手法
- 学習エポック数を多めに指定しておけば自動的に終了してくれるので、作業が楽になるメリットもある。

## その他
以下の項目は前記事参照のこと。　[fast.ai初心者目線の道案内 - Qiita](https://qiita.com/lilacs/items/abac45188daf8277489a)
  - 転移学習
  - WeightDecay（荷重減衰、L2正則化）
  - 学習率の探索
  - 1 Cycle Rule
  - FineTuning（ファインチューニング）

# 🌱初期化

```python
%reload_ext autoreload
%autoreload 2
%matplotlib inline
```

fastaiのimport

```python
from fastai.vision.all import *
from fastai.callback.fp16 import *    # Mixed Precisionで学習するときに必要
```

その他のライブラリのimport


```python
from pathlib import Path
import tqdm
import json
import glob
from torchsummary import summary
import datetime  
```

環境とfastaiのバージョン確認

```python
%%script false
from fastai.test_utils import show_install; show_install()

# 出力例
# === Software === 
# python        : 3.9.10
# fastai        : 2.6.3
# fastcore      : 1.3.27
# fastprogress  : 1.0.2
# torch         : 1.11.0+cu113
# nvidia driver : 512.77
# torch cuda    : 11.3 / is available
# torch cudnn   : 8200 / is enabled

# === Hardware === 
# nvidia gpus   : 2
# torch devices : 2
#   - gpu0      : NVIDIA GeForce RTX 3070 Ti
#   - gpu1      : NVIDIA GeForce GTX 1070

# === Environment === 
# platform      : Windows-10-10.0.19044-SP0
# conda env     : Unknown
# python        : C:/Users/User/AppData/Local/Programs/Python/Python39/python.exe
# sys.path      : ...

```

## 🎨パラメータ

- 計算時のバッチサイズ(`bs_step`)はGPUメモリが許す限り大きい値とした。
  - 計算バッチサイズがなるべく大きい方が学習が速いため。
- パラメータ更新するときのバッチサイズ（`bs_update`）は、データセットサイズの約1/4以下でなるべく大きい２の倍数とした。
  - 1/4以下にした理由
    - 前提１：確率的勾配降下法（オンライン学習、１サンプルずつ勾配更新）では、サンプル数が少ないためサンプル個体差によるノイズが大きい。このノイズには、「局所解から抜け出す」良い効果と「学習が不安定になる」悪い効果がある。
    - 前提２：逆に、バッチ学習（全サンプルから勾配更新）ではノイズが最小になる。
    - 全サンプルを４等分するぐらいのミニバッチサイズなら、バッチ学習の状況から遠く、ノイズが十分確保できるのではないかと考えた。

```python
bs_step = 64       
bs_update = 1024
imsize_pre=256
imsize_croped=224
np.random.seed(42)
```

# 📁データ読み込み

fastaiの機能でデータセット取得、約1.8GB

```python
# データのルートフォルダ
img_root_path = untar_data(URLs.PETS) 
```

データセットのフォルダ構成確認。imagesフォルダなどがある。


```python
[p.relative_to(img_root_path) for p in img_root_path.ls()]
```

```bash:出力
[Path('annotations'),
 Path('crappy'),
 Path('images'),
 Path('image_gen'),
 Path('models')]
```

今回はimagesフォルダのデータだけ使う。

```python
# 前処理前の画像のフォルダ
img_original_path = img_root_path/'images'
```

```python
# 前処理後の画像を配置するフォルダ
img_preprocessed_path = Path('D:/bulk/dataset/image/PETS/train256')
```

画像のpathへのリストを取得。画像は約7000件

```python
files = get_image_files(img_original_path); len(files)
```

```bash:出力
7390
```


# 🔪前処理

- 全画像を256x256に変換して保存する。
- 学習時に毎回リサイズしなくてよいようにするため。

### 前処理内容の確認

動作確認用に１枚画像を取得

```python
img = PILImage.create(files[0])
print(img.shape)
img
```

```bash:出力
(400, 600)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186338/61239ef7-d325-67e9-9099-33a8d35fe5ac.png)

アス比を保ったまま、長辺が256pxになるようにリサイズ

```python
resized = RatioResize(256)(img); resized
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186338/a119264d-712f-6b33-64f7-7f16c2ed72c0.png)

正方形になるようにpadding


```python
padded = CropPad(256, pad_mode='border')(resized); padded
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186338/c39caeae-e44b-f30a-1b16-3a6dda518dd0.png)

### まとめて変換

全画像のpathのリストを取得。
ファイル名がラベルになっていて、大文字で始まるのは猫、小文字なら犬。


```python
original_files = get_image_files(img_original_path); 
[p.relative_to(img_original_path) for p in original_files[0:3]]
```



```bash:出力
[Path('Abyssinian_1.jpg'),
 Path('Abyssinian_10.jpg'),
 Path('Abyssinian_100.jpg')]
```


変換実施


```python
%%script false
%%time
folder = img_preprocessed_path
folder.mkdir(parents=True,exist_ok=True)    
for p in tqdm.tqdm(original_files):
    img = PILImage.create(p)
    resized = RatioResize(256)(img)
    padded = CropPad(256, pad_mode='border')(resized)
    padded.save(folder/p.name, quality=95)
    # break
```


# 📝学習の準備

## DataLoadersの作成

- [Data Block API](https://docs.fast.ai/data.block.html)でデータセットの画像・ラベルの読み込み方法を指定。
- DataBlockからDataLoadersを作成


```python
def label_from_path(p:Path)->str:
    "画像のパスから正解ラベルを取得"
    return str(p.name).split('_')[0]

dblock = DataBlock(blocks=(ImageBlock, CategoryBlock),   # ImageとCategoryを１サンプルとする
    get_items=get_image_files,                           # itemの集合の取得関数の指定。get_image_files(path)の出力、つまりPathのリストをitemsとする。
    splitter=RandomSplitter(valid_pct=0.2),              # train/validの分割方法。　20%をvalidにする
    get_y=label_from_path,                               # ラベルの取得方法。　label_number(path)
    item_tfms=CropPad(256),                              # 入力画像を読込むときの変換
    batch_tfms=aug_transforms(size=imsize_croped),       # バッチ処理するときの変換。オーグメンテーションを指定。
    n_inp=1
)

dls = dblock.dataloaders(img_preprocessed_path, batch_size=bs_step)
```

```bash:出力
Due to IPython and Windows limitation, python multiprocessing isn't available now.
So `number_workers` is changed to 0 to avoid getting stuck
```

クラス数の確認


```python
dls.c
```

```bash:出力
35
```


データ数の確認


```python
len(dls.train_ds), len(dls.valid_ds)
```

```bash:出力
(5912, 1478)
```


### train/validの変換を確認

trainはオーグメンテーション([aug_transforms](https://docs.fast.ai/vision.augment.html#aug_transforms))が有効で、「歪み・見切れ」のある画像となる。


```python
dls.train.show_batch(max_n=7**2)
```

![output_49_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186338/3738123d-3ea7-c0ec-7580-61f930ee2c51.png)

validはオーグメンテーションが無効

```python
dls.valid.show_batch(max_n=7**2)
```

![output_51_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186338/f77bbe43-08a4-3d2b-2d8c-d68b3cd0828d.png)

## Learnerの作成

### 🧩Model

今回は自作Modelを定義する。転移学習時のパラメータ固定範囲のためにsplitterが必要。


```python
class Net(Module):
    def __init__(self, arch=resnet34, nc=dls.c, pretrained=True):
        self.body = create_body(arch, pretrained=pretrained)
        nf = num_features_model(nn.Sequential(*self.body.children()))
        self.head = create_head(nf,nc,lin_ftrs=[nc*4])
        
    def forward(self, x):    
        x = self.body(x)
        return self.head(x)
        
def splitter(model:Net):
    """
    Model分割方法の指示。
    転移学習のとき、どこまでパラメータ固定するか判断するのに使用する。
    今回は、Modelパラメータをbodyとheadの２つに分けた。
    """
    return [params(model.body), params(model.head)]        

```

### Metrics
学習中に表示する評価指標。fastai標準の関数のほかに、任意の関数を定義して利用できる。
今回は、「MAP(Mean Average Precision)@10」をMetricに追加する。


```python
SCORE_TEMPLATE = (1/(torch.arange(10)+1)).cuda()  # 例）tensor([1.0000, 0.5000, 0.3333, 0.2500, 0.2000, 0.1667, 0.1429, 0.1250, 0.1111, 0.1000])
def map10(preds:Tensor, ans:Tensor):
    "評価関数「MAP（Mean Average Precision）@10」"
    assert preds.ndim==2, f'期待：preds.ndim==2, actual:{preds.ndim}. (batch_size, cls_confidence)'
    confidence = preds.argsort(descending=True)[:,:10]
    n_sample = len(preds)
    score = 0
    for pred, y in zip(confidence, ans):
        mask = torch.eq(pred,y).type(torch.uint8)  # 正解なら1のマスク　例）TensorCategory([1, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=torch.uint8)
        score += torch.sum(SCORE_TEMPLATE*mask)
    return score/n_sample

```

### Learnerを作成


```python
model = Net()
learn = Learner(dls, model=model,
                splitter=splitter,           
                loss_func=CrossEntropyLossFlat(),  #`F.cross_entropy`だと混同行列の描画時にエラーになるので、Flat版を選択
                metrics=[accuracy, map10],
                cbs=[CSVLogger(), ShowGraphCallback()]
).to_fp16()
```

※備考

- [ShowGraphCallback()](https://docs.fast.ai/callback.progress.html#ShowGraphCallback) : 学習中にtrain/validのロスをグラフ表示
  - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186338/32a7f12f-a5f8-aab7-dcc4-2f3a04cf5894.png)
- `.to_fp16()` : Mixed Precisionを使用

# 👨‍🎓学習

## 転移学習

### パラメータ固定

headだけを学習対象にする。


```python
learn.freeze()
```


```python
learn.summary()
```

```bash:出力
Net (Input shape: 64 x 3 x 224 x 224)
============================================================================
Layer (type)         Output Shape         Param #    Trainable 
============================================================================
                        64 x 64 x 112 x 112 
Conv2d                                    9408       False     
BatchNorm2d                               128        True      
ReLU                                                           
____________________________________________________________________________
                        64 x 64 x 56 x 56   
MaxPool2d                                                      
Conv2d                                    36864      False     
BatchNorm2d                               128        True      
ReLU                                                           
```

<details><summary>（中略）</summary><div>

```bash:出力
Conv2d                                    36864      False     
BatchNorm2d                               128        True      
Conv2d                                    36864      False     
BatchNorm2d                               128        True      
ReLU                                                           
Conv2d                                    36864      False     
BatchNorm2d                               128        True      
Conv2d                                    36864      False     
BatchNorm2d                               128        True      
ReLU                                                           
Conv2d                                    36864      False     
BatchNorm2d                               128        True      
____________________________________________________________________________
                        64 x 128 x 28 x 28  
Conv2d                                    73728      False     
BatchNorm2d                               256        True      
ReLU                                                           
Conv2d                                    147456     False     
BatchNorm2d                               256        True      
Conv2d                                    8192       False     
BatchNorm2d                               256        True      
Conv2d                                    147456     False     
BatchNorm2d                               256        True      
ReLU                                                           
Conv2d                                    147456     False     
BatchNorm2d                               256        True      
Conv2d                                    147456     False     
BatchNorm2d                               256        True      
ReLU                                                           
Conv2d                                    147456     False     
BatchNorm2d                               256        True      
Conv2d                                    147456     False     
BatchNorm2d                               256        True      
ReLU                                                           
Conv2d                                    147456     False     
BatchNorm2d                               256        True      
____________________________________________________________________________
                        64 x 256 x 14 x 14  
Conv2d                                    294912     False     
BatchNorm2d                               512        True      
ReLU                                                           
Conv2d                                    589824     False     
BatchNorm2d                               512        True      
Conv2d                                    32768      False     
BatchNorm2d                               512        True      
Conv2d                                    589824     False     
BatchNorm2d                               512        True      
ReLU                                                           
Conv2d                                    589824     False     
BatchNorm2d                               512        True      
Conv2d                                    589824     False     
BatchNorm2d                               512        True      
ReLU                                                           
Conv2d                                    589824     False     
BatchNorm2d                               512        True      
Conv2d                                    589824     False     
BatchNorm2d                               512        True      
ReLU                                                           
Conv2d                                    589824     False     
BatchNorm2d                               512        True      
Conv2d                                    589824     False     
BatchNorm2d                               512        True      
ReLU                                                           
Conv2d                                    589824     False     
BatchNorm2d                               512        True      
Conv2d                                    589824     False     
BatchNorm2d                               512        True      
ReLU                                                           
Conv2d                                    589824     False     
BatchNorm2d                               512        True      
____________________________________________________________________________
                        64 x 512 x 7 x 7    
Conv2d                                    1179648    False     
BatchNorm2d                               1024       True      
ReLU                                                           
Conv2d                                    2359296    False     
BatchNorm2d                               1024       True      
Conv2d                                    131072     False     
BatchNorm2d                               1024       True      
Conv2d                                    2359296    False     
BatchNorm2d                               1024       True      
ReLU                                                           
Conv2d                                    2359296    False     
BatchNorm2d                               1024       True      
Conv2d                                    2359296    False     
BatchNorm2d                               1024       True      
ReLU                                                           
Conv2d                                    2359296    False     
BatchNorm2d                               1024       True      
```
</div></details>

```bash:出力
____________________________________________________________________________
                        64 x 512 x 1 x 1    
AdaptiveAvgPool2d                                              
AdaptiveMaxPool2d                                              
____________________________________________________________________________
                        64 x 1024           
Flatten                                                        
BatchNorm1d                               2048       True      
Dropout                                                        
____________________________________________________________________________
                        64 x 140            
Linear                                    143360     True      
ReLU                                                           
BatchNorm1d                               280        True      
Dropout                                                        
____________________________________________________________________________
                        64 x 35             
Linear                                    4900       True      
____________________________________________________________________________

Total params: 21,435,260
Total trainable params: 167,612
Total non-trainable params: 21,267,648

Optimizer used: <function Adam at 0x0000021ADEDE6040>
Loss function: FlattenedLoss of CrossEntropyLoss()

Model frozen up to parameter group #1

Callbacks:
    - TrainEvalCallback
    - MixedPrecision
    - Recorder
    - ProgressCallback
    - CSVLogger
    - ShowGraphCallback
```



bodyだけパラメータ固定され（Trainable列がFalse）、head(AdaptiveAvgPool2d以降）は学習対象であることが分かる。  

- bodyのBatchNormはTrainableがTrueの理由
  - 転移学習では統計量が異なるケースが多いため、転移学習時にbodyの正規化層も学習する。
  - 参考　[Why are BatchNorm layers set to trainable in a frozen model - Part 1 (2019) - Deep Learning Course Forums](https://forums.fast.ai/t/why-are-batchnorm-layers-set-to-trainable-in-a-frozen-model/46560/3)

### 学習率の探索

一般的には、「Valley > Slide > Steep > Minimum」の順に良い精度が得られる。

- valley : 最も長い谷の部分
- slide : [interval slide rule](https://forums.fast.ai/t/automated-learning-rate-suggester/44199?u=muellerzr)に従って学習速度を提案
- steep　傾きが最も急な部分
- minimum : Lossが最も小さい部分より、1/10小さい値


参考：新しい(2021年6月)学習率探索の概要　[New LR Finder Output?! - fastai users - Deep Learning Course Forums](https://forums.fast.ai/t/new-lr-finder-output/89236)



```python
lrs = learn.lr_find(suggest_funcs=(minimum, steep, valley, slide))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186338/15cc6f4f-3aca-138e-3a5c-6b4938f552f8.png)
    


今回は簡単のため、最も学習率の高いものを採用した。


```python
lr = max(lrs); lr, lrs
```

```bash:出力
(0.019054606556892395,
 SuggestedLRs(minimum=0.010000000149011612, steep=0.019054606556892395, valley=0.0012022644514217973, slide=0.004365158267319202))
```


### 学習実行

備考
- 累積勾配（Gradient Accumulation）：実効バッチサイズを`bs_update`相当にする。
  - 複数ステップ（ミニバッチ）の勾配を累積してまとめて更新する。
  - 参考　[GradientAccumulation](https://docs.fast.ai/callback.training.html#GradientAccumulation)
- EarlyStopping：「map10」メトリックが過去のベストを３エポック上回らなかったら早期終了させる。  
  - 参考：[EarlyStoppingCallback](https://docs.fast.ai/callback.tracker.html#EarlyStoppingCallback)



```python
learn.fit_one_cycle(30, lr_max=lr, 
    cbs=[
        GradientAccumulation(n_acc=bs_update),    # 累積勾配
        EarlyStoppingCallback(monitor='map10', min_delta=0.01, patience=3),  # 早期終了
    ])
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: left;">
      <th>epoch</th>
      <th>train_loss</th>
      <th>valid_loss</th>
      <th>accuracy</th>
      <th>map10</th>
      <th>time</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td>2.757626</td>
      <td>1.852016</td>
      <td>0.713126</td>
      <td>0.815030</td>
      <td>00:21</td>
    </tr>
    <tr>
      <td>1</td>
      <td>1.514403</td>
      <td>0.787316</td>
      <td>0.855210</td>
      <td>0.916997</td>
      <td>00:20</td>
    </tr>
    <tr>
      <td>2</td>
      <td>0.869610</td>
      <td>0.408244</td>
      <td>0.903248</td>
      <td>0.943415</td>
      <td>00:20</td>
    </tr>
    <tr>
      <td>3</td>
      <td>0.516415</td>
      <td>0.298472</td>
      <td>0.905277</td>
      <td>0.946489</td>
      <td>00:20</td>
    </tr>
    <tr>
      <td>4</td>
      <td>0.367728</td>
      <td>0.269805</td>
      <td>0.906631</td>
      <td>0.948079</td>
      <td>00:20</td>
    </tr>
    <tr>
      <td>5</td>
      <td>0.322179</td>
      <td>0.263880</td>
      <td>0.904601</td>
      <td>0.945956</td>
      <td>00:20</td>
    </tr>
  </tbody>
</table>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186338/e2a20d43-5c0f-a915-80d2-508ad58a1110.png)

```bash:出力
No improvement since epoch 2: early stopping
```

GPUメモリ使用量の確認

```python
f'{torch.cuda.max_memory_allocated()*1e-9:.3g}GB'
```

```bash:出力
'2.72GB'
```


## ファインチューニング

### パラメータ固定の解除

全パラメータを学習対象にする。


```python
learn.unfreeze()
```


```python
learn.summary()
```

```bash:出力
Net (Input shape: 64 x 3 x 224 x 224)
============================================================================
Layer (type)         Output Shape         Param #    Trainable 
============================================================================
                        64 x 64 x 112 x 112 
Conv2d                                    9408       True      
BatchNorm2d                               128        True      
ReLU                                                           
____________________________________________________________________________
                        64 x 64 x 56 x 56   
MaxPool2d                                                      
Conv2d                                    36864      True      
BatchNorm2d                               128        True      
ReLU                                                           
```

<details><summary>（後略）</summary><div>

```bash:出力
Conv2d                                    36864      True      
BatchNorm2d                               128        True      
Conv2d                                    36864      True      
BatchNorm2d                               128        True      
ReLU                                                           
Conv2d                                    36864      True      
BatchNorm2d                               128        True      
Conv2d                                    36864      True      
BatchNorm2d                               128        True      
ReLU                                                           
Conv2d                                    36864      True      
BatchNorm2d                               128        True      
____________________________________________________________________________
                        64 x 128 x 28 x 28  
Conv2d                                    73728      True      
BatchNorm2d                               256        True      
ReLU                                                           
Conv2d                                    147456     True      
BatchNorm2d                               256        True      
Conv2d                                    8192       True      
BatchNorm2d                               256        True      
Conv2d                                    147456     True      
BatchNorm2d                               256        True      
ReLU                                                           
Conv2d                                    147456     True      
BatchNorm2d                               256        True      
Conv2d                                    147456     True      
BatchNorm2d                               256        True      
ReLU                                                           
Conv2d                                    147456     True      
BatchNorm2d                               256        True      
Conv2d                                    147456     True      
BatchNorm2d                               256        True      
ReLU                                                           
Conv2d                                    147456     True      
BatchNorm2d                               256        True      
____________________________________________________________________________
                        64 x 256 x 14 x 14  
Conv2d                                    294912     True      
BatchNorm2d                               512        True      
ReLU                                                           
Conv2d                                    589824     True      
BatchNorm2d                               512        True      
Conv2d                                    32768      True      
BatchNorm2d                               512        True      
Conv2d                                    589824     True      
BatchNorm2d                               512        True      
ReLU                                                           
Conv2d                                    589824     True      
BatchNorm2d                               512        True      
Conv2d                                    589824     True      
BatchNorm2d                               512        True      
ReLU                                                           
Conv2d                                    589824     True      
BatchNorm2d                               512        True      
Conv2d                                    589824     True      
BatchNorm2d                               512        True      
ReLU                                                           
Conv2d                                    589824     True      
BatchNorm2d                               512        True      
Conv2d                                    589824     True      
BatchNorm2d                               512        True      
ReLU                                                           
Conv2d                                    589824     True      
BatchNorm2d                               512        True      
Conv2d                                    589824     True      
BatchNorm2d                               512        True      
ReLU                                                           
Conv2d                                    589824     True      
BatchNorm2d                               512        True      
____________________________________________________________________________
                        64 x 512 x 7 x 7    
Conv2d                                    1179648    True      
BatchNorm2d                               1024       True      
ReLU                                                           
Conv2d                                    2359296    True      
BatchNorm2d                               1024       True      
Conv2d                                    131072     True      
BatchNorm2d                               1024       True      
Conv2d                                    2359296    True      
BatchNorm2d                               1024       True      
ReLU                                                           
Conv2d                                    2359296    True      
BatchNorm2d                               1024       True      
Conv2d                                    2359296    True      
BatchNorm2d                               1024       True      
ReLU                                                           
Conv2d                                    2359296    True      
BatchNorm2d                               1024       True      
____________________________________________________________________________
                        64 x 512 x 1 x 1    
AdaptiveAvgPool2d                                              
AdaptiveMaxPool2d                                              
____________________________________________________________________________
                        64 x 1024           
Flatten                                                        
BatchNorm1d                               2048       True      
Dropout                                                        
____________________________________________________________________________
                        64 x 140            
Linear                                    143360     True      
ReLU                                                           
BatchNorm1d                               280        True      
Dropout                                                        
____________________________________________________________________________
                        64 x 35             
Linear                                    4900       True      
____________________________________________________________________________

Total params: 21,435,260
Total trainable params: 21,435,260
Total non-trainable params: 0

Optimizer used: <function Adam at 0x0000021ADEDE6040>
Loss function: FlattenedLoss of CrossEntropyLoss()

Model unfrozen

Callbacks:
    - TrainEvalCallback
    - MixedPrecision
    - Recorder
    - ProgressCallback
    - CSVLogger
    - ShowGraphCallback
```

</div></details>


### 学習率の探索


```python
lrs = learn.lr_find(suggest_funcs=(minimum, steep, valley, slide))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186338/2116d9fc-9d3c-f906-92cd-02c1e4e90d1e.png)


簡単のため、最も学習率の高いものを採用した。


```python
lr = max(lrs); lr, lrs
```

```bash:出力
(2.0892961401841603e-05,
 SuggestedLRs(minimum=3.0199516913853586e-06, steep=3.981071586167673e-06, valley=9.999999747378752e-06, slide=2.0892961401841603e-05))
```

### 学習実行

`lr_max=slice(...)` : 入力に近い層は低い学習率とする（Discriminative Learning Rates）


```python
learn.fit_one_cycle(30, lr_max=slice(lr*1e-2,lr),
    cbs=[
        GradientAccumulation(n_acc=bs_update),    # 累積勾配
        EarlyStoppingCallback(monitor='map10', min_delta=0.01, patience=3),  # 早期終了
    ])
```


<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: left;">
      <th>epoch</th>
      <th>train_loss</th>
      <th>valid_loss</th>
      <th>accuracy</th>
      <th>map10</th>
      <th>time</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td>0.291610</td>
      <td>0.262367</td>
      <td>0.901218</td>
      <td>0.944829</td>
      <td>00:36</td>
    </tr>
    <tr>
      <td>1</td>
      <td>0.274321</td>
      <td>0.266308</td>
      <td>0.901894</td>
      <td>0.944724</td>
      <td>00:23</td>
    </tr>
    <tr>
      <td>2</td>
      <td>0.279457</td>
      <td>0.265085</td>
      <td>0.901218</td>
      <td>0.944401</td>
      <td>00:29</td>
    </tr>
    <tr>
      <td>3</td>
      <td>0.281349</td>
      <td>0.262056</td>
      <td>0.901894</td>
      <td>0.944968</td>
      <td>00:24</td>
    </tr>
  </tbody>
</table>


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186338/660dd995-328a-bf42-b6f8-1e1de43f69f6.png)

```bash:出力
No improvement since epoch 0: early stopping
```


# 📊評価

学習結果を簡単にイメージで見れる


```python
learn.show_results(max_n=7**2)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186338/15e9a3d5-2260-7a41-7b09-8ec7808fed8a.png)

## 混同行列

```python
interp = ClassificationInterpretation.from_learner(learn)
```

```python
interp.plot_confusion_matrix(figsize=(24, 24))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186338/05f58510-0ed5-24f9-4009-4eb5a6f58bda.png)


誤りの多かったクラスの確認


```python
interp.most_confused()
```

```bash:出力
[('american', 'staffordshire', 10),
 ('Russian', 'British', 8),
 ('British', 'Russian', 6),
 ('Egyptian', 'Bengal', 6),
```

<details><summary>（後略）</summary><div>

```bash:出力
 ('Ragdoll', 'Birman', 6),
 ('staffordshire', 'american', 6),
 ('Bengal', 'Egyptian', 4),
 ('Birman', 'Ragdoll', 4),
 ('Siamese', 'Birman', 4),
 ('basset', 'beagle', 4),
 ('Maine', 'Ragdoll', 3),
 ('chihuahua', 'miniature', 3),
 ('english', 'newfoundland', 3),
 ('yorkshire', 'havanese', 3),
 ('Maine', 'Persian', 2),
 ('beagle', 'basset', 2),
 ('great', 'samoyed', 2),
 ('newfoundland', 'leonberger', 2),
 ('scottish', 'havanese', 2),
 ('shiba', 'chihuahua', 2),
 ('staffordshire', 'boxer', 2),
 ('wheaten', 'great', 2),
 ('wheaten', 'scottish', 2),
 ('Abyssinian', 'Bengal', 1),
 ('Abyssinian', 'British', 1),
 ('Abyssinian', 'shiba', 1),
 ('Bengal', 'Abyssinian', 1),
 ('Bengal', 'Maine', 1),
 ('Birman', 'Siamese', 1),
 ('Bombay', 'British', 1),
 ('Bombay', 'Russian', 1),
 ('British', 'Abyssinian', 1),
 ('British', 'Ragdoll', 1),
 ('Egyptian', 'Russian', 1),
 ('Persian', 'Bombay', 1),
 ('Persian', 'Maine', 1),
 ('Ragdoll', 'British', 1),
 ('Ragdoll', 'Maine', 1),
 ('Ragdoll', 'Persian', 1),
 ('Ragdoll', 'Siamese', 1),
 ('Russian', 'Abyssinian', 1),
 ('Russian', 'Bombay', 1),
 ('Siamese', 'British', 1),
 ('Sphynx', 'Abyssinian', 1),
 ('american', 'Sphynx', 1),
 ('american', 'boxer', 1),
 ('american', 'german', 1),
 ('american', 'miniature', 1),
 ('american', 'saint', 1),
 ('basset', 'german', 1),
 ('beagle', 'american', 1),
 ('beagle', 'chihuahua', 1),
 ('beagle', 'german', 1),
 ('boxer', 'Sphynx', 1),
 ('boxer', 'american', 1),
 ('boxer', 'beagle', 1),
 ('boxer', 'saint', 1),
 ('boxer', 'staffordshire', 1),
 ('chihuahua', 'Russian', 1),
 ('chihuahua', 'scottish', 1),
 ('english', 'beagle', 1),
 ('english', 'chihuahua', 1),
 ('english', 'scottish', 1),
 ('german', 'newfoundland', 1),
 ('havanese', 'yorkshire', 1),
 ('japanese', 'havanese', 1),
 ('japanese', 'pomeranian', 1),
 ('keeshond', 'leonberger', 1),
 ('miniature', 'Sphynx', 1),
 ('miniature', 'staffordshire', 1),
 ('newfoundland', 'staffordshire', 1),
 ('pomeranian', 'Ragdoll', 1),
 ('pomeranian', 'newfoundland', 1),
 ('pomeranian', 'samoyed', 1),
 ('pomeranian', 'yorkshire', 1),
 ('saint', 'basset', 1),
 ('samoyed', 'pomeranian', 1),
 ('shiba', 'Siamese', 1),
 ('shiba', 'miniature', 1),
 ('wheaten', 'leonberger', 1)]
```
</div></details>





```python
interp.plot_top_losses(9, figsize=(24,24))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186338/34acea00-d4ab-e896-bba4-18ca2c18a83d.png)



# 💾モデル保存・復元

## 保存

pytorchモデルだけ保存する。

（fastaiのLearnerを保存して運用環境で復元する方法もある（Learner#export, Learner#save/load）。しかし、保存・復元が失敗したり、運用環境にLearnerやDataLoadersのコードが必要で煩雑だったため不採用とした。）


```python
import datetime
modelname = 'baseline1'
model_no = len(list(Path('./models').glob('*.pth')))
save_id = f'{str(model_no).zfill(3)}_{datetime.date.today()}_{modelname}'
model_output_path = f'./models/{save_id}_model.pth'
model_output_path
```

```bash:出力
'./models/000_2022-06-24_baseline1_model.pth'
```


モデル本体（torch.nn.Module）のパラメータだけ保存


```python
torch.save(learn.model.state_dict(), model_output_path)   # Modelだけを保存
```

モデル出力のidxとラベル名の対応（vocab）をファイルに保存


```python
p = Path(model_output_path)
model_vocab_output_path = str(p.parent/p.stem)+f'_vocab.txt'
model_vocab_output_path
```

```bash:出力
'models\\000_2022-06-24_baseline1_model_vocab.txt'
```

```python
with open(model_vocab_output_path, 'w') as f: f.write(','.join(learn.dls.vocab))
```

## 復元
ただのpytorchモデルとして読込む。

事前学習済みモデルを外部サイトからダウンロードする必要はないので、`pretrained=False`とした。


```python
model = Net(pretrained=False)
model = model.to("cuda")
model.load_state_dict(torch.load(model_output_path), strict=True)
model.eval()   # モデルを実行可能にするために必要
```

```bash:出力
Net(
    (body): Sequential(
    (0): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
    (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (2): ReLU(inplace=True)
```

<details><summary>（後略）</summary><div>

```bash:出力
    (3): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
    (4): Sequential(
        (0): BasicBlock(
        (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (1): BasicBlock(
        (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (2): BasicBlock(
        (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
    )
    (5): Sequential(
        (0): BasicBlock(
        (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (downsample): Sequential(
            (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
            (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        )
        (1): BasicBlock(
        (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (2): BasicBlock(
        (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (3): BasicBlock(
        (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
    )
    (6): Sequential(
        (0): BasicBlock(
        (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (downsample): Sequential(
            (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
            (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        )
        (1): BasicBlock(
        (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (2): BasicBlock(
        (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (3): BasicBlock(
        (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (4): BasicBlock(
        (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (5): BasicBlock(
        (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
    )
    (7): Sequential(
        (0): BasicBlock(
        (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (downsample): Sequential(
            (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
            (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        )
        (1): BasicBlock(
        (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (2): BasicBlock(
        (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
    )
    )
    (head): Sequential(
    (0): AdaptiveConcatPool2d(
        (ap): AdaptiveAvgPool2d(output_size=1)
        (mp): AdaptiveMaxPool2d(output_size=1)
    )
    (1): Flatten(full=False)
    (2): BatchNorm1d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (3): Dropout(p=0.25, inplace=False)
    (4): Linear(in_features=1024, out_features=140, bias=False)
    (5): ReLU(inplace=True)
    (6): BatchNorm1d(140, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (7): Dropout(p=0.5, inplace=False)
    (8): Linear(in_features=140, out_features=35, bias=False)
    )
)
```
</div></details>


モデル出力のidxとラベル名の対応（vocab）をファイルから読み込み


```python
with open(model_vocab_output_path, 'r') as f: vocab = f.read().split(',')
vocab
```


```bash:出力
['Abyssinian',
 'Bengal',
 'Birman',
```
<details><summary>（後略）</summary><div>

```bash:出力
 'Bombay',
 'British',
 'Egyptian',
 'Maine',
 'Persian',
 'Ragdoll',
 'Russian',
 'Siamese',
 'Sphynx',
 'american',
 'basset',
 'beagle',
 'boxer',
 'chihuahua',
 'english',
 'german',
 'great',
 'havanese',
 'japanese',
 'keeshond',
 'leonberger',
 'miniature',
 'newfoundland',
 'pomeranian',
 'pug',
 'saint',
 'samoyed',
 'scottish',
 'shiba',
 'staffordshire',
 'wheaten',
 'yorkshire']
```
</div></details>



# 📈運用

## 入力画像と正解ラベルの取得

### 前処理を定義

fastaiのLearnerで行われていた変換（ミニバッチの次元合わせや、0~1に正規化など）を行うため。


```python
def preprocess(img:PILImage)->PILImage:
    """
    前処理
    1. 画像をモデル入力サイズに変換
    2. PILImageをFloatTensorに変換して、0~1に正規化
    3. Tensorの形状をNCHW形式に変換
    """
    _img = CropPad(imsize_croped, pad_mode='border')(img)
    _X = torch.tensor(np.array(_img)).float().cuda()/255
    X = torch.permute(_X,[2,0,1])[None,:]  # [None,:] ミニバッチの次元を追加
    return X
```

画像を読込んで、Tensorに変換。正解ラベルも読み込む


```python
imgpath = files[0]
X = preprocess(PILImage.create(imgpath))
y = label_from_path(imgpath)
y, X.shape, X
```

```bash:出力
('Abyssinian',
    torch.Size([1, 3, 224, 224]),
    tensor([[[[0.3843, 0.3843, 0.3882,  ..., 0.7529, 0.7686, 0.7804],
            [0.3804, 0.3843, 0.3882,  ..., 0.7765, 0.7843, 0.7922],
            [0.3725, 0.3804, 0.3882,  ..., 0.7882, 0.8000, 0.8000],
            ...,
            [0.1686, 0.1686, 0.1725,  ..., 0.1725, 0.1804, 0.1843],
            [0.1686, 0.1725, 0.1725,  ..., 0.1686, 0.1725, 0.1765],
            [0.1686, 0.1725, 0.1725,  ..., 0.1647, 0.1686, 0.1725]],
    
            [[0.4196, 0.4196, 0.4235,  ..., 0.6941, 0.7098, 0.7216],
            [0.4157, 0.4196, 0.4235,  ..., 0.7098, 0.7255, 0.7333],
            [0.4078, 0.4157, 0.4235,  ..., 0.7216, 0.7412, 0.7373],
            ...,
            [0.1843, 0.1843, 0.1882,  ..., 0.1882, 0.1961, 0.2000],
            [0.1843, 0.1882, 0.1882,  ..., 0.1843, 0.1882, 0.1922],
            [0.1843, 0.1882, 0.1882,  ..., 0.1804, 0.1843, 0.1882]],
    
            [[0.3451, 0.3451, 0.3490,  ..., 0.6667, 0.6902, 0.7098],
            [0.3412, 0.3451, 0.3490,  ..., 0.6824, 0.7059, 0.7216],
            [0.3333, 0.3412, 0.3490,  ..., 0.6941, 0.7216, 0.7373],
            ...,
            [0.1294, 0.1294, 0.1333,  ..., 0.1333, 0.1412, 0.1451],
            [0.1294, 0.1333, 0.1333,  ..., 0.1294, 0.1333, 0.1373],
            [0.1294, 0.1333, 0.1333,  ..., 0.1255, 0.1294, 0.1333]]]],
        device='cuda:0'))
```


## １画像推論

モデル出力は-inf~infの値を返すが、多クラス分類なのでsoftmax関数で合計が１になるような確率値に変換する。


```python
with torch.no_grad():
    out = nn.Softmax(dim=1)(model(X))
out.shape, out.sum(), out
```

```bash:出力
(torch.Size([1, 35]),
    TensorBase(1.0000, device='cuda:0'),
    TensorBase([[9.9877e-01, 2.6242e-04, 2.4370e-06, 4.6254e-06, 1.6050e-04, 1.4321e-05,
            2.2727e-05, 2.1453e-06, 2.1390e-05, 2.8081e-04, 2.1475e-04, 1.5450e-04,
            1.0967e-06, 1.9131e-06, 5.1935e-06, 7.6392e-06, 2.0327e-06, 5.0545e-08,
            5.2137e-06, 9.4197e-07, 3.2433e-08, 2.0186e-08, 5.0262e-08, 1.8544e-06,
            3.8601e-06, 7.7373e-08, 4.5021e-07, 2.2699e-06, 3.6550e-06, 3.3962e-07,
            1.0858e-07, 5.5254e-05, 1.4583e-07, 1.5566e-06, 5.3693e-07]],
        device='cuda:0'))
```

推論した確率を、ラベルに置き換える。


```python
def pred2labels(preds:Tensor, vocab:list, k=10)->{str:[]}:
    """
    推論結果のTensorから上位`k`件までを、ラベルで表示
    """
    prob_list, idx_list = [x[:,:k].detach().numpy().flatten().tolist() for x in preds.cpu().sort(descending=True)]
    
    return {vocab[i]:p for i,p in zip(idx_list, prob_list)}
```


```python
y, pred2labels(out, vocab)
```

```bash:出力
('Abyssinian',
{'Abyssinian': 0.998765230178833,
 'Russian': 0.0002808065328281373,
 'Bengal': 0.00026241858722642064,
 'Siamese': 0.00021474898676387966,
 'British': 0.000160503841470927,
 'Sphynx': 0.00015449656348209828,
 'shiba': 5.525376400328241e-05,
 'Maine': 2.272747406095732e-05,
 'Ragdoll': 2.1389962057583034e-05,
 'Egyptian': 1.4321315575216431e-05})
```

# 💯まとめ

Abyssinianの画像を、99%以上の自信で正しく分類できた。