今回すること
今回は集合演算(Max, Sumなど)を実装していきますが、基本的には前回実装した抽象クラスUnaryFunctionを継承して付け加えるだけです。
Sum
今回実装するSumは以下のように軸を指定して、軸方向の総和を計算できるようにします。
B = Sum(A, axis)
Shapeが$N_0\times N_1\times \cdots \times N_i \times \cdots \times N_{s - 1}$のTensorを考える。
この場合にaxis=$i$として総和を計算するとき、Shapeを$(N_0\cdot N_1 \cdot \,
...\, \cdot N_{i-1})\times N_i \times (N_{i+1}\cdot\, ...\, \cdot N_{s-1})$として以下の計算を行う。
B[n, m] = A[n, 1, m] + A[n, 2, m] + ... + A[n, Ni, m]
勾配については$A.Grad[n, 1, m]=A.Grad[n, 2, m]=...=A.Grad[n, N_i, m]=B.Grad[n, m]$なのでそのまま実装できる。
実装
勾配計算の際にはArray.Copyを使用する。
namespaceRein.Functions.Set{publicclassSum:UnaryFunction{privateintAxis;privateboolKeepDim;publicSum(intaxis,boolkeepDim=true):base("Sum"){this.Axis=axis;this.KeepDim=keepDim;}protectedoverrideTensorUnaryForward(TensorinTensor){List<int>shape=newList<int>(inTensor.Shape);intbiggerStep,step,outSize,vecSize;R[]data;vecSize=shape[this.Axis];outSize=this.In.Size/vecSize;step=this.In.Size/shape.GetRange(0,this.Axis+1).Aggregate((now,next)=>now*next);biggerStep=step*vecSize;data=newR[this.In.Size/shape[this.Axis]];for(inti=0;i<outSize;i+=step){for(intj=0;j<biggerStep;j+=step){for(intk=0;k<step;k++)data[i+k]+=this.In.Data[vecSize*i+j+k];}}if(this.KeepDim){shape[this.Axis]=1;}else{shape.RemoveAt(this.Axis);}returnnewTensor(data,shape);}protectedoverridevoidUnaryBackward(){List<int>shape=newList<int>(this.In.Shape);shape[this.Axis]=1;intstep,repNum;R[]grad;step=this.Out.Size/shape.GetRange(0,this.Axis+1).Aggregate((now,next)=>now*next);repNum=this.In.Shape[this.Axis];grad=newR[this.In.Size];for(inti=0;i<this.Out.Size;i+=step){for(intj=0;j<repNum;j++){Array.Copy(this.Out.Grad,i,grad,i*repNum+j*step,step);}}for(inti=0;i<this.In.Size;i++){this.In.Grad[i]+=grad[i];}}}}Mean
Mean関数はSum関数の出力を個数で割ればいいので、代入の際に工夫するだけで計算できます。
実装
namespaceRein.Functions.Set{publicclassMean:UnaryFunction{privateintAxis;privateboolKeepDim;publicMean(intaxis,boolkeepDim=true):base("Mean"){this.Axis=axis;this.KeepDim=keepDim;}protectedoverrideTensorUnaryForward(TensorinTensor){List<int>shape=newList<int>(inTensor.Shape);intbiggerStep,step,vecSize,outSize;R[]data;vecSize=shape[this.Axis];outSize=this.In.Size/vecSize;step=this.In.Size/shape.GetRange(0,this.Axis+1).Aggregate((now,next)=>now*next);biggerStep=step*vecSize;data=newR[this.In.Size/vecSize];for(inti=0;i<outSize;i+=step){for(intj=0;j<biggerStep;j+=step){for(intk=0;k<step;k++)data[i+k]+=this.In.Data[vecSize*i+j+k]/vecSize;}}if(this.KeepDim){shape[Axis]=1;}else{shape.RemoveAt(Axis);}returnnewTensor(data,shape);}protectedoverridevoidUnaryBackward(){List<int>shape=newList<int>(this.In.Shape);intmeanNum=1;meanNum*=shape[this.Axis];shape[this.Axis]=1;intstep,repNum;R[]grad;step=this.Out.Size/shape.GetRange(0,this.Axis+1).Aggregate((now,next)=>now*next);repNum=this.In.Shape[this.Axis];grad=newR[this.In.Size];// 総和処理for(inti=0;i<this.Out.Size;i+=step){for(intj=0;j<repNum;j++){Array.Copy(this.Out.Grad,i,grad,i*repNum+j*step,step);}}for(inti=0;i<this.In.Size;i++){this.In.Grad[i]+=grad[i]/meanNum;}}}}Max
Max関数も基本はMeanやSumと同じですが、勾配処理のためにForwardの際に取り出した値のインデックスを保存しておく処理を追加します。
実装
namespaceRein.Functions.Set{publicclassMax:UnaryFunction{privateintAxis;privateint[]ExtractIndex;privateboolKeepDim;publicMax(intaxis,boolkeepDim=true):base("Max"){this.Axis=axis;this.KeepDim=keepDim;}protectedoverrideTensorUnaryForward(TensorinTensor){List<int>shape=newList<int>(inTensor.Shape);intbiggerStep,step,vecSize,outSize;R[]data;int[]extractIndex;vecSize=shape[this.Axis];outSize=this.In.Size/vecSize;step=this.In.Size/shape.GetRange(0,this.Axis+1).Aggregate((now,next)=>now*next);biggerStep=step*vecSize;data=newR[this.In.Size/vecSize];extractIndex=newint[this.In.Size/vecSize];for(inti=0;i<outSize;i+=step){for(intk=0;k<step;k++){RmaxR=this.In.Data[vecSize*i+k];intindex=0;for(intj=0;j<biggerStep;j+=step){if(maxR<this.In.Data[vecSize*i+j+k]){maxR=this.In.Data[vecSize*i+j+k];index=j;}data[i+k]+=this.In.Data[vecSize*i+j+k]/vecSize;}data[i+k]=maxR;extractIndex[i+k]=index;}}if(this.KeepDim){shape[Axis]=1;}else{shape.RemoveAt(Axis);}this.ExtractIndex=extractIndex;returnnewTensor(data,shape);}protectedoverridevoidUnaryBackward(){intstep,repNum;R[]grad;grad=this.Out.Grad;step=this.Out.Size/this.Out.Shape.GetRange(0,this.Axis+1).Aggregate((now,next)=>now*next);repNum=this.In.Shape[this.Axis];grad=newR[this.In.Size];for(inti=0;i<this.Out.Size;i+=step){for(intk=0;k<step;k++){this.In.Grad[repNum*i+this.ExtractIndex[i+k]+k]+=this.Out.Grad[i+k];}}}}}Min関数は比較演算子を逆にするだけなので省略します。
コード
ここまでの実装はこちらに置いています。
終わりに
今回は集合演算を実装しました。正直前回のように、集合演算の抽象クラスを作った方が良いのではないかと思ったのですが、Sum, Mean, Max, Minの四つしか使用しないのとどう抽象クラスを定義したら良いか分からなかったので実装しませんでした。
次回はいよいよ機械学習らしいLinear層の実装をしたいと思います。