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

# はじめに
　今回は前回言ったように単項演算(一つのTensorを受け取る)クラスを定義していきたいと思います。

# UnaryFunction
　UnaryFunctionは以下のように一つのTensorを受け取り一つのTensorを返すような操作を想定しています。

![unary_function.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179010/177140c2-9481-3362-63f8-62be9c7f1b23.jpeg)

## UnaryFunctionの実装
　それでは実装していきますが内容はほぼBinaryFunctionと変わらないです。ForwardとBackwardを加工して返す関数を定義しているのと、プロパティを定義しているだけです。

```cs:UnaryFunction.cs
namespace Rein.Functions{
    public abstract class UnaryFunction: BaseFunction{
        
        protected abstract Tensor UnaryForward(Tensor tensor);
        protected abstract void UnaryBackward();
        protected Tensor In{
            get{
                return this.Inputs[0];
            }
            set{
                this.Inputs[0] = value;
            }
        }

        protected Tensor Out{
            get{
                return this.Outputs[0];
            }
            set{
                this.Outputs[0] = value;
            }
        }
        
        public UnaryFunction(string name):base(name){
            this.FunctionForward = (tensors) => new Tensor[1]{ this.UnaryForward(tensors[0]) };
            this.FunctionBackward = this.UnaryBackward;
        }


    }
}
```

これで、単項演算の実装に移ってもいいのですがさらに具体性を高めたクラスを追加します。


# Lambda
　ここで機械学習で用いるMean関数とReLU関数について考えます。これらはどちらも一つの入力と一つの出力を扱うようにでき、UnaryFunctionと見なすことができます。しかし、ReLU関数はMean関数とは異なり、出力されたTensorの一つの要素が入力の一つの要素によって作られているということです。

　これを図で表すと以下のようになります。
　↓ReLU
![lambda.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179010/269875f9-ca11-384d-88d4-e19b257daed0.jpeg)

 ↓Mean(集合演算)
![setFunction (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179010/c599576f-1d8d-f8cd-e456-9bf8d8e1afa8.jpeg)

　上のような関数では、要素ごとに行う関数のForwardとBackwardを定義しておけば処理ができるので、
　このような関数クラスをLambdaとして実装します。

## Lambdaの実装
 $f(x)$と$f'(x)$をコンストラクタに入力するように実装します。

```cs:Lambda.cs
 namespace Rein.Functions{
    public class Lambda: UnaryFunction{
        protected Func<R, R> LambdaForward, LambdaBackward;
        public Lambda(string name, Func<R, R> forward, Func<R, R> backward):base(name){
            this.LambdaForward = forward;
            this.LambdaBackward = backward;
        }

        protected override Tensor UnaryForward(Tensor tensor)
        {
            R[] data = new R[this.In.Size];

            for (int i = 0; i < this.In.Size; i++){
                // zi = f(xi)の操作
                data[i] = this.LambdaForward(this.In.Data[i]);
            }

            return new Tensor(data, new List<int>(this.In.Shape));
        }

        protected override void UnaryBackward()
        {
            for (int i = 0; i < this.In.Size; i++){
                // x.grad[i] <- f'(xi)　の操作
                this.In.Grad[i] += this.Out.Grad[i] * this.LambdaBackward(this.In.Data[i]);
            }
        }
    }
}
```

# 単項演算(Minus)の実装
　Lambdaは実装しなくてもMinusは実装できますが、これによってReLUや他の関数が簡単に定義できるようになります。それではTensorクラスにマイナスオペレーターを実装します。

```cs:Tensor.Operator.cs
namespace Rein
{
    public partial class Tensor
    {
        // ...

        public static Tensor operator -(Tensor tensor)
        {
            return new Rein.Functions.Lambda(
                "Minus",
                (x) => -x, // f
                (x) => -1  // f'
            ).Forward(tensor);
        }

        // ...
    }
}
```

　これだけで済みます。Lambdaは具体性が高い分実装時に大幅に手間を省くことができます。

# その他の単項演算の実装
　モデルを作る際に$nn.Linear$だとか$F.relu$だとか短く関数を使用できれば大変便利ですが、私のライブラリで使用しようとすると

```
new Rein.Function.ReLU().Forward(Input);
```
　
　となり大変使いづらいです。そこでstaticクラスを用意して関数として上の処理を呼び出すようにします。
　ついでにReLUなども実装してみます。

```cs:F.cs
using Rein.Functions;

namespace Rein{
    public static class F{
        // -X
        public static Tensor Minus(Tensor In){
            return new Lambda(
                "Minus",
                (x) => -x,
                (x) => -1
            ).Forward(In);
        }

        // ReLU
        public static Tensor ReLU(Tensor In){
            return new Lambda(
                "ReLU",
                (x) => x > 0 ? x : 0,
                (x) => x > 0 ? 1 : 0
            ).Forward(In);
        }

        // Exponential
        public static Tensor Exp(Tensor In){
            return new Lambda(
                "Exp",
                (x) => System.Math.Exp(x),
                (x) => System.Math.Exp(x)
            ).Forward(In);
        }

        // Log
        public static Tensor Log(Tensor In){
            return new Lambda(
                "Log_e",
                (x) => System.Math.Log(x),
                (x) => 1 / x
            ).Forward(In);
        }
    }
}
```

　これで$F.ReLU(input)$でForward処理を呼び出せるようになりました。

# 終わりに
　今回は単項演算を実装しました。追加で、ライブラリの利便性を高めるための実装も行いました。本当は行列積と単項演算は一回でまとめるつもりでしたが、思いの外行列積に手間取ってしまい三回に膨れ上がってしまいました。これでまだ機械学習には触れてないというのが恐ろしいです。
　次は集合演算を実装したいと思います。具体的には(Mean, Sum, Max, Min)です。