
【前編】PyTorchの自動微分を使って線形回帰をやってみた
2021.12.20
前編~PyTorchの自動微分を使って線形回帰に挑戦!~
「PyTorch」を使っていると、次のような疑問を持つ人は多いはず…。
「 model.zero_grad() って何やってるんだろう?」
「 loss.backward() では、何が計算されているの?」
「Tensor の属性の requires_grad って何?」
ここでは、そんな方のために、「PyTorch」の自動微分による線形回帰を、わかりやすく解説していきます!
「自動微分」の理解がカギを握る
冒頭の PyTorch の疑問や、Tensor の以下の属性は、すべて自動微分(automatic differentiation)に関係しています。
- requires_grad
- grad
- grad_fn
- is_leaf
つまり、PyTorch の「自動微分」を理解すれば、すべての疑問をスッキリと解消できるわけです。
では次から、そのカギとなる「自動微分」について、深く見ていきましょう!
まずは「自動微分」の準備
まずは、PyTorch で自動微分をするために、準備をしていきましょう!
ライブラリのインポート
はじめに、自動微分に必要なライブラリを、インポートしていきましょう。
1 2 3 | import numpy as np import matplotlib.pyplot as plt import torch |
「requires_grad=True」を指定する
入力データを x に設定し、変数で w と b も、それぞれTensor(テンソル)として定義します。
1 2 3 | x = torch.tensor(5.) w = torch.tensor(2., requires_grad=True) b = torch.tensor(1., requires_grad=True) |
このとき、 w と b には、「 requires_grad=True 」をつけています。
これを「True」にすることで、「微分の対象にしますよ!」と指定しているわけですね!
ちなみに、デフォルトの状態では、「False」となっています。
まずは、上のコードで代入されたものを、全部プリントしてみましょう!
1 2 3 4 5 6 7 | print('x =', x) print('w =', w) print('b =', b) >>> x = tensor(5.) >>> w = tensor(2., requires_grad=True) >>> b = tensor(1., requires_grad=True) |
しっかりと指定されているのが、わかりますね!
自動微分の「グラフ」を理解しよう
PyTorch は、計算の流れを、グラフにして記憶しています。
具体的な例は、次から紹介していきますので、順にみていきましょう!
自動微分のグラフ
それでは、簡単な計算をしてみましょう。
用意した計算式は、次のとおり。
1 | y = w * x + b |
上のコードに、先ほど代入した値を当てはめていくと、答えは次のようになります。
1 | y = 2 * 5 + 1 = 11 |
では、これもプリントしてみましょう。
1 2 3 | print(y) >>> 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 を使うことで、参照することができます。
1 2 3 | y.grad_fn >>> <AddBackward0 at 0x7f975b9776a0> |
そのため、関数を利用していないユーザーが定義した変数では、「None」となるのです。
1 2 3 | w.grad_fn is None >>> True |
grad_fn の grad は、あとで出てくるグラディエント(gradient)の略です。
fn は、関数(function)の略となります。
末端変数「is_leaf」
ちなみに、 w と b はユーザーが定義した変数で、「leaf Variable」と呼ばれています。
英語の「leaf」は、木の葉っぱのことなので、訳すとすれば「グラフの末端の変数」ですね!
w と b は、この末端変数となるので、もちろん以下のように True が返ってきます。
1 2 3 | w.is_leaf >>> True |
ちなみに、 x も定数ですが末端の値なので、 is_leaf を呼ぶと True が返ってきます。
PyTorchのドキュメントでは、「leaf Tensor」と呼ばれています。
1 2 3 | x.is_leaf >>> True |
そして、「leaf Tensor」の grad_fn も、 None を返します。
1 2 3 | x.grad_fn is None >>> True |
自動微分を行うタイミング
まず、計算の「終わり → 始まり」へ向かって、微分計算していく手法を、「誤差逆伝播法(Back propagation、あるいはBackprop)」と呼びます。
PyTorch の自動微分の機能では、グラフを自動的に作り、Backprop を行えるように準備してくれるのです。
最終的に、いつ自動微分を行うのかは、ユーザーが決めることができます。
自動微分とグラディエント(grad)
では、ここで y に対して、自動微分を実行してみましょう。
1 | y.backward() |
y を計算する際に使われた変数、これに微分が自動で計算されます。
このとき、 y の計算に使われた変数には、 grad という属性が作られます。
こちらも、プリントしてみましょう!
1 2 3 4 5 | print('w.grad = ', w.grad) print('b.grad = ', b.grad) >>> w.grad = tensor(5.) >>> b.grad = tensor(1.) |
grad は、「gradient」の略で、日本語では「グラディエント」とか「勾配」と呼ばれています。
グラディエント(勾配)の例
数学や物理では、「勾配」とはある関数の「最大傾斜を表すベクトル」のことで、 y の全微分から導くことができます。
参考までに、全微分から勾配を求める式も見ていきましょう!
「\(y = f(w, b)\)」とし、 y は、 w と b の関数だとします。
$$dy = \frac{\partial{f}}{\partial{w}}dw + \frac{\partial{f}}{\partial{b}}db
=
\begin{pmatrix}
\frac{\partial{f}}{\partial{w}} \\
\frac{\partial{f}}{\partial{b}}
\end{pmatrix}
\cdot
\begin{pmatrix}
dw \\
db
\end{pmatrix}
$$
よって、
$$\mathrm{grad} \, y =
\begin{pmatrix}
\frac{\partial{f}}{\partial{w}} \\
\frac{\partial{f}}{\partial{b}}
\end{pmatrix}
$$
となります。
つまり「グラディエント(勾配)」とは
グラディエントとは、簡単にいうと 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 2 3 4 5 6 7 8 | # ランダムに200このデータを直線の周りに分散 N = 200 x = np.random.rand(N)*30-15 y = 2*x + np.random.randn(N)*5 # float32型にしておく x = x.astype(np.float32) y = y.astype(np.float32) |
ここでは、 y = 2x という直線の式に、ノイズを加えたものを用意しました。
また、データ型は「フロート32」にしておきます。
理由は、あとで使う PyTorch のモジュール( nn.Linea など)がフロート32対応のものが多く、フロート64型のままだとエラーになるためです。
データ描画
では、データをプロットしましょう。
1 2 3 4 5 | # データを描画 plt.scatter(x, y, marker='.') plt.xlabel('x') plt.ylabel('y') plt.show() |

直線のまわりに、散乱したデータができましたね!
線形回帰のモデルとパラメータを設定する
では、データと変数を PyTorch の「Tensor」として定義しましょう。
x と y は、与えられたデータであり、定数なので from_numpy で Tensor に変換します。
w と b は求めたい値なので、変数として、適当に初期化しておきましょう!
1 2 3 4 5 | x = torch.from_numpy(x) y = torch.from_numpy(y) w = torch.tensor(1.0, requires_grad=True) b = torch.tensor(0.0, requires_grad=True) |
この変数 w と b を使って、入力データ x から y の値を予測する、「線形モデル」を定義します。
1 2 | def model(x): return w*x + b |
線形回帰の損失関数
線形回帰を数値計算するときは、最適なパラメータを「最小2乗法」を使って求めていきます。
最小2乗法では、2乗誤差(予測値と実測値の差の2乗)が最小になるように、パラメータを調整します。
「損失関数」として、予測値 p と実測値 y との「平均2乗誤差(MSE: Mean Squared Error)」を定義しておきましょう。
1 2 | def mse(p, y): return ((p-y)**2).mean() |
上の mse は、2乗誤差の平均を計算したものです。
線形回帰モデルを訓練する
準備ができたので、トレーニングをしましょう!
データは小さいので、ミニバッチは考えずに、全てのデータを与えて何度もエポックを繰り返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | # 学習率 lr = 1.0e-4 # 変数を初期化します w = torch.tensor(1.0, requires_grad=True) b = torch.tensor(0.0, requires_grad=True) losses = [] for epoch in range(3000): # 線形モデルによる値の予測 p = model(x) # 損失値と自動微分 loss = mse(p, y) loss.backward() # グラディエントを使って変数`w`と`b`の値を更新する。 with torch.no_grad(): w -= w.grad * lr b -= b.grad * lr w.grad.zero_() b.grad.zero_() # グラフ描画用 losses.append(loss.item()) print('loss = ', loss.item()) print('w = ', w.item()) print('b = ', b.item()) >>> loss = 23.644838333129883 >>> w = 1.9873875379562378 >>> b = -0.07110784947872162 |
すると、「 w は2に近い値、 b は0に近い値」になったので、ほぼ正解が得られましたね!
書いた人はこんな人

- 「好きを仕事にするエンジニア集団」の(株)ライトコードです!
ライトコードは、福岡、東京、大阪の3拠点で事業展開するIT企業です。
現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。
いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。
システム開発依頼・お見積もり大歓迎!
また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」「WEBディレクター」を積極採用中です!
インターンや新卒採用も行っております。
以下よりご応募をお待ちしております!
https://rightcode.co.jp/recruit
ITエンタメ10月 13, 2023Netflixの成功はレコメンドエンジン?
ライトコードの日常8月 30, 2023退職者の最終出社日に密着してみた!
ITエンタメ8月 3, 2023世界初の量産型ポータブルコンピュータを開発したのに倒産!?アダム・オズボーン
ITエンタメ7月 14, 2023【クリス・ワンストラス】GitHubが出来るまでとソフトウェアの未来