旅行好きなソフトエンジニアの備忘録

プログラミングや技術関連のメモを始めました

【C#】 最急降下法による多変数関数の最適化

最急降下法を利用して多変数関数の最適化を行うクラスを実装します。最急降下法ではここ(【C#】 勾配ベクトルを計算するクラス - 旅行好きなソフトエンジニアの備忘録)で作成したGradientというクラスを利用します。

public class SteepestDescentMethodMV
{
    private readonly Func<double[], double> _f;
    private readonly double[] _xn;
    private readonly double _learningRate;

    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="f">多変数関数</param>
    /// <param name="initialX">初期値</param>
    /// <param name="learningRate">学習係数</param>
    public SteepestDescentMethodMV(Func<double[], double> f, double[] initialX, double learningRate)
    {
        ValidateArguments(initialX, learningRate);

        _f = f;
        _xn = new double[initialX.Length];
        Array.Copy(initialX, _xn, _xn.Length);
        _learningRate = learningRate;
    }

    private static void ValidateArguments(double[] initialX, double learningRate)
    {
        if (initialX == null)
        {
            throw new ArgumentNullException("initialX");
        }
        if (initialX.Length <= 1)
        {
            throw new ArgumentOutOfRangeException("initialX", "initialX length must be more than 1");
        }
        if (learningRate <= 0.0)
        {
            throw new ArgumentOutOfRangeException("learningRate", "learningRate must be positive");
        }
    }

    /// <summary>
    /// 現在の解を取得するためのプロパティ
    /// </summary>
    public double[] Xn
    {
        get { return _xn; }
    }

    /// <summary>
    /// 最急降下法を利用して多変数関数の最小値を探索するメソッド
    /// </summary>
    /// <param name="f">多変数関数</param>
    /// <param name="initialX">初期値</param>
    /// <param name="iteration">計算回数</param>
    /// <param name="learningRate">学習係数</param>
    /// <returns>関数の最小値を与える変数</returns>
    public static double[] Compute(Func<double[], double> f, double[] initialX, int iteration, double learningRate)
    {
        ValidateArguments(initialX, iteration, learningRate);

        var xn = new double[initialX.Length];
        Array.Copy(initialX, xn, xn.Length);

        for (int i = 0; i < iteration; ++i)
        {
            double[] gradient = Gradient.Compute(f, xn);

            for (int j = 0; j < xn.Length; ++j)
            {
                xn[j] -= learningRate*gradient[j];
            }
        }

        return xn;
    }

    private static void ValidateArguments(double[] initialX, int iteration, double learningRate)
    {
        ValidateArguments(initialX, learningRate);

        if (iteration <= 0)
        {
            throw new ArgumentOutOfRangeException("iteration", "iteration must be positive");
        }
    }

    /// <summary>
    /// 現在の解を更新するメソッド
    /// </summary>
    public void Update()
    {
        double[] gradient = Gradient.Compute(_f, _xn);

        for (int i = 0; i < _xn.Length; ++i)
        {
            _xn[i] -= _learningRate*gradient[i];
        }
    }
}

次にSteepestDescentMethodMVクラスを利用してf(x_0,x_1)=x_0^2+x_1^2+1の最小値を与える(x_0,x_1)を求めます。

Func<double[], double> f = x => x[0]*x[0] +x[1]*x[1] + 1.0;
var initialX = new double[] { 5.0, 1.0 };
int iteration = 100;
double learningRate = 0.1;
double[] answer = SteepestDescentMethodMV.Compute(f, initialX, iteration, learningRate);

answer[0]がx_0、answer[1]がx_1に対応しており、両方に0に近い値が入っているため正しく動作していることが分かります。更新時のxの遷移を様子を確認したい場合はstaticメソッドではなくSteepestDescentMethodMVクラスをインスタンス化して下記のように利用します。

Func<double[], double> f = x => x[0]*x[0] + x[1]*x[1] + 1.0;
var initialX = new double[] { 5.0, 1.0 };
double learningRate = 0.1;
var optimizer = new SteepestDescentMethodMV(f, initialX, learningRate);
int iteration = 100;
var xHistory = new List<double[]>();
for (int i = 0; i < iteration; ++i)
{
    optimizer.Update();
    xHistory.Add(optimizer.Xn);
}

上記コードでxHistoryの中身を調べることでxの遷移の様子が分かります。

メタヒューリスティクスとナチュラルコンピューティング

メタヒューリスティクスとナチュラルコンピューティング