あやめの品種を推測するニューラルネットワーククラスの動作を確認しながら、その内部でどんな処理が行われているのかを見ていきましょう。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
前回はあやめのデータセットをスタート地点として、データセットにまつわるさまざまな話をしました。今回は、ニューラルネットワーククラス(のインスタンス)が実際にはどんな処理をしているのかを見ながら、全結合層、重みとバイアスを使った計算の実際などについて見ていきます。
以下では、第2回に作成したニューラルネットワーククラスを例に、これが一体どんなことをしているのかを簡単に見ていきましょう。なお、今回のコードはこのリンク先で公開しています。記事を読みながら、実際に実行してみるのもよいでしょう。
念のため、このニューラルネットワークがどんな構造であるかを以下に図示しておきます。
このときに記述した、データセットを読み込んで、分割するコードと、ニューラルネットワークを表すクラスのコードを以下に示します(ただし、import文は先頭にまとめて、分割後の要素数を表示するコードなどは削除しました)。
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import torch
from torch import nn
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target)
X_train = torch.from_numpy(X_train).float()
y_train = torch.tensor([[float(x)] for x in y_train])
X_test = torch.from_numpy(X_test).float()
y_test = torch.tensor([[float(x)] for x in y_test])
INPUT_FEATURES = 4
HIDDEN = 5
OUTPUT_FEATURES = 1
class Net(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(INPUT_FEATURES, HIDDEN)
self.fc2 = nn.Linear(HIDDEN, OUTPUT_FEATURES)
def forward(self, x):
x = self.fc1(x)
x = torch.sigmoid(x)
x = self.fc2(x)
return x
ここでは、このコードを基に、少し手を動かしてコードを試しながら、Netクラスがどんなクラスであるのかを試してみましょう。
第1回や第2回では「学習とは、ニューラルネットワークモデルへの入力に対して、適切な推測値を算出できるように、重みやバイアスを更新していく過程である」といったことを述べましたが、実際のコードにはそんなものは少しも登場していなかったことを疑問に思っていた方もいるでしょう。そこで、まずは実際にそんなものがあるかを確認してみましょう。
net = Net()
print('weight')
print(net.fc1.weight)
print('bias')
print(net.fc1.bias)
これはNetクラスのインスタンスを生成して、そのインスタンス変数fc1が持っているweight属性とbias属性を表示するコードです。インスタンス変数の名前から分かるように、weight属性はfc1(入力層)の重みを、bias属性はバイアスを表します。
実際に実行してみると、次のような結果になります。ただし、それぞれの値はfc1に代入されるLinearクラスのインスタンスの初期化時にランダムに決定されるので、読者が同じコードを試しても、その結果は以下とは異なるものになるでしょう。
今述べたことからも想像できますが、上のコードをもう一度実行すれば、重みとバイアスの値はまた別のものになります(実行結果は省略)。
重みとバイアスの初期値がどう決まるかは、Linearクラスのドキュメントに記述があります。
一番の下の方にあるweight属性の説明を見ると、何やら難しそうなことが書いてあります。少し説明しましょう。ここで、インスタンス変数fc1に代入されるLinearクラスのインスタンスは「nn.Linear(INPUT_FEATURES, HIDDEN)」のようにして生成していました。このINPUT_FEATURESが上図の「in_features」に相当します。
上のコードでは、INPUT_FEATURESの値は「4」なので、kの値は1/4、その平方根は1/2です。そして、上の画像に見られる「U」は「uniform distribution」(一様分布*1)を意味します。これは、特定の範囲(ここでは-1/2〜1/2)に一様に分布している値からランダムに選ばれたものを使って、重みが初期化されるということです(これはバイアスも同様です)。そう思って、上の実行結果を見ると、今述べたように-1/2〜1/2の範囲の値が並んでいることが分かると思います。
*1 その範囲に含まれる全ての値が、他の値と同じ間隔で並んでいるといった意味です。言い換えると、その範囲にある数値が全て同じ確率で選択されると考えてもよいでしょう。例えば、サイコロを振ったときに出る目は全て1/6の確率で選ばれます。よって、サイコロの出目を考えると、「1〜6」は一様分布していると考えられます(実際にサイコロを6回振ったときに全ての値が1回ずつ出るわけではないことは既にご存じの通りです)。これと同様に、上の例では-1/2〜1/2の範囲の数値が同じ確率でランダムに選択されると考えられます。
なお、重みをどのような値に初期化するかの方法にはいろいろとあります。ただ、0や1のような一定の値ではなく、何らかのルールに従ったランダムな値に初期化するのが一般的です。また、使用する活性化関数との関係から、相性がよい初期化方法というのもありますが、ここでは詳細な説明は省略します。
何はともあれ、ニューラルネットワークの層を表すインスタンス変数に重みとバイアスが格納されていることはこれで分かりました。これらを使って、ニューラルネットワークのモデルではどんな計算が行われるかを実際に見てみましょう。
Copyright© Digital Advantage Corp. All Rights Reserved.