MNIST データセットを読み込んでベクトルに変換

C# でゼロから Deep Learning を実装する挑戦の続き。 この挑戦では、『ゼロから作る Deep Learning』同様に、 手書き数字認識のニューラルネットワークを実装するので、 MNIST データセットを利用する。

MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges

MNIST データセットを読み込んで、 Math.NET Numerics の Vector に変換するコードを書いてみた。

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

namespace MnistSample
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length != 2)
            {
                Console.WriteLine("MnistSample.exe <pixelsFilePath> <labelsFilePath>");
                Console.WriteLine("Press enter to quit");
                Console.ReadLine();
                return;
            }


            var cancel = false;
            Console.CancelKeyPress += (sender, e) =>
            {
                cancel = true;
            };

            // MNIST データセットをロードする
            var images = MnistImage.Load(pixelFilePath: args[0], labelFilePath: args[1]);
            Console.WriteLine($"Image count : {images.Length}");

            // 入力したインデックスにある画像の情報を表示する
            Console.WriteLine($"Press <Ctrl> + <C> to quit");
            while (cancel == false)
            {
                Console.WriteLine($"Input index (0 ~ {images.Length - 1})");

                var input = Console.ReadLine();
                int index;
                if (int.TryParse(input, out index) &&
                    (0 <= index) &&
                    (index <= images.Length))
                {
                    var image = images[index];
                    Console.WriteLine($"label : {image.Label}");
                    Console.WriteLine(image.ToVector());
                }
            }
        }
    }

    /// <summary>
    /// MNIST データセットの画像を表します。
    /// </summary>
    public class MnistImage
    {
        /// <summary>
        /// 画像の高さを取得します。
        /// </summary>
        public int Height { get; }

        /// <summary>
        /// 画像の幅を取得します。
        /// </summary>
        public int Width { get; }

        /// <summary>
        /// MNIST 画像のピクセルを取得します。
        /// </summary>
        public byte[][] Pixels { get; }

        /// <summary>
        /// 0 ~ 9 までのラベルを取得します。
        /// </summary>
        public byte Label { get; }

        /// <summary>
        /// <see cref="MnistImage"/> クラスの新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="height">画像の高さ</param>
        /// <param name="width">画像の幅</param>
        /// <param name="pixels">画像を構成するピクセル</param>
        /// <param name="label">ラベル</param>
        public MnistImage(int height, int width, byte[][] pixels, byte label)
        {
            Height = height;
            Width = width;
            Label = label;
            Pixels = new byte[height][];
            for (var i = 0; i < height; i++)
            {
                Pixels[i] = new byte[width];
            }
            for (var i = 0; i < height; i++)
            {
                for (var j = 0; j < width; j++)
                {
                    Pixels[i][j] = pixels[i][j];
                }
            }
        }

        // Vector<T> と Matrix<T> は byte をサポートしていない

        /// <summary>
        /// ベクトルに変換します。
        /// </summary>
        /// <returns>ベクトル</returns>
        public Vector<double> ToVector()
        {
            var flatten = Pixels.SelectMany(row => row)
                .Select(b => Convert.ToDouble(b));
            var vector = Vector<double>.Build.DenseOfEnumerable(flatten);
            return vector;
        }

        /// <summary>
        /// 行列に変換します。
        /// </summary>
        /// <returns>行列</returns>
        public Matrix<double> ToMatrix()
        {
            return Matrix<double>.Build.Dense(Height, Width, (row, col) => Pixels[row][col]);
        }

        /// <summary>
        /// MNIST データセットとラベルをロードします。
        /// </summary>
        /// <param name="pixelFilePath">MNIST データセットのパス</param>
        /// <param name="labelFilePath">ラベルのパス</param>
        /// <returns><see cref="MnistImage"/> の配列</returns>
        public static MnistImage[] Load(string pixelFilePath, string labelFilePath)
        {
            using (var imageStream = File.OpenRead(pixelFilePath))
            using (var labelStream = File.OpenRead(labelFilePath))
            using (var imageReader = new BinaryReader(imageStream))
            using (var labelReader = new BinaryReader(labelStream))
            {
                int magic1 = imageReader.ReadInt32();
                magic1 = ReverseBytes(magic1);

                // 画像の枚数を取得
                int imageCount = imageReader.ReadInt32();
                imageCount = ReverseBytes(imageCount);

                // 画像の高さを取得
                int imageHeight = imageReader.ReadInt32();
                imageHeight = ReverseBytes(imageHeight);

                // 画像の幅を取得
                int imageWidth = imageReader.ReadInt32();
                imageWidth = ReverseBytes(imageWidth);

                int magic2 = labelReader.ReadInt32();
                magic2 = ReverseBytes(magic2);

                // ラベルの個数を取得
                int labelCount = labelReader.ReadInt32();
                labelCount = ReverseBytes(labelCount);

                // 読み込んだ1枚分の画像データを格納するバッファを作成
                var pixels = new byte[imageHeight][];
                for (var i = 0; i < pixels.Length; i++)
                {
                    pixels[i] = new byte[imageWidth];
                }

                // 読み込んだすべての MNIST 画像を格納する配列を作成
                var result = new MnistImage[imageCount];

                for (int di = 0; di < imageCount; di++)
                {
                    for (int i = 0; i < imageHeight; i++) // get 28x28 pixel values
                    {
                        for (int j = 0; j < imageWidth; j++)
                        {
                            byte b = imageReader.ReadByte();
                            pixels[i][j] = b;
                        }
                    }

                    // ラベルを取得
                    byte label = labelReader.ReadByte();

                    var image = new MnistImage(imageHeight, imageWidth, pixels, label);
                    result[di] = image;
                }

                return result;
            }
        }

        /// <summary>
        /// 整数のビットを逆順にします。
        /// </summary>
        /// <param name="value">整数</param>
        /// <returns>ビットを逆順にした整数</returns>
        public static int ReverseBytes(int value)
        {
            byte[] intAsBytes = BitConverter.GetBytes(value);
            Array.Reverse(intAsBytes);
            return BitConverter.ToInt32(intAsBytes, 0);
        }
    }
}

読み込んだデータセットのうち、指定した位置の画像情報を出力できるようにしてある。 試しに 3 を入力してみた結果がこちら。

活性化関数とソフトマックス関数を実装

『ゼロから作る Deep Learning』を読んで C# で実装したみたくなった続き。 今度はニューラルネットワークで使う関数に挑戦してみた。

実装するのは活性化関数であるシグモイド関数、ReLU 関数。 そして出力層で使うソフトマックス関数。 あとおまけでステップ関数も。

ひとまず畳み込まないニューラルネットワークを作りたいので、 行列を扱う活性化関数として実装。 ソフトマックス関数は出力層で使うので、こいつだけはベクトルを扱う。

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

namespace NeuralNetworkSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var m = Matrix<double>.Build.Random(3, 3);
            Console.WriteLine(m);

            Console.WriteLine("step function");
            Console.WriteLine(StepFunction(m));

            Console.WriteLine("ReLU");
            Console.WriteLine(ReLU(m));

            Console.WriteLine("sigmoid");
            Console.WriteLine(Sigmoid(m));

            var v = m.Column(0);
            Console.WriteLine("softmax");
            Console.WriteLine(Softmax(v));

            Console.ReadLine();
        }

        public static Matrix<double> StepFunction(Matrix<double> a)
        {
            // 行列の要素ごとの演算に Map を使う
            return a.Map(x => x > 0 ? 1.0 : 0.0);
        }

        public static Matrix<double> Sigmoid(Matrix<double> a)
        {
            return a.Map(x => 1 / (1 + Math.Exp(-x)));
        }

        public static Matrix<double> ReLU(Matrix<double> a)
        {
            return a.Map(x => Math.Max(x, 0));
        }

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

実行結果はこちら。

正しく動いている、ように見える。 今回も Math.NET Numerics を使ってみたが、NumPy と比べると前回以上に冗長に感じた。 NumPy はよくできたライブラリだ。

パーセプトロンで論理回路を実装

『ゼロから作る Deep Learning』を読んでC# で実装してみたくなった。 問題は NumPy の変わりに何を使うかだけど、C# には Math.NET Numerics というのがある。

numerics.mathdotnet.com

ベクトルや行列の計算はできそう。テンソルはやってみないと分からない。 まずはパーセプトロン論理回路を実装するところからやってみた。

詳しくは『ゼロから作る Deep Learning』を読んでもらうとして、C# で実装するとこんな感じ。

using MathNet.Numerics.LinearAlgebra.Double;
using System;

namespace PerceptronSample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("AND");
            Console.WriteLine("x1 x2 y");
            Console.WriteLine($"0  0  {AND(0, 0)}");
            Console.WriteLine($"1  0  {AND(1, 0)}");
            Console.WriteLine($"0  1  {AND(0, 1)}");
            Console.WriteLine($"1  1  {AND(1, 1)}");

            Console.WriteLine("OR");
            Console.WriteLine("x1 x2 y");
            Console.WriteLine($"0  0  {OR(0, 0)}");
            Console.WriteLine($"1  0  {OR(1, 0)}");
            Console.WriteLine($"0  1  {OR(0, 1)}");
            Console.WriteLine($"1  1  {OR(1, 1)}");

            Console.WriteLine("NAND");
            Console.WriteLine("x1 x2 y");
            Console.WriteLine($"0  0  {NAND(0, 0)}");
            Console.WriteLine($"1  0  {NAND(1, 0)}");
            Console.WriteLine($"0  1  {NAND(0, 1)}");
            Console.WriteLine($"1  1  {NAND(1, 1)}");

            Console.WriteLine("XOR");
            Console.WriteLine("x1 x2 y");
            Console.WriteLine($"0  0  {XOR(0, 0)}");
            Console.WriteLine($"1  0  {XOR(1, 0)}");
            Console.WriteLine($"0  1  {XOR(0, 1)}");
            Console.WriteLine($"1  1  {XOR(1, 1)}");

            Console.ReadLine();
        }

        static int AND(int x1, int x2)
        {
            var x = Vector.Build.DenseOfArray(new double[] { x1, x2 });
            var w = Vector.Build.DenseOfArray(new double[] { 0.5, 0.5 });
            var b = -0.7;
            
            // ベクトルの要素どうしの掛け算には PointwiseMultiply を使う
            var tmp = x.PointwiseMultiply(w).Sum() + b;
            if (tmp <= 0)
            {
                return 0;
            }
            else
            {
                return 1;
            }
        }

        static int OR(int x1, int x2)
        {
            var x = Vector.Build.DenseOfArray(new double[] { x1, x2 });
            var w = Vector.Build.DenseOfArray(new double[] { 0.5, 0.5 });
            var b = -0.2;
            var tmp = x.PointwiseMultiply(w).Sum() + b;
            if (tmp <= 0)
            {
                return 0;
            }
            else
            {
                return 1;
            }
        }

        static int NAND(int x1, int x2)
        {
            var x = Vector.Build.DenseOfArray(new double[] { x1, x2 });
            var w = Vector.Build.DenseOfArray(new double[] { -0.5, -0.5 });
            var b = 0.7;
            var tmp = x.PointwiseMultiply(w).Sum() + b;
            if (tmp <= 0)
            {
                return 0;
            }
            else
            {
                return 1;
            }
        }

        static int XOR(int x1, int x2)
        {
            // XOR ゲートは多層パーセプトロンで実現できる
            var s1 = NAND(x1, x2);
            var s2 = OR(x1, x2);
            var y = AND(s1, s2);
            return y;
        }
    }
}

実行結果。ちゃんと論理回路になっている。

Python + NumPy と比べると、コードが冗長になってしまうのは気にしない。

『ゼロから作る Deep Learning』を読んだ

AlphaGo が碁の元世界王者イ・セドル氏に勝利したり、 IBM のワトソンが難症例患者の正しい病名を見抜いたり、 Google 翻訳がニューラルネットワーク版に切り替わって翻訳精度が劇的に向上したりと、AI が急激に身近になった昨今。

今さらながら AI、とりわけ Deep Learning について知っておきたいと思って購入。 今年買った技術書で一番勉強になった。

Deep Learning を扱った本は雨後のタケノコのようにたくさん出版されたけど、書店でパラパラっと眺めて、難しい数式に拒否反応が出て棚に戻すものが殆どだった。高度なコンピュータサイエンスを学んでいないこの身がツライ。

その点、本書は Python のコードが中心だったので心理的障壁が低かった。 そんな本書でも数式は避けられないので当然出てくるが、計算グラフを使った解説のおかげで、自分みたいな数式アレルギーでもなんとか理解できた。

技術の仕組みについて勉強するなら、実際に作ってみるのが一番。 本書では、Python と NumPy を使って Deep Learning を実装していく。 パーセプトロンに始まり、 ニューラルネットワーク、 畳み込みニューラルネットワーク、 そして Deep Learning へと、 段階的に進んでいく。 Deep Learning が突然生まれた技術ではなく、過去から続く研究がビッグデータとマシンパワーで花開いた、というのが良くわかる。

紙面の都合なのか、後半はソースコードの省略が結構あるので、サポートサイトからソースコードをダウンロードするのは必須。

一度読み終えただけでは、とても理解できたとは言いがたいので、 何度も読み直すつもり。 それにしても、自分でもゼロから Deep Learning が作れそうに思えてくるから不思議だ。 それくらい本書が良書なわけだが。 今度は Python ではなく C# で実装しながら読んでみたいと思う。

『松坂牛 100% 黄金のハンバーグ』を食べた

前から気になっていて、いつか食べたいと思っていた『松坂牛 100% 黄金のハンバーグ』を、ついに注文してしまった。 送料が 1000 円以上かかるから躊躇していたんだけど、楽天ポイントが送料分くらいたまっていたのでポチッと。

最初、勘違いして冷蔵庫で1晩解凍しようとしていたので、危機一髪。 間違ってステーキの説明を読んでいたみたいだ。 冷凍のまま焼けるので、着いたその日にいただくことにした。

両面に焼き目をつけたら、水を入れて蒸し焼きにする。 ちゃんと説明書を読めば難しくない。 焼き上がりはこんな感じ。

柔らかいだけでなく、肉肉しさもあって、今までに食べたことがないハンバーグだった。 味がしっかりついていて、ソースは不要。 それも、肉の味・旨みが凝縮されたような味わい。 白米との相性も格別。 リピーターが多いというのも頷ける。 送料がネックだけど、また注文したいと思える一品だった。


御○屋

昼休みにちょっと足を伸ばして、ビックカメラ1号館横にある『御○屋』に行ってみた。カウンター席が空いていたので、直ぐに座れてラッキー。並んでたら、昼休み中に会社に戻れなかったかもしれない。

ランチのつけ麺セットを注文。プラス120円で大盛りにするか悩んだけど、初めての店なので止めておいた。

麺はツルシコ。明太子で和えたもやしが美味い。

つけ汁は魚介の味がしっかりしていて、そしてピリ辛で美味。そして、予想していたよりも濃厚だった。

ランチは白米か焼豚おにぎりが選べたので、焼豚おにぎりを選択。昼はやっぱりお米が食べたくなる。

デザートに杏仁豆腐まで付いていて、コスパは結構良いのでは。杏仁豆腐好きなので嬉しい。

期待以上の味で、昼休み中に会社に戻れないかもしれないリスクを取ってまで足を運んだ甲斐があった。ランチは担々麺のセットもやっているんで、今度は担々麺も食べてみたいと思えた。

関連ランキング:居酒屋 | 天神南駅西鉄福岡駅(天神)渡辺通駅

VirtualBox でゲスト OS からホスト OS にアクセスするときに指定する IP アドレス

VirtualBox のゲスト OS からホスト OS にアクセスしたいことが結構ある。 例えば、ゲスト OS から、ホスト OS でデバッグ実行している Web API にアクセスしたいときとか。

必要になるたびにネットで調べているので、ブログにメモしておく。

ネットワークアダプタの方式 ホスト OS の IP アドレス
NAT 10.0.2.2
ホストオンリーネットワーク 192.168.56.1