morikomorou’s blog

自分が学んだことなどの備忘録的なやつ

【python】言語処理100本ノック2020を解く(第8章後半)


はじめに

前回の続きで言語処理100本ノック解いていきたいと思います。

今回は第8章後半です。
前回から引き続きニューラルネットの実装にチャレンジしてます。




第8章: ニューラルネット

深層学習フレームワークの使い方を学び,ニューラルネットワークに基づくカテゴリ分類を実装します.

第6章で取り組んだニュース記事のカテゴリ分類を題材として,ニューラルネットワークでカテゴリ分類モデルを実装する.なお,この章ではPyTorch, TensorFlow, Chainerなどの機械学習プラットフォームを活用せよ.

問題70~76は前半のほうでやっています。
前半の変数等引き継いでます。

77. ミニバッチ化

問題76のコードを改変し,B事例ごとに損失・勾配を計算し,行列Wの値を更新せよ(ミニバッチ化).Bの値を1,2,4,8,…と変化させながら,1エポックの学習に要する時間を比較せよ.

データローダのバッチサイズの引数を変えるだけでミニバッチ化できるように作ってあるので簡単ですね。
学習用の関数内で時間を測る処理を加えておきます。

import time

# 学習用の関数を定義
def train_model(net, dataloaders_dict, criterion, optimizer, num_epochs):
    train_loss = []
    train_acc = []
    valid_loss = []
    valid_acc = []
    # epochのループ
    for epoch in range(num_epochs):
        # 開始時刻の記録
        start = time.time()
        print('Epoch {} / {}'.format(epoch + 1, num_epochs))
        print('--------------------------------------------')
        
        # epochごとの学習と検証のループ
        for phase in ['train', 'val']:
            if phase == 'train':
                net.train() # 訓練モード
            else:
                net.eval() # 検証モード
            
            epoch_loss = 0.0 # epochの損失和
            epoch_corrects = 0 # epochの正解数
            
            # データローダーからミニバッチを取り出すループ
            for inputs, labels in tqdm(dataloaders_dict[phase]):
                optimizer.zero_grad() # optimizerを初期化
                
                # 順伝播計算(forward)
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = net(inputs)
                    loss = criterion(outputs, labels) # 損失を計算
                    _, preds = torch.max(outputs, 1) # ラベルを予想
                    
                    # 訓練時は逆伝播
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()
                    
                    # イテレーション結果の計算
                    # lossの合計を更新
                    epoch_loss += loss.item() * inputs.size(0)
                    # 正解数の合計を更新
                    epoch_corrects += torch.sum(preds == labels.data)
            
            # epochごとのlossと正解率の表示
            epoch_loss = epoch_loss / len(dataloaders_dict[phase].dataset)
            epoch_acc = epoch_corrects.double() / len(dataloaders_dict[phase].dataset)
            if phase == 'train':
                train_loss.append(epoch_loss)
                train_acc.append(epoch_acc)
            else:
                valid_loss.append(epoch_loss)
                valid_acc.append(epoch_acc)
            
            print('{} Loss: {:.4f}, Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))
        # 修了時刻の記録
        end = time.time()
        calc_time = end - start
        print('batch_size {} calc_time: {:.4f} sec'.format(batch_size, calc_time))
    return train_loss, train_acc, valid_loss, valid_acc, calc_time


# 学習を実行する
batch_sizes = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048]
cpu_times = []
for batch_size in batch_sizes:
    print('batch_size: {}'.format(batch_size))
    # DataLoaderを作成
    train_dataloader = data.DataLoader(
                train_dataset, batch_size=batch_size, shuffle=True)
    valid_dataloader = data.DataLoader(
                valid_dataset, batch_size=len(valid_dataset), shuffle=False)
    test_dataloader = data.DataLoader(
                test_dataset, batch_size=len(test_dataset), shuffle=False)

    dataloaders_dict = {'train': train_dataloader,
                        'val': valid_dataloader,
                        'test': test_dataloader,
                       }
    # モデルの定義
    net = SLNet(300, 4)
    net.train()

    # 損失関数の定義
    criterion = nn.CrossEntropyLoss()

    # 最適化手法の定義
    optimizer = torch.optim.SGD(net.parameters(), lr=0.01, momentum=0.9)

    num_epochs = 1
    train_loss, train_acc, valid_loss, valid_acc, calc_time = \
                        train_model(net, dataloaders_dict, criterion, optimizer,
                                    num_epochs=num_epochs)
    cpu_times.append(calc_time)

以下出力です。

batch_size: 1
Epoch 1 / 1
--------------------------------------------
100%|██████████████████████████████████████████████████████████████████████████| 10672/10672 [00:03<00:00, 3022.37it/s]
train Loss: 0.4164, Acc: 0.8588
100%|████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 99.74it/s]
val Loss: 0.3099, Acc: 0.9025
batch_size 1 calc_time: 3.5450 sec
batch_size: 2
Epoch 1 / 1
--------------------------------------------
100%|████████████████████████████████████████████████████████████████████████████| 5336/5336 [00:02<00:00, 2278.40it/s]
train Loss: 0.4742, Acc: 0.8438
100%|████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 62.50it/s]
val Loss: 0.3447, Acc: 0.8936
batch_size 2 calc_time: 2.3640 sec
batch_size: 4
Epoch 1 / 1
--------------------------------------------
100%|████████████████████████████████████████████████████████████████████████████| 2668/2668 [00:01<00:00, 2203.14it/s]
train Loss: 0.5576, Acc: 0.8087
100%|████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 83.34it/s]
val Loss: 0.4072, Acc: 0.8718
batch_size 4 calc_time: 1.2290 sec
batch_size: 8
Epoch 1 / 1
--------------------------------------------
100%|████████████████████████████████████████████████████████████████████████████| 1334/1334 [00:00<00:00, 2194.08it/s]
train Loss: 0.6572, Acc: 0.7843
100%|███████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 111.10it/s]
val Loss: 0.4944, Acc: 0.8441
batch_size 8 calc_time: 0.6220 sec
batch_size: 16
Epoch 1 / 1
--------------------------------------------
100%|██████████████████████████████████████████████████████████████████████████████| 667/667 [00:00<00:00, 1961.75it/s]
train Loss: 0.7730, Acc: 0.7547
100%|████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 76.91it/s]
val Loss: 0.5991, Acc: 0.7931
batch_size 16 calc_time: 0.3570 sec
batch_size: 32
Epoch 1 / 1
--------------------------------------------
100%|██████████████████████████████████████████████████████████████████████████████| 334/334 [00:00<00:00, 1525.13it/s]
train Loss: 0.8799, Acc: 0.7579
100%|████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 83.33it/s]
val Loss: 0.7069, Acc: 0.7849
batch_size 32 calc_time: 0.2360 sec
batch_size: 64
Epoch 1 / 1
--------------------------------------------
100%|██████████████████████████████████████████████████████████████████████████████| 167/167 [00:00<00:00, 1018.30it/s]
train Loss: 0.9974, Acc: 0.7436
100%|████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 83.33it/s]
val Loss: 0.8305, Acc: 0.7834
batch_size 64 calc_time: 0.1820 sec
batch_size: 128
Epoch 1 / 1
--------------------------------------------
100%|█████████████████████████████████████████████████████████████████████████████████| 84/84 [00:00<00:00, 595.76it/s]
train Loss: 1.1072, Acc: 0.7258
100%|████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 83.34it/s]
val Loss: 0.9553, Acc: 0.7826
batch_size 128 calc_time: 0.1610 sec
batch_size: 256
Epoch 1 / 1
--------------------------------------------
100%|█████████████████████████████████████████████████████████████████████████████████| 42/42 [00:00<00:00, 362.07it/s]
train Loss: 1.2273, Acc: 0.5810
100%|████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 83.32it/s]
val Loss: 1.0692, Acc: 0.7714
batch_size 256 calc_time: 0.1330 sec
batch_size: 512
Epoch 1 / 1
--------------------------------------------
100%|█████████████████████████████████████████████████████████████████████████████████| 21/21 [00:00<00:00, 253.01it/s]
train Loss: 1.3051, Acc: 0.4915
100%|████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 90.91it/s]
val Loss: 1.1971, Acc: 0.7076
batch_size 512 calc_time: 0.0990 sec
batch_size: 1024
Epoch 1 / 1
--------------------------------------------
100%|█████████████████████████████████████████████████████████████████████████████████| 11/11 [00:00<00:00, 146.67it/s]
train Loss: 1.3788, Acc: 0.2822
100%|███████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 111.11it/s]
val Loss: 1.3110, Acc: 0.5600
batch_size 1024 calc_time: 0.0900 sec
batch_size: 2048
Epoch 1 / 1
--------------------------------------------
100%|████████████████████████████████████████████████████████████████████████████████████| 6/6 [00:00<00:00, 67.42it/s]
train Loss: 1.4050, Acc: 0.1233
100%|████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 76.92it/s]
val Loss: 1.3746, Acc: 0.4018
batch_size 2048 calc_time: 0.1070 sec

バッチサイズ大きくなればなるほどメモリは多く使うけど早く計算できるといった感じでしょうか?

78. GPU上での学習

問題77のコードを改変し,GPU上で学習を実行せよ.

学習用の関数をGPU対応に変更します。

# 学習用の関数を定義
def train_model(net, dataloaders_dict, criterion, optimizer, num_epochs):
    
    # 初期設定
    # GPUが使えるか確認
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print("使用デバイス:", device)
    
    # ネットワークをgpuへ
    net.to(device)
    
    # ネットワークがある程度固定なら高速化させる
    torch.backends.cudnn.benchmark = True
    
    train_loss = []
    train_acc = []
    valid_loss = []
    valid_acc = []
    # epochのループ
    for epoch in range(num_epochs):
        # 開始時刻の記録
        start = time.time()
        print('Epoch {} / {}'.format(epoch + 1, num_epochs))
        print('--------------------------------------------')
        
        # epochごとの学習と検証のループ
        for phase in ['train', 'val']:
            if phase == 'train':
                net.train() # 訓練モード
            else:
                net.eval() # 検証モード
            
            epoch_loss = 0.0 # epochの損失和
            epoch_corrects = 0 # epochの正解数
            
            # データローダーからミニバッチを取り出すループ
            for inputs, labels in tqdm(dataloaders_dict[phase]):
                # GPUが使えるならGPUにおっくる
                inputs = inputs.to(device)
                labels = labels.to(device)
                optimizer.zero_grad() # optimizerを初期化
                
                # 順伝播計算(forward)
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = net(inputs)
                    loss = criterion(outputs, labels) # 損失を計算
                    _, preds = torch.max(outputs, 1) # ラベルを予想
                    
                    # 訓練時は逆伝播
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()
                    
                    # イテレーション結果の計算
                    # lossの合計を更新
                    epoch_loss += loss.item() * inputs.size(0)
                    # 正解数の合計を更新
                    epoch_corrects += torch.sum(preds == labels.data)
            
            # epochごとのlossと正解率の表示
            epoch_loss = epoch_loss / len(dataloaders_dict[phase].dataset)
            epoch_acc = epoch_corrects.double() / len(dataloaders_dict[phase].dataset)
            if phase == 'train':
                train_loss.append(epoch_loss)
                train_acc.append(epoch_acc)
            else:
                valid_loss.append(epoch_loss)
                valid_acc.append(epoch_acc)
            
            print('{} Loss: {:.4f}, Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))
        # 修了時刻の記録
        end = time.time()
        calc_time = end - start
        print('batch_size {} calc_time: {:.4f} sec'.format(batch_size, calc_time))
    return train_loss, train_acc, valid_loss, valid_acc, calc_time

出力は以下。

batch_size: 1
使用デバイス: cuda:0
Epoch 1 / 1
--------------------------------------------
100%|███████████████████████████████████████████████████████████████████████████| 10672/10672 [00:13<00:00, 819.16it/s]
train Loss: 0.4165, Acc: 0.8594
100%|███████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 110.79it/s]
val Loss: 0.3130, Acc: 0.8966
batch_size 1 calc_time: 13.0489 sec
batch_size: 2
使用デバイス: cuda:0
Epoch 1 / 1
--------------------------------------------
100%|████████████████████████████████████████████████████████████████████████████| 5336/5336 [00:04<00:00, 1236.44it/s]
train Loss: 0.4785, Acc: 0.8397
100%|███████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 125.03it/s]
val Loss: 0.3481, Acc: 0.9003
batch_size 2 calc_time: 4.3276 sec
batch_size: 4
使用デバイス: cuda:0
Epoch 1 / 1
--------------------------------------------
100%|████████████████████████████████████████████████████████████████████████████| 2668/2668 [00:02<00:00, 1233.47it/s]
train Loss: 0.5596, Acc: 0.8135
100%|███████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 125.02it/s]
val Loss: 0.4078, Acc: 0.8718
batch_size 4 calc_time: 2.1750 sec
batch_size: 8
使用デバイス: cuda:0
Epoch 1 / 1
--------------------------------------------
100%|████████████████████████████████████████████████████████████████████████████| 1334/1334 [00:01<00:00, 1188.95it/s]
train Loss: 0.6587, Acc: 0.7796
100%|███████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 125.02it/s]
val Loss: 0.4937, Acc: 0.8403
batch_size 8 calc_time: 1.1340 sec
batch_size: 16
使用デバイス: cuda:0
Epoch 1 / 1
--------------------------------------------
100%|██████████████████████████████████████████████████████████████████████████████| 667/667 [00:00<00:00, 1160.00it/s]
train Loss: 0.7698, Acc: 0.7682
100%|███████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 125.01it/s]
val Loss: 0.5959, Acc: 0.7954
batch_size 16 calc_time: 0.5880 sec
batch_size: 32
使用デバイス: cuda:0
Epoch 1 / 1
--------------------------------------------
100%|███████████████████████████████████████████████████████████████████████████████| 334/334 [00:00<00:00, 982.35it/s]
train Loss: 0.8840, Acc: 0.7559
100%|███████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 111.15it/s]
val Loss: 0.7097, Acc: 0.7879
batch_size 32 calc_time: 0.3530 sec
batch_size: 64
使用デバイス: cuda:0
Epoch 1 / 1
--------------------------------------------
100%|███████████████████████████████████████████████████████████████████████████████| 167/167 [00:00<00:00, 818.63it/s]
train Loss: 1.0040, Acc: 0.7269
100%|████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 16.39it/s]
val Loss: 0.8337, Acc: 0.7871
batch_size 64 calc_time: 0.2700 sec
batch_size: 128
使用デバイス: cuda:0
Epoch 1 / 1
--------------------------------------------
100%|█████████████████████████████████████████████████████████████████████████████████| 84/84 [00:00<00:00, 651.11it/s]
train Loss: 1.1024, Acc: 0.7233
100%|███████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 124.99it/s]
val Loss: 0.9509, Acc: 0.7781
batch_size 128 calc_time: 0.1410 sec
batch_size: 256
使用デバイス: cuda:0
Epoch 1 / 1
--------------------------------------------
100%|█████████████████████████████████████████████████████████████████████████████████| 42/42 [00:00<00:00, 466.68it/s]
train Loss: 1.1995, Acc: 0.6498
100%|███████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 124.57it/s]
val Loss: 1.0585, Acc: 0.7714
batch_size 256 calc_time: 0.1030 sec
batch_size: 512
使用デバイス: cuda:0
Epoch 1 / 1
--------------------------------------------
100%|█████████████████████████████████████████████████████████████████████████████████| 21/21 [00:00<00:00, 295.79it/s]
train Loss: 1.3066, Acc: 0.4277
100%|███████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 111.12it/s]
val Loss: 1.1904, Acc: 0.6184
batch_size 512 calc_time: 0.0840 sec
batch_size: 1024
使用デバイス: cuda:0
Epoch 1 / 1
--------------------------------------------
100%|█████████████████████████████████████████████████████████████████████████████████| 11/11 [00:00<00:00, 161.77it/s]
train Loss: 1.3539, Acc: 0.3698
100%|████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 90.89it/s]
val Loss: 1.2941, Acc: 0.6229
batch_size 1024 calc_time: 0.0840 sec
batch_size: 2048
使用デバイス: cuda:0
Epoch 1 / 1
--------------------------------------------
100%|████████████████████████████████████████████████████████████████████████████████████| 6/6 [00:00<00:00, 88.20it/s]
train Loss: 1.3785, Acc: 0.3351
100%|███████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 142.89it/s]
val Loss: 1.3489, Acc: 0.5330
batch_size 2048 calc_time: 0.0810 sec

バッチサイズが小さいときはCPUのほうが早いですが、ある程度のバッチサイズになってくるとgpuのほうが高速で学習が進んでおりますね。
GPUのほうが行列計算速度に分があるはずなので、バッチサイズが小さいときにはあまりその利点を生かせないんだと思います。




79. 多層ニューラルネットワーク

問題78のコードを改変し,バイアス項の導入や多層化など,ニューラルネットワークの形状を変更しながら,高性能なカテゴリ分類器を構築せよ.

層のサイズの設定とかよくわかりませんが、多層ニューラルネットモデル適当に作ってみます。
今回は入力層(サイズ300)、隠れ層(サイズ256)、出力層(サイズ4)の構成で行きます。
過学習を抑えるために適当にbatch normalizationの層を挟んでおきます。
まずはモデルの定義から行います。

class MLNet(nn.Module):
    def __init__(self, input_size, mid_size, output_size):
        super().__init__()
        self.layers = nn.Sequential(
                    nn.Linear(input_size, mid_size),
                    nn.BatchNorm1d(mid_size),
                    nn.ReLU(),
                    nn.Linear(mid_size, mid_size),
                    nn.BatchNorm1d(mid_size),
                    nn.ReLU(),
                    nn.Linear(mid_size, output_size),
                    )
    
    def forward(self, x):
        logits = self.layers(x)
        return logits

net = MLNet(300, 256, 4)
print(net)

出力は以下。

MLNet(
  (layers): Sequential(
    (0): Linear(in_features=300, out_features=256, bias=True)
    (1): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (2): ReLU()
    (3): Linear(in_features=256, out_features=256, bias=True)
    (4): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (5): ReLU()
    (6): Linear(in_features=256, out_features=4, bias=True)
  )
)

学習用関数を定義して、学習していきます。
バッチサイズは2048とします。

# 学習用の関数を定義
def train_model(net, dataloaders_dict, criterion, optimizer, num_epochs):
    
    # 初期設定
    # GPUが使えるか確認
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print("使用デバイス:", device)
    
    # ネットワークをgpuへ
    net.to(device)
    
    # ネットワークがある程度固定なら高速化させる
    torch.backends.cudnn.benchmark = False
    torch.backends.cudnn.deterministic = True
    
    train_loss = []
    train_acc = []
    valid_loss = []
    valid_acc = []
    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, num_epochs, eta_min=1e-6, last_epoch=-1)
    
    # epochのループ
    for epoch in range(num_epochs):
        # epochごとの学習と検証のループ
        for phase in ['train', 'val']:
            if phase == 'train':
                net.train() # 訓練モード
            else:
                net.eval() # 検証モード
            
            epoch_loss = 0.0 # epochの損失和
            epoch_corrects = 0 # epochの正解数
            
            # データローダーからミニバッチを取り出すループ
            for inputs, labels in dataloaders_dict[phase]:
                # GPUが使えるならGPUにおっくる
                inputs = inputs.to(device)
                labels = labels.to(device)
                optimizer.zero_grad() # optimizerを初期化
                
                # 順伝播計算(forward)
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = net(inputs)
                    loss = criterion(outputs, labels) # 損失を計算
                    _, preds = torch.max(outputs, 1) # ラベルを予想
                    
                    # 訓練時は逆伝播
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()
                    
                    # イテレーション結果の計算
                    # lossの合計を更新
                    epoch_loss += loss.item() * inputs.size(0)
                    # 正解数の合計を更新
                    epoch_corrects += torch.sum(preds == labels.data)
            
            # epochごとのlossと正解率の表示
            epoch_loss = epoch_loss / len(dataloaders_dict[phase].dataset)
            epoch_acc = epoch_corrects.double() / len(dataloaders_dict[phase].dataset)
            if phase == 'train':
                train_loss.append(epoch_loss)
                train_acc.append(epoch_acc.cpu())
            else:
                valid_loss.append(epoch_loss)
                valid_acc.append(epoch_acc.cpu())
            
        print('Epoch {} / {} (train) Loss: {:.4f}, Acc: {:.4f}, (val) Loss: {:.4f}, Acc: {:.4f}'.format(epoch + 1, num_epochs, train_loss[-1], train_acc[-1], valid_loss[-1], valid_acc[-1]))
        scheduler.step()
    return train_loss, train_acc, valid_loss, valid_acc

batch_size = 2048
num_epochs = 200

# DataLoaderを作成
train_dataloader = data.DataLoader(
            train_dataset, batch_size=batch_size, shuffle=True)
valid_dataloader = data.DataLoader(
            valid_dataset, batch_size=len(valid_dataset), shuffle=False)
test_dataloader = data.DataLoader(
            test_dataset, batch_size=len(test_dataset), shuffle=False)

dataloaders_dict = {'train': train_dataloader,
                    'val': valid_dataloader,
                    'test': test_dataloader,
                   }
# モデルの定義
net = MLNet(300, 256, 4)
net.train()

# 損失関数の定義
criterion = nn.CrossEntropyLoss()

# 最適化手法の定義
optimizer = torch.optim.Adam(net.parameters(), lr=0.0001)

train_loss, train_acc, valid_loss, valid_acc = \
            train_model(net, dataloaders_dict, criterion, optimizer,
                        num_epochs=num_epochs)

出力は以下長いので途中省略。

使用デバイス: cuda:0
Epoch 1 / 200 (train) Loss: 1.3631, Acc: 0.3142, (val) Loss: 1.4112, Acc: 0.0697
Epoch 2 / 200 (train) Loss: 1.1337, Acc: 0.6022, (val) Loss: 1.3906, Acc: 0.1642
Epoch 3 / 200 (train) Loss: 0.9533, Acc: 0.7463, (val) Loss: 1.3463, Acc: 0.5397
Epoch 4 / 200 (train) Loss: 0.8177, Acc: 0.7941, (val) Loss: 1.2753, Acc: 0.6402
Epoch 5 / 200 (train) Loss: 0.7179, Acc: 0.8168, (val) Loss: 1.1769, Acc: 0.6957
Epoch 6 / 200 (train) Loss: 0.6449, Acc: 0.8293, (val) Loss: 1.0551, Acc: 0.7579
Epoch 7 / 200 (train) Loss: 0.5897, Acc: 0.8396, (val) Loss: 0.9165, Acc: 0.7939
Epoch 8 / 200 (train) Loss: 0.5472, Acc: 0.8471, (val) Loss: 0.7792, Acc: 0.8268
Epoch 9 / 200 (train) Loss: 0.5137, Acc: 0.8550, (val) Loss: 0.6590, Acc: 0.8493
Epoch 10 / 200 (train) Loss: 0.4856, Acc: 0.8641, (val) Loss: 0.5668, Acc: 0.8576
...
Epoch 196 / 200 (train) Loss: 0.0702, Acc: 0.9903, (val) Loss: 0.2397, Acc: 0.9205
Epoch 197 / 200 (train) Loss: 0.0703, Acc: 0.9898, (val) Loss: 0.2395, Acc: 0.9205
Epoch 198 / 200 (train) Loss: 0.0702, Acc: 0.9898, (val) Loss: 0.2398, Acc: 0.9190
Epoch 199 / 200 (train) Loss: 0.0701, Acc: 0.9899, (val) Loss: 0.2398, Acc: 0.9183
Epoch 200 / 200 (train) Loss: 0.0700, Acc: 0.9900, (val) Loss: 0.2398, Acc: 0.9190

最終的な正解率の出力及び、学習中のロス、精度をプロットしてみましょう。

def calc_acc(net, dataloader):
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    net.eval()
    corrects = 0
    with torch.no_grad():
        for inputs, labels in dataloader:
            inputs = inputs.to(device)
            labels = labels.to(device)
            outputs = net(inputs)
            _, preds = torch.max(outputs, 1) # ラベルを予想
            corrects += torch.sum(preds == labels.data).cpu()
    return corrects / len(dataloader.dataset)

acc_train = calc_acc(net, train_dataloader)
acc_valid = calc_acc(net, valid_dataloader)
acc_test = calc_acc(net, test_dataloader)
print('学習データの正解率: {:.4f}'.format(acc_train))
print('検証データの正解率: {:.4f}'.format(acc_valid))
print('テストデータの正解率: {:.4f}'.format(acc_test))

fig, ax = plt.subplots(1,2, figsize=(10, 5))
epochs = np.arange(num_epochs)
ax[0].plot(epochs, train_loss, label='train')
ax[0].plot(epochs, valid_loss, label='valid')
ax[0].set_title('loss')
ax[0].set_xlabel('epoch')
ax[0].set_ylabel('loss')
ax[1].plot(epochs, train_acc, label='train')
ax[1].plot(epochs, valid_acc, label='valid')
ax[1].set_title('acc')
ax[1].set_xlabel('epoch')
ax[1].set_ylabel('acc')
ax[0].legend(loc='best')
ax[1].legend(loc='best')
plt.tight_layout()
plt.savefig('fig79.png')
plt.show()

結果は以下の通り
単層の時に比べ1%ほど精度が上がりました!うれしいですね。

学習データの正解率: 0.9897
検証データの正解率: 0.9190
テストデータの正解率: 0.9033


おわりに

8章終わりです。
pytorch初めて使いましたが、以下の本が入門としても大変参考になりました。