ブラウザで試すディープラーニング入門

# この記事の内容
ブラウザで Deep Learning が 試せるサービス「[Colaboratory](https://colab.research.google.com)」で
ディープラーニングを試す方法を紹介します。

題材はディープラーニング界の"Hello World"、「MNIST」です。

なお、このチュートリアルで必要なディープラーニングの最低限必要な知識については「[ディープラーニング入門への最小知識](https://qiita.com/ozota/items/8ef4065ba458e3af05e4)」で解説しています

# MNISTとは
* ディープラーニングのチュートリアル用の画像データ集です
  * 0～9の手書き数字画像集で、28*28ピクセルの単色画像です
  * 画像に加え、正解ラベル（その画像が何の数値を表すかのデータ）も含まれています
  * こんな感じの画像集です　　
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/e9bf2034-62a1-69fd-5c51-1812559a41d9.png)
* この画像から書かれている数字を判定するAIを作ってみます

# Colaboratoryとは
* ブラウザで Deep Learning が試せるサービスです
* Googleのサービスです
* 無料です
* GPUが12時間無料で使えます。(K80という古いものです)
* `pip install`が出来るのでどんなフレームワーク（AI作成ライブラリ）でも試せます
  * 今回は`PyTorch`を使います 

# Colaboratoryの学習環境セットアップ
* https://colab.research.google.com へアクセス
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/ba305dde-876a-d871-83bb-8646f61c7f9e.png)
* ポップアップ右下にある![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/aeccc8a3-7960-df08-2110-5464285ccdfa.png)を選択

* こんな画面が出ます
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/c8a2deb2-9433-c4a5-31e2-652f8862f035.png)

  * GPUを有効化します。上部にある「ランタイム」から「ランタイムのタイプを変更」を選びます  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/1fca5368-1549-6ca9-e189-0e1de99ce3b7.png)

  * 「ハードウェアアクセラレータ」でGPUを選びます。「保存」を押して環境セットアップ完了です
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/020e2f74-d4d9-85cc-b6f9-53873744f84b.png)

## Colaboratoryの使い方
* このカーソルのある部分にコードを貼り付けます  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/37e97da2-1b4a-4358-198c-2caa8a694481.png)
  * ![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/3b3ee0f4-9191-8b11-5436-c12f7a593a5c.png)
を押すと実行されます
  * pythonのインタプリタです
  * OSコマンドを実行する場合は先頭に`!`を付けてください  
    実行例  
   ![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/3d63fafc-4ddc-8b88-014f-6e2204e40e67.png)

* 編集して別のコードを実行することもできますが、左上の![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/4e9e6804-8174-6772-8983-a195a6aae97f.png)を押すと新しい入力欄が出てきます。
  * 入力欄が分かれていても変数等は共有されています
  * 例    
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/f018b1a5-69ee-3b71-c2d1-121862a13326.png)


## Colaboratoryの挙動がおかしくなったら
* 「変数を定義しなおして整合性が取れなくなった」などでうまく動かなくなることがあります。 
* そういう場合はランタイムの再起動をしましょう   
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/fc6fe1c6-0313-6a26-e139-234bb8fb904f.png)


# AI作成してみよう
以下に出てくるコードを適宜Colaboratory上にコピペ・実行してください

## フレームワークインストール
* フレームワークには[PyTorch](https://pytorch.org/)を使ってみます。次のコマンドでColaboratoryにインストールしてください

```bash
!pip3 install torch torchvision
```

## データの読み込み
* PyTorchにはMNISTデータを読み込むライブラリが存在します。これでMNISTデータのダウンロードと読み込みをします。

    ```python
    import torchvision.transforms as transforms
    from torch.utils.data import DataLoader
    from torchvision.datasets import MNIST

    # 訓練データとテストデータを用意
    train_data = MNIST('~/tmp/mnist', train=True, download=True, transform=transforms.ToTensor())
    train_loader = DataLoader(train_data, shuffle=True)
    test_data = MNIST('~/tmp/mnist', train=False, download=True, transform=transforms.ToTensor())
    test_loader = DataLoader(test_data, shuffle=False)
    ```

  * コードについて
      * `train=True`: MNISTの中で学習用、テスト用に既にデータが分かれています。読み込み時に学習用かテスト用か指定します
      * `transform=transforms.ToTensor()`: 読み込み後に画像データを行列に変換します。この時点では(28,28)の2次元配列です

## モデルを定義

```python
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.l1 = nn.Linear(28 * 28, 50) # 入力層から隠れ層へ
        self.l2 = nn.Linear(50, 10) # 隠れ層から出力層へ
    
    def forward(self, inputs):
        o0 = inputs.view(1, 28 * 28) # テンソルのリサイズ
        o1 = self.l1(o0)
        o2 = self.l2(o1)
        return o2

net = Net()
```

  * コードについて
      * `class Net(nn.Module):`: PyTorchのニューラルネットクラスを継承してニューラルネットを定義します。
      * `def __init__(self):`: ニューラルネットの定義をしています。l1,l2の2層構造です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/aa51afae-1cd0-b46e-d636-a6c91b0fba17.png)

      * `def forward(self, inputs):`: 入力を与えたときの計算方法を指定しています
          * `inputs.view(1, 28 * 28)`: 入力が2次元配列なので、ニューラルネットに入れるために28*28=784の1次元配列に変換しています
      * このニューラルネットの出力は要素数10の配列です。配列内で最も大きい数値をもつインデックスが判定結果です。
          * 例えば、`output = [1,-2,3,7,100,4,6,7,3,2]`の場合は`output[5]`が最も大きいので`5`が判定結果です         

## 学習方法の指定

```python
import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.01)
```
    
* 損失関数(criterion)には「クロスエントロピー」を用います
* 重み更新には`SGD`を使用します
    * 学習率`lr`は0.01にしています

## 学習実行

```python
for i, data in enumerate(train_loader):
    inputs, labels = data  
    # 勾配情報をリセット
    optimizer.zero_grad()     
    # 入力を与える
    outputs = net(inputs)       
    # 損失関数を使って損失を計算する
    loss = criterion(outputs, labels)       
    # 勾配計算
    loss.backward()       
    # 重みの更新
    optimizer.step()
            
print('Finished Training')
```

* `enumerate(train_loader)`: 学習用画像を一枚ずつ取り出してループを回します
  * 取り出したデータから`inputs, labels = data`として画像とラベルを取り出しています
* 実行すると1分ほどで`'Finished Training'`が出力されます

## テストする

```python
import torch
correct = 0
total = 0
for data in test_loader:
    inputs, labels = data

    outputs = net(inputs)
    _, predicted = torch.max(outputs.data, 1)
    total += labels.size(0)
    correct += (predicted == labels).sum()
    
print('Accuracy %d / %d = %f' % (correct, total, float(correct) / total))
```
* 実行するとテストデータで判定した結果の精度が見れます

## テストデータ画像と判定結果のペアを表示

```python
import matplotlib.pyplot as plt
test_iter = iter(test_loader)
inputs, labels = test_iter.next()
outputs = net(inputs)
_, predicted = torch.max(outputs.data, 1)

plt.imshow(inputs[0].numpy().reshape(28, 28), cmap='gray')
print('判定結果', predicted[0])
```

* 実行すると次のような画像が出ます  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/11870ff5-2724-672e-d86d-b441c8276dab.png)

ちゃんと7の画像を7と判定できたようです

# 備考
* 普通のAI開発でやるであろうことを大量に割愛しています。
  * バッチ
  * エポック
  * 画像の正規化
  * エポック内で学習後にテストを行う。ステップごとに精度を計る
  * 等...

* MNISTの参考モデルは[Basic MNIST Example](https://github.com/pytorch/examples/tree/master/mnist)にあります  

# 参考にしたサイト

https://www.madopro.net/entry/pytorch_mnist
https://stackoverflow.com/questions/41990250/what-is-cross-entropy
http://nkdkccmbr.hateblo.jp/entry/2016/10/06/222245
http://cookie-box.hatenablog.com/entry/2017/05/07/121607