Scala関西Summit 2018に参加しました

11月10日(土)、11日(日)の2日間に渡り大阪で開催されたScala関西Summit 2018に参加してきました。

上記のツイートの通り、今年も昨年に引き続きGitBucketでスポンサーさせていただくとともに、ありがたいことにスピーカーとして「長期的にメンテナンスの必要なScala製アプリケーションにおいて気をつけるべきこと」という発表をさせていただきました。発表資料は以下になります。

www.slideshare.net

現実と向き合うような内容になってしまったかもと思いますが、仕事で8年以上Scalaを使ってきての現在の率直な感想です。転職して自分の立場がこれまでとは大きく変わったこともあり、振り返りの意味も込めて発表させていただきました。

最後の方で少しだけ、トレジャーデータではAirframeというDIコンテナを中心としたライブラリに必要な機能を集約することでメンテナンスの必要な箇所を局所化しているという話をさせていただきました。もちろんすべてをAirframeでカバーできているわけではありませんしが、Scalaのバージョンアップにきちんと追従していくための1つの現実的な戦術なのではないかと思います。

全体としてはAkka関連のセッションが多かったのと、ZOZOさんがhttp4sを使っているというのがちょっと驚きでした。個人的には@smdtmsさんのDatabricksのセッションも興味深く聴講させていただきました。

2日目のアンカンファレンスにも参加させていただいたのですが、OSSハッカソンは簡単にできそうなものでとにかくPRしてみようという感じで一応ゴールまではたどり着くことができたものの、もう少し時間をかけて難しいイシューに取り組んでもよかったかも知れませんね。また、@grimroseさんがAirframeハンズオンを開催してくださっていました。ありがとうございました^^;

f:id:takezoe:20181114213551j:plain

昨年は朝一のセッションでの登壇にも関わらず気合いの日帰り弾丸ツアーだったのでだいぶしんどかったのですが、今年は2daysということもあり、前日入りして2泊するという余裕ある日程で参加できたので、いろんな方とお話しできたり、ちょっとだけ観光っぽいこともできて良い気分転換になりました。

ScalaMatsuriが海外で開催されているScalaカンファレンスに引けを取らない国際カンファレンスとして成長している一方、Scala関西Summitはまた違った温かみのあるカンファレンスだと感じています。この素晴らしい雰囲気のカンファレンスがまた来年も関西で開催されることを願ってやみません。

Scalafixで未使用のインポート文を削除する

Scalaで未使用のインポート文を自動で消せないかという話があり、Scalafixだとできそうだったのでやってみました。

github.com

ドキュメントに書かれている通りでいけるのですが、手順をまとめておきます。

まずはproject/plugins.sbtにsbtプラグインを追加。Scalaのバージョンによって使用するsbtのバージョンが違うので注意してください。

addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.0")

とりあえずこれだけでScalafixを使えるようにはなるのですが、未使用のインポート文を削除するには追加の設定が必要です。build.sbtに以下の設定を追加します。

addCompilerPlugin(scalafixSemanticdb),
scalacOptions ++= List(
  "-Yrangepos",
  "-Ywarn-unused:import"
)

これでsbt "scalafix RemoveUnused"を実行すると未使用のインポート文が削除されます。

また、プロジェクトのルートディレクトリの.scalafix.confというファイルでデフォルトで実行するルールを指定することができるので、以下のような設定をしておくとsbt scalafixと入力するだけで未使用のインポート文を削除できるようになります。

rules = [
  RemoveUnused
]

ちょっと設定が面倒ですし、sbtプラグインだけでなくコンパイラプラグインも必要になります。また、Scala.jsプロジェクトでは動作しないという問題もあるのですが、未使用のインポート文を消す以外にもProcedure Syntaxの自動修正や指定した構文を利用禁止にする機能もあるのでこれらの機能にメリットを見い出せるのであれば導入するのもありかもしれません。

なお、Play FrameworkなどでTwirlを使用している場合、-Ywarn-unused:importオプションを有効にするとコンパイル時に大量の警告が出力されてしまいます。こちらで紹介されているように、

TwirlKeys.templateImports := Seq()

を指定して自動インポートをしないようにすれば警告も出なくなるのですが、この場合はTwirlがデフォルトでインポートしてくれているHtmlなども明示的にインポートしないと使えなくなるのでかなり不便な気がします。こういう点も考えると未使用のインポート文の削除のためにScalafixを導入するのはやや微妙かなというのが個人的な印象です。

実践Scala入門

「実践Scala入門」を技術評論社さんからお送りいただきました。Scalaの入門書というとコップ本を思い浮かべる方が多いと思いますが、下のツイートでも書いている通りこの本はかなりコンパクトです。

A5判本文300ページ弱という分量でScalaの基本的な部分を押さえつつ、sbt、Futureを使った並行プログラミング、ユニットテスト(ScalaCheckによるパラメータテストやMockitoを使ったテストまで!)などもカバーされています。また、さすがScalaを使い込んでいる著者陣だけあってScalaの各機能の使い方だけでなく、注意事項やどのように使うと便利なのかなど、実用的な内容も詰め込まれています。

もちろん他の言語でのプログラミング経験がある、実用的なプログラムを書くには別途Javaライブラリなどの知識が必要になるといった前提や、関数型プログラミングやアドバンスドな機能に敢えて触れないことでトピックを絞っている側面はありますが、それにしても一切無駄がないという表現がぴったりの、まさに「コンパクトなコップ本」というコンセプト通りのScala入門書になっています。

なお、巻末でScalaの日本語書籍としてScala逆引きレシピやScalaパズル、またWebフレームワークとしてScalatraを紹介していただいています。 Scala逆引きレシピは内容的にだいぶ古くなってしまっているので改訂できるといいなと思っているのですが、Scala 3も控えているのでまたタイミングが難しいところです。そういう意味ではこの「実践Scala入門」は成熟したScala 2.12系が全盛の非常に良いタイミングでリリースされたのではないでしょうか。

現状間違いなく日本語でのScala入門書の決定版といえる書籍かと思いますのでこれからScalaを初めてみようという方はもちろん、すでにScalaを使っている方も是非一度手に取ってみていただければと思います。

実践Scala入門

実践Scala入門

sbtサーバとRustで書かれたsbtクライアント

Scalaのビルドツールsbtには1.xからLanguage Server Protocol 3.0に対応したサーバモードが実装されており、常駐させたサーバに別プロセスから接続してコマンドを実行することができます。サーバモードはIDEやエディタプラグインのために実装されたものだと思いますが、sbtにはクライアントモードも実装されており、コマンドラインで動作を確認することもできます。

たとえばあるプロジェクトでsbtのプロセスを立ち上げておき、

$ sbt
...
sbt:gitbucket>

別のターミナルから以下のようにしてコマンドを送信します。

$ sbt client clean
...
[info] entering *experimental* thin client - BEEP WHIRR
> compile

サーバ側ではこんな感じのログが表示されます。

[info] new client connected: network-1
[success] Total time: 0 s, completed Oct 21, 2018 9:16:38 PM
[success] Total time: 0 s, completed Oct 21, 2018 9:16:38 PM

sbt clientコマンドはサーバが立ち上がっていない場合は自動的に起動するので直接実行してもよいのですが、コマンドの出力が見えないので別ターミナルでsbtのプロセスを立ち上げておいた方が動きがわかりやすいです。ただ、ログにも出ている通りこのsbt clientコマンドは実験的なもののようで、何故かわかりませんがコンパイルを実行すると帰ってこなくなったりします。

スタンドアロンで利用できるsbtクライアントとしてRustで書かれた以下のものがあります。Macならバイナリが用意されているのでダウンロードしてPATHの通っている場所に置くだけで使えます。

github.com

こちらもsbtサーバが立ち上がっていない場合は自動的に起動するのですが、sbt clientコマンドが止まってしまうようなケースでもちゃんと動作しますし、起動にJVMのオーバーヘッドがないという意味で実用的なクライアントと言えるかもしれません。

プロジェクト管理に便利なGitBucketプラグイン

GitBucketはプラグイン機構を持っており、様々な機能をプラグインで拡張することができます。最近プロジェクト管理に便利なプラグインを作成されている方がいらしたので簡単に紹介させていただきたいと思います。

カンバンプラグイン

イシューやプルリクエストをカンバン形式で表示できるプラグインです。レーンは特別なプレフィックスを付けたラベルで管理されており、GitBucketの既存の機能をうまく活用していただいていますね。

f:id:takezoe:20181014125056p:plain

github.com

ガントチャートプラグイン

こちらはカンバンプラグインと同じ作者の方によるマイルストーン毎にイシューやプルリクエストの進捗状況を表示してくれるプラグインです。

f:id:takezoe:20181014125209p:plain

github.com

Excelプラグイン

そしてプロジェクト管理といえば何といってもExcelでしょう。このプラグインはまだ開発中ですが、GitBucket上でExcelファイルのブラウズが可能になるようです。GitBucketの新たな可能性を感じさせるプラグインですね!

f:id:takezoe:20181014125328p:plain

github.com

このように、GitBucketには様々なプラグインが存在します。既存のプラグインの中から目的のものが見つからなければ、自分でプラグインを作ることもできますので、是非トライしてみていただければと思います。

SalesforceのScala製AutoMLライブラリ「TransmogrifAI」を触ってみた

AutoMLはこれまで専門のエンジニアを必要としていたような機械学習の処理を自動化し、誰でも機械学習を利用できるようにするという分野です。PythonだとTPOTなどのライブラリが存在しますが、先日Salefsforce社からScala + SparkベースのAutoMLライブラリが発表されたとのことで軽く触ってみました。

github.com

Hello Worldの最初のサンプルがタイタニック号の乗客の生存予測だったのですが、データの加工処理なども入っていたのでそれらを除いてシンプルにした形のサンプルを紹介したいと思います。

まずはこんな感じのimport文が必要です。

import com.salesforce.op._
import com.salesforce.op.evaluators.Evaluators
import com.salesforce.op.features.FeatureBuilder
import com.salesforce.op.features.types._
import com.salesforce.op.readers.DataReaders
import com.salesforce.op.stages.impl.classification.BinaryClassificationModelSelector
import com.salesforce.op.stages.impl.classification.BinaryClassificationModelsToTry._
import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession

CSVデータに対応するケースクラスを作っておきます。

case class Passenger
(
  id: Int,
  survived: Int,
  pClass: Option[Int],
  name: Option[String],
  sex: Option[String],
  age: Option[Double],
  sibSp: Option[Int],
  parCh: Option[Int],
  ticket: Option[String],
  fare: Option[Double],
  cabin: Option[String],
  embarked: Option[String]
)

Sparkを使うのでSparkSessionも必要になります。

val conf = new SparkConf().setAppName("OpTitanicSimple")
implicit val spark = SparkSession.builder.config(conf).getOrCreate()

ここからがTransmogrifAIならではの部分になります。まずはケースクラスのフィールドに対応した特徴を定義します。予測対象は.asResponse、推定に使うフィールドは.asPredictorにしておきます。ここで型を付けておくのでいろいろ自動化が可能らしいです。

val survived = FeatureBuilder.RealNN[Passenger].extract(_.survived.toRealNN).asResponse
val pClass = FeatureBuilder.PickList[Passenger].extract(_.pClass.map(_.toString).toPickList).asPredictor
val name = FeatureBuilder.Text[Passenger].extract(_.name.toText).asPredictor
val sex = FeatureBuilder.PickList[Passenger].extract(_.sex.map(_.toString).toPickList).asPredictor
val age = FeatureBuilder.Real[Passenger].extract(_.age.toReal).asPredictor
val sibSp = FeatureBuilder.Integral[Passenger].extract(_.sibSp.toIntegral).asPredictor
val parCh = FeatureBuilder.Integral[Passenger].extract(_.parCh.toIntegral).asPredictor
val ticket = FeatureBuilder.PickList[Passenger].extract(_.ticket.map(_.toString).toPickList).asPredictor
val fare = FeatureBuilder.Real[Passenger].extract(_.fare.toReal).asPredictor
val cabin = FeatureBuilder.PickList[Passenger].extract(_.cabin.map(_.toString).toPickList).asPredictor
val embarked = FeatureBuilder.PickList[Passenger].extract(_.embarked.map(_.toString).toPickList).asPredictor

特徴をSeqにまとめてtransmogrify()でベクトルに変換し、モデルを定義します。

// 特徴ベクトルを作成
val passengerFeatures = Seq(
  pClass, name, sex, age, sibSp, parCh, ticket, fare, 
  cabin, embarked
).transmogrify()

// モデルを定義
val prediction =
  BinaryClassificationModelSelector.withTrainValidationSplit(
    modelTypesToUse = Seq(OpLogisticRegression)
  ).setInput(survived, passengerFeatures).getOutput()

学習データを使って学習を行います。

// ケースクラスをエンコードするために必要
import spark.implicits._
// CSVファイルから学習データを読み込むReader
val trainDataReader = DataReaders.Simple.csvCase[Passenger](
  path = Option("/tmp/TitanicPassengersTrainData.csv"), 
  key = _.id.toString
)

// ワークフローを定義
val workflow =
  new OpWorkflow()
    .setResultFeatures(survived, prediction)
    .setReader(trainDataReader)

// 学習
val fittedWorkflow = workflow.train()

学習済みのワークフローはファイルに保存したり、新しいデータに対して予測を行ったりできます。ちなみにscore()の戻り値はDataFrameで返ってきます。

// 学習済みのワークフローをファイルに保存可能
fittedWorkflow.save("/tmp/OpTitanicSimple.model", true)

// ファイルから読み込んで使用
val loadedWorkflow = new OpWorkflow()
    .setResultFeatures(survived, prediction)
    .loadModel("/tmp/OpTitanicSimple.model")

// 新しいデータを読み込むReader
val newDataReader = DataReaders.Simple.csvCase[Passenger](
  path = Some("/tmp/NewData.csv"), 
  key = _.id.toString
)

// 新しいデータに対して予測
loadedWorkflow.setReader(newDataReader)
val result = fittedWorkflow.score()

score()の代わりにscoreAndEvaluate()を使うと結果を評価することもできます。この場合、resultに予測結果、metricsに評価結果が入ってきます。TransmogrifAIには学習、予測、評価などをテンプレート化したラッパーも用意されており、それを使用することでこのあたりをもう少しスッキリ記述することもできます。

val evaluator = Evaluators.BinaryClassification()
    .setLabelCol(survived)
    .setPredictionCol(prediction)

val (result, metrics) = fittedWorkflow
    .scoreAndEvaluate(evaluator = evaluator)

少し手数は多いですが、基本的には特徴を渡すだけで予測ができるというコンセプトは伝わるのではないかと思います。データをちゃんと加工するともっと精度が良くなるのですが(実際にTransmogrifAIに付属しているサンプルコードでは多少加工が行われています)、このあたりや特徴の型付けまで自動化されると凄いですね。

TransmogrifAIのモチベーションや技術的背景については以下のブログ記事に書かれています。Sparkを使用しているのは、Salesforceでは大量データを扱う必要があること、バッチとストリーミングの両方の形態でモデルをserveする必要があることが理由として挙げられています。

engineering.salesforce.com

自分は機械学習エンジニアというわけではないので、内部の実装や他のライブラリの事情などよくわかっていないのですが、汎用的な用途でそこそこの精度が出ればよいという使い方であれば機械学習の専門知識がないエンジニアでもできるようになるのではという感覚があり、なかなか未来を感じますね。

入門 PySpark ―PythonとJupyterで活用するSpark 2エコシステム

Sparkはちょくちょく触る機会があるのですが、PySparkは使ったことがなかったので読んでみました。最近のSparkの流れを見ていてもPythonのサポートが強化されてきているので軽く押さえておきたいなと…。

入門 PySpark ―PythonとJupyterで活用するSpark 2エコシステム

入門 PySpark ―PythonとJupyterで活用するSpark 2エコシステム

Amazonのレビューではかなりの低評価ですが、レビュアーの方々の期待値とズレがあったという話で、タイトル通りPySparkの入門書としては分量も手頃かつ機械学習やグラフ処理など幅広い範囲をカバーし、Sparkの基礎も押さえた良い書籍だと思います。訳書な上に出てから1年近く経ってしまっているのでちょっと情報が古くなってしまっていたり、物足りない部分があるのは事実ですが、Spark + PySparkの概要をさらっと押さえるに良いのではないでしょうか。

PySparkの入門のつもりで購入したのですが、このくらいの浅く広くな感じでいいので、Scalaで書かれたSparkの入門書で新しいSparkのバージョンに対応した本が出るといいなぁと思ってしまいました。