【前編】PyTorchの自動微分を使って線形回帰をやってみた
IT技術
前編~PyTorchの自動微分を使って線形回帰に挑戦!~
「PyTorch」を使っていると、次のような疑問を持つ人は多いはず…。
「model.zero_grad() って何やってるんだろう?」
「loss.backward() では、何が計算されているの?」
「Tensor の属性の requires_grad って何?」
ここでは、そんな方のために、「PyTorch」の自動微分による線形回帰を、わかりやすく解説していきます!
「自動微分」の理解がカギを握る
冒頭の PyTorch の疑問や、Tensor の以下の属性は、すべて自動微分(automatic differentiation)に関係しています。
- requires_grad
- grad
- grad_fn
- is_leaf
つまり、PyTorch の「自動微分」を理解すれば、すべての疑問をスッキリと解消できるわけです。
では次から、そのカギとなる「自動微分」について、深く見ていきましょう!
まずは「自動微分」の準備
まずは、PyTorch で自動微分をするために、準備をしていきましょう!
ライブラリのインポート
はじめに、自動微分に必要なライブラリを、インポートしていきましょう。
1import numpy as np
2import matplotlib.pyplot as plt
3import torch
「requires_grad=True」を指定する
入力データを x に設定し、変数で w と b も、それぞれTensor(テンソル)として定義します。
1x = torch.tensor(5.)
2w = torch.tensor(2., requires_grad=True)
3b = torch.tensor(1., requires_grad=True)
このとき、w と b には、「 requires_grad=True 」をつけています。
これを「True」にすることで、「微分の対象にしますよ!」と指定しているわけですね!
ちなみに、デフォルトの状態では、「False」となっています。
まずは、上のコードで代入されたものを、全部プリントしてみましょう!
1print('x =', x)
2print('w =', w)
3print('b =', b)
4
5>>> x = tensor(5.)
6>>> w = tensor(2., requires_grad=True)
7>>> b = tensor(1., requires_grad=True)
しっかりと指定されているのが、わかりますね!
自動微分の「グラフ」を理解しよう
PyTorch は、計算の流れを、グラフにして記憶しています。
具体的な例は、次から紹介していきますので、順にみていきましょう!
自動微分のグラフ
それでは、簡単な計算をしてみましょう。
用意した計算式は、次のとおり。
1y = w * x + b
上のコードに、先ほど代入した値を当てはめていくと、答えは次のようになります。
1y = 2 * 5 + 1 = 11
では、これもプリントしてみましょう。
1print(y)
2
3>>> tensor(11., grad_fn=<AddBackward0>)
答えは、確かに「11」になりました!
計算内容をグラフにして記憶
さっきのプリント結果では、「11」のあとに、「grad_fn=<AddBackward0> 」がついていました。
これは、y が足し算( Add )によって生まれたものだと、記憶しているということです。
まずは、PyTorch がどんなグラフを持っているのか、以下の図で見てみましょう!
MulBackward0 の Mul は、「Multiply」の略で、「掛け算」のこと。
つまり、w と x が掛け算だったことも、記憶されているわけですね!
このように、計算に変数が含まれていると、PyTorch は計算の内容をグラフにして記憶していきます。
そうすることで、y に対して自動微分を実行する時に、PyTorch は逆の順番で計算をたどることができるのです。
y は、 w * x と b の足し算なので、微分を w * x と b とで、別々に計算できることがわかります。
さらに、w * x のところは、「定数」と「変数」の掛け算の微分になるということですね。
グラディエント関数「grad_fn」と末端変数「is_leaf」
「grad_fn」と「is_leaf」についても、理解を深めていきましょう!
グラディエント関数「grad_fn」
記憶された関数は、grad_fn を使うことで、参照することができます。
1y.grad_fn
2
3>>> <AddBackward0 at 0x7f975b9776a0>
そのため、関数を利用していないユーザーが定義した変数では、「None」となるのです。
1w.grad_fn is None
2
3>>> True
grad_fn の grad は、あとで出てくるグラディエント(gradient)の略です。
fn は、関数(function)の略となります。
末端変数「is_leaf」
ちなみに、w と b はユーザーが定義した変数で、「leaf Variable」と呼ばれています。
英語の「leaf」は、木の葉っぱのことなので、訳すとすれば「グラフの末端の変数」ですね!
w と b は、この末端変数となるので、もちろん以下のように True が返ってきます。
1w.is_leaf
2
3>>> True
ちなみに、x も定数ですが末端の値なので、is_leaf を呼ぶと True が返ってきます。
PyTorchのドキュメントでは、「leaf Tensor」と呼ばれています。
1x.is_leaf
2
3>>> True
そして、「leaf Tensor」の grad_fn も、None を返します。
1x.grad_fn is None
2
3>>> True
自動微分を行うタイミング
まず、計算の「終わり → 始まり」へ向かって、微分計算していく手法を、「誤差逆伝播法(Back propagation、あるいはBackprop)」と呼びます。
PyTorch の自動微分の機能では、グラフを自動的に作り、Backprop を行えるように準備してくれるのです。
最終的に、いつ自動微分を行うのかは、ユーザーが決めることができます。
自動微分とグラディエント(grad)
では、ここで y に対して、自動微分を実行してみましょう。
1y.backward()
y を計算する際に使われた変数、これに微分が自動で計算されます。
このとき、y の計算に使われた変数には、grad という属性が作られます。
こちらも、プリントしてみましょう!
1print('w.grad = ', w.grad)
2print('b.grad = ', b.grad)
3
4>>> w.grad = tensor(5.)
5>>> b.grad = tensor(1.)
grad は、「gradient」の略で、日本語では「グラディエント」とか「勾配」と呼ばれています。
グラディエント(勾配)の例
数学や物理では、「勾配」とはある関数の「最大傾斜を表すベクトル」のことで、y の全微分から導くことができます。
参考までに、全微分から勾配を求める式も見ていきましょう!
「」とし、y は、w と b の関数だとします。
よって、
となります。
つまり「グラディエント(勾配)」とは
グラディエントとは、簡単にいうとw や b の値を増やすときに、y
の値がどの程度変わるのかを表したもの。
先ほどのy は、直線の式なので、次のように簡単に表現できます。
- 「w.grad = 5 は 、w が1増えると、 y が5増える」
- 「b.grad = 1 は 、b が1増えると 、y が1増える」
もともとの式が、 y = w * x + b で x = 5 なので、正しいことがわかりますね!
線形回帰をやってみる
いよいよ、与えられたデータに対して、「線形回帰」を適用してみましょう!
「線形回帰」とは、データの分布を直線によって、近似させる手法です。
y = w * x + b では、x が入力値で、 y が x に対するデータの実測値となります。
これらデータを直線で最も近似させて表すとき、最適なパラメータ値「w」と「b」は、一体いくつなのかを求めるのです。
教師データを作る
まず、データを作ります。
1# ランダムに200このデータを直線の周りに分散
2N = 200
3x = np.random.rand(N)*30-15
4y = 2*x + np.random.randn(N)*5
5
6# float32型にしておく
7x = x.astype(np.float32)
8y = y.astype(np.float32)
ここでは、y = 2x という直線の式に、ノイズを加えたものを用意しました。
また、データ型は「フロート32」にしておきます。
理由は、あとで使う PyTorch のモジュール( nn.Linea など)がフロート32対応のものが多く、フロート64型のままだとエラーになるためです。
データ描画
では、データをプロットしましょう。
1# データを描画
2plt.scatter(x, y, marker='.')
3plt.xlabel('x')
4plt.ylabel('y')
5plt.show()
直線のまわりに、散乱したデータができましたね!
線形回帰のモデルとパラメータを設定する
では、データと変数を PyTorch の「Tensor」として定義しましょう。
x と y は、与えられたデータであり、定数なので from_numpy で Tensor に変換します。
w と b は求めたい値なので、変数として、適当に初期化しておきましょう!
1x = torch.from_numpy(x)
2y = torch.from_numpy(y)
3
4w = torch.tensor(1.0, requires_grad=True)
5b = torch.tensor(0.0, requires_grad=True)
この変数 w と b を使って、入力データ x から y の値を予測する、「線形モデル」を定義します。
1def model(x):
2 return w*x + b
線形回帰の損失関数
線形回帰を数値計算するときは、最適なパラメータを「最小2乗法」を使って求めていきます。
最小2乗法では、2乗誤差(予測値と実測値の差の2乗)が最小になるように、パラメータを調整します。
「損失関数」として、予測値 p と実測値 y との「平均2乗誤差(MSE: Mean Squared Error)」を定義しておきましょう。
1def mse(p, y):
2 return ((p-y)**2).mean()
上の mse は、2乗誤差の平均を計算したものです。
線形回帰モデルを訓練する
準備ができたので、トレーニングをしましょう!
データは小さいので、ミニバッチは考えずに、全てのデータを与えて何度もエポックを繰り返します。
1# 学習率
2lr = 1.0e-4
3
4# 変数を初期化します
5w = torch.tensor(1.0, requires_grad=True)
6b = torch.tensor(0.0, requires_grad=True)
7
8losses = []
9for epoch in range(3000):
10 # 線形モデルによる値の予測
11 p = model(x)
12
13 # 損失値と自動微分
14 loss = mse(p, y)
15 loss.backward()
16
17 # グラディエントを使って変数`w`と`b`の値を更新する。
18 with torch.no_grad():
19 w -= w.grad * lr
20 b -= b.grad * lr
21 w.grad.zero_()
22 b.grad.zero_()
23
24 # グラフ描画用
25 losses.append(loss.item())
26
27print('loss = ', loss.item())
28print('w = ', w.item())
29print('b = ', b.item())
30
31>>> loss = 23.644838333129883
32>>> w = 1.9873875379562378
33>>> b = -0.07110784947872162
すると、「w は2に近い値、b は0に近い値」になったので、ほぼ正解が得られましたね!
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
「好きを仕事にするエンジニア集団」の(株)ライトコードです! ライトコードは、福岡、東京、大阪、名古屋の4拠点で事業展開するIT企業です。 現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。 いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。 システム開発依頼・お見積もり大歓迎! また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」を積極採用中です! インターンや新卒採用も行っております。 以下よりご応募をお待ちしております! https://rightcode.co.jp/recruit