BETA

fastai動かしてみたLesson5③ WeightDecay

投稿日:2019-05-26
最終更新:2019-05-26

今回はLesson5 https://course.fast.ai/videos/?lesson=5

notebookはこちら https://github.com/fastai/course-v3/tree/master/nbs/dl1

WeightDecay

今までの講義でfastaiでtrainをする時には、これを設定していなかった

上の図の様に、より多くの変数を持つモデルは右のグラフのようにOverfitしがちな傾向がある
という事で変数の数を減らしたい
なぜ、変数が増えるとOverfitする形になってしまうのだろうか
変数が多いという事は、より非線形に、より相互に、より曲がった線分(?)になる
そこで、変数が増えることによって複雑になる事を防ぐために、変数の値の二乗を合計する

モデルを作り、損失関数の中に変数の二乗の合計を加えてみよう
しかし、そのままでは数値が大きすぎており、全ての変数を0にする事が最も損失を小さくすることが最良になってしまう
そこで、そうならないように単に変数の二乗をモデルに加えるのではなく、ある数を二乗に掛ける事で、モデルに加える
その数をfastaiではwdで設定することが出来る

learn = collab_learner(data, n_factors=40, y_range=y_range, wd=1e-1)  

デフォルトでは0.01に設定されている

Lesson2のSGD notebookに戻る

notebookはこちら:https://github.com/fastai/course-v3/blob/master/nbs/dl1/lesson2-sgd.ipynb

SGDのnotebookの中で、適当にデータを作り、損失関数を加え、updateという関数で予測を行った

def update():  
    y_hat = [email protected]  
    loss = mse(y, y_hat)  
    if t % 10 == 0: print(loss)  
    loss.backward()  
    with torch.no_grad():  
        a.sub_(lr * a.grad)  
        a.grad.zero_()  

今回は層が1つしかないので、ReLUはない
損失関数はMSEを使い、勾配をloss.backwardで計算した
learning rate から learning rate × gradieant、つまり勾配降下を行い学習率を更新した
ここで行ったことを数式化するとこの様になる

$$ w_t = w_{t-1}-lr × \frac{dL}{dw_{t-1}} $$

ここで、喪失というのは独立変数Xと重みL(x,w)の事である
今回はMSEを使ったので、
$$L(x,w) = mse(\hat{y}, y)$$
となる
xとwはどこに入るのだろうか
今回の予測というのはモデルを実行する事によって得ることが出来、そのモデルは重みを含んでいることから
$$L(x,w) = mse(m(x, w), y)$$
これでupdate関数の中のa.grad、つまり勾配を求めることが出来た
これに、今回はWeight Decayを追加するので、以下のようになる
$$L(x,w) = mse(m(x, w), y) + wd \sum{w^2}$$

Lesson5のMNIST SGDのnotebookを見てみる

notebookはこちら:https://github.com/fastai/course-v3/blob/master/nbs/dl1/lesson5-sgd-mnist.ipynb

MNISTのデータはhttp://deeplearning.net/data/mnist/mnist.pkl.gz からダウンロードして、以下のpathで自分で指定してください

pathを指定し、ファイルを解凍して訓練と評価データに分けてみる

path = Path(".")  
with gzip.open(path/'mnist.pkl.gz', 'rb') as f:  
    ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding='latin-1')  
plt.imshow(x_train[0].reshape((28,28)), cmap="gray")  
x_train.shape  

中身はこんな感じ

plt.imshow(x_train[0].reshape((28,28)), cmap="gray")  
x_train.shape  


mnistの画像は28×28だが、今回は1×784のフラットなデータを使う
そのままのデータだとnumpyのarrayであるため、torchのtensorに変換する

x_train,y_train,x_valid,y_valid = map(torch.tensor, (x_train,y_train,x_valid,y_valid))  
n,c = x_train.shape  
x_train.shape, y_train.min(), y_train.max()  

(torch.Size([50000, 784]), tensor(0), tensor(9))

Lesson2のSGDではバイアスを考慮しなかった

x = torch.ones(n, 2)   

def mse(y_hat,y):  
    return((y_hat - y)**2).mean()  
y_hat = [email protected]

しかし、今回はバイアスを考慮した物を作る

今回は50000件のデータであり、十分に大きいのでミニバッチを作る
PytorchはTensorDatasetという関数で2つのtensorを使ってデータセットを作成する
そして、Databunch.createでミニバッチを作成する事が出来る
ここで、bsはバッチサイズの事であり、今回は64とした

bs=64  
train_ds = TensorDataset(x_train, y_train)  
valid_ds = TensorDataset(x_valid, y_valid)  
data = DataBunch.create(train_ds, valid_ds, bs=bs)

dataloaderは何番目か指定しなくても、nextを使う事によって別のミニバッチを返してくれる

x,y = next(iter(data.train_dl))  
x.shape,y.shape  

(torch.Size([64, 784]), torch.Size([64]))

バッチサイズ分のミニバッチを得ることが出来た

ここから行う事は、GPUを使った方が速度が速いのでいい

次に行う事として、今まではy_hat = [email protected] を行っていたが、ここではnn.Moduleを作成する
PythonのclassはPytorchではよく扱うので、勉強した方がいい

class Mnist_Logistic(nn.Module):  
    def __init__(self):  
        super().__init__()  
        self.lin = nn.Linear(784, 10, bias=True)  

    def forward(self, xb): return self.lin(xb)  

主にやる事はinitをオーバーライドする事と、super().__init()を呼び出す事が出来るようにする事だ

ここで追加したいことは、クラスに線形層(nn.Linear)を含む属性を作成したいという事だ
nn.Linearとは何だろうか
これは[email protected]+bを行う物である
それだけでなく、nn.Linearを使うだけで、今までdef forwardで計算していた[email protected]+bを計算することが出来る

全てのnn.Moduleは関数のように使う事が出来るので、ここではxb(Xのミニバッチ)をselg.linに渡すだけで[email protected]+bを得ることが出来る

これがロジスティック線形モデルである
ロジスティック線形モデルは隠れ層のないニューラルネットであり、1層のニューラルネットで非線形性はない

.cuda()をつける事でGPU上で動くよう設定

model = Mnist_Logistic().cuda()  
model  

Mnist_Logistic(
(lin): Linear(in_features=784, out_features=10, bias=True)
)

model.lin  

Linear(in_features=784, out_features=10, bias=True)

model(x).shape  

torch.Size([64, 10])

[p.shape for p in model.parameters()]  

[torch.Size([10, 784]), torch.Size([10])]

parametesはこの図の黄色の四角のすべてを含んでいる
具体的には重み行列やバイアス行列である

784次元の入力を、10次元の出力ので、10×786のtensorにする
それによってactivationsを得ることが出来るので、バイアスを加え、長さ10のベクトルを得る

lr = 2e-2  

loss_func = nn.CrossEntropyLoss()  

学習率を設定し、損失関数を今回はMSEでなくCrossEntropyLossとする
今回はどれぐらいあるクラスに近いかという事をしようとしているのではない
予測が3で実際は4であった場合と、予測が0で実際は4であった場合は、どちらも等しく4から離れている
従って今回はCrossEntropyLossを使う

def update(x,y,lr):  
    wd = 1e-5  
    y_hat = model(x)  
    # weight decay  
    w2 = 0.  
    for p in model.parameters(): w2 += (p**2).sum()  
    # add to regular loss  
    loss = loss_func(y_hat, y) + w2*wd  
    loss.backward()  
    with torch.no_grad():  
        for p in model.parameters():  
            p.sub_(lr * p.grad)  
            p.grad.zero_()  
    return loss.item()  

最終的にupdate関数はこのようになる
Lesson2のSGDで使った物と比べて、y_hatでは[email protected]を行うのではなくmodelを呼び出す事で[email protected]+bとした
また、lossではCrossEntropyLossを使った
そして、変数(黄色い四角内の物)を更新している

また、w2というものを追加した
w2にはmodel.parameters()で呼び出した変数(黄色い四角内の物)の二乗を加えている
従って、w2には二乗した重みの合計を含んでいる
その後、wdをw2に掛けることによってWeight decayを行う

losses = [update(x,y,lr) for x,y in data.train_dl]  
plt.plot(losses);  


update(x,y,lr)はlossを返す
つまりこれはdataの中のtrainのデータのlossのリストである
これをプロットすると上図のようになる

lossは減るにしたがって上下の振れ幅が大きくなっているように見える

これは学習率が大きいので、極を飛び越えているという事である(?)


何故なら、勾配だけを扱っているからである
勾配は実際は重みを更新する物である
従って下線部の$$wdΣw^2$$はとても興味深い

そこで、勾配(wdΣw^2)を見てみよう

wdΣw^2からΣを取り除き、wで微分してみよう
$$\frac{d}{dw}wd×w^2 = 2wd×w$$
2wd×wが得られた

lrはここでは1e-5であり、wは重みである
定式化するために係数2を取っ払う

つまり、全てのWeight Decayは、バッチを実行するたびにある倍数を重みに掛けるという事である
これをWeight Decayという

loss + wd×w^2を行う事をL2正則化と呼ぶ

grad - wd×wを行う事をWeightDecayという

MNISTニューラルネットワーク

新たなupdate関数を作ることが出来たので、新しいニューラルネットワークを作る事が出来るようになった

class Mnist_NN(nn.Module):  
    def __init__(self):  
        super().__init__()  
        self.lin1 = nn.Linear(784, 50, bias=True)  
        self.lin2 = nn.Linear(50, 10, bias=True)  

    def forward(self, xb):  
        x = self.lin1(xb)  
        x = F.relu(x)  
        return self.lin2(x)  

2つの線形層をinitで導入する
初めの層を784×50に設定する
二つ目の層は10個の数字のどれかを出力したいので50×10に設定する
そしてforwardは
1つ目の線形層の計算を行い、ReLUを計算し、2つ目の線形層の結果を返す

そしてこのモデルを使ってみる

model = Mnist_NN().cuda()  
losses = [update(x,y,lr) for x,y in data.train_dl]  
plt.plot(losses);  

ここまで出来たら、別のモデルを試す事も出来る
updateの中身をPytorchで変更する事で、簡単に出来る

今回はoptではSGDを扱っていたが、ここでAdamを使ってみるとどうだろうか

def update(x,y,lr):  
    opt = optim.Adam(model.parameters(), lr)  
    y_hat = model(x)  
    loss = loss_func(y_hat, y)  
    loss.backward()  
    opt.step()  
    opt.zero_grad()  
    return loss.item()  

Adamにしたので、lrを1e-3に設定する

losses = [update(x,y,1e-3) for x,y in data.train_dl]  
plt.plot(losses);  

より少ないepoch数でlossが0.5になっている事がわかる

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

大学生の書きなぐりブログ 間違ってる事も書いてるので自己責任で勉強しましょう

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
or 外部アカウントではじめる
10秒で技術ブログが作れます!