活性化層
今回は、活性化関数に相当する活性化層を実装します。ただし、Softmax関数に関しては少し特殊なので次回に。
活性化層のインターフェース
活性化層は、基本的に行列を入力して、入力行列と同じ大きさの行列が出力されます。前回と同様、出力先を呼び出し元で与える場合と、メソッド内で動的確保する場合の2種類のメソッドを用意しています。
using MathNet.Numerics.LinearAlgebra.Single; namespace NeuralNET.Layers.Activation { /// <summary> /// 活性化層(活性化関数)が実装するインターフェース /// </summary> public interface IActivationLayer : ILayer { public DenseMatrix Forward(DenseMatrix x, DenseMatrix y); public DenseMatrix Forward(DenseMatrix x); public DenseMatrix Backward(DenseMatrix dOutput, DenseMatrix res); public DenseMatrix Backward(DenseMatrix dOutput); } }
シグモイド層
シグモイド関数(標準シグモイド関数) を計算する層です。この関数は、よく中間層の活性化関数や2値分類問題において出力層の活性化関数に用いられます。微分するととなり、元の関数が導関数に登場する形になります。それゆえ、以下のSigmoidLayerクラスでは、順伝播時に出力を保存します。
(2024/04/07 追記) SigmoidLayer、TanhLayer、ReLULayerそれぞれについて、Backwardメソッドにバグがあったので修正しました。
using MathNet.Numerics.LinearAlgebra.Single; namespace NeuralNET.Layers.Activation { /// <summary> /// 標準シグモイド関数 /// </summary> public class SigmoidLayer : IActivationLayer { DenseMatrix? output; readonly bool SAVE_OUTPUT_REF; public SigmoidLayer() : this(false) { } public SigmoidLayer(bool saveOutputRef) => this.SAVE_OUTPUT_REF = saveOutputRef; public DenseMatrix Forward(DenseMatrix x, DenseMatrix y) { x.PointwiseSigmoid(y); SaveOutput(y); return y; } public DenseMatrix Forward(DenseMatrix x) { var y = DenseMatrix.Create(x.RowCount, x.ColumnCount, 0.0f); Forward(x, y); SaveOutput(y); return y; } public DenseMatrix Backward(DenseMatrix dOutput, DenseMatrix res) { if (this.output is null) throw new InvalidOperationException("Backward method must be called after forward."); this.output.Negate(res); res.Add(1.0f, res); res.PointwiseMultiply(this.output, res); res.PointwiseMultiply(dOutput, res); return res; } public DenseMatrix Backward(DenseMatrix dOutput) { if (this.output is null) throw new InvalidOperationException("Backward method must be called after forward."); var res = DenseMatrix.Create(this.output.RowCount, this.output.ColumnCount, 0.0f); return Backward(dOutput, res); } void SaveOutput(DenseMatrix output) { if (this.SAVE_OUTPUT_REF) { this.output = output; return; } this.output = output.CopyToOrClone(this.output); } } }
DenseMatrix.PointwiseSigmoidメソッドは自作の拡張メソッドであり、LinearAlgebraExtensionsクラス内で以下のように実装しています。
(2024/04/07 追記) シグモイド関数のバグを修正しました.
public static void PointwiseSigmoid(this DenseMatrix x, DenseMatrix y) { x.Negate(y); y.PointwiseExp(y); y.Add(1.0f, y); y.Map(x => 1.0f / x, y); }
複数のメソッドを組み合わせて表現しているので、ぱっと見、コードからは数式が浮かびにくいです。まあ、しょうがない。
tanh層
tanh関数(双曲正接関数)を計算する層です。値域は異なりますが、tanh関数はシグモイド関数の一種です。そのため、シグモイド関数と同様に導関数に自分自身が登場します。
using MathNet.Numerics.LinearAlgebra.Single; namespace NeuralNET.Layers.Activation { /// <summary> /// tanh関数 /// </summary> public class TanhLayer : IActivationLayer { DenseMatrix? output; readonly bool SAVE_OUTPUT_REF; public TanhLayer() : this(false) { } public TanhLayer(bool saveOutputRef) => this.SAVE_OUTPUT_REF = saveOutputRef; public DenseMatrix Forward(DenseMatrix x, DenseMatrix y) { x.PointwiseTanh(y); SaveOutput(y); return y; } public DenseMatrix Forward(DenseMatrix x) { var y = DenseMatrix.Create(x.RowCount, x.ColumnCount, 0.0f); Forward(x, y); SaveOutput(y); return y; } public DenseMatrix Backward(DenseMatrix dOutput, DenseMatrix res) { if (this.output is null) throw new InvalidOperationException("Backward method must be called after forward."); this.output.PointwiseMultiply(this.output, res); res.Negate(res); res.Add(1.0f, res); res.PointwiseMultiply(dOutput, res); return res; } public DenseMatrix Backward(DenseMatrix dOutput) { if (this.output is null) throw new InvalidOperationException("Backward method must be called after forward."); var res = DenseMatrix.Create(this.output.RowCount, this.output.ColumnCount, 0.0f); return Backward(dOutput, res); } void SaveOutput(DenseMatrix output) { if (this.SAVE_OUTPUT_REF) { this.output = output; return; } this.output = output.CopyToOrClone(this.output); } } }
Math.NETには、行列の要素ごとにtanh関数を適用するメソッドが用意されているので、容易に実装できます。
ReLU層
ReLU関数を計算する層です。ReLU関数は、入力が0以下なら0を、0以上なら入力された値をそのまま出力する単純な関数です。中間層の活性化関数に非常によく用いられます。ReLUの導関数はReLU関数の出力が0以下ならば0を、そうでなければ1をとる単純な関数です*1。シグモイド関数の導関数と比べると0と1しか出力しないので、勾配消失が起きづらいというメリットがあります。
using MathNet.Numerics.LinearAlgebra.Single; namespace NeuralNET.Layers.Activation { /// <summary> /// ReLU関数 /// </summary> public class ReLULayer : IActivationLayer { DenseMatrix? output; readonly bool SAVE_OUTPUT_REF; public ReLULayer() : this(false) { } public ReLULayer(bool saveOutputRef) => this.SAVE_OUTPUT_REF = saveOutputRef; public DenseMatrix Forward(DenseMatrix x, DenseMatrix y) { x.PointwiseMaximum(0.0f, y); SaveOutput(y); return y; } public DenseMatrix Forward(DenseMatrix x) { var y = DenseMatrix.Create(x.RowCount, x.ColumnCount, 0.0f); Forward(x, y); SaveOutput(y); return y; } public DenseMatrix Backward(DenseMatrix dOutput, DenseMatrix res) { if (this.output is null) throw new InvalidOperationException("Backward method must be called after forward."); this.output.PointwiseSign(res); res.PointwiseMultiply(dOutput, res); return res; } public DenseMatrix Backward(DenseMatrix dOutput) { if (this.output is null) throw new InvalidOperationException("Backward method must be called after forward."); var res = DenseMatrix.Create(this.output.RowCount, this.output.ColumnCount, 0.0f); return Backward(dOutput, res); } void SaveOutput(DenseMatrix output) { if (this.SAVE_OUTPUT_REF) { this.output = output; return; } this.output = output.CopyToOrClone(this.output); } } }
すでにMath.NET側で用意されている、DenseMatrix.PointwiseMaximumメソッドとDenseMatrix.PointwiseSignメソッドを利用して容易に実装できます。