ガジェット通信 GetNews

見たことのないものを見に行こう

Scalaプログラマから見た機械学習サーバー「Apache PredictionIO」とは?─by Scala福岡2017

DATE:
  • ガジェット通信 GetNewsを≫

機械学習の課題

竹添直樹さんはビズリーチでScalaのプログラマを務めながら、OSS開発や技術書(「Scalaパズル」「Scala逆引きレシピ」)の執筆、さらにはPredictionIOやGitBucket、Scalatraのコミッターを務めている。

今回は、セッションではScala製機械学習サーバ「Apache PredictionIO」について紹介した。

機械学習の一般的な課題は大きく2つある。1つはスケーラビリティ。データが増えたときに学習させたり予測する処理、学習用のデータが大量にあるのでストレージのスケーラビリティが求められる。

もう一つがガバナンス。開発・メンテナンス、運用などの効率などだ。これらの課題を解決するのがオープンソースの機械学習サーバ「Apache PredictionIO」である。

PredictionIOの特徴は、第一にカスタマイズ可能なテンプレートから予測APIをWebサービスとしてデプロイできること。

第二にライブラリとしてSparl MLlibやOpenNLPをサポートしていること。第三に学習データやモデルを格納するためのストレージの管理も統合していることだ。

つまりPredictionIOとは、AWSやGCPなどが提供するような機械学習サービスを自前で構築するための基盤ソフトウェア。細かいチューニングが可能でクラウドロックインが回避でき、オンプレでの活用ができるというものだ。

一般的に、機械学習はCPUのリソースを非常に使う。その際にマネージドサービスでは費用対効果が悪くなる場合があり、オンプレサーバで運用する方がコストメリットが出るケースが多い。

株式会社ビズリーチ 竹添直樹さん

Apache Software Foundationの役割

PredictionIOは2013年、PredictionIO社が設立され、オープンソースでの提供が始まる。2016年にSalesforceに買収されて、ASF(Apache Software Foundation)に寄贈された。現在、Apache Incubatorで開発が続けられている。

ASFはもともとApacheのためにできた非営利団体で、オープンソースのソフトウェアプロジェクトを支援。JavaやBigDataとオープンソースのトレンドを支え続けている。

具体的な役割は大きく3つ。まずは、オープンソースプロジェクトへのインフラ提供。次に法的訴訟から開発者の保護。第三はソフトウェアの法的権利の保障である。

最近、ReactなどFacebook製のOSSで採用されているBDS+patentsライセンスに関する議論が起こった。

このライセンスは当該ソフトウェアの利用ユーザに対してFacebookが保有する特許の利用許諾を与えるものの、ユーザがFacebookを特許侵害で訴えた場合にはその許諾を取り消すというものだ。

これは事実上、利用ユーザはFacebookに対して特許侵害の訴訟を起こすことができないということを意味する。

ASFではBSD+patents licenseはApache Licenseに適合しないとし、ASFのプロダクトでは使ってはならないという判断がなされた。

問題の発端になったのは、FacebookのオープンソースDB、RocksDBである。先のライセンスを採用していたため、ASFから使ってはいけないというアナウンスがされたのである。RocksDBが急遽、ライセンスを変更して解決したが、ReactについてはライセンスはそのままなのでASFのプロダクトでReactを使えないという問題は残ったままだ。

現在、AFSはReactのプロジェクトに対して、ライセンスを見直してもらえないかという働きかけをしているという。(その後、Reactもライセンスが変更された)

オープンソースにはこういうややこしい問題があるが、ASFのプロダクトであれば安心なので、そういう法的権利の保障という大きな役割をしている。

一方で、ASFはインフラが使いにくい、リリースや重要な意思決定の際などはコミッター内で投票する必要があるなど官僚的なルールが多く、開発が停滞しがちな側面もある。

PredictionIOが提供している機能

PredictionIOの基本的な利用フローは、以下の図の通り。最初にレコメンド、クラスタリングなど、用途別にベースとなるテンプレートの中から、やりたいことができるテンプレートを選び、GitHubからクローンする。

クローンしたテンプレートをカスタマイズしてエンジンを作ったら、学習するためのデータ(ユーザーの行動のログやアクセスログなど)を、イベントサーバに入れる。

pio trainというコマンドで学習したものをエンジンに食わせて予測モデルを作る。そこの処理はSparkで行う。そうすると予測モデルができるので、モデルストレージに入れる。これで準備が完了だ。

pio deployでエンジンサーバをWebサービスとしてデプロイし、アプリで問い合せると結果を返すJSONベースのWeb APIを立ち上げることができる。こういう一連の仕組みを作れるのがPredictionIOである。

PredictionIOのストレージはメタデータ(エンジンの情報など)、イベントデータ(学習用データ)、モデルデータ(予測モデル)の3種類。いずれもPostgreSQL、MySQL、Elasticsearchなど、さまざまな製品をサポートしているため、好みのものが使えるようになっている。

先述したとおり、テンプレートも用途に応じて用意されている。GitHubリポジトリで提供されているので、git cloneして試用する。テンプレートのソースコードは次の通り。

ご覧の通り、Algorithmクラスでは学習の処理はtrain()というメソッド、学習モデルを使って結果を返す処理はpredict()というメソッドが定義されている。

詳しい実装の方法については、たけぞう瀕死ブログに書かれているので、ぜひ読んでみてほしい。

ビズリーチではレコメンドなどに機械学習を採用

ビズリーチは主に転職サービスを提供している会社。求職者に求人情報をレコメンドする仕組みに機械学習を応用しているのだが、機械学習チームのエンジニアがインフラや運用の仕組みまで作ったりしているので、本業に集中できないのが問題だった。

しかもスタンダート名やり方が特に決まっておらず、少人数なので各自独自のやり方で作ってしまう。そのため、属人化も発生したのだ。

そこでPredictionIOを導入し、テンプレート化することで効率化し、本来の作業に集中できるようにしたのである。

現在、ビズリーチにはPredictionIOのコミッタが4人在籍している。機械学習人材とScala人材に2人ずつ、スキルセットが分かれており、お互いサポートし合いながら開発を進めている。

これまでの主なコントリビューションとしてはElasticsearch5.xやS3のサポート、Scalaコードのリファクタリングやサンプル、ドキュメントの整理、バイナリディストリビューションの作成、デフォルトのサポートバージョンの最新化、依存ライブラリのライセンス問題の解決など。

将来はリッチなWebコンソールを作ることやPythonサポートを入れること、オンライン学習のためにストリーム形式のデータを処理できるようにすること、Windowsでも動かせるなどもう少し汎用的にしていくことなどを計画。

さらには、日本Predictionユーザーグループ(Japan PredictionIO User Group:JPIOUG)を立ち上げているという。

PredictionIO、イケていないコードあるある

PredictionIOは機械学習のプラットフォームなので、Scalaで書かれている必要はないが、Scalaで書かれているのはSparkがあるから。それがScalaである大きな理由だ。

Scalaプログラマから見ると、PredictionIOにはフラストレーションが溜まる部分もある。その理由は後方互換性を重視しているためだ。

しかも、Scala的にいけていないコードがたくさんある。これはPredictionIOだけではなくSparkやKafkaでも同様の傾向がある。PredictionIOは元々関数型界隈ではなく、機械学習界隈の人がSparkやMLlibを使うためにScalaを使っているためだ。

Scala的微妙なコードあるある

1.Procedure Syntax

Scalaには、メソッドの戻り値がUnitの場合は、メソッド定義の「=」を省略できるという記法がある。

def hello() {
“Hello World!!”
}

これがだめな理由は、戻り値の型がUnitになってしまうこと。特にJavaから来た人が間違いやすいため、将来のバージョンのScalaでは廃止予定となっている。

Procedure Syntaxを使わずに次のように書くのが望ましい。

def hello(): String = {
“Hello World!!”
}

または、

def hello() = {
“Hello World!!”
}

2.Unit is not Unit Value

これがだめな理由は、Unit値は()、UnitはUnitオブジェクトで値ではない。メソッドの戻り値など実害はないケースが多いので気付きにくいが、Scala警察としては見逃せない。

Unitではなく()と書くことで解決する。

def hello(): Unit = {
// …いろいろ処理…
()
}

3.Auto Tupling

これは引数を自動的にタプルに変換する機能である。
普通に2つの引数にして書くと、自動的にタプルにする便利機能がある。

def hello(x: (String, String)): String = {
x._1 + ” ” + x._2
}
// 本来であればこう呼び出す
hello((“Naoki”, “Takezoe”))
// こう書ける
hello(“Naoki”, “Takezoe”)

これがだめな理由は、コンパイルエラーになるのかわからないケースがあること、リファクタリング時に意図せず、コンパイルが通ってしまうケースがあることだ。

解決方法としては基本的にAuto Tuplingを使わないこと、名前付き引数で渡すことだ。

Any型の引数を定義する場合やリファクタリング時はAuto Tuplingが適用される場合が多いので、特に注意が必要だ。

この辺りは「Salaパズル」に書いてあるので、ぜひ、チェックしてほしい。

4.Escape by “return”

メソッドの実行途中でreturnで脱出するコーディングスタイルはScalaでは推奨されていない。

def hello(names: Seq[String]): String = {
// Seqが空の場合
if(names.isEmpty) return “”
// Seqに空文字列が含まれている場合
names.foreach { name =>
if(name.isEmpty) return “”
}
// 実際の処理
names.mkString(“, “)
}

メソッドの戻り値の型推論が聞かなくなるので、戻り値の型をStringで明示的に記述する必要がある。場合によっては例外にコンパイルされているケースがあるので、トラブルのもととなる。

解決方法は不要な場合は書かないこと。if elseやコレクション操作に置き換えるなど、使う場合は例外処理に気をつけることだ。

例外を処理する場合は、Throwableでキャッチしないこと。Throwableをキャッチする必要がある場合は、NonFatalを使うことだ。これを使うと、例外はキャッチするが、ControlThrowableはスルーしてくれる。

そのほかのあるあるパターンとしては、varやmutableコレクション、whileループなどがある。

Scala警察になってバリューを発揮しよう

今一つイケていないコードを見つけるのは大変な作業である。コンパイラオプションを設定したりすることで、自動化しよう。

PredictionIOやSpark MLlib、Mahout-Spark、Elasticserch-Hadoopなどは、基本的にScalaプログラマが作っていないプロダクト。したがって、先のようなScalaプログラマから見るといけていないコードがたくさん発生している。

Scalaプログラマが、こういうところに参入するとかなりバリューを発揮できる。Apacheコミッタになるのは敷居が高いが、いけていないコードを見つけるScala警察として参画すると、コミッターになれる道があるという。

最後に竹添氏は「興味がある方は、ぜひScala警察となって共にバリューを上げましょう」と呼びかけ、セッションを締めくくった。

講演資料:Scala警察のすすめ


Scala警察のすすめ from takezoe

「Scala福岡2017」レポート特集

Scala/Spark/Mahoutでレコメンドエンジンを作る─by Scala福岡2017
Direct Manipulationとプログラミング環境をScalaで書いてみる
はてな粕谷氏が語る、安全なPlay Frameworkのバージョンアップのコツとは

カテゴリー : デジタル・IT タグ :
CodeIQ MAGAZINEの記事一覧をみる ▶
  • 誤字を発見した方はこちらからご連絡ください。
  • ガジェット通信編集部への情報提供はこちらから
  • 記事内の筆者見解は明示のない限りガジェット通信を代表するものではありません。