UnityでPytorchライクの機械学習ライブラリを作る。6日目：集合演算の実装

# 今回すること
　今回は集合演算(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を使用する。

```cs:Sum.cs
namespace Rein.Functions.Set{
    public class Sum: UnaryFunction{
        private int Axis;
        private bool KeepDim;
        public Sum(int axis, bool keepDim = true):base("Sum"){
            this.Axis = axis;
            this.KeepDim = keepDim;
        }

        protected override Tensor UnaryForward(Tensor inTensor){
            List<int> shape = new List<int>(inTensor.Shape);
            int biggerStep, 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 = new R[this.In.Size / shape[this.Axis]];

            for (int i = 0; i < outSize; i+=step){
                for (int j = 0; j < biggerStep; j+=step){
                    for (int k = 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);
            }

            return new Tensor(data, shape);
        }


        protected override void UnaryBackward(){

            List<int> shape = new List<int>(this.In.Shape);

            shape[this.Axis] = 1;

            int step, 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 = new R[this.In.Size];
            
            for (int i = 0; i < this.Out.Size; i+=step){
                for (int j = 0; j < repNum; j++){
                    Array.Copy(this.Out.Grad, i, grad, i * repNum + j * step, step);
                }
            }

            for (int i = 0; i < this.In.Size; i++){
                this.In.Grad[i] += grad[i];
            }
        }
    }
}
```

# Mean
　Mean関数はSum関数の出力を個数で割ればいいので、代入の際に工夫するだけで計算できます。

## 実装

```cs:Mean.cs
namespace Rein.Functions.Set{
    public class Mean: UnaryFunction{
        private int Axis;
        private bool KeepDim;
        public Mean(int axis, bool keepDim = true):base("Mean"){
            this.Axis = axis;
            this.KeepDim = keepDim;
        }

        protected override Tensor UnaryForward(Tensor inTensor){
            List<int> shape = new List<int>(inTensor.Shape);
            int biggerStep, 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 = new R[this.In.Size / vecSize];

            for (int i = 0; i < outSize; i+=step){
                for (int j = 0; j < biggerStep; j+=step){
                    for (int k = 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);
            }

            return new Tensor(data, shape);
        }


        protected override void UnaryBackward(){

            List<int> shape = new List<int>(this.In.Shape);
            int meanNum = 1;

            meanNum *= shape[this.Axis];
            shape[this.Axis] = 1;

            int step, 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 = new R[this.In.Size];
            
            // 総和処理
            for (int i = 0; i < this.Out.Size; i+=step){
                for (int j = 0; j < repNum; j++){
                    Array.Copy(this.Out.Grad, i, grad, i * repNum + j * step, step);
                }
            }


            for (int i = 0; i < this.In.Size; i++){
                this.In.Grad[i] += grad[i] / meanNum;
            }
        }
    }
}
```

# Max
　Max関数も基本はMeanやSumと同じですが、勾配処理のためにForwardの際に取り出した値のインデックスを保存しておく処理を追加します。

## 実装

```cs:Max.cs
namespace Rein.Functions.Set{
    public class Max: UnaryFunction{
        private int Axis;
        private int[] ExtractIndex;
        private bool KeepDim;
        public Max(int axis, bool keepDim = true):base("Max"){
            this.Axis = axis;
            this.KeepDim = keepDim;
        }

        protected override Tensor UnaryForward(Tensor inTensor){
            List<int> shape = new List<int>(inTensor.Shape);
            int biggerStep, 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 = new R[this.In.Size / vecSize];
            extractIndex = new int[this.In.Size / vecSize];

            for (int i = 0; i < outSize; i+=step){
                for (int k = 0; k < step; k++){
                    R maxR = this.In.Data[vecSize * i + k];
                    int index = 0;
                    for (int j = 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;

            return new Tensor(data, shape);
        }


        protected override void UnaryBackward(){
            int step, 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 = new R[this.In.Size];
            
            for (int i = 0; i < this.Out.Size; i+=step){
                for (int k = 0; k < step; k++){
                    this.In.Grad[repNum * i + this.ExtractIndex[i + k] + k] += this.Out.Grad[i + k];
                }
            }
        }
    }
}
```

Min関数は比較演算子を逆にするだけなので省略します。

# コード
 ここまでの実装は[こちら](https://github.com/aokyut/Rein/tree/v0.0.4)に置いています。

# 終わりに
　今回は集合演算を実装しました。正直前回のように、集合演算の抽象クラスを作った方が良いのではないかと思ったのですが、Sum, Mean, Max, Minの四つしか使用しないのとどう抽象クラスを定義したら良いか分からなかったので実装しませんでした。
　次回はいよいよ機械学習らしいLinear層の実装をしたいと思います。