はじめに
今回はいよいよニューラルネットワークの基本とも言える、Linear層を実装したいと思います。他の実装と比べて特殊な点は、インスタンスごとにParamデータを格納しておき、学習時にそれらの更新ができるようになっているという点です。
Linearが行っている操作
全結合層(LinearまたはAffine, Fully Connected Layerなど)で行われているのは以下のような操作です。これらは基本的に今まで実装した関数で実装できると思いましたが、バイアスの足し算の際に次元を補完するためにRepeat関数を追加します。
\boldsymbol{O} = {\bf{}A\cdot W} + \bf{B}
これは実装上では以下のように表されます。$*$は0またはそれより大きい数の次元が入ります。
O[*, H_{out}] = A[*, H_{in}]\cdot W[H_{in}, H_{out}]+B[*, H_{out}]
また、学習の際にパラメータを操作するために、ParamersプロパティをIFunctionに設定します。
次元補完用のRepeatクラスを実装
Repeatクラスの実装は集合演算のクラスと似ていて、対象となる次元と繰り返し回数を否定することで指定した次元方向に複製したTensorを出力します。
実装
namespaceRein.Functions.Process{publicclassRepeat:UnaryFunction{intDim,RepeatNum,Step;publicRepeat(intdim,intrep):base($"Expand-d:{dim}-r:{rep}"){this.Dim=dim;this.RepeatNum=rep;}protectedoverrideTensorUnaryForward(Tensortensor){if(tensor.Shape.Count<this.Dim-1)thrownewInvalidShapeException();R[]data=newR[tensor.Size*this.RepeatNum];// 対象となるDim以下の要素数を取得this.Step=tensor.Shape[this.Dim]*tensor.Size/tensor.Shape.GetRange(0,this.Dim+1).Aggregate((now,next)=>now*next);for(inti=0;i<tensor.Size;i+=this.Step){for(intj=0;j<this.RepeatNum;j++){Array.Copy(tensor.Data,i,data,i*this.RepeatNum+j*this.Step,this.Step);}}List<int>shape=newList<int>(tensor.Shape);shape[this.Dim]*=this.RepeatNum;returnnewTensor(data,shape);}protectedoverridevoidUnaryBackward(){for(inti=0;i<this.In.Size;i+=this.Step){for(intj=0;j<this.RepeatNum;j++){for(intk=0;k<this.Step;k++){this.In.Grad[i+k]+=this.Out.Grad[i*this.RepeatNum+j*this.Step+k];}}}}}}
学習パラメータの追加
学習対象となるパラメータを持った関数からパラメータを取り出すためにIFunctionインターフェースにParametersプロパティとBaseFunctionにParamsメンバを追加しておきます。
IFunctionの変更
namespaceRein.Functions{publicinterfaceIFunction{// ...publicTensor[]Parameters{get;}}}
BaseFunctionの変更
namespaceRein.Functions{publicabstractclassBaseFunction:IFunction{// ...publicTensor[]Params=newTensor[]{};// ...publicTensor[]Parameters{get{returnthis.Params;}}}}
これでIFunction.Parametersにアクセスできるようになりました。
Linearクラスの実装
これでLinearクラスに必要なものが揃いましたので実装していきます。
実装
冒頭で書いていた計算に加えて、Activationに関数を設定しておくこともできるようにしておきます。
namespaceRein.Functions.Layer{publicclassLinear:UnaryFunction{publicTensorWeight,Bias;publicIFunctionActivation;publicLinear(intinputSize,intoutputSize,boolbias=true,IFunctionactivation=null):base("Linear"){this.Weight=newTensor(newint[]{inputSize,outputSize});this.Activation=activation;if(bias){this.Bias=newTensor(newint[]{outputSize});}this.Params=newTensor[]{Weight,Bias};}protectedoverrideTensorUnaryForward(Tensortensor){TensoroutTensor=F.Dot(tensor,this.Weight);if(this.Bias!=null){if(outTensor.Shape.Count==0){outTensor+=this.Bias;}else{outTensor+=this.Bias.Repeat(0,outTensor.Size/outTensor.Shape.Last());}}if(this.Activation!=null){returnthis.Activation.Forward(outTensor);}returnoutTensor;}protectedoverridevoidUnaryBackward(){// 何もしない}}}
終わりに
今回はLinear関数の実装をしました。やっとパラメータを持った関数を実装することができました。ここで改めて小目標として設定した「DQNを実装する」ですが、現時点でこれに必要なのは
- optimizer(簡単なものを二つほど実装する)
- detachメソッド(勾配を伝播させないようにするためのメソッド)
- ロス関数(二乗誤差などは既に実装できますが)
- ネットワークのパラメータを保存、読み込み
の四つぐらいですかね。上三つに関してはこれまでの延長線上で出来そうな気がするのですが、パラメータの保存、読み込み辺りはどういう形式で保存すれば良いか分からないです。Pytorchとかではどう実装してるんでしょうか。
そこら辺は追々考えることにして次回はdetachメソッド、ロス関数あたりを実装していきます。