🔥

【PyTorch①】簡単な一次関数モデルを使って、Deep Learnigを一通り理解しよう

に公開

はじめに

生成AIの仕組みを理解するにあたって、避けても通れないのが深層学習、Deep Learnigですね。
今回は、PyTorchを使ってコードを見ながら全体像を把握してみましょう。

概念的な理解としては、ヨビノリさんの動画がとてもよかったので、おすすめです!
https://www.youtube.com/watch?v=xzzTYL90M8s&list=PLHtaIkfroUbtxLN8vxUIvNvWQQ4xs8TCM

https://www.youtube.com/watch?v=0itH0iDO8BE&list=PLHtaIkfroUbtxLN8vxUIvNvWQQ4xs8TCM&index=2

今回のやること

今回は、一番簡単な、1次元かつ1層の関数を使って深層学習をやってみましょう。もはやシンプルすぎて深層学習とは呼べません。線形モデルの学習ですね。

今回のコードはこちらです。

import torch

# y = 2x + 1
x = torch.tensor([[1.0], [2.0], [3.0], [4.0]])
y = torch.tensor([[3.0], [5.0], [7.0], [9.0]])

model = torch.nn.Linear(1, 1)

loss_fn = torch.nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)

for epoch in range(100):
    pred = model(x)        
    loss = loss_fn(pred, y)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

print(model.weight.item(), model.bias.item())

コードの解説

それぞれのコードを順番に見ていきます。
途中途中で深層学習でよく聞く単語の解説もしていきましょう。

データの準備

# y = 2x + 1
x = torch.tensor([[1.0], [2.0], [3.0], [4.0]])
y = torch.tensor([[3.0], [5.0], [7.0], [9.0]])

今回はy = 2x + 1 になるようなxとyを用意しました。

モデルの準備

model = torch.nn.Linear(1, 1)

このLinear(in_features, out_features) は、入力ベクトルの要素数と、出力ベクトルの要素数を表します。
今回は、入力は1.0などの数字だけで、出力も3.0の数字ですね。なので、入力要素数も、出力要素数も1になります。

また、今回は中間層(入力層と出力層の間にある層。隠れ層ともいう)がありません。

なので、今回のモデルはシンプルに
y = wx + b (w = weight, b = bias)
という一次関数になります。
また、初期設定ではwもbもランダムな値がセットされています。

例えば、「28×28=784ピクセルの画像を、0~9の数字に分類する」というモデルであれば、入力層は784、出力層は10になります。(実際は中間層が何層もあるはずです)

損失関数

loss_fn = torch.nn.MSELoss()

loss_fn は損失関数です。これは予測データと正解の差分を検出するための関数になります。
深層学習の実際に行われていることは、この誤差関数をなるべく小さくするためにmodelのパラメーターをいじる、ということです。

今回だと、PyTorchが用意してくれているMSE(Mean Squared Error = 平均二乗誤差)という関数を使います。
中身としては、「データの差分を2乗して、それの平均」という関数です。

loss = \frac{1}{N}\sum_{i=1}^{N}(y_i-\hat{y}_i)^2

なぜ2乗にするかというと、①正の差分と負の差分をそれぞれ打ち消さないようにする ②大きなズレに対してより損失を大きくすることで罰する、という理由です。
損失関数はこのMSE以外にも色々な関数があります。

最適化手法

optimizer = torch.optim.SGD(model.parameters(), lr=0.1)

optimizer は最適化手法になります。損失関数をなるべく小さくするために、どのようにパラメーターをいじるか、というのを宣言します。
今回もPyTorchが関数を用意してくれて、SGD(Stochastic Gradient Descent = 確率的勾配降下法)という関数です。
こちらの詳細は他の動画にお任せするのですが、誤差関数の極小値を探すために、関数の勾配(傾き)を計算し、その勾配に応じてパラメーターを調節するやり方です。
このSGDの引数として、modelのパラメーター(今回で言うとweightとbias)と、lr = learning_rateを指定します。
このlearning_rateは学習率というのですが、勾配に対してどれくらいパラメーターを大きく動かすか、を指定する変数になります。
後ほど、このlearning_rateを調節して、実行された結果がどうなるかを検証します。

学習の中身

for epoch in range(100):
    pred_y = model(x)        
    loss = loss_fn(pred_y, y)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

range(100) は、100回このfor文を回すという回数を指定しています。
後ほどこの回数もいじっていきます。

その後、現状のmodelにxを入れてみた時の学習途中の結果 pred_yを算出し、先ほど定義した損失関数 loss_fn に正解のyと共に渡して、損失のloss を算出します。

optimizer.zero_grad() は最後に説明しますね。
loss.backward()を実行することで、modelのパラメーターであるweightとbiasに対しての勾配が計算されます。
この勾配はlossが小さくなる方に傾くわけですね。
このタイミングでweight.gradbias.grad に値がセットされます。

その後optimizer.step() を実行することで、最初に定義した最適化関数のlearning_rateと、weight.grad bias.grad に従って、weightとbiasが更新されます。
ここで実際にmodelの学習がされているわけですね。

この2行前のoptimizer.zero_grad()weight.gradbias.gradをリセットする処理です。これを実行しないと、それぞれのgrad が重ねて更新されてしまいます。

実行してみよう

この関数を実行することで、学習されたweightとbiasが出力されます。
色々と条件を変えて、試してみましょう。

基本の数字

lr データ数 試行回数 weight bias
0.1 4個 100 2.000824213 0.9975770712

元々渡している関数が y = 2x + 1なので、weight = 2, bias = 1が正解です。
なので、この時点でかなり精密な結果が出ていますね

learning_rateをいじってみる

lr weight bias 備考
0.05 2.072940826 0.7855448127
0.1 2.000824213 0.9975770712
0.13 -9059124 -3081206.75 結構ブレあり

先ほどの通り、lr = learning_rateは、正解との誤差の勾配に対しての学習率になります。
例えば、「weightを上げる」という学習が回る場合、learning_rateが高いほど「思い切って一回で一気に上げる」learning_rateが低いほど「ちょっとずつ上げていく」という変更になります。
今回の場合だと、learning_rateが0.1だとちょうどいいですが、0.05になると学習が回るのが遅くて追いつかず、0.13だと遥か彼方に暴走してしまっています。

試行回数をいじってみる

試行回数 weight bias 備考
50 1.9766628742218018 1.0686137676239014 結構ブレあり
100 2.000824213 0.9975770712
1000 2.0000007152557373 0.9999982118606567

試行回数は多いほど正確な値に収束していきそうです。50回の試行回数で何回か実行してみたところ、実行ごとの差分がかなりありました。
これは最初にランダムで設定されているweightやbiasに対して、学習が十分ではないことが原因です。weightが2, biasが1に収束する試行対数が足りていないので、最初のランダム度合いがまだ残っているんですね。

終わりに

今回は、PyTorchを使い、一番簡単な中間層のない一次関数のmodelで学習をさせてみました。
次回はもう少し複雑なmodelで検証してみようと思います。

Discussion