ニューラルネットワークに対する勾配を実装してみた

C# でゼロから Deep Learning を実装する挑戦の続き。 4 章もようやく終盤で、いよいよ機械学習に入る。 今回はニューラルネットワークに対する勾配を実装してみた。

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

namespace GradientSimpleNet
{
    class Program
    {
        static void Main(string[] args)
        {
            var net = new SimpleNet();

            // 予測
            var x = Vector<double>.Build.DenseOfArray(new[] { 0.6, 0.9 });
            var p = net.Predict(x);
            Console.WriteLine("予測");
            Console.WriteLine(p);

            // 損失関数の値
            var t = Vector<double>.Build.DenseOfArray(new[] { 0, 0, 1.0 });
            var loss = net.Loss(x, t);
            Console.WriteLine("損失関数の値");
            Console.WriteLine(loss);

            // 勾配
            Func<Matrix<double>, double> f = W => net.Loss(x, t);
            var dW = Gradient.NumericalGradient(f, net.W);
            Console.WriteLine("勾配");
            Console.WriteLine(dW);

            Console.ReadLine();
        }
    }

    static class Functions
    {
        /// <summary>
        /// ソフトマックス
        /// </summary>
        public static Vector<double> Softmax(Vector<double> a)
        {
            var c = a.Max();

            // ベクトルの要素ごとの演算にも Map を使う
            var exp_a = a.Map(x => Math.Exp(x - c));
            var sum_exp_a = exp_a.Sum();
            var y = exp_a.Map(x => x / sum_exp_a);
            return y;
        }

        /// <summary>
        /// 交差エントロピー誤差
        /// </summary>
        public static double CrossEntropyError(Vector<double> y, Vector<double> t)
        {
            var delta = 1e-7;
            return -(y + delta).PointwiseLog().PointwiseMultiply(t).Sum();
        }
    }

    static class Gradient
    {
        /// <summary>
        /// 数値微分
        /// </summary>
        public static Matrix<double> NumericalGradient(Func<Matrix<double>, double> f, Matrix<double> x)
        {
            var h = 1e-4;
            var grad = Matrix<double>.Build.Dense(x.RowCount, x.ColumnCount);

            for (int row = 0; row < x.RowCount; row++)
            {
                for (int column = 0; column < x.ColumnCount; column++)
                {
                    var tmpVal = x[row, column];

                    x[row, column] = tmpVal + h;
                    var fxh1 = f(x);

                    x[row, column] = tmpVal - h;
                    var fxh2 = f(x);

                    grad[row, column] = (fxh1 - fxh2) / (2.0 * h);

                    // 値を元に戻す
                    x[row, column] = tmpVal;
                }
            }

            return grad;
        }
    }

    class SimpleNet
    {
        /// <summary>
        /// 重みパラメーター
        /// </summary>
        public Matrix<double> W { get; }

        public SimpleNet()
        {
            // ゼロから作る Deep Learning と同じ重みパラメーターを指定する
            W = Matrix<double>.Build.DenseOfArray(new double[,]
            {
                { 0.47355232, 0.9977393, 0.84668094 },
                { 0.85557411, 0.03563661, 0.69422093 }
            });
        }

        public Vector<double> Predict(Vector<double> x)
        {
            return x * W;
        }

        public double Loss(Vector<double> x, Vector<double> t)
        {
            var z = Predict(x);
            var y = Functions.Softmax(z);
            var loss = Functions.CrossEntropyError(y, t);
            return loss;
        }
    }
}

実行結果は次の通り。

『ゼロから作る Deep Learning』と同じような傾向になっている。 完全に一致しないのは、自分の実装が間違っているんだろうか。

ア・ラ・カンパーニュ

会社帰りに、福岡三越地下2階にある『ア・ラ・カンパーニュ』でタルトを買って帰った。 まだバレンタインセール中で、タルトとはいえチョコが使われているものを買うのは、 心理的ハードルが高めだ。

チョコレートのタルトは予想していたよりもかなりビターテイスト。 これバレンタイン限定商品っぽいけど、 かなりビターなチョコレートを貰った人は素直に喜べているんだろうか。 まぁ爆発して欲しいことには変わりない。

イチゴのタルトはこちらも生地がガトーショコラみたいになっていて、 イチゴの酸味とクリームの甘さと生地のほろ苦さが絶妙。 こっちの方が好みだったけど、こいつも限定商品なんだよな。 定番にすればいいのに。

www.alacampagne.jp

『まつもとゆきひろ 言語のしくみ』を読んだ

本書は、Matz こと『まつもとゆきひろ』氏が Streem という新しいプログラミング言語を開発するという、 日経 Linux で連載した記事をまとめて書籍にしたもの。

Streem のプログラミングモデルや文法を考え、 ガベージコレクションを含めた言語処理系だけでなくライブラリまで実装する、 という一部始終が詳細に書かれていた。 Streem の開発を通して、プロの言語デザイナーである Matz の頭の中を覗くことができる。

やはり、プログラミング言語のデザインについて書ける日本人は Matz しかいない。 いや、書くだけなら他にもいるだろうけど、 Ruby のパパである Matz が書くと説得力がケタ違いだ。

あと、タイムマシンコラムというのは面白い試みだった。 連載を書籍化したから生まれた発明かもしれない。 今後、同じようにタイムマシンコラムを載せる書籍が増えたら面白いだろうな。

自分にもプログラミング言語が作れそうな気がしてくる。作ってみたくなる。 そんな魅力が凝縮された一冊だった。

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

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;
        }
    }
}

実行結果。

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

勾配を実装してみた

C# でゼロから Deep Learning を実装する挑戦を少しずつ進めている。 まだ第 4 章だけど。 今回は勾配を実装してみた。

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

namespace NumericGradientSample
{
    class Program
    {
        static void Main(string[] args)
        {
            Func<Vector<double>, double> function2 =
                x => x[0] * x[0] + x[1] * x[1];

            Console.WriteLine("(x0, x1) = (3.0, 4.0) のとき");
            Console.WriteLine(
                NumericalGradient(
                    function2,
                    Vector<double>.Build.DenseOfArray(new[] { 3.0, 4.0 })));

            Console.WriteLine("(x0, x1) = (0.0, 2.0) のとき");
            Console.WriteLine(
                NumericalGradient(
                    function2,
                Vector<double>.Build.DenseOfArray(new[] { 0.0, 2.0 })));

            Console.WriteLine("(x0, x1) = (3.0, 0.0) のとき");
            Console.WriteLine(
                NumericalGradient(
                    function2,
                    Vector<double>.Build.DenseOfArray(new[] { 3.0, 0.0 })));

            Console.ReadLine();
        }

        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;
        }
    }
}

実行結果がこちら。

『かぐや様は告らせたい(1)〜(3)』を読んだ

ヤングジャンプで連載中の『かぐや様は告らせたい〜天才たちの恋愛頭脳戦〜』の 1 〜 3 巻を Kindle で買って読んだ。ラブコメを買ったのはかなり久しぶりだ。

主人公の白銀とかぐや、 二人の天才が繰り広げる「いかにして相手に告らせるか」の頭脳戦に、 グイグイと引き込まれてしまった。 主人公やヒロインがハイスペックなマンガは星の数ほどあるけど、 恋愛頭脳戦というアイデアでこうも突出した面白さになるとは。 それだけアイデアが優れているということか。 最近はラブコメのコメ要素が強めで、天才という設定が影を潜めている気がしないでもない。

もちろん登場人物が皆、個性が強くて魅力的なのは言うまでもない。 主人公に感情移入はしない方だけど、白銀は弱点だらけなところに親しみを感じる。 かぐや様はお可愛いは当然として、それよりもたまに黒くなるところが良い。藤原書記を蔑むところなんか特に。 その藤原書記は色んな意味でオフェンス力が高くて、自分が爆笑した話ではどれも大活躍している。

最近読んだマンガの中で一番のヒットだ。 ヤングジャンプの連載も読み始めた。 電子書籍縛りを自分に課しているので、4 巻の Kindle 版が出るのが待ち遠しい。

数値微分を実装してみた

C# でゼロから Deep Learning を実装する挑戦の続き。 今回は第 4 章の数値微分を実装してみた。

using System;

namespace NumericalDifferentiationSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // 微分
            Func<double, double> function1 =
                x => 0.01 * Math.Pow(x, 2) + 0.1 * x;
            Console.WriteLine("y = 0.01x^2 + 0.1x");
            Console.WriteLine($"x = 5,  y = {NumericalDiff(function1, 5)}");
            Console.WriteLine($"x = 10, y = {NumericalDiff(function1, 10)}");
            Console.WriteLine();

            // 偏微分
            Func<double, double, double> function2 =
                (x0, x1) => Math.Pow(x0, 2) + Math.Pow(x1, 2);
            Console.WriteLine("f(x0, x1) = x0^2 + x1^2");
            Console.WriteLine("x0 = 3.0, x1 = 4.0 のとき");

            Func<double, double> functionTemp1
                = (x0) => function2(x0, 4);
            Console.WriteLine($"x0 に対する偏微分 : {NumericalDiff(functionTemp1, 3)}");

            Func<double, double> functionTemp2
                = (x1) => function2(3, x1);
            Console.WriteLine($"x1 に対する偏微分 : {NumericalDiff(functionTemp2, 4)}");

            Console.ReadLine();
        }

        static double NumericalDiff(Func<double, double> f, double x)
        {
            var h = 1e-4;   // 0.0001
            return (f(x + h) - f(x - h)) / (2 * h);
        }
    }
}

実行結果は次の通り。 偏微分の結果に誤差が出てしまっている。

余談だけど、『ゼロからつくる Deep Learning』に書いてある微分の説明は非常に分かりやすいと思った。 自分が学生の頃使った教科書も、これぐらい分かりやすく書いてあればな。