ニューラルネットワークの流れ

本章では、ディープラーニングフレームワーク PyTorch の基本的な機能を使って、PyTorch を使ったネットワークの訓練がどのような処理で構成されているかを簡潔に紹介します。

今回は Colab に PyTorch がインストールされているため環境構築の必要はありません。もし自分の PC 等に PyTorch をインストールしたい場合は、PyTorch 公式ページ の Quick Start に書かれた手順を参考にインストールしてください。

本章の構成

  • PyTorch とは
  • PyTorch の準備
  • 計算の流れを確認

PyTorch とは

20_1

PyTorch はオープンソースのディープラーニングフレームワークです。こちらの Github リポジトリ:pytorch で活発に開発が行われています。オープンソースソフトウェア (OSS: open source software) であるため、誰でも PyTorch の全てのコードを見ることができます。

ディープラーニングフレームワークとは、これまでの章で説明してきたようなニューラルネットワークの設計・学習・評価などに必要な一連の実装を容易にするためのフレームワークです。特に層数の多いニューラルネットワークを GPU などを用いて高速に学習できるようにする機能を備えたものを指すことが多く、PyTorch の他にも Google が開発している TensorFlow など、様々なディープラーニングフレームワークが発表されています。

他の様々なフレームワークと比較して、PyTorch は研究開発の分野で盛んに使用されているという特徴があります。新技術の実装やテストが容易にできるという点が PyTorch の大きな強みです。

PyTorch の準備

環境上に PyTorch が予めインストールされています。最初に PyTorch を import して PyTorch を使う準備をしましょう。PyTorch は torch という名前で登録されています。

import torch
      

PyTorch のバージョンを確認します。

torch.__version__
      
'1.4.0'

計算の流れを確認

入力層から出力層までの一連の計算の流れを一つずつ確認していきます。

全結合層の定義

数学においてもプログラミングにおいても、全結合層(fully-connected layer)では、2 層で 1 セットとして扱います。まずは、3 ノードの入力層と 2 ノードの出力層の部分を表現していきましょう。

21_1

PyTorch で全結合層を定義するためには、torch.nn.Linear を使用します。今後ネットワーク内での計算を行う場合に torch.nntorch.nn.Functional を用いる 2 通りの選択肢が出てきますが、それぞれの違いについては本章の後半で紹介します。ぜひ違いについて考えながら進めていってください。

import torch.nn as nn
      

3 → 2 のリンクを fc (fully-connected layer) として、以下のように宣言します。

# 全結合層の定義
fc = nn.Linear(3, 2)
      

これで完了です。nn.Linear は、皆さんが既に勉強された重回帰分析で説明すると線形結合という意味です。

宣言したリンクの重み (W\mathbf{W})バイアス (bb) はランダムな初期値が格納されています。重みとバイアスにどのような値が入っているかは、fc から確認することができます。

# 重み
fc.weight
      
Parameter containing: tensor([[ 0.4490, -0.4901, 0.2704], [ 0.2480, 0.2888, 0.1535]], requires_grad=True)
# バイアス
fc.bias
      
Parameter containing: tensor([-0.2725, -0.1834], requires_grad=True)

乱数のシードを固定して再現性を確保

ここでの問題として、パラメータ(重みやバイアス)の値がランダムに初期化されるため、実行するごとに結果が異なってしまいます。皆さんが実行した値と資料に表示されている値は違うのではないでしょうか。これでは、例えば他の誰かの成果物を確認する際、再現性がとれず結果が異なってしまうということが起きてしまいます。これは現実問題において、重要な問題です。

そこで、乱数のシードを固定するという方法を使います。これによって乱数の作られる過程が固定され再現性を確保できます。それでは乱数のシードを固定してみましょう。

# CPU のシードを固定
torch.manual_seed(1)
      
<torch._C.Generator at 0x7fbe71744af0>

こちらでは CPU のシードを固定する方法を紹介しています。GPU を使用する場合には、他の固定方法がありますので GPU を扱う際に紹介します。

乱数のシードを固定した後で重みを確認してみましょう。

fc = nn.Linear(3, 2)
      
# 重み 
fc.weight
      
Parameter containing: tensor([[ 0.2975, -0.2548, -0.1119], [ 0.2710, -0.5435, 0.3462]], requires_grad=True)
# バイアス
fc.bias
      
Parameter containing: tensor([-0.1188, 0.2937], requires_grad=True)

この値と同じになっていれば、乱数のシードを上手く固定できていることが分かります。計算過程のどこかでランダムな値が発生している場合には、乱数のシードを固定する工程が再現性を確保する上で必須になりますので意識してください。

線形変換

中間層の uu の値を計算してみましょう。uu の値を求める際の線形変換は torch.nn.Linearcall メソッドとして定義されています。計算の前に、PyTorch で使用するデータ型について初学者の人はつまづきやすいため確認しておきます。PyTorch では、torch.tensor により PyTorch で使用するデータ型 torch.Tensor 型に変換します。

# リストから PyTorch の Tensor に変換
x = torch.tensor([[1, 2, 3]])
      
# 型の変換
type(x)
      
torch.Tensor

上記のように torch.Tensor という型が標準になるため覚えておきましょう。しかし、まだ落とし穴が存在します。線形変換は nn.Linearcall メソッドとして定義されているため、以下のように計算を行います。

# 線形変換
u = fc(x)
      
---------------------------------------------------------------------------RuntimeError Traceback (most recent call last)<ipython-input-13-d2620a228d16> in <module>() ----> 1 u = fc(x) /usr/local/lib/python3.6/dist-packages/torch/nn/modules/module.py in __call__(self, *input, **kwargs) 530 result = self._slow_forward(*input, **kwargs) 531 else: --> 532 result = self.forward(*input, **kwargs) 533 for hook in self._forward_hooks.values(): 534 hook_result = hook(self, input, result) /usr/local/lib/python3.6/dist-packages/torch/nn/modules/linear.py in forward(self, input) 85 86 def forward(self, input): ---> 87 return F.linear(input, self.weight, self.bias) 88 89 def extra_repr(self): /usr/local/lib/python3.6/dist-packages/torch/nn/functional.py in linear(input, weight, bias) 1368 if input.dim() == 2 and bias is not None: 1369 # fused op is marginally faster -> 1370 ret = torch.addmm(bias, input, weight.t()) 1371 else: 1372 output = input.matmul(weight.t()) RuntimeError: Expected object of scalar type Float but got scalar type Long for argument #2 'mat1' in call to _th_addmm

ここでエラーが発生します。この原因は入力変数 x のデータ型にあります。torch.Tensor の形式で正しいのですが、さらに詳細なデータ型の設定が必要です。

# 詳細なデータ型の確認
x.dtype
      
torch.int64

公式ドキュメントにそれぞれのデータ型の設定方法が載っているのですが、エラー文を見てみましょう。Expected object of scalar type Float but got scalar type Long for argument と書いており、「Float を期待していたが、Long になっていますよ」 と書いてあります。Long は公式ドキュメントから int64 型を指しています。int ではなく、float にしてほしいということです。入力変数は実数値 (float) で 32 bit とすることが多いため、このデータ型で再定義します。

# float32 に変換
x = torch.tensor([[1, 2, 3]], dtype=torch.float32)
# x = torch.tensor([[1, 2, 3]], dtype=torch.FloatTensor)
      
x, x.dtype
      
(tensor([[1., 2., 3.]]), torch.float32)

これでもう一度、線形変換の計算を行いましょう。

# 線形変換
u = fc(x)
u
      
tensor([[-0.6667, 0.5164]], grad_fn=<AddmmBackward>)

このように、線形変換後の値を求めることができました。データ型でエラーが起きることもあるので、注意しながら進めていきます。

非線形変換

今回は活性化関数 (Activation) として ReLU 関数を採用します。ReLU 関数は、torch.nn.functions に含まれており、こちらは慣習的に F と読み込まれます。

import torch.nn.functional as F
      
# ReLU 関数
z = F.relu(u)
z
      
tensor([[0.0000, 0.5164]], grad_fn=<ReluBackward0>)

結果の通り、正の値はそのまま出力され、負の値は 0 となる ReLU 関数が、正しくて適用できていることが分かります。

例題

下図に示すようなニューラルネットワークを定義して、x=[1,2,3]x=[1, 2, 3] に対する予測値 yy を求めてみましょう。ただし、乱数のシードは 1 で固定します。

21_2

解答は、以下の通りです。一度実装してみたあとに、解答を見るようにしましょう。

# シードの固定
torch.manual_seed(1)
      
<torch._C.Generator at 0x7fbe71744af0>
# 入力値の定義
x = torch.tensor([[1, 2, 3]], dtype=torch.float32)
x
      
tensor([[1., 2., 3.]])
# 全結合層の定義
fc1 = nn.Linear(3, 2)
fc2 = nn.Linear(2, 1)
      
# 線形変換
u1 = fc1(x)
u1
      
tensor([[-0.6667, 0.5164]], grad_fn=<AddmmBackward>)
# 非線型変換
z1 = F.relu(u)
z1
      
tensor([[0.0000, 0.5164]], grad_fn=<ReluBackward0>)
# 線形変換
y = fc2(z1)
y
      
tensor([[0.1514]], grad_fn=<AddmmBackward>)

この値が計算できれば正解です。

目的関数

目的関数の計算には目標値 tt が必要であるため、今回は t=1t=1 とします。今回の問題設定を回帰とした場合、目的関数は平均二乗誤差 (MSE: mean squared error) を用います。また、目標値のデータ型も適切に設定しないとエラーが出るため、本資料では回帰と分類でそれぞれ以下のように定義します。

  • 回帰:float32
  • 分類:int64
# 目標値
t = torch.tensor([[1]], dtype=torch.float32)
t
      
tensor([[1.]])
# 平均二乗誤差
loss = F.mse_loss(t, y)
loss
      
tensor(0.7201, grad_fn=<MeanBackward0>)

こちらのように目的関数も PyTorch では torch.nn.function (F) の中に定義されています。

これで一連の流れを理解することができましたが、PyTorch ではネットワーク内での計算を行う場合に、torch.nn (nn) を用いる場合と torch.nn.functional (F) を用いる場合の 2 通りの選択肢がありました。この違いは以下の通りです。

  • nn:調整すべきパラメータを持つ
  • F:調整すべきパラメータを持たない

nn.Linear で定義した全結合層は、重み W\mathbf{W} やバイアス bb といったデータから調整すべきパラメータを持っていました。それに対して、ReLU 関数や平均二乗誤差では関数の処理内でパラメータを持っていません。PyTorch ではこの基準で nnF のどちらに格納されているか決まっているため、これを理解しておくと欲しい機能があるときに探しやすくなります。

shareアイコン