1
3

More than 3 years have passed since last update.

【PyTorch】自由にネットワークを設計するよ

Last updated at Posted at 2021-02-27

自分の手で自由にパラメータを定義したい

PyTorchを使ったDeepLearningが流行ってるらしいですが、
Qiitaとかはてなブログで他人の記事を読んでると
h = nn.Linear(N, M)
みたいに、$\boldsymbol{h}=\boldsymbol{x}^T\boldsymbol{W} + \boldsymbol{b}$における$\boldsymbol{W}$と$\boldsymbol{b}$(ネットワークパラメータ)の定義を全て
PyTorch任せにしてるのが多いですよね。

まぁこれで所望のネットワークを構築できるなら全く問題ないですし、
むしろ恩恵も多いんだろうな〜とは思いますよ。

ただ、$\boldsymbol{W} = \boldsymbol{AH}$みたいに別々のパラメータを用いて表現したい場合は困っちゃいます。

だからこの記事では

h = nn.Linear(N, M)

みたいな書き方じゃなくて、ちゃんと全てのパラメータを手動で定義する方法をメモとして残します。

シンプルなDNN

ということでDNNを作りたいと思います。
問題設定はとりあえずどうでも良いので、、、

まず最初

import torch
import numpy as np
from torch import nn
import torch.nn.functional as F
import torch.optim as optim

FはDeepLearningで使う便利な関数ライブラリです。活性化関数とかも入ってます。
optimは最適化手法のライブラリです。SGDとかAdamとか。

パラメータ・最適化手法の定義

class DNN(nn.Module):
  def __init__(self, I, J, K):
  super().__init__()
    self.I, self.J, self.K = I, J, K 

    self.W = [nn.Parameter(torch.tensor(np.random.randn(I,K))),\
              nn.Parameter(torch.tensor(np.random.randn(K,K))),\
              nn.Parameter(torch.tensor(np.random.randn(K,J)))]
    self.b = [nn.Parameter(torch.tensor(np.zeros(K))),\
              nn.Parameter(torch.tensor(np.zeros(K))),\
              nn.Parameter(torch.tensor(np.zeros(J)))]

    self.params = self.W + self.b
    self.optimizer = optim.Adam(self.params, lr=0.01)

I, J, Kはとりあえず入力層の次元、出力層の次元、中間層の次元とします。
面倒なのでとりあえず中間層の次元は同じとします。
(あくまで今回の記事は、自分でネットワークを定義するためのメモなので次元数はどうでもE)

nn.Parameter(torch.tensor(x))

これはtorch.tensor()で変数xをPyTorchで扱う変数として定義して、
nn.Parameter()で勾配計算によって更新するパラメータですよっていう宣言というか
そういう設定にするための記述ですかね。
ちなみに今回は中間層は2層です。

self.params = self.W + self.b

これは、ただネットワークのパラメータをリスト化してるだけです。
今回はパラメータをリスト化してるのでリスト同士を足してあげて
1つのリストにまとめた感じです。

self.optimizer(最適化手法)にAdamを指定しました。
別に何でも良いですよ。SGDでも。
第一引数が更新したいパラメータ(リスト)です。
そのためにさっきリストでまとめ上げました。

ちなみに

最適化手法の定義時に、今回はパラメータをリスト化してself.paramsと自分で定義して渡しました。
ただ、optim.Adam(self.parameters(), ~)という記述もよく見ます。

実は、今回のようにパラメータをリストで梱包した場合、
self.parameters()には定義したパラメータが反映されません!
詳細は忘れましたが、nn.Parameterで宣言したパラメータをlistで梱包してしまったので、
このモデルでは「リスト」として認識されてます。
なのでわざわざself.paramsなるものを用意してoptimizerの引数に渡し直しました。

self.W = nn.ParameterList([nn.Parameter(torch.tensor(~)), nn.Parameter(torch.tensor(~), ~])
のように、「nn.ParameterrList」を使用してあげれば、
このモデルでもリスト内の各パラメータはちゃんと「パラメータ」として認識されます。
なのでoptim.Adam(self.parameters(), ~)でもちゃんと動きます。

目的関数の定義

  def compute_loss(self, y, x):
    loss = torch.sum(torch.square(y - x), axis=1)
    return torch.mean(loss)

目的関数です。ただの平均2乗誤差です。ここも別に何でも良いです。
ちなみに

nn.MSELoss(y,x)

でも同じです。ただここも自分で自由に定義できるように!というコンセプトの元、
あえて自分で書いてます。

FeedForwardについて

  def feedforward(self, x):
    h = F.sigmoid(self.linear(x, self.W[0], self.b[0]))
    for l in range(1, len(self.W)):
      h = F.sigmoid(self.linear(h, self.W[l], self.b[l]))

    return h

  def linear(self, x, w, b):
    return torch.matmul(x, w) + b

まぁここは特に説明する必要は無いでしょう。

ぼちぼち面倒臭くなってきました。。。

学習について

  def fit(self, X, Y, nbatch=1024, nepoch=100):
    N = X.shape[0] 

    for epoch in range(nepoch):
      perm = np.random.permutation(N)

      for i in range(0, N-nbatch, nbatch):
        x = X[perm[i:i + nbatch]]
        x_ = torch.tensor(x.astype(np.float32), requires_grad=False)

        y = Y[perm[i:i + nbatch]]
        y_ = torch.tensor(y.astype(np.float32), requires_grad=False)

        x2y = self.feedforward(x_)
        loss = self.compute_loss(y_, x2y)

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

まず、Xは学習データでN-by-Iのnumpy.arrayとします。
Nはデータ数(音声系のデータならフレーム数とか)、Iは各データの次元数です。
Yは教師データです。作りはXと基本同じですが、N-by-Jになります(Jは出力データの次元数)。

とりあえずミニバッチ法で学習するとして、
Xからミニバッチxを取り出し、
お馴染みtorch.tensorでPyTorchのTensorであるx_に変換し、
feedforwardネットで出力y2xを得ます。

※お馴染みtorch.tensorで、データ型を指定したり、requires_grad=False(変数だから勾配計算要らないよ、という)を指定したりしてますがもしかしたら不要かもしれません。面倒臭いので確認してません。。。

教師データy_と出力データx2yのロスであるlossを計算します。

ほいで、

self.optimizer.zero_grad()

はネットワークのパラメータ勾配を0にリセットするという意味です。
直前の勾配はとりあえず関係ないので0にしましょうってことですかね。

loss.backward()

これはネットワークのパラメータの勾配を計算するための記述です。
lossには「入力x_からパラメータを用いてx2yを算出した」という計算履歴が残ってるので(知らんけど。まぁそうだろうという勝手な解釈です)、
それを辿って勾配を計算してるんですかね。

self.optimizer.step()

lossを算出するために使ったパラメータのうち、optimizer定義時に「更新するよ!」と宣言したself.paramsに含まれるパラメータを、いまさっき求めた勾配を用いて更新します。

以上をまとめると

import torch
import numpy as np
from torch import nn
import torch.nn.functional as F
import torch.optim as optim

class DNN(nn.Module):
  def __init__(self, I, J, K):
  super().__init__()
    self.I, self.J, self.K = I, J, K 

    self.W = [nn.Parameter(torch.tensor(np.random.randn(I,K))),\
              nn.Parameter(torch.tensor(np.random.randn(K,K))),\
              nn.Parameter(torch.tensor(np.random.randn(K,J)))]
    self.b = [nn.Parameter(torch.tensor(np.zeros(K))),\
              nn.Parameter(torch.tensor(np.zeros(K))),\
              nn.Parameter(torch.tensor(np.zeros(J)))]

    self.params = self.W + self.b
    self.optimizer = optim.Adam(self.params, lr=0.01)

  def compute_loss(self, y, x):
    loss = torch.sum(torch.square(y - x), axis=1)
    return torch.mean(loss)

  def feedforward(self, x):
    h = F.sigmoid(self.linear(x, self.W[0], self.b[0]))
    for l in range(1, len(self.W)):
      h = F.sigmoid(self.linear(h, self.W[l], self.b[l]))

    return h

  def linear(self, x, w, b):
    return torch.matmul(x, w) + b


  def fit(self, X, Y, nbatch=1024, nepoch=100):
    N = X.shape[0] 

    for epoch in range(nepoch):
      perm = np.random.permutation(N)

      for i in range(0, N-nbatch, nbatch):
        x = X[perm[i:i + nbatch]]
        x_ = torch.tensor(x.astype(np.float32), requires_grad=False)

        y = Y[perm[i:i + nbatch]]
        y_ = torch.tensor(y.astype(np.float32), requires_grad=False)

        x2y = self.feedforward(x_)
        loss = self.compute_loss(y_, x2y)

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

ということで

だいたいの理解は正しいのかなと思い、自分のメモも兼ねて書きましたが
間違ってたらお恥ずかしい限りです 笑

それと、うんコードを量産しちゃうタイプなので
もっと頭良い書き方があれば

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3