読者です 読者をやめる 読者になる 読者になる

NuGet CLI でパッケージをダウンロード

NuGet CLI の install コマンドで、指定した場所にパッケージをインストールできた。依存パッケージもインストールしてくれた。

nuget install Microsoft.EntityFrameworkCore -o .\NuGetPackages

packages.config を指定してもいい。ただ、この場合 packages.config に書いてあるパッケージだけしかインストールされなかった。

nuget install packages.config -o .\NuGetPackages

ローカルに NuGet のパッケージソースを構築するとき役立つ。

指定した URL に付けられたすべてのブックマークを取得する

はてなブックマークネタ第3弾。 最後は、指定した URL に付けられたすべてのブックマークを取得してみた。

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace HatenaBookmarkSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var bookmarks = GetAllBookmarks("http://hatenablog.com")
                .GetAwaiter().GetResult();

            foreach(var bookmark in bookmarks.Bookmarks)
            {
                Console.WriteLine($"{bookmark.User} : {bookmark.Comment}");
            }

            Console.WriteLine("Enter で終了します。");
            Console.ReadLine();
        }

        static async Task<EntryBookmarks> GetPopularBookmarks(string url)
        {
            var httpClient = new HttpClient();

            httpClient.DefaultRequestHeaders
                .Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            // User-Agent を設定しないと nginx にはじかれる
            httpClient.DefaultRequestHeaders
                .UserAgent.Add(new ProductInfoHeaderValue("HatenaClient", "0.0.1"));

            var requestUri = $"http://b.hatena.ne.jp/api/viewer.popular_bookmarks?url={url}";

            var json = await httpClient.GetStringAsync(requestUri);

            var bookmarks = JsonConvert.DeserializeObject<EntryBookmarks>(json);

            return bookmarks;
        }

        static async Task<EntryBookmarks> GetAllBookmarks(string url)
        {
            var httpClient = new HttpClient();

            httpClient.DefaultRequestHeaders
                .Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            // User-Agent を設定しないと nginx にはじかれる
            httpClient.DefaultRequestHeaders
                .UserAgent.Add(new ProductInfoHeaderValue("HatenaClient", "0.0.1"));

            var requestUri = $"http://b.hatena.ne.jp/entry/jsonlite/?url={url}";

            var json = await httpClient.GetStringAsync(requestUri);

            var bookmarks = JsonConvert.DeserializeObject<EntryBookmarks>(json);

            return bookmarks;
        }
    }

    [JsonObject]
    public class EntryBookmark
    {
        /// <summary>
        /// ブックマーク ID を取得または設定します。
        /// </summary>
        [JsonProperty("eid")]
        public string Id { get; set; }

        /// <summary>
        /// タイトルを取得または設定します。
        /// </summary>
        [JsonProperty("title")]
        public string Title { get; set; }

        /// <summary>
        /// 記事の URL を取得または設定します。
        /// </summary>
        [JsonProperty("url")]
        public string Url { get; set; }

        /// <summary>
        /// ブックマークしたユーザーを取得または設定します。
        /// </summary>
        [JsonProperty("user")]
        public string User { get; set; }

        /// <summary>
        /// ブックマークコメントを取得または設定します。
        /// </summary>
        [JsonProperty("comment")]
        public string Comment { get; set; }

        /// <summary>
        /// ブックマークした時間を取得または設定します。
        /// </summary>
        [JsonProperty("timestamp")]
        public string Timestamp { get; set; }
    }

    [JsonObject]
    public class EntryBookmarks
    {
        /// <summary>
        /// ブックマーク件数を取得または設定します。
        /// </summary>
        [JsonProperty("count")]
        public int Count { get; set; }

        /// <summary>
        /// エントリの ID を取得または設定します。
        /// </summary>
        [JsonProperty("eid")]
        public string EntryId { get; set; }

        /// <summary>
        /// エントリのタイトルを取得または設定します。
        /// </summary>
        [JsonProperty("title")]
        public string Title { get; set; }

        /// <summary>
        /// エントリの URL を取得または設定します。
        /// </summary>
        [JsonProperty("entry_url")]
        public string EntryUrl { get; set; }

        /// <summary>
        /// ブックマークの URL を取得または設定します。
        /// </summary>
        [JsonProperty("url")]
        public string Url { get; set; }

        /// <summary>
        /// ブックマーク一覧を取得または設定します。
        /// </summary>
        [JsonProperty("bookmarks")]
        public List<EntryBookmark> Bookmarks { get; set; } = new List<EntryBookmark>();
    }
}

実行結果はこちら。

f:id:griefworker:20170405172557p:plain

はてなブックマークが提供する API は、REST APIAtom API や件数取得 API などなど、 たくさんあって分かりにくい。 REST API に一本化して、機能を充実してほしいところだ。

指定した URL の人気ブックマーク一覧を取得する

はてなブックマークネタ第2弾。 今度は、指定した URL に付けられたブックマークのうち、 人気のヤツを取得してみた。

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace HatenaBookmarkSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var bookmarks = GetPopularBookmarks("http://hatenablog.com")
                .GetAwaiter().GetResult();

            foreach(var bookmark in bookmarks.Bookmarks)
            {
                Console.WriteLine($"{bookmark.User} : {bookmark.Comment}");
            }

            Console.WriteLine("Enter で終了します。");
            Console.ReadLine();
        }

        static async Task<EntryBookmarks> GetPopularBookmarks(string url)
        {
            var httpClient = new HttpClient();

            httpClient.DefaultRequestHeaders
                .Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            // User-Agent を設定しないと nginx にはじかれる
            httpClient.DefaultRequestHeaders
                .UserAgent.Add(new ProductInfoHeaderValue("HatenaClient", "0.0.1"));

            var requestUri = $"http://b.hatena.ne.jp/api/viewer.popular_bookmarks?url={url}";

            var json = await httpClient.GetStringAsync(requestUri);

            var bookmarks = JsonConvert.DeserializeObject<EntryBookmarks>(json);

            return bookmarks;
        }
    }

    [JsonObject]
    public class EntryBookmark
    {
        /// <summary>
        /// ブックマーク ID を取得または設定します。
        /// </summary>
        [JsonProperty("eid")]
        public string Id { get; set; }

        /// <summary>
        /// タイトルを取得または設定します。
        /// </summary>
        [JsonProperty("title")]
        public string Title { get; set; }

        /// <summary>
        /// 記事の URL を取得または設定します。
        /// </summary>
        [JsonProperty("url")]
        public string Url { get; set; }

        /// <summary>
        /// ブックマークしたユーザーを取得または設定します。
        /// </summary>
        [JsonProperty("user")]
        public string User { get; set; }

        /// <summary>
        /// ブックマークコメントを取得または設定します。
        /// </summary>
        [JsonProperty("comment")]
        public string Comment { get; set; }

        /// <summary>
        /// ブックマークした時間を取得または設定します。
        /// </summary>
        [JsonProperty("timestamp")]
        public string Timestamp { get; set; }
    }

    [JsonObject]
    public class EntryBookmarks
    {
        /// <summary>
        /// ブックマーク件数を取得または設定します。
        /// </summary>
        [JsonProperty("count")]
        public int Count { get; set; }

        /// <summary>
        /// エントリの ID を取得または設定します。
        /// </summary>
        [JsonProperty("eid")]
        public string EntryId { get; set; }

        /// <summary>
        /// エントリのタイトルを取得または設定します。
        /// </summary>
        [JsonProperty("title")]
        public string Title { get; set; }

        /// <summary>
        /// エントリの URL を取得または設定します。
        /// </summary>
        [JsonProperty("entry_url")]
        public string EntryUrl { get; set; }

        /// <summary>
        /// ブックマークの URL を取得または設定します。
        /// </summary>
        [JsonProperty("url")]
        public string Url { get; set; }

        /// <summary>
        /// ブックマーク一覧を取得または設定します。
        /// </summary>
        [JsonProperty("bookmarks")]
        public List<EntryBookmark> Bookmarks { get; set; } = new List<EntryBookmark>();
    }
}

実行結果はこちら。

f:id:griefworker:20170405164319p:plain

指定したユーザーのはてなブックマーク一覧を取得する

いいかげん、そろそろプログラミングネタを書かねば。 ってことで、C# で「指定したユーザーのはてなブックマーク一覧を取得する」サンプルを書いてみた。

はてなブックマークに専用の API が見当たらないので、RSS フィードを取得してパースしている。 オフセットも指定できるから、これはこれで XML を返す Web API と見れなくもない。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace HatenaBookmarkSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var bookmarks = GetUserBookmarks("griefworker", 100)
                .GetAwaiter().GetResult();

            foreach(var b in bookmarks)
            {
                Console.WriteLine(b.Title);
                Console.WriteLine(b.Url);
                Console.WriteLine(b.Comment);
                Console.WriteLine();
            }

            Console.WriteLine("Enter で終了します。");
            Console.ReadLine();
        }

        static async Task<IEnumerable<Bookmark>> GetUserBookmarks(string userId, int offset = 0)
        {
            var httpClient = new HttpClient();

            // RSS フィードなのでレスポンスは XML
            httpClient.DefaultRequestHeaders
                .Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));

            // User-Agent を設定しないと nginx にはじかれる
            httpClient.DefaultRequestHeaders
                .UserAgent.Add(new ProductInfoHeaderValue("HatenaClient", "0.0.1"));

            var requestUri = $"http://b.hatena.ne.jp/{userId}/rss";
            if (0 < offset)
            {
                requestUri += $"?of={offset}";
            }

            var rss = await httpClient.GetStringAsync(requestUri);

            var document = XDocument.Parse(rss);

            var bookmarks = document.Descendants()
                .Where(e => e.Name.LocalName == "item")
                .Select(ToBookmark)
                .ToList();

            return bookmarks;
        }

        static Bookmark ToBookmark(XElement element)
        {
            var bookmark = new Bookmark();

            foreach(var e in element.Descendants())
            {
                switch (e.Name.LocalName)
                {
                    case "title":
                        bookmark.Title = e.Value;
                        break;
                    case "link":
                        bookmark.Url = e.Value;
                        break;
                    case "description":
                        bookmark.Comment = e.Value;
                        break;
                }
            }

            return bookmark;
        }
    }

    class Bookmark
    {
        public string Title { get; set; }

        public string Url { get; set; }

        public string Comment { get; set; }
    }
}

実行結果は次の通り。

f:id:griefworker:20170405153751p:plain

はてなブックマークREST API で用意してくれたらいいんだけどな。

Xamarin Forms でナビゲーションバーの左側にアイテムを配置する方法メモ

最近、Xamarin Forms で iOS アプリを開発している。 Xamarin Forms は XAML で記述できるので、WPF に慣れている身としては、 Storyboard よりも UI を作成しやすくて良いね。 Grid や StackLayout といったレイアウトが超便利だ。

一方で、「こんなこともできないの?」と思うことも度々ある。 例えば、ナビゲーションバーの左側にアイテムを配置する方法すら提供されていない、とかね。 今回は iOS プロジェクトでカスタムレンダラーを書いて実現できたけど。 こんなやつ。

using System.Collections.Generic;
using System.Linq;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(ContentPage), typeof(XamarinFormsSample.iOS.ContentPageRenderer))]

namespace XamarinFormsSample.iOS
{
    public class ContentPageRenderer : PageRenderer
    {
        public override void ViewWillAppear(bool animated)
        {
            base.ViewWillAppear(animated);

            var toolbarItems = ((ContentPage)Element).ToolbarItems.OrderBy(w => w.Priority);
            var navigationItem = NavigationController.TopViewController.NavigationItem;
            var rightItems = new List<UIBarButtonItem>();
            var leftItems =  new List<UIBarButtonItem>();

            foreach (var item in toolbarItems)
            {
                // Priority がマイナスだったらナビゲーションバーの左側に配置する
                if (item.Priority < 0)
                {
                    leftItems.Add(item.ToUIBarButtonItem());
                }
                else
                {
                    rightItems.Add(item.ToUIBarButtonItem());
                }
            }

            navigationItem.SetRightBarButtonItems(rightItems.ToArray(), animated);
            navigationItem.SetLeftBarButtonItems(leftItems.ToArray(), animated);
        }
    }
}

シンプルな UI のアプリなので Xamarin Forms ですんなり実装できると思っていたけど、 結構つまづいている。UIKit 使って実装した方が早かったかも。 今のところ iOS だけが対象だし。

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

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

実行結果は次の通り。

f:id:griefworker:20170220113529p:plain

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

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

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

実行結果。

f:id:griefworker:20170215145505p:plain

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