アルスラーン戦記(17)

ルシタニアに囚われたイリーナ王女を奪還したヒルメスは、ちょっと見直したな。

メルレインは、惚れてたイリーナ王女の望みを無事果たしたわけだけど、送り届けた相手が親の仇というのは不憫。でも、そのおかげで、アルスラーンの元にいるアルフリードに再会できたのだから、これも巡り合わせ。

しかし、メルレインだけでなく、まさかエステルまでアルスラーンの元に戻ってくるとは思わなかった。思いの外早い再会。エクバターナで幽閉されているルシタニア王を救おうとしたのは、正義感というか使命感に駆られてだろうけど、空回ってるな。それも魅力か。

さらにペシャワール城から、ジムサだけでなくザラーヴァントまで脱走し、アルスラーンの元に。キシュワードもパルス王家に忠誠を誓いつつも、内心はアルスラーンに傾いてそう。クバートはどちらでも良さそうだけど。

アンドラゴラス王の武力は万騎将相当だろう。そこで、以前ナルサスダリューンに言った馬と騎手の例えが思い出される。上に立つ者の器としては、もう既にアルスラーンの方が上だろうな。

NumSharp を使って mnist.py を C# に移植

「ゼロから作る Deep Learning」を読んで、Python ではなく C# でゼロから作ってみる試み中。

今回は、本書「3.6.1 MNIST データセット」で MNIST データセットを扱うときに使う mnist.py を C# に移植してみた。

using System.IO.Compression;
using NumSharp;

namespace MnistSharp;

public static class Mnist
{
    private const string UrlBase = "http://yann.lecun.com/exdb/mnist/";

    private const int ImageSize = 784;

    private static readonly Dictionary<string, string> s_keyFile = new Dictionary<string, string>
    {
        [nameof(Dataset.TrainImage)] = "train-images-idx3-ubyte.gz",
        [nameof(Dataset.TrainLabel)] = "train-labels-idx1-ubyte.gz",
        [nameof(Dataset.TestImage)] = "t10k-images-idx3-ubyte.gz",
        [nameof(Dataset.TestLabel)] = "t10k-labels-idx1-ubyte.gz",
    };

    private static readonly string s_datasetDir = AppContext.BaseDirectory;

    private static readonly HttpClient s_httpClient = new HttpClient();

    private static async Task DownloadAsync()
    {
        foreach (var v in s_keyFile.Values)
        {
            await DownloadAsync(v);
        }
    }

    private static async Task DownloadAsync(string fileName)
    {
        var filePath = Path.Combine(s_datasetDir, fileName);
        if (File.Exists(filePath))
        {
            return;
        }
        Console.WriteLine("Downloading " + fileName + " ... ");

        var response = await s_httpClient.GetAsync(UrlBase + fileName);
        using var stream = await response.Content.ReadAsStreamAsync();
        using var f = File.OpenWrite(filePath);
        await stream.CopyToAsync(f);
        Console.WriteLine("Done");
    }

    private static async Task<NDArray> LoadLabelsAsync(string fileName)
    {
        var filePath = Path.Combine(s_datasetDir, fileName);

        Console.WriteLine("Converting " + fileName + " to NumSharp Array ...");

        using var f = File.OpenRead(filePath);
        using var gzip = new GZipStream(f, CompressionMode.Decompress);
        using var memory = new MemoryStream();
        await gzip.CopyToAsync(memory);

        var buffer = memory.ToArray().AsSpan(8).ToArray();
        var labels = np.frombuffer(buffer, np.uint8);
        Console.WriteLine("Done");

        return labels;
    }

    private static async Task<NDArray> LoadImagesAsync(string fileName)
    {
        var filePath = Path.Combine(s_datasetDir, fileName);

        Console.WriteLine("Converting " + fileName + " to NumSharp Array ...");

        using var f = File.OpenRead(filePath);
        using var gzip = new GZipStream(f, CompressionMode.Decompress);
        using var memory = new MemoryStream();
        await gzip.CopyToAsync(memory);

        var buffer = memory.ToArray().AsSpan(16).ToArray();
        var data = np.frombuffer(buffer, np.uint8);
        data = data.reshape(-1, ImageSize);
        Console.WriteLine("Done");

        return data;
    }

    private static async Task<Dataset> ConvertNumSharpAsync()
    {
        var trainImage = await LoadImagesAsync(s_keyFile[nameof(Dataset.TrainImage)]);
        var trainLabel = await LoadLabelsAsync(s_keyFile[nameof(Dataset.TrainLabel)]);
        var testImage = await LoadImagesAsync(s_keyFile[nameof(Dataset.TestImage)]);
        var testLabel = await LoadLabelsAsync(s_keyFile[nameof(Dataset.TestLabel)]);
        return new Dataset(trainImage, trainLabel, testImage, testLabel);
    }

    public static async Task InitializeAsync()
    {
        await DownloadAsync();
        var dataset = await ConvertNumSharpAsync();
        Console.WriteLine("Creating npy files ...");
        np.save(Path.Combine(s_datasetDir, nameof(dataset.TrainImage)), dataset.TrainImage);
        np.save(Path.Combine(s_datasetDir, nameof(dataset.TrainLabel)), dataset.TrainLabel);
        np.save(Path.Combine(s_datasetDir, nameof(dataset.TestImage)), dataset.TestImage);
        np.save(Path.Combine(s_datasetDir, nameof(dataset.TestLabel)), dataset.TestLabel);
        Console.WriteLine("Done!");
    }

    private static NDArray ChangeOneHotLabel(NDArray x)
    {
        var t = np.zeros(x.size, 10);
        var i = 0;
        foreach (var n in x)
        {
            var j = Convert.ToInt32(n);
            t[i, j] = 1;
            i++;
        }
        Console.WriteLine(t.ToString());
        return t;
    }

    public static async Task<Dataset> LoadAsync(bool normalize = true, bool flatten = true, bool oneHotLabel = false)
    {
        if (!File.Exists(Path.Combine(s_datasetDir, nameof(Dataset.TrainImage) + ".npy")) ||
            !File.Exists(Path.Combine(s_datasetDir, nameof(Dataset.TrainLabel) + ".npy")) ||
            !File.Exists(Path.Combine(s_datasetDir, nameof(Dataset.TestImage) + ".npy")) ||
            !File.Exists(Path.Combine(s_datasetDir, nameof(Dataset.TestLabel) + ".npy")))
        {
            await InitializeAsync();
        }

        var trainImage = np.load(Path.Combine(s_datasetDir, nameof(Dataset.TrainImage) + ".npy"));
        var trainLabel = np.load(Path.Combine(s_datasetDir, nameof(Dataset.TrainLabel) + ".npy"));
        var testImage = np.load(Path.Combine(s_datasetDir, nameof(Dataset.TestImage) + ".npy"));
        var testLabel = np.load(Path.Combine(s_datasetDir, nameof(Dataset.TestLabel) + ".npy"));

        if (normalize)
        {
            trainImage = trainImage.astype(np.float32);
            trainImage /= 255.0;
            testImage = testImage.astype(np.float32);
            testImage /= 255.0;
        }

        if (oneHotLabel)
        {
            trainLabel = ChangeOneHotLabel(trainLabel);
            testLabel = ChangeOneHotLabel(testLabel);
        }

        if (!flatten)
        {
            trainImage = trainImage.reshape(-1, 1, 28, 28);
            testImage = testImage.reshape(-1, 1, 28, 28);
        }

        return new Dataset(trainImage, trainLabel, testImage, testLabel);
    }
}

public record Dataset(
    NDArray TrainImage,
    NDArray TrainLabel,
    NDArray TestImage,
    NDArray TestLabel);

Mnist クラスを使って、MNIST データセットを読み込んでみる。

using MnistSharp;

var (xTrain, tTrain, xTest, tTest) = await Mnist.LoadAsync(flatten: true, normalize: false);
Console.WriteLine(xTrain.Shape);
Console.WriteLine(tTrain.Shape);
Console.WriteLine(xTest.Shape);
Console.WriteLine(tTest.Shape);
Console.ReadLine();

.NET 6 で実行。

Python + NumPy だとシンプルに書けていたものが、C# + NumSharp だと少々書くの面倒だった。gzip の展開とか、NDArray の列挙とかね。

あと、mnist.py ではキャッシュをまとめて pickle で保存していた。C# には pickle 無いので、NumSharp がサポートしている .npy で代替。

NumSharp で 3 層ニューラルネットワーク

「ゼロから作る Deep Learning」を読んで、Python ではなく C# でゼロから作ってみる続き。今回は 3 層ニューラルネットワークを実装してみた。

NumSharp の np.dot が、1 次元の配列から作った NDArray とジャグ配列から作った NDArray の内積をサポートしていなかったけど、ジャグ配列で揃えたら上手くいった。

using NumSharp;

var network = InitNetwork();
var x = np.array(new double[][]
{
    new [] { 1.0, 0.5 },
});
var y = Forward(network, x);
Console.WriteLine(y.ToString());

Console.ReadLine();


static NDArray IdentityFunction(NDArray x) => x;

static NDArray Sigmoid(NDArray x) =>
    1 / (1 + np.exp(x * -1));

static IReadOnlyDictionary<string, NDArray> InitNetwork()
{
    var network = new Dictionary<string, NDArray>();

    network["W1"] = np.array(new double[][]
    {
        new [] { 0.1, 0.3, 0.5 },
        new [] { 0.2, 0.4, 0.6 },
    });
    network["b1"] = np.array(0.1, 0.2, 0.3);
    network["W2"] = np.array(new double[][]
    {
        new [] { 0.1, 0.4 },
        new [] { 0.2, 0.5 },
        new [] { 0.3, 0.6 },
    });
    network["b2"] = np.array(0.1, 0.2);
    network["W3"] = np.array(new double[][]
    {
        new [] { 0.1, 0.3 },
        new [] { 0.2, 0.4 },
    });
    network["b3"] = np.array(0.1, 0.2);

    return network;
}

static NDArray Forward(IReadOnlyDictionary<string, NDArray> network, NDArray x)
{
    var W1 = network["W1"];
    var W2 = network["W2"];
    var W3 = network["W3"];
    var b1 = network["b1"];
    var b2 = network["b2"];
    var b3 = network["b3"];

    var a1 = np.dot(x, W1) + b1;
    var z1 = Sigmoid(a1);
    var a2 = np.dot(z1, W2) + b2;
    var z2 = Sigmoid(a2);
    var a3 = np.dot(z2, W3) + b3;
    var y = IdentityFunction(a3);

    return y;
}

.NET 6 で実行。

四捨五入したら書籍と同じ値になるのでヨシ!

アオアシ(28)

トップチームの練習参加することになったアシト。エスペリオンユースに入団して、まだ1年経っていないのに、ここまで大出世するとはね。ユースの同期ごぼう抜き。

まぁ、さっそく壁にぶち当たるわけだけど。エスペリオンユースのAチームに上がったときは、初日から爪痕を残せていたが、日本代表や各国代表が集うトップチームは世界が違うようだ。

そんな世界に放り込まれても、相手が元日本代表であろうとお構いなしに、初日から教えを訊きに行く。今すぐできないことが我慢できない、アシトの姿勢は見習いたいな。

NumSharp で活性化関数とソフトマックス関数

『ゼロから作る Deep Learning』を読んで、Python ではなく C# でゼロから作ってみる続き。リベンジ。

今回はニューラルネットワークで使う関数を、NumSharp で実装してみた。実装するのは活性化関数であるステップ関数、シグモイド関数、ReLU 関数、そして出力層で使うソフトマックス関数。

using NumSharp;

Console.WriteLine(nameof(StepFunction));
{
    var x = np.array(-1.0, 1.0, 2.0);
    var y = StepFunction(x);
    Console.WriteLine(y.ToString());
}

Console.WriteLine(nameof(Sigmoid));
{
    var x = np.array(-1.0, 1.0, 2.0);
    var y = Sigmoid(x);
    Console.WriteLine(y.ToString());
}

Console.WriteLine(nameof(ReLU));
{
    var x = np.array(-1.0, 1.0, 2.0);
    var y = ReLU(x);
    Console.WriteLine(y.ToString());
}

Console.WriteLine(nameof(Softmax));
{
    var x = np.array(0.3, 2.9, 4.0);
    var y = Softmax(x);
    Console.WriteLine(y.ToString());
    Console.WriteLine(np.sum(y, NPTypeCode.Double).ToString());
}

Console.ReadLine();

static NDArray StepFunction(NDArray x)
{
    var y = x > 0.0;
    return y.astype(NPTypeCode.Int32);
}

static NDArray Sigmoid(NDArray x)
{
    return 1 / (1 + np.exp(x * -1));
}

static NDArray ReLU(NDArray x)
{
    return np.maximum(0.0, x);
}

static NDArray Softmax(NDArray a)
{
    var c = np.max(a);
    var exp_a = np.exp(a - c);
    var sum_exp_a = np.sum(exp_a, NPTypeCode.Double);
    var y = exp_a / sum_exp_a;
    return y;
}

.NET 6 で実行。

NumSharp では、メソッドによっては型もしくは NPTypeCode を指定しないと例外発生する場合があり、ハマったときは大抵そこ。sum とかね。慣れてきたけど。

NumSharp でパーセプトロン

以前、「ゼロから作るDeep Learning」を読みながら、C# でゼロから Deep Learning を実装する試みをやっていた。

当時、行列の計算には Math.NET Numerics を使っていたけど、API が NumPy と違い過ぎて、途中で頓挫してしまった…。

numerics.mathdotnet.com

NumPy の C# 移植版と言える NumSharp というものがある。機能は NumPy にだいぶ追いついてそう。NumPy と同じように書けるようにする方針なのも好ましい。

github.com

NumSharp で再挑戦すれば、今度は完走できるかも。まずは、パーセプトロンからやり直してみた。

using NumSharp;

Console.WriteLine("AND");
Console.WriteLine("x1\tx2\ty");
Console.WriteLine($"0\t0\t{AND(0, 0)}");
Console.WriteLine($"1\t0\t{AND(1, 0)}");
Console.WriteLine($"0\t1\t{AND(0, 1)}");
Console.WriteLine($"1\t1\t{AND(1, 1)}");

Console.WriteLine("OR");
Console.WriteLine("x1\tx2\ty");
Console.WriteLine($"0\t0\t{OR(0, 0)}");
Console.WriteLine($"1\t0\t{OR(1, 0)}");
Console.WriteLine($"0\t1\t{OR(0, 1)}");
Console.WriteLine($"1\t1\t{OR(1, 1)}");

Console.WriteLine("NAND");
Console.WriteLine("x1\tx2\ty");
Console.WriteLine($"0\t0\t{NAND(0, 0)}");
Console.WriteLine($"1\t0\t{NAND(1, 0)}");
Console.WriteLine($"0\t1\t{NAND(0, 1)}");
Console.WriteLine($"1\t1\t{NAND(1, 1)}");

Console.WriteLine("XOR");
Console.WriteLine("x1\tx2\ty");
Console.WriteLine($"0\t0\t{XOR(0, 0)}");
Console.WriteLine($"1\t0\t{XOR(1, 0)}");
Console.WriteLine($"0\t1\t{XOR(0, 1)}");
Console.WriteLine($"1\t1\t{XOR(1, 1)}");

Console.ReadLine();

static int AND(int x1, int x2)
{
    var x = np.array<double>(x1, x2);
    var w = np.array(0.5, 0.5);
    var b = -0.7;
    var tmp = np.sum(w * x, typeof(double)) + b;
    if ((double)tmp <= 0)
        return 0;
    else
        return 1;
}

static int NAND(int x1, int x2)
{
    var x = np.array<double>(x1, x2);
    var w = np.array(-0.5, -0.5);
    var b = 0.7;
    var tmp = np.sum(w * x, typeof(double)) + b;
    if ((double)tmp <= 0)
        return 0;
    else
        return 1;
}

static int OR(int x1, int x2)
{
    var x = np.array<double>(x1, x2);
    var w = np.array(0.5, 0.5);
    var b = -0.2;
    var tmp = np.sum(w * x, typeof(double)) + b;
    if ((double)tmp <= 0)
        return 0;
    else
        return 1;
}

static int XOR(int x1, int x2)
{
    var s1 = NAND(x1, x2);
    var s2 = OR(x1, x2);
    var y = AND(s1, s2);
    return y;
}

.NET 6 で実行。

NumPy とは微妙に書き味が違ったけど、Math.NET Numerics と比べたら誤差だな。AND・OR・NAND・XOR がちゃんと動いたのでヨシ!

服を着るならこんなふうに(9)

9 巻はノームコアの先、ビッグシルエットとかが出始めの頃のお話。現実世界ではビッグシルエットも過ぎ去りつつあるけど。自分のクローゼットには、ビッグシルエットのアイテムが 1 つもないな。イマイチ好みじゃないというか。あと、流行初期にに買っておけば数年着れただけに、今さら感もあり。

主人公が成長していくにつれ、このマンガで扱われるテーマも、ファッションを楽しむようになった人向けに変わってきた印象。ジャージやスウェットを着こなすのはハードル高いな。

ZOZO のようなファッション通販サイトを使って、服をお得に購入できるのは、自分も経験がある。シーズン半ばやシーズン過ぎた頃のアイテムがセールになるので、そのタイミングでダウンジャケットやブルゾンを買った。服は実店舗で買う派だったけど、間違い無いと確信できたり、ダメでも諦めがつくくらい安い場合は、ネットで買うのもアリだな。セールを見落とさないよう、気になるアイテムは片っ端からお気に入りに登録している。