アクセスありがとうございます!次は「とあるエンジニアのエソラゴト」で検索して頂けると嬉しいです!

【PyTorch】線形回帰のサンプルコード【初心者】

はじめに

冬休みに入ったので、今まで勉強をサボっていたDeep Learningについて勉強をしていきます

私は文系学生だったので、Deep Learningに関する数学的な知識はありません。

しかし、数学的な知識を最初に勉強してからプログラミングに入ると、挫折がしそうなので、プログラミングをしながら分からない数学的な知識があったら、都度勉強をしていこうという戦略で行きます!

とりあえず触ってなれて、初心者を脱したかなと思ったら数学的な知識も追求してきたいと思っています。

前提

PyTorchを使います。

開発環境は「Google Colaboratory」を使っていきます。

Google Colaboratoryはブラウザで操作出来て、環境構築も不要だし、GPUも制限はありますが、使えるのでオススメです。

コードの全体はGitHubにアップロードしています。

ソースコードはこちら

線形回帰のデータ準備

まず必要なライブラリをインポートします。

import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
plt.style.use("ggplot")

そして、今回は乱数を使用しますので、以下を書きます。

# 乱数を使用した時に、毎回同じ結果を得るために指定
torch.manual_seed(123)

そして、訓練データを作っていきます。

a = 5
b = 2
# 0〜10の間のデータを100個作成
x = torch.linspace(0, 10, 100)

xの値を見てみると以下のようになります。

x

# 以下は出力値
tensor([ 0.0000,  0.1010,  0.2020,  0.3030,  0.4040,  0.5051,  0.6061,  0.7071,
         0.8081,  0.9091,  1.0101,  1.1111,  1.2121,  1.3131,  1.4141,  1.5152,
         1.6162,  1.7172,  1.8182,  1.9192,  2.0202,  2.1212,  2.2222,  2.3232,
         2.4242,  2.5253,  2.6263,  2.7273,  2.8283,  2.9293,  3.0303,  3.1313,
         3.2323,  3.3333,  3.4343,  3.5354,  3.6364,  3.7374,  3.8384,  3.9394,
         4.0404,  4.1414,  4.2424,  4.3434,  4.4444,  4.5455,  4.6465,  4.7475,
         4.8485,  4.9495,  5.0505,  5.1515,  5.2525,  5.3535,  5.4545,  5.5556,
         5.6566,  5.7576,  5.8586,  5.9596,  6.0606,  6.1616,  6.2626,  6.3636,
         6.4646,  6.5657,  6.6667,  6.7677,  6.8687,  6.9697,  7.0707,  7.1717,
         7.2727,  7.3737,  7.4747,  7.5758,  7.6768,  7.7778,  7.8788,  7.9798,
         8.0808,  8.1818,  8.2828,  8.3838,  8.4848,  8.5859,  8.6869,  8.7879,
         8.8889,  8.9899,  9.0909,  9.1919,  9.2929,  9.3939,  9.4950,  9.5960,
         9.6970,  9.7980,  9.8990, 10.0000])

0〜10までの間でデータが入っていることが確認出来ると思います。

次にxのサイズ数を調整します。

# view:サイズ数を調整する。
# 第一引数はバッチの次元を指定する。
x = x.view(100, 1)

すると、以下は元々1個の配列に100個データが入っていたが、100個の配列に1個のデータが入るように変形されます。

以下のような感じ。

x

# 以下は出力値
tensor([[ 0.0000],
        [ 0.1010],
        [ 0.2020],
        (省略)
        [ 9.6970],
        [ 9.7980],
        [ 9.8990],
        [10.0000]])

長くなるので、途中省略していますが、100個のデータが一つの配列に入っています。

そして、1次関数式を以下のように定義します。

eps = torch.randn(100, 1)
y = a * x + b + eps

先程作った式をmatplotlibで散布図で描画します。

# 散布図を描画する。第1引数にx軸、第2引数にy軸を指定する。
plt.scatter(x, y)

すると、以下のような図になります。

線形回帰のモデル実装

次に線形回帰のモデルを実装していきます。

class LR(nn.Module):
    def __init__(self):
        super().__init__()
        # in_features:入力値の数、out_features:出力値の数
        self.linear = nn.Linear(in_features=1, out_features=1)
    # 順伝播、forwardを定義しておくと、学習中に自動的に呼ばれる
    def forward(self, x):
        output = self.linear(x)
        return output

スーパークラスとして、「torch.nn」を継承し、初期化処理でスーパークラスの初期化、モデルの定義をしています。

今回は線形回帰と言うことで、nnから「Linear」モデルを使います。

nn.Linearの第1引数には入力値の数を指定し、第2引数には出力値の数を指定します。

今回はどちらも1になります。

次にforward関数を定義して、順伝播の処理内容を書きます。

第2引数には入力値であるxを渡します。

今回はself.linear(x)が計算した結果をoutputとして返却しています。

ここで定義したforward関数は学習中にスーパークラスから自動的に呼び出されます。

そして定義したモデルを使えるようにmodel変数に代入をします。

model = LR()

学習前の線形回帰モデルの性能を確認

学習前のモデルの性能を確認するために、x2というテストデータを作成して、そのデータをmodelに引数として渡して、y_predという予測値を得ます。

x2 = torch.linspace(0, 3, 100).view(100, 1)
y_pred = model(x2)
y_pred

# 以下は出力値
tensor([[ 1.1556e-01],
        [ 9.2342e-02],
        [ 6.9127e-02],
        [ 4.5912e-02],
        (省略)
        [-2.1363e+00],
        [-2.1595e+00],
        [-2.1827e+00]], grad_fn=<AddmmBackward>)

さて、ここで図を出力してみて、どれくらい予想が合っているのか確認をします。

そのために予測値であるy_predを一本の線として出力し、正解データであるxとyを散布図として出力します。

# 順伝播をしているので、勾配計算処理を外す => detach()
# plot:一本の線を描画
plt.plot(x2, y_pred.detach(), label="prediction")
plt.scatter(x, y, label="data")
plt.legend()

図を出力するために、勾配計算処理を外す「detach()」が必要なことに注意して下さい。

出力した図は以下のようになりました。

学習をしていないため、全く見当外れのところに予想されて線が引かれていることが分かると思います!

線形回帰モデルの学習

さて、ここからは線形回帰モデルを使って、実際にデータを学習させて行きたいと思います。

# 損失関数
criterion = nn.MSELoss()
# 最適化関数
# 第1引数:model.parameters() => 重みとバイアスの情報が入っている。これを更新したい。
# 第2引数:学習率
optimizer = optim.SGD(model.parameters(), lr=0.001)

データを学習するために、以下のようにコードを書きます。

losses = []
num_epoch = 500
for epoch in range(num_epoch):
    # ミニバッチのloopがないため、バッチ学習
    
    # 勾配の初期化を行う
    optimizer.zero_grad()
    # 予測値の計算
    y_pred = model(x)
    # 損失の計算:予想値と正解値を与える
    loss = criterion(y_pred, y)
    # 損失を誤差逆伝播する
    loss.backward()
    # 重みとバイアスの更新
    optimizer.step()
    if epoch % 10 == 0:
        print("epoch: {}, loss: {}".format(epoch, loss.item()))
        losses.append(loss.item())

すると、以下のような感じで学習が進むたびに、損失の値が小さくなっていることがわかります。

# 以下は出力値
epoch: 0, loss: 1221.19384765625
epoch: 10, loss: 296.3516540527344
epoch: 20, loss: 72.65264129638672
epoch: 30, loss: 18.543027877807617
epoch: 40, loss: 5.452998161315918
epoch: 50, loss: 2.284636974334717
epoch: 60, loss: 1.516102910041809
epoch: 70, loss: 1.328049898147583
epoch: 80, loss: 1.280423879623413
epoch: 90, loss: 1.2667850255966187
(省略)
epoch: 400, loss: 1.1888651847839355
epoch: 410, loss: 1.1868232488632202
epoch: 420, loss: 1.1848024129867554
epoch: 430, loss: 1.182801365852356
epoch: 440, loss: 1.180820107460022
epoch: 450, loss: 1.1788581609725952
epoch: 460, loss: 1.1769163608551025
epoch: 470, loss: 1.1749935150146484
epoch: 480, loss: 1.1730895042419434
epoch: 490, loss: 1.1712037324905396

損失の経過を図にします。

plt.plot(losses)

すると以下のような感じになります。

かなり早い段階から学習が収束しているのが分かります。

それでは、学習した結果、どれくらい予想値が合っているか図にして見てみましょう。

x_test = torch.linspace(0, 10, 100).view(100, 1)
y_test = model(x_test)
plt.plot(x_test, y_test.detach(), label="prediction")
plt.scatter(x, y, label="data")
plt.legend()

図にした結果がこちらです。

単純なデータなので当たり前ですが、かなりいい感じで線を引いてくれて、モデルの性能がかなり良いことが分かります。

最後に

ここまでお付き合いありがとうございました。

今後も勉強の過程として、PyTorchを使ったDeep Learningのコードサンプルを記事にしたいと思っていますので、よろしくお願いします。

最新情報をチェックしよう!