自然言語処理100本ノック2020を解く(8章-後半)

73.確率的勾配降下法による学習

確率的勾配降下法による学習を行なっていきます.
まずはニューラルネットワークのモデルを定義します.


コード

class MyNet(nn.Module):
    def __init__(self, input_size):
        super(MyNet, self).__init__()
        self.fc1 = torch.nn.Linear(input_size, 4)
        
    def forward(self, x):
        x = self.fc1(x)
        return x


本問題では,とりあえず入力→出力のレイヤのみとします.
forward()は,推論処理をする部分です.順方向伝搬とも呼びます.


コード

mynet = MyNet(dim)
optimizer = optim.SGD(mynet.parameters(), lr = 0.01, momentum = 0.9)
num_epochs = 100
save_path = '../data/params.tar'


入力は,単語ベクトルの次元と同じにします.次に,pytorchからSGDを持ってきてインスタンスを作ります.
確率的勾配法や学習係数,モメンタムについてここで説明するとN番煎じな感じになるので,割愛します.
プロフェッショナルシリーズの深層学習が数式が載っていてわかりやすいです.


コード

import time
import matplotlib.pyplot as plt
%matplotlib inline

class Model():
    def __init__(self, model):
        self.model = model
        
    def set_params(self, num_epochs, num_batches, optimizer, criterion):
        # エポック数,バッチサイズ,最適化アルゴリズム,損失関数の初期化
        self.num_epochs = num_epochs
        self.num_batches = num_batches
        self.optimizer = optimizer
        self.criterion = criterion
        
        # epoch毎の損失と精度を保持するリスト
        self.losses = np.zeros(num_epochs)
        self.accs = np.zeros(num_epochs)

    def fit(self, x_train, y_train):
        num_data = x_train.shape[0]
        for epoch in range(self.num_epochs):

            # epoch毎の損失と精度
            running_loss = 0.0
            accuracy = 0.0

            # 開始時間
            t = time.time()

            # ミニバッチ学習のために訓練データのインデックスをランダムに並べ替える
            index = np.random.permutation(num_data)

            for i in range(0, num_data, self.num_batches):
                # NNへの入力と正解ラベル
                in_, label = torch.tensor(x_train[index[i : i + self.num_batches if i + self.num_batches < num_data else num_data]]), \
                             torch.tensor(y_train[index[i : i + self.num_batches if i + self.num_batches < num_data else num_data]]).long()

                # 最適化関数の初期化
                self.optimizer.zero_grad()

                # 予測とロスの計算
                out_ = self.model(in_.float())
                loss = self.criterion(out_, label)

                _, idx = torch.max(out_, 1)

                # バッチ数のラベルが出てくるので,いくつ正解しているかカウント
                cnt = 0
                for i in range(idx.shape[0]):
                    if idx[i] == label[i]:
                        cnt += 1
                accuracy += cnt / idx.shape[0]

                # パラメーターの更新
                loss.backward()
                optimizer.step()
                running_loss += loss.data

            # 各種データの出力・保存
            num_loop = num_data / self.num_batches
            accuracy /= num_loop
            loss = running_loss / num_loop
            print('[ epoch:{0}, time:{3} ]  acc: {1:.4f}  loss:{2:.4f}'.format(epoch + 1, accuracy, loss, time.time() - t))
            self.accs[epoch] = accuracy
            self.losses[epoch] = loss
 
            # 状況の保存
            torch.save({
              'epoch': epoch,
              'model_state_dict': self.model.state_dict(),
              'optimizer_state_dict': self.optimizer.state_dict(),
              'loss': loss,
              'accuracy': accuracy,
              }, save_path)
    
    def predict(self, x):
        out_ = self.model(x)
        return out_
    
    def evaluate(self, x, y):
        p = self.predict(x)
        _, idx = torch.max(p, 1)
        cnt = 0
        for i in range(idx.shape[0]):
            if idx[i] == y[i]:
                cnt += 1
        accuracy = cnt / idx.shape[0]
        
        return accuracy
    
    def visualize(self):
        x = np.arange(len(self.accs))
        fig = plt.figure()
        ax = fig.add_subplot(211)
        ax.plot(x, self.accs, color = 'red', linewidth = 2, label = 'accuracy')
        ax2 = fig.add_subplot(212)
        ax2.plot(x, self.losses, color = 'green', linewidth = 2, label = 'loss')
        ax.legend()
        ax2.legend()
        plt.plot()

model = Model(mynet)
model.set_params(num_epochs, 1, optimizer, criterion)

model.fit(x_feature, y_train)


実行結果

[ epoch:1, time:2.592939853668213 ]  acc: 0.8776  loss:0.3732
[ epoch:2, time:2.466198205947876 ]  acc: 0.9112  loss:0.2633
[ epoch:3, time:2.4603400230407715 ]  acc: 0.9181  loss:0.2404
...
[ epoch:98, time:2.58657169342041 ]  acc: 0.9427  loss:0.1618
[ epoch:99, time:2.535313129425049 ]  acc: 0.9415  loss:0.1610
[ epoch:100, time:2.559777021408081 ]  acc: 0.9419  loss:0.1612


細かい説明などは問題に合わせてしていきます.
エポックは100で学習しています.
ロスが減っていくのが確認できます.




74.正解率の計測

学習データと評価データに対する予測をし,その正解率を求めます.
Modelクラスのevaluateメソッドを使います.

コード

    # Modelクラス内
    def evaluate(self, x, y):
        p = self.predict(x)
        _, idx = torch.max(p, 1)
        cnt = 0
        for i in range(idx.shape[0]):
            if idx[i] == y[i]:
                cnt += 1
        accuracy = cnt / idx.shape[0]
        
        return accuracy

print(model.evaluate(x_feature.float(), y_train))
print(model.evaluate(torch.tensor(vec2sum(x_valid, dim)).float(), y_valid))


実行結果

0.9435002296738632
0.9237132352941176

75.損失と正解率のプロット

この問題では,Modelクラス内のvisualizeメソッドを使います.

    def visualize(self):
        x = np.arange(len(self.accs))
        fig = plt.figure()
        ax = fig.add_subplot(211)
        ax.plot(x, self.accs, color = 'red', linewidth = 2, label = 'accuracy')
        ax2 = fig.add_subplot(212)
        ax2.plot(x, self.losses, color = 'green', linewidth = 2, label = 'loss')
        ax.legend()
        ax2.legend()
        plt.plot()

model.visualize()


実行結果

f:id:homuhomu0131:20200511211840p:plain
正解率と損失の推移




76.チェックポイント

73のコードの#状況の保存 の部分でチェックポイントを記録しています.



77.ミニバッチ化

いくつかの事例毎にパラメータを更新するようにします.
73のコードでは,num_batchesでバッチサイズを指定しています.
毎エポックでインデックスをランダムにしたものを作成し,まとめて入力とします.


コード

model2 = Model(mynet)
model2.set_params(num_epochs, 4, optimizer, criterion)
model2.fit(x_feature, y_train)


実行結果

[ epoch:1, time:0.7158238887786865 ]  acc: 0.8294  loss:0.5254
[ epoch:2, time:0.6863267421722412 ]  acc: 0.8870  loss:0.3467
[ epoch:3, time:0.6839478015899658 ]  acc: 0.9010  loss:0.3032
...
[ epoch:98, time:0.7376530170440674 ]  acc: 0.9402  loss:0.1720
[ epoch:99, time:0.723405122756958 ]  acc: 0.9386  loss:0.1723
[ epoch:100, time:0.7342729568481445 ]  acc: 0.9388  loss:0.1723


バッチサイズを4にしたところ,1エポックにかかる時間が大体1/4になりました.



78.GPU上での学習

割愛します.



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

ニューラルネットワークをカスタマイズします.


コード

class MyNet2(nn.Module):
    def __init__(self, input_size, h_size, output_size):
        super(MyNet2, self).__init__()
        self.fc1 = torch.nn.Linear(input_size, h_size)
        self.fc2 = torch.nn.Linear(h_size, h_size / 2)
        self.fc3 = torch.nn.Linear(h_size / 2, output_size)
        
    def forward(self, x):
        x = self.fc1(x)
        x = func.relu(x)
        x = self.fc2(x)
        x = func.relu(x)
        x = self.fc3(x)
        return x

mynet = MyNet2(dim, 100, 4)
optimizer = optim.SGD(mynet.parameters(), lr = 0.01, momentum = 0.9)
criterion = torch.nn.CrossEntropyLoss()
save_path = '../data/params.tar'

model = Model(mynet)
model.set_params(num_epochs, 2, optimizer, criterion)
model.fit(x_feature, y_train)

実行結果

[ epoch:1, time:2.569727897644043 ]  acc: 0.8461  loss:0.4161
[ epoch:2, time:2.516397714614868 ]  acc: 0.9080  loss:0.2681
[ epoch:3, time:2.531559944152832 ]  acc: 0.9192  loss:0.2329
...
[ epoch:98, time:3.5405173301696777 ]  acc: 0.9985  loss:0.0020
[ epoch:99, time:3.155651092529297 ]  acc: 0.9987  loss:0.0020
[ epoch:100, time:3.258673906326294 ]  acc: 0.9985  loss:0.0020


中間層を追加しました.
精度も見てみます.


コード

print(model.evaluate(x_feature.float(), y_train))
print(model.evaluate(torch.tensor(vec2sum(x_valid, dim)).float(), y_valid))
model.visualize()
0.9989664676159853
0.9365808823529411


f:id:homuhomu0131:20200511212316p:plain
正解率と損失の推移


過適合してますね...

とりあえず,以上とします.

何かございましたらコメントください...



全コード:
github.com



前回:
pongyun.hatenablog.com