勾配降下法を実装してみた

C# でゼロから Deep Learning を実装する挑戦はまだ 4 章。 機械学習で使う勾配降下法を実装してみた。 勾配を計算するメソッドは前回記事を流用している。

using MathNet.Numerics.LinearAlgebra;
using System;
using System.Linq;

namespace GradientDescentSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // f(x0, x1) = x0^2 + x1^2
            Func<Vector<double>, double> function2 =
                (x) => Math.Pow(x[0], 2) + Math.Pow(x[1], 2);

            var initX = Vector<double>.Build.DenseOfArray(new double[]
            {
                -3.0,
                4.0
            });
            var result = GradientDescent(
                f: function2,
                initX: initX,
                learningRate: 0.1,
                stepNum: 100
                );

            Console.WriteLine(result);
            Console.ReadLine();
        }

        /// <summary>
        /// 勾配降下法を使って最小値を計算します。
        /// </summary>
        /// <param name="f">最適化したい関数</param>
        /// <param name="initX">初期値</param>
        /// <param name="learningRate">学習率</param>
        /// <param name="stepNum">勾配法による繰り返しの数</param>
        /// <returns>最小値</returns>
        static Vector<double> GradientDescent(
            Func<Vector<double>, double> f,
            Vector<double> initX,
            double learningRate,
            int stepNum = 100
            )
        {
            var x = initX;

            foreach (var i in Enumerable.Range(0, stepNum))
            {
                var grad = NumericalGradient(f, x);

                x = x - learningRate * grad;
            }

            return x;
        }

        /// <summary>
        /// 勾配を計算します。
        /// </summary>
        /// <param name="f">関数</param>
        /// <param name="x">関数に渡す引数</param>
        /// <returns>
        /// <paramref name="x"/> の各要素に対しての数値微分。
        /// </returns>
        static Vector<double> NumericalGradient(Func<Vector<double>, double> f, Vector<double> x)
        {
            var h = 0.0001;
            var grad = Vector<double>.Build.Dense(x.Count);

            foreach (var idx in Enumerable.Range(0, x.Count))
            {
                var tmpVal = x[idx];

                // f(x+h) の計算
                x[idx] = tmpVal + h;
                var fxh1 = f(x);

                // f(x-h) の計算
                x[idx] = tmpVal - h;
                var fxh2 = f(x);

                grad[idx] = (fxh1 - fxh2) / (2 * h);

                // 値を元に戻す
                x[idx] = tmpVal;
            }

            return grad;
        }
    }
}

実行結果。

数式だと難しく見えるが、コードにすると非常にシンプルだ。