1.3 パラメータ

Affineレイヤを作成します。

from ivory.layers.affine import Affine

affine = Affine((2, 3))
affine

[1] 2019-06-12 20:00:14 (186ms) python3 (186ms)

<Affine('Affine.1', (2, 3)) at 0x2920e1bafd0>

重みweightsを覗いてみます。

affine.W

[2] 2019-06-12 20:00:15 (6.97ms) python3 (193ms)

<Weight('Affine.1.W', (2, 3)) at 0x2920e1c9080>

重みは2次元配列なので、Variableインスタンスでよさそうです。しかしながら、実際は本節で説明するParameterインスタンスになっています。

from ivory.core.parameter import Parameter

isinstance(affine.W, Parameter)

[3] 2019-06-12 20:00:15 (15.6ms) python3 (209ms)

True

実際はそのサブクラスWeightのインスタンスです。

type(affine.W)

[4] 2019-06-12 20:00:15 (31.3ms) python3 (240ms)

ivory.core.parameter.Weight

他も見てみます。

print(type(affine.x))
print(type(affine.b))
print(type(affine.weight_decay))
print(type(affine.y))

[5] 2019-06-12 20:00:15 (11.1ms) python3 (251ms)

<class 'ivory.core.parameter.Input'>
<class 'ivory.core.parameter.Weight'>
<class 'ivory.core.parameter.State'>
<class 'ivory.core.parameter.Output'>

属性を確認します。

vars(affine.W)

[6] 2019-06-12 20:00:15 (12.0ms) python3 (263ms)

{'shape': (2, 3),
 'layer': <Affine('Affine.1', (2, 3)) at 0x2920e1bafd0>,
 'name': 'W',
 'variable': None,
 'init': <function ivory.core.parameter.Parameter.randn.<locals>.init(std='he')>}

layer属性によって自分がどのレイヤに属しているかが分かります。

Parameterには実データを保持するVariableインスタンスを生成して自分に割り当てるset_variableメソッドが用意されています。

v = affine.W.set_variable()
v

[7] 2019-06-12 20:00:15 (7.02ms) python3 (270ms)

<Variable(['Affine.1.W'], (2, 3)) at 0x2920ef757f0>

値を確認してみます。

print(v.data, v.data.dtype)

[8] 2019-06-12 20:00:15 (19.1ms) python3 (289ms)

[[ 0.31447205  0.64247173 -0.7479663 ]
 [ 1.6699485  -1.1002742   1.509816  ]] float32

乱数で初期化されています。

Parameterインスタンスからも変数のデータと勾配にアクセスできます。この場合にはdプロパティとgプロパティを使います。

print(affine.W.d)
print(affine.W.g)

[9] 2019-06-12 20:00:15 (15.6ms) python3 (305ms)

[[ 0.31447205  0.64247173 -0.7479663 ]
 [ 1.6699485  -1.1002742   1.509816  ]]
None

勾配はまだ計算されていません。

値を代入できるでしょうか?

affine.W.d = 0

[10] 2019-06-12 20:00:15 (176ms) python3 (481ms)

AttributeError: can't set attribute
AttributeError                            Traceback (most recent call last)
<ipython-input-11-a0e3e703c681> in <module>
----> 1 affine.W.d = 0

重みに関してはデータの読み出しは可能ですが、書き込みは不可能です(当然、W.d[0, 0] = 0といった書き換えは可能ですが)。なぜならレイヤ自身が重みの値を直接操作することは必要ないからです。出力に関してはどうでしょうか。適当な値を代入してみます。(以下では簡単のために、スカラー値を代入しますが、実際にはバッチ数×データ次元の2次元配列になります。)

affine.y.set_variable()
affine.y.d = 1
affine.y.d

[11] 2019-06-12 20:00:15 (93.2ms) python3 (574ms)

1

レイヤは、所望の計算を行って値を出力する必要があるので、変数に値を代入できるのは当然です。もう一回代入してみます。

affine.y.d = 2
affine.y.d

[12] 2019-06-12 20:00:15 (8.40ms) python3 (583ms)

3

通常では2となると思われますが、加算されて3になりました。これは、パラメータを介した変数への値の代入が通常とは違う論理で行われているからです。同じ変数に複数回代入があるということは、その変数に複数のレイヤから値が出力されたことを意味します。それぞれの出力を加算することは暗黙的にSumノードを想定しています。このようにしているは、逆伝搬において分岐したノードからの勾配が加算されることに対称な動作とするためです。実際、

affine.x.set_variable()
affine.x.g = 1
affine.x.g = 1
affine.x.g

[13] 2019-06-12 20:00:15 (31.3ms) python3 (614ms)

2

となります。ところで、

affine.x.d = 1
affine.y.g = 1

[14] 2019-06-12 20:00:15 (18.8ms) python3 (633ms)

AttributeError: can't set attribute
AttributeError                            Traceback (most recent call last)
<ipython-input-15-f4a2e3433d84> in <module>
----> 1 affine.x.d = 1
      2 affine.y.g = 1

となり、入力のデータおよび出力の勾配には値の代入ができません。順伝搬、逆伝搬の動作を考えれば妥当な仕様です。以下にパラメータの種類と挙動を示します。

クラス名 データ 勾配
Input 代入不可 加算代入
Output 加算代入 代入不可
Weight 代入不可 加算代入
State 通常代入 なし
Loss 加算代入 なし

次に、Affineレイヤに別のAffineレイヤを接続することを考えます。1つ目の出力と2つ目の入力に同じ変数を割り当てるために、Variable.add_parameterメソッドを使います。

a = Affine((2, 3))
b = Affine((3, 2))
v = a.y.set_variable()
print(v)
v.add_parameter(b.x)
print(v)

[15] 2019-06-12 20:00:15 (29.1ms) python3 (662ms)

<Variable(['Affine.2.y'], (3,)) at 0x2920ef97c18>
<Variable(['Affine.2.y', 'Affine.3.x'], (3,)) at 0x2920ef97c18>

上記のように、Variablerepr関数は、接続されているParameterのリストを表示します。動作を確認します。

a.y.d = 1
print(b.x.d)
b.x.g = 10
print(a.y.g)

[16] 2019-06-12 20:00:15 (15.6ms) python3 (678ms)

1
10

このように値が伝搬することが確認できます。

データおよび勾配が加算される理由は、以下のような並列構造で明らかになります。

a = Affine((1, 1), name="A")
b = Affine((1, 1), name="B")
c = Affine((1, 1), name="C")
x = a.x.set_variable()
x.add_parameter(b.x)
x.add_parameter(c.x)
y = a.y.set_variable()
y.add_parameter(b.y)
y.add_parameter(c.y)
print(x)
print(y)

[17] 2019-06-12 20:00:15 (15.7ms) python3 (693ms)

<Variable(['A.x', 'B.x', 'C.x'], (1,)) at 0x2920f09cd30>
<Variable(['A.y', 'B.y', 'C.y'], (1,)) at 0x2920f09ce80>

それぞれのレイヤが別の値を出力したとします。

y.data = 0
a.y.d = 1
b.y.d = 2
c.y.d = 3
print(y.data)

[18] 2019-06-12 20:00:15 (6.00ms) python3 (699ms)

6

変数の値は加算されたものとなります。逆伝搬も同じです。

x.grad = 0
a.x.g = 10
b.x.g = 20
c.x.g = 30
print(x.grad)

[19] 2019-06-12 20:00:15 (7.06ms) python3 (706ms)

60

損失関数の例を見てみます。

from ivory.layers.loss import SoftmaxCrossEntropy

s = SoftmaxCrossEntropy((3,))
print(type(s.y))
print(type(s.loss))

[20] 2019-06-12 20:00:15 (7.98ms) python3 (714ms)

<class 'ivory.core.parameter.Output'>
<class 'ivory.core.parameter.Loss'>

損失パラメータlossは特別なLossクラスのインスタンスです。ニューラルネットワークの学習では、損失に対して各種の操作を行うため、Ivoryライブラリでは特別のクラスを与えています。

なお、実験用に、レイヤを作成した後に、全てのパラメータに変数を自動で割り当てるメソッドLayer.set_variablesも用意されています。

s.set_variables()

[21] 2019-06-12 20:00:16 (6.99ms) python3 (721ms)

[<Variable(['SoftmaxCrossEntropy.1.x'], (3,)) at 0x2920f09c208>,
 <Variable(['SoftmaxCrossEntropy.1.y'], (3,)) at 0x2920f09c198>,
 <Variable(['SoftmaxCrossEntropy.1.t'], ()) at 0x2920f09c240>,
 <Variable(['SoftmaxCrossEntropy.1.loss'], ()) at 0x2920f09c278>]

set_dataメソッドを使えば、入力データをセットできます。

s.set_data([1, 2, 3], 2)
print(s.x.d)
print(s.t.d)

[22] 2019-06-12 20:00:16 (18.1ms) python3 (739ms)

[1, 2, 3]
2