GitBucket 4.13をリリースしました

Scalaで実装されたオープンソースのGitサーバ、GitBucket 4.13をリリースしました。

https://github.com/takezoe/gitbucket/releases/tag/4.13

ローカルファイルのリポジトリへのアップロード

ローカルのファイルをリポジトリにドラッグ&ドロップでアップロードできるようになりました。

f:id:takezoe:20170527033557p:plain

f:id:takezoe:20170527033611p:plain

Markdown中にHTMLを記述可能に

GitBucketが使用しているJavaベースのMarkdownパーサ、markedjが最新版でホワイトリスト形式でHTMLタグのレンダリングをサポートしました。そのためGitBucketでも一部のHTMLタグのレンダリングが可能になりました。

f:id:takezoe:20170527033628p:plain

f:id:takezoe:20170527033638p:plain

ドロップダウンメニューのフィルタ

これまでは一部のドロップダウンメニューで利用可能だったフィルタボックスがほとんどのドロップダウンメニューで利用できるようになりました。

f:id:takezoe:20170527033652p:plain

今回のバージョンではこの他にも様々な改善やバグフィックスを行っています。詳細についてはIssueの一覧をご覧ください。

Scala福岡 2017に参加します

f:id:takezoe:20170527103119p:plain

7月29日(土)に福岡のヌーラボさん本社オフィスで開催されるScala福岡 2017に参加させていただくことになりました。

scala.connpass.com

セッションでは最近コミッタになったApache PredictionIOについてお話しさせていただきたいと思います。概要は以下の通りですが、この他にもApache Software Foundationでの開発の様子なども紹介できればと思っています。

Apache PredictionIOはSparkやMLlib、HDFS、Elasticsearchなど、注目を集めるオープンソースをベースとした機械学習サーバで、機械学習を活用したアプリケーションの開発・運用を統合的にサポートするプラットフォームを提供します。開発者は様々な機械学習の手法をテンプレートに記述するだけでSparkをベースにした学習タスクの分散処理が可能になります。本セッションでは機械学習のインフラデザインとしても参考になるPredictionIOのアーキテクチャや実践導入のなかでのノウハウ、さらには今後の開発計画について紹介したいと思います。

セッションでは私の他にも@kmizuさんや@daiksyさんもお話しされるようですし、Akka Streamsのハンズオンなどもあるようです。まだまだ参加枠余っているようなので、当日お時間のある方は是非参加登録いただければと思います!!

Type-Driven Development with Idrisの読書会をやっています

社内で4月から週一でType-Driven Development with Idrisの読書会をやっています。

Type-Driven Development with Idris

Type-Driven Development with Idris

今年のScalaMatsuriで@cbirchallさんにオススメされたのがキッカケなのですが、依存型(Dependent Type)を使って変態的なことがいろいろできるという話を聞いて面白そうということで有志(いまのところ6人くらい)で集まって緩く読み進めています。

ようやく2章の中盤に差し掛かったところなのでまだ先は長そうですが、1章でIdrisの概要をウォークスルーしたのでIdrisの思想やIdrisでのプログラミングのお作法のようなものが大体わかってきました。いまのところ言語として特徴的な機能だなと感じたのは以下の2点です。

  • 依存型では型に値を持たせることができ、型同士の演算が可能
    • たとえばString型の要素を2つ持つVec 2 StringとString型の要素を3つ持つVec 3 Stringを結合するとVec 5 Stringになる
  • ホール(hole)というScala???に似た機能があり、型だけ書いて実装を省略した状態でコンパイルを通すことができる
    • コンパイラが残っているホールを検出してくれるので、先に型だけ書いて後からホールを埋めていくというプログラミングが可能

数値型にもIntIntegerBigDecimal的なもの)に加えて正の整数を表すNatがあり、配列の添え字などにはNatを使うというあたりもなるほどと思いました。

このような強力な型の表現力を活かしてより最適な型にリファインを繰り返しながらプログラミングしていくのがType-Driven Developmentのスタイルのようです。まだ、コードを見てもなかなかしっくり来ませんが、後半の章はATMをモデリングしてみるというケーススタディ的な内容になっているようなのでなんとかそこまでたどり着きたいと思います。

ちなみに社内では入門OCamlの読書会も行われているようです。

入門OCaml ~プログラミング基礎と実践理解~

入門OCaml ~プログラミング基礎と実践理解~

さて、どちらが先に完走できるでしょうか。

PredictionIOで使用するSpark等のバージョンを変更する

PredictionIOはデフォルトでは以下のバージョンを使用します。

  • Scala 2.10.6
  • Spark 1.6.3
  • Hadoop 2.6.5
  • Elasticsearch 1.7.6

Scalaのバージョンはともかく、Spark(ストレージにElasticsearchを使用する場合はElasticsearchも)のバージョンがかなり古いのが気になるところですが、ソースからのインストール時に以下のようにオプションで使用するバージョンを指定できるようになっています。

$ ./make-distribution.sh -Dscala.version=2.11.8 -Dspark.version=2.1.0 -Delasticsearch.version=5.3.0

指定できるのは以下のプロパティです。

  • scala.version
  • akka.version
  • spark.version
  • hadoop.version
  • elasticsearch.version

サポートしているバージョンはドキュメントには以下のように書かれているのですが、厳密にどのバージョンをサポートしているかは明記されていません。

  • Scala 2.10.x, 2.11.x
  • Spark 1.6.x, 2.x
  • Hadoop 2.4.x to 2.7.x
  • Elasticsearch 1.x, 5.x

参考までに、現在Travisでの自動テストは以下のパターンで実行されています。

Scala Spark Akka Hadoop Elasticsearch
2.10.6 1.6.3 2.3.15 2.6.5 1.7.3
2.10.6 1.6.3 2.3.15 2.6.5 5.2.2
2.11.8 2.1.0 2.4.17 2.7.3 1.7.3
2.11.8 2.1.0 2.4.17 2.7.3 5.2.2

PredictionIOは後方互換性を非常に重視しているためこのような感じになっていますが、さすがにデフォルトはScala 2.11.x、Spark 2.x、Elasticsearch 5.xでよいのではという気がしますね…。

Scala製の機械学習サーバApache PredictionIOを使ってみよう

PredictionIOはSparkを中心としたJVMベースの機械学習アプリケーションを開発・運用するために必要なミドルウェアフレームワークなどを統合的に提供するもので、開発者はPredictionIOのフレームワークに従ってエンジンを作成することで、機械学習を使用したアプリケーションをWebサービスとしてデプロイすることができます。

まずはPredictionIOのWebサイトに掲載されているレコメンデーションのテンプレートを動かしてみます。

PredictionIOのインストール

ドキュメントを見るとソースからビルドするべしとなっており中々スパルタンな感じです。Dockerで動かすこともできるようですが、折角なのでソースからビルドしてみることにします(Javaは予めインストールしておく必要があります)。

まずはこちらから現時点で最新版の0.11.0-incubatingの配布物をダウンロードし、適当なディレクトリに展開します。ルートディレクトリがないようなのでディレクトリを掘ってから展開するのが良いです。

$ wget http://ftp.riken.jp/net/apache/incubator/predictionio/0.11.0-incubating/apache-predictionio-0.11.0-incubating.tar.gz
$ mkdir apache-predictionio-0.11.0-incubating
$ tar xvzfC apache-predictionio-0.11.0-incubating.tar.gz apache-predictionio-0.11.0-incubating

展開したディレクトリ内にあるmake-distribution.shでおもむろにビルドします。

$ cd apache-predictionio-0.11.0-incubating
$ ./make-distribution.sh

ビルドが成功するとPredictionIO-0.11.0-incubating.tar.gzというファイルができるので、これを適当な場所に展開します。

Sparkのインストール

PredictionIO-0.11.0-incubating/vendorsにSparkのディストリビューションを展開しておきます。が、PredictionIOがデフォルトでサポートしているのはSpark 1.6.3という古いのバージョンのようです。オプションでSpark 2系を使用するようビルドすることもできるのですが、デフォルトで最新のものが使われるようになって欲しいところです。

$ wget http://d3kbcqa49mib13.cloudfront.net/spark-1.6.3-bin-hadoop2.6.tgz
$ mkdir PredictionIO-0.11.0-incubating/vendors
$ tar zxvfC spark-1.6.3-bin-hadoop2.6.tgz PredictionIO-0.11.0-incubating/vendors

ストレージのインストール

PredictionIOの動作に必要なデータや、学習に使用するデータなどを格納するためのストレージを別途インストールする必要があります。PredictionIOは以下のストレージをサポートしています(ストレージに保存するデータにはメタデータ、イベントデータ、モデルデータの3種類があり、ストレージごとに保存できるデータが異なります)。

デフォルトではPostgreSQLを使用するようになっているのでこれを使ってみます。MacであればHomebrewでPostgreSQLをインストールできます。

$ brew install postgresql

ついでにbrew servicesで自動起動するようにしておきます(brew services stop postgresqlすると自動起動をやめることができます)。

$ brew services start postgresql

PredictionIOで使用するデータベースやユーザを作成します。

$ createdb pio
$ psql pio -c "create user pio with password 'pio'"

PostgreSQLのJDBCドライバをダウンロードしてPredictionIO-0.11.0-incubating/libに配置します。

$ cd PredictionIO-0.11.0-incubating/lib
$ wget https://jdbc.postgresql.org/download/postgresql-42.0.0.jar

PredictionIOを起動する

ここまでできたらPredictionIOを起動してみます。

$ cd PredictionIO-0.11.0-incubating/bin
$ ./pio eventserver &

すると、7070ポートでイベントサーバが起動します。この後の操作ではPredictionIO-0.11.0-incubating/binにあるpioコマンドを多用することになるのでこのディレクトリにPATHを通しておくとよいです。

学習データを登録する

PredictionIOのイベントサーバにアクセスするためのアクセスキーを発行します。

$ pio app new MyApp1

こんな感じでアクセスキーが発行されます。

...
[INFO] [App$] Initialized Event Store for this app ID: 1.
[INFO] [Pio$] Created a new app:
[INFO] [Pio$]       Name: MyApp1
[INFO] [Pio$]         ID: 1
[INFO] [Pio$] Access Key: i-zc4EleEM577EJhx3CzQhZZ0NnjBKKdSbp3MiR5JDb2zdTKKzH9nF6KLqjlMnvl

このアクセスキーを毎回指定するのは面倒なので以下のように環境変数に設定しておきます。

$ ACCESS_KEY=i-zc4EleEM577EJhx3CzQhZZ0NnjBKKdSbp3MiR5JDb2zdTKKzH9nF6KLqjlMnvl

学習データとして使用するデータをイベントサーバに登録してみます。

$ curl -i -X POST http://localhost:7070/events.json?accessKey=$ACCESS_KEY \
-H "Content-Type: application/json" \
-d '{
  "event" : "rate",
  "entityType" : "user",
  "entityId" : "u0",
  "targetEntityType" : "item",
  "targetEntityId" : "i0",
  "properties" : {
    "rating" : 5
  }
  "eventTime" : "2014-11-02T09:39:45.618-08:00"
}'
$ curl -i -X POST http://localhost:7070/events.json?accessKey=$ACCESS_KEY \
-H "Content-Type: application/json" \
-d '{
  "event" : "buy",
  "entityType" : "user",
  "entityId" : "u1",
  "targetEntityType" : "item",
  "targetEntityId" : "i2",
  "eventTime" : "2014-11-10T12:34:56.123-08:00"
}'

登録したイベントを検索してみます。

$ curl "http://localhost:7070/events.json?accessKey=$ACCESS_KEY"

以下のように、2つのイベントが登録されていることが確認できます。

[
  {
    "eventId":"774fdb3a572f48479e76e34681c7371b",
    "event":"rate",
    "entityType":"user",
    "entityId":"u0",
    "targetEntityType":"item",
    "targetEntityId":"i0",
    "properties": {
      "rating":5
    },
    "eventTime":"2014-11-02T09:39:45.618-08:00",
    "creationTime":"2017-05-05T09:35:54.604Z"
  },  
  {
    "eventId":"c1182fe0451d4744a0b695770c8098e9",
    "event":"buy",
    "entityType":"user",
    "entityId":"u1",
    "targetEntityType":"item",
    "targetEntityId":"i2",
    "properties":{},
    "eventTime":"2014-11-10T12:34:56.123-08:00",
    "creationTime":"2017-05-05T09:36:05.372Z"
  }
]

レコメンデーションエンジンを動かしてみる

本題のレコメンデーションエンジンです。PredictionIOのオフィシャルで提供されているエンジンテンプレートを使用してみます。まずはgitコマンドでテンプレートのリポジトリを適当な場所にクローンします。

$ git clone https://github.com/apache/incubator-predictionio-template-recommender.git MyRecommendation
$ cd MyRecommendation

準備として予め用意されているデータをまとめてインポートします。まずはPredictionIOのPythonSDKをインストールします。

$ pip install predictionio

以下のようにするとサンプルデータをまとめてイベントサーバにインポートできます。

$ curl https://raw.githubusercontent.com/apache/spark/master/data/mllib/sample_movielens_data.txt --create-dirs -o data/sample_movielens_data.txt
$ python data/import_eventserver.py --access_key $ACCESS_KEY

次にMyRecommendation/engine.jsonを編集してappNameに先ほどアクセスキーを発行したしたアプリケーション名を設定します。

...
"datasource": {
  "params" : {
    "appName": "MyApp1"
  }
},
...

レコメンデーションエンジンをビルドします。

$ pio build --verbose

登録したイベントデータを使用して学習を行います。

$ pio train

ここでスタックオーバーフローが発生してしまうことがあります。その場合はMyRecommender/src/main/scala/ALSAlgorithm.scalaの以下の行のコメントアウトを外してチェックポイントを有効にします。

sc.setCheckpointDir("checkpoint")

ここまででようやく準備完了です。レコメンデーションエンジンをWebサービスとしてデプロイします。

$ pio deploy

8000ポートでレコメンデーションエンジンのWebサービスが起動します。ブラウザでアクセスすると以下のような画面が表示されるはずです。

f:id:takezoe:20170511132241p:plain

実際にレコメンデーションエンジンを呼び出してみます。

$ curl -H "Content-Type: application/json" \
-d '{ "user": "1", "num": 4 }' http://localhost:8000/queries.json

すると以下のような結果が返ってくるはずです。指定したユーザに対してスコアが高い順に4つのアイテムが返却されていることがわかります。

{
  "itemScores":[
    {"item":"22","score":4.072304374729956},
    {"item":"62","score":4.058482414005789},
    {"item":"75","score":4.046063009943821},
    {"item":"68","score":3.8153661512945325}
  ]
}

まとめ

だいぶ手順が長くなってしまいましたが、PredictionIOは機械学習を使用したアプリケーションを開発・運用するために以下のような機能を提供していることがおわかりいただけたのではないかと思います。

今回は実際にPredictionIOでどのようにエンジンを開発するのかについては触れませんでしたが、興味のある方はエンジンテンプレートのソースコードを参照していただければと思います。また、PredictionIOのWebサイトには他にもいくつかのエンジンテンプレートが紹介されており、同様の手順で動かすことができますのでこちらも試してみていただければと思います。

Apache PredictionIOのコミッタになりました

f:id:takezoe:20170503224901p:plain

Apache PredictionIOはSpark MLlibを中心に、学習データやモデルデータを格納するためのストレージ、機械学習を利用したアプリケーションを定型的に開発するためのフレームワーク、作成したアプリケーションをWebサービスとしてデプロイする機能などを提供するもので、元々は2013年に創業されたPredictionIO社で開発されていたのですが、2016年2月にSalesforce社に買収され、Apache Software Foundationに寄贈されたものです。

github.com

弊社の機械学習チームでは機械学習を応用したアプリケーションを組織的に開発していくにあたり、開発・運用基盤としてApache PredictionIOに取り組んでいるのですが、このたび弊社から以下の4名がコミッタとしてPredictionIOの開発に参加させていただけることになりました(Elasticsearch周りなどで積極的に貢献されていた菅谷さんのプレゼンスが大きかったと思います)。

github.com

github.com

github.com

github.com

自分は以前Apache Clickのコミッタをやっていたのですが(ちなみに菅谷さんもApache Portalsのコミッタです!)、ClickがIncubatorに移行したのが2008年なのでもう9年前になるんですね…。その後2010年にトップレベルプロジェクトに昇格し、開発が停止してAtticに移ったのが2014年なので、約3年ぶりにコミット権のあるプロジェクトに復帰したことになります。まずはIncubatorを卒業するのが目標ということになるでしょうか。

自分は機械学習やSpark周りについては素人同然なのですが、PredictionIOはScalaで作られてはいるものの正直Scala的にはあまりよろしくないコードや、作り的に微妙な部分が多々ありますので(Java的にもどうなのかという部分もありますが)、引き続きScala警察として活動していきたいと思います。

MozillaのスクレイピングフレームワークFathomを試してみる

こちらのOSDNの記事で知ったのですが、MozillaでFathomというJavaScript用のスクレイピングフレームワークを開発しているようです。仕事でクローラーを作ったりしていたこともあり、面白そうだと思ったので軽く調べてみました。

mag.osdn.jp

以下のGitHubリポジトリで開発されています。GitHub Pagesに詳しいドキュメントもあります。

github.com

OSDNの記事で触れられている開発者のErik Rose氏のブログエントリはこちら。

hacks.mozilla.org

ドキュメントやErikさんのブログエントリなどをざっと眺めてみたところ、それほど複雑なものではなく、ある程度曖昧なルールとスコアリングを定義しておき、最もスコアの高い要素の選択するというのが基本的なコンセプトのようです。

なにはともあれ試してみます。まずは適当なディレクトリを掘り、npmでFathomとjsdomをインストールします。

$ npm install fathom-web
$ npm install jsdom

プログラムはこんな感じです。FathomのドキュメントのBasic Useに記載されているものをベースにしています。

const {dom, props, out, rule, ruleset, score, type} = require('fathom-web');
const jsdom = require('jsdom');
const { JSDOM } = jsdom;

function containsColonsOrDashes(element){
  // 要素にコロン(:)かダッシュ(-)が含まれてたらtrueを返す
}

const rules = ruleset(
  // titeタグ、スコアは1
  rule(dom('title'), type('titley')),
  // OpenGraphのmetaタグ、スコアは2
  rule(dom('meta[property="og:title"]'), type('titley').score(2)),
  // タイトルにコロン(:)かダッシュ(-)が含まれている場合は
  // ナビゲーション用っぽいのでペナルティとしてスコアを×0.5する
  rule(type('titley'), score(fnode => containsColonsOrDashes(fnode.element) ? .5 : 1)),
  // スコアが最も高いものをtitleキーとして提供する
  rule(type('titley').max(), out('title'))
);

// jsdomでDOMツリーを生成
const { document } = (new JSDOM('<html>...</html>')).window;
// 指定したDOMツリーに対する適用結果を取得
const facts = rules.against(document);

// 最もスコアの高いノードを取得してコンソールに表示
const bestTitleFnode = facts.get('title');
console.log(bestTitleFnode[0].element);

見ての通り、そんなに難しいことはしていません。上記のコードでは最もスコアの高い要素を取り出していますが、ルールにマッチした要素をすべて取得したり、要素からスコアを取得したりということもできます。

ルールの定義の仕方次第かもしれませんが、タイトルなどわかりやすいものはともかくデータとして使用する目的でスクレイピングするにはある程度きちんと論理的なマークアップがされていないとこのアプローチでは厳しいのではないかという気もします。ちゃんと使ってみないとなんとも言えない感じですね。実際は要素を取り出した後の切り出しや加工も必要だったりしますし…。

とはいえ、スコアリングの最適化機能など面白い機能もあるので時間があるときにもう少しちゃんとしたデータを使って試してみたいところです。