• トップ
  • ブログ一覧
  • 【前編】PyTorchの自動微分を使って線形回帰をやってみた
  • 【前編】PyTorchの自動微分を使って線形回帰をやってみた

    広告メディア事業部広告メディア事業部
    2020.09.23

    IT技術

    前編~PyTorchの自動微分を使って線形回帰に挑戦!~

    PyTorch」を使っていると、次のような疑問を持つ人は多いはず…。

    model.zero_grad() って何やってるんだろう?」

    loss.backward() では、何が計算されているの?」

    「Tensor の属性の requires_grad って何?」

    ここでは、そんな方のために、「PyTorch」の自動微分による線形回帰を、わかりやすく解説していきます!

    「自動微分」の理解がカギを握る

    冒頭の PyTorch の疑問や、Tensor の以下の属性は、すべて自動微分(automatic differentiation)に関係しています。

    1. requires_grad
    2. grad
    3. grad_fn
    4. is_leaf

    つまり、PyTorch の「自動微分」を理解すれば、すべての疑問をスッキリと解消できるわけです。

    では次から、そのカギとなる「自動微分」について、深く見ていきましょう!

    まずは「自動微分」の準備

    まずは、PyTorch で自動微分をするために、準備をしていきましょう!

    ライブラリのインポート

    はじめに、自動微分に必要なライブラリを、インポートしていきましょう。

    1import numpy as np
    2import matplotlib.pyplot as plt
    3import torch

    「requires_grad=True」を指定する

    入力データを x に設定し、変数で wb も、それぞれTensor(テンソル)として定義します。

    1x = torch.tensor(5.)
    2w = torch.tensor(2., requires_grad=True)
    3b = torch.tensor(1., requires_grad=True)

    このとき、wb には、「 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 がどんなグラフを持っているのか、以下の図で見てみましょう!

    MulBackward0Mul は、「Multiply」の略で、「掛け算」のこと。

    つまり、wx が掛け算だったことも、記憶されているわけですね!

    このように、計算に変数が含まれていると、PyTorch は計算の内容をグラフにして記憶していきます。

    そうすることで、y に対して自動微分を実行する時に、PyTorch は逆の順番で計算をたどることができるのです。

    y は、 w * xb の足し算なので、微分を w * xb とで、別々に計算できることがわかります。

    さらに、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_fngrad は、あとで出てくるグラディエント(gradient)の略です。

    fn は、関数(function)の略となります。

    末端変数「is_leaf」

    ちなみに、wb はユーザーが定義した変数で、「leaf Variableと呼ばれています。

    英語の「leaf」は、木の葉っぱのことなので、訳すとすれば「グラフの末端の変数」ですね!

    wb は、この末端変数となるので、もちろん以下のように 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=f(w,b)y = f(w, b)」とし、y は、wb の関数だとします。

    dy=fwdw+fbdb=(fwfb)(dwdb)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}

    よって、

    grady=(fwfb)\mathrm{grad} \, y = \begin{pmatrix} \frac{\partial{f}}{\partial{w}} \\ \frac{\partial{f}}{\partial{b}} \end{pmatrix}

    となります。

    つまり「グラディエント(勾配)」とは

    グラディエントとは、簡単にいうとwb の値を増やすときに、yの値がどの程度変わるのかを表したもの。

    先ほどのy は、直線の式なので、次のように簡単に表現できます。

    1. w.grad = 5 は 、w が1増えると、 y が5増える」
    2. b.grad = 1 は 、b が1増えると 、y が1増える」

    もともとの式が、 y = w * x + bx = 5 なので、正しいことがわかりますね!

    線形回帰をやってみる

    いよいよ、与えられたデータに対して、「線形回帰」を適用してみましょう!

    「線形回帰」とは、データの分布を直線によって、近似させる手法です。

    y = w * x + b では、x が入力値で、 yx に対するデータの実測値となります。

    これらデータを直線で最も近似させて表すとき、最適なパラメータ値「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」として定義しましょう。

    xy は、与えられたデータであり、定数なので from_numpy で Tensor に変換します。

    wb は求めたい値なので、変数として、適当に初期化しておきましょう!

    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)

    この変数 wb を使って、入力データ 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に近い値」になったので、ほぼ正解が得られましたね!

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    広告メディア事業部

    広告メディア事業部

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background