NumSharp でニューラルネットワークに対する勾配

「ゼロから作る Deep Learning」を読んで C# でゼロから作ってみる試みは、NumSharp を使って最初からやり直して、ようやく4章終盤まできた。

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

using NumSharp;

var net = new simpleNet();
Console.WriteLine(net.W.ToString()); // 重みパラメータ

var x = np.array(0.6, 0.9);
var p = net.predict(x);
Console.WriteLine(p.ToString());
Console.WriteLine(np.argmax(p)); // 最大値のインデックス

var t = np.array(0, 0, 1.0); // 正解ラベル
var loss = net.loss(x, t);
Console.WriteLine(loss.ToString());

Func<NDArray, NDArray> f = W => net.loss(x, t);
var dW = gradient.numerical_gradient(f, net.W);
Console.WriteLine(dW.ToString());

static class functions
{
    public static NDArray softmax(NDArray x)
    {
        var c = np.max(x);
        var exp_x = np.exp(x - c);
        var sum_exp_x = np.sum(exp_x, NPTypeCode.Double);
        var y = exp_x / sum_exp_x;
        return y;
    }

    public static NDArray cross_entropy_error(NDArray y, NDArray t)
    {
        var delta = 1e-7;
        return (-1) * np.sum(t * np.log(y + delta), NPTypeCode.Double);
    }
}

static class gradient
{
    static NDArray numerical_gradient_1d(Func<NDArray, NDArray> f, NDArray x)
    {
        var h = 1e-4; // 0.0001
        var grad = np.zeros_like(x); // x と同じ形状の配列を生成

        foreach (var idx in Enumerable.Range(0, x.size))
        {
            var tmp_val = (double)x[idx];

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

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

            grad[idx] = (fxh1 - fxh2) / (2 * h);
            x[idx] = tmp_val; // 値を元に戻す
        }

        return grad;
    }

    public static NDArray numerical_gradient(Func<NDArray, NDArray> f, NDArray X)
    {
        if (X.ndim == 1)
        {
            return numerical_gradient_1d(f, X);
        }
        else
        {
            var grad = np.zeros_like(X);

            for (var idx = 0; idx < X.ndim; idx++)
            {
                var x = X[idx];
                grad[idx] = numerical_gradient_1d(f, x);
            }

            return grad;
        }
    }
}

class simpleNet
{
    public NDArray W { get; }

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

    public NDArray predict(NDArray x)
    {
        // NumSharp が 1-D と 2-D の内積をサポートしていなかったので、
        // x を 2-D に変換して計算したあと、1-D に戻して回避。
        // 無理矢理。
        x = x.reshape_unsafe(2, 2);
        var y = np.dot(x, W);
        return y.reshape_unsafe(3);
    }

    public NDArray loss(NDArray x, NDArray t)
    {
        var z = predict(x);
        var y = functions.softmax(z);
        var loss = functions.cross_entropy_error(y, t);
        return loss;
    }
}

.NET 6 で実行。

NumSharp が 1-D と 2-D の内積をサポートしてないので、無理矢理 reshape して回避。一瞬、Numpy のバインディングライブラリの Numpy.NET に移行したくなった。今後も移行する可能性はゼロじゃないな。

何はともあれ、Math.NET Numerics を使っていたころに追いついた。

NumSharp で勾配下降法

「ゼロから作る Deep Learning」を C# で粛々と写経中。

NumSharp を使って、機械学習で使う勾配下降法を実装してみた。

using NumSharp;

var init_x = np.array(-3.0, 4.0);
var y = gradient_descent(
    function_2,
    init_x: init_x,
    lr: 0.1,
    step_num: 100);
Console.WriteLine(y.ToString());

// f(x0, x1) = x0 ^ 2 + x1 ^ 2
static double function_2(NDArray x) =>
    x[0] * x[0] + x[1] * x[1];

// 勾配
static NDArray numerical_gradient(
    Func<NDArray, double> f,
    NDArray x)
{
    var h = 1e-4; // 0.0001
    var grad = np.zeros_like(x); // x と同じ形状の配列を生成

    foreach (var idx in Enumerable.Range(0, x.size))
    {
        var tmp_val = (double)x[idx];

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

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

        grad[idx] = (fxh1 - fxh2) / (2 * h);
        x[idx] = tmp_val; // 値を元に戻す
    }

    return grad;
}

static NDArray gradient_descent(
    Func<NDArray, double> f,
    NDArray init_x,
    double lr = 0.01,
    int step_num = 100)
{
    var x = init_x;

    foreach (var i in Enumerable.Range(0, step_num))
    {
        var grad = numerical_gradient(f, x);
        x -= lr * grad;
    }

    return x;
}

.NET 6 で実行。

x -= lr * grad がちゃんと動くか心配だったけど杞憂だった。NumSharp は気が利いていないようで、たまに利いていることもあるな。

アクセストークンを指定して Microsoft Graph を呼び出す

Microsoft Graph を使って Microsoft Todo の CRUD ができることは確認したけど、アクセストークンは単独で取得しておきたい。そして、そのアクセストークンを GraphServiceClient で使いたい。

GitHubソースコードを見たところ、アクセストークンは、InteractiveBrowserCredential. GetTokenAsync で取得できそう。DelegateAuthenticationProvider を使えば、Microsoft Graphを呼び出すときのアクセストークンを指定できそうだった。

using System.Net.Http.Headers;
using Azure.Core;
using Azure.Identity;
using Microsoft.Graph;

// Microsoft Todo のタスクとリストを CRUD するには
// Tasks.ReadWrite の許可が必要。
var scopes = new[]
{
    "Tasks.ReadWrite",
};

// 一般ユーザーの Microsoft Todo にアクセスするときは
// common を指定すれば良いみたい。
var tenantId = "common";

// Azure AD で登録したアプリケーションの ID (Client ID)
var clientId = "YOUR_CLIENT_ID";

// Web ブラウザを表示して、
// ユーザーに Microsoft アカウントにログインするように設定。
var options = new InteractiveBrowserCredentialOptions
{
    TenantId = tenantId,
    ClientId = clientId,
    AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
    RedirectUri = new Uri("http://localhost"),
};
var interactiveCredential = new InteractiveBrowserCredential(options);

// アクセストークンを先に取得
var token = await interactiveCredential.GetTokenAsync(new TokenRequestContext(scopes));
Console.WriteLine($"ExpiresOn: {token.ExpiresOn}");

// DelegateAuthenticationProvider を使って、
// 取得しておいたアクセストークンを Authorization ヘッダーにセットして
// Microsoft Graph を呼び出せるようにする。
var authProvider = new DelegateAuthenticationProvider(request =>
{
    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Token);
    return Task.CompletedTask;
});
var graphClient = new GraphServiceClient(authProvider);

// リスト一覧表示
var todoLists = await graphClient.Me
    .Todo
    .Lists
    .Request()
    .GetAsync();
foreach (var todoList in todoLists)
{
    Console.WriteLine($"既存のリスト: {todoList.DisplayName}");
}

Console.ReadLine();

.NET 6 で実行。

アクセストークンを取得し、それを GraphServiceClient で使って、Microsoft Graph を呼び出せた。ただ、アクセストークンの有効期限は1時間か。保存してもムダだな。リフレッシュトークン取得方法を調べる必要がありそうだ。

NumSharp で勾配

「ゼロから作る Deep Learning」を読んで、Python ではなく C# でゼロから作ってみる試みの続き。ちょっと間が空いてしまったな。

4章の勾配を NumSharp で実装してみた。

using NumSharp;

Console.WriteLine("(x0, x1) = (3.0, 4.0) のとき");
Console.WriteLine(
    numerical_gradient(
        function_2,
        np.array(3.0, 4.0)).ToString());

Console.WriteLine("(x0, x1) = (0.0, 2.0) のとき");
Console.WriteLine(
    numerical_gradient(
        function_2,
        np.array(0.0, 2.0)).ToString());

Console.WriteLine("(x0, x1) = (3.0, 0.0) のとき");
Console.WriteLine(
    numerical_gradient(
        function_2,
        np.array(3.0, 0.0)).ToString());

Console.ReadLine();

static double function_2(NDArray x) =>
    x[0] * x[0] + x[1] * x[1];

static NDArray numerical_gradient(Func<NDArray, double> f, NDArray x)
{
    var h = 1e-4; // 0.0001
    var grad = np.zeros_like(x); // x と同じ形状の配列を生成

    foreach (var idx in Enumerable.Range(0, x.size))
    {
        var tmp_val = (double)x[idx];

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

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

        grad[idx] = (fxh1 - fxh2) / (2 * h);
        x[idx] = tmp_val; // 値を元に戻す
    }

    return grad;
}

.NET 6 で実装。

最初、var tmp_val = (double)x[idx] ではなく var tmp_val = x[idx] って書いて x が NDArray になってしまい、期待した結果がまったく得られず地味にハマった。NumSharp にはちょっとした落とし穴がたくさんある。

Dr.STONE(1)~(26)

週刊少年ジャンプで連載していた Dr.STONE は、科学を題材にした少年マンガ。それもジャンプで。少年誌でここまで科学を主軸に置いた作品は、他にどれくらいあるだろうか。自分は初めて読んだ。

石化光線に地球が包まれて文明が滅んだ世界で、ゼロから科学文明を再び起こす物語。ホントに一歩ずつ、地道にロードマップを進めて行くところは良い意味で少年マンガらしくない。肺炎を治療するためにサルファ剤を作るまでが特にアツイ。水力発電で電気を作りだしたところなんかは目頭が熱くなった。

終盤日本を飛び出してからは、科学クラフトが駆け足になったのは仕方ない。コンピューターに、人工衛星に有人ロケット。これらを地道にやってたら100巻をゆうに超えてしまう。さすがに最初のころほど丁寧に描写できないだろう。それでも、宇宙開発の全体の雰囲気は充分掴める。ちなみに、作中時間ではロケットのトライ&エラーに何年費やしたんだろうな。

主人公の千空はこれまた良い意味で、ジャンプ漫画の主人公っぽくない。戦闘力ない。体力もない。だけど頭脳はピカイチ。第一印象は、アイシールド21の蛭魔みたいだな、って思った。大樹やクロムの方がジャンプ漫画の主人公っぽい。正義の科学者的な立ち位置だけど、超のつく効率厨。石化した仲間を救い出すために、一度砕いて運び出す発想とか、一歩間違えばマッドサイエンティストで最高だった。

Dr.STONE を読んで科学に目覚めた子どもが、結構いるんじゃないだろうか。いるといいな。勉強は、マンガやゲームで楽しく身につくなら、それが一番だ。というのは自論。小学校の図書室に全巻置いて欲しい。

あと、石化光線によって文明が一度滅んだ世界が舞台だから、Dr.STONEなのかと思っていた。または、石化光線の元凶を指しているのかと。千空の苗字は石神で、ストーンワールドに科学文明を再興した偉大な科学者ってことで、Dr.STONE。なるほど、これ以上無いタイトルだ。

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

中のコメントで書いてあるけど、12巻は辞書のような単語本というコンセプト。レディースと比べて種類が少ないと思っていたメンズファッションでも、これだけ多様なアイテムの種類があったとはね。ただ、結構知っていたので、知らないうちに身についていたんだな。まぁ、知識あるだけじゃ意味ない訳だけど。

今年の夏はまだ続くので、クロップドパンツがもう一本欲しいと思ってきた。シャツも足したいけど、気になった素材感や柄のアイテムは、スタンドカラーだったりオープンカラーだったりして、手が伸びない。スタンドカラーやオープンカラーは、自分に似合わないし、好みでもないんだよなぁ。レギュラーやボタンダウン選びがち。

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

11巻は40歳以上をターゲットにしたコーディネートのお話。若者向けのショップに入るのはもう心理的に厳しいだけに、ユニクロやワークマン+は救世主だな。

IT 技術の進歩が振り子ではなく螺旋なように、ファッションの流行も同じ。今年の夏は新しい服買わなかったけど、ビッグシルエットのシャツは買ってもよかったかな、と思えた。お腹まわりが気になってきたら、Yラインでカバー。