プロジェクト管理に便利な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()

学習済みのワークフローはファイルに保存したり、新しいデータに対して予測を行ったりできます。ちなみにsocre()の戻り値は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()

socre()の代わりに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のバージョンに対応した本が出るといいなぁと思ってしまいました。

GitBucket 4.29.0をリリースしました

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

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

オフィシャルDockerイメージが利用可能に

DockerHub上でオフィシャルのDockerイメージが利用可能になりました(と言っても今まで個人のリポジトリに置いてあったものをOrganizationに移しただけですが)。

f:id:takezoe:20180929150954p:plain

以下のようにして簡単に起動できます。

$ docker run -d -p 8080:8080 gitbucket/gitbucket

SSH経由での接続も行う場合は-p 29418:29418を指定します。

$ docker run -d -p 8080:8080 -p 29418:29418 gitbucket/gitbucket

データディレクトリの場所は-vで指定できます。

$ docker run -d -p 8080:8080 -v `pwd`/gitbucket:/gitbucket gitbucket/gitbucket

リポジトリビューアのファイル編集ボタン等の改善

READMEを直接変更できるようになった他、編集・削除ボタンをアイコン化しました。

f:id:takezoe:20180929151358p:plain

f:id:takezoe:20180929151406p:plain

危険な操作時に確認ダイアログを表示

これまでリポジトリの削除時は確認ダイアログを表示していたのですが、これに加えてリポジトリのオーナー変更時(Transfer Ownership)、GCの実行時(Garbage collection)にも確認ダイアログを表示するようになりました。

f:id:takezoe:20180929151543p:plain

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

AirframeのFinagleサポートを試してみる

AirframeScala向けのDIコンテナを中心とした様々な便利機能を提供するプロジェクトなのですが、最近airframe-httpとairframe-http-finagleというモジュールが追加され、Finagleを使ったWebアプリケーションを簡単に作れるようになったので感じを掴むために軽く試してみました。

まず、build.sbtの依存関係はこんな感じ。

libraryDependencies ++= Seq(
  "org.wvlet.airframe" %% "airframe-http"         % "0.66",
  "org.wvlet.airframe" %% "airframe-http-finagle" % "0.66",
  "org.wvlet.airframe" %% "airframe"              % "0.66"
)

Webアプリケーションの実際の処理を作ります。ルーティングやパラメータのマッピングなどはアノテーションで指定する方式です。また、ケースクラスを返すと自動的にJSONレスポンスを返してくれるようです。とりあえずハローワールド的な感じでGETメソッドでパスパラメータを1つ受け取るだけのエンドポイントを定義しています。

import wvlet.airframe.http._

object MyResource {
  case class User(name: String)
}

trait MyResource {
  import MyResource._

  @Endpoint(path = "/user/:name", method = HttpMethod.GET)
  def getUser(name: String): User = User(name)

}

これを以下のようなMainオブジェクトで実行します。最小構成だとこんな感じですが、動作に必要なコンポーネントはAirframeのDI機能で合成されているので、いろいろカスタマイズすることが可能です。

import wvlet.airframe.http._
import wvlet.airframe.http.finagle._

object Main extends App {
  val router = Router.of[MyResource]

  val module = finagleDefaultDesign
    .bind[MyResource].toSingleton
    .bind[Router].toInstance(router)
    .bind[FinagleServerConfig].toInstance(
      FinagleServerConfig(port = 8080))

  module.build[FinagleServer] { server =>
    server.waitServerTermination
  }
}

ひとまずこれで動作を確認することができました。

Finagleを直接使うのはまあまあしんどいので、普通のWebアプリっぽく実装できるのはいいですね。また、httpサポートとfinagleサポートのモジュールが分割されているので将来的に他のバックエンドで動かすということもできるようになるかもしれません。

JIRAのチケット番号をリンクに変えるChrome拡張が便利

f:id:takezoe:20180918232337p:plain

仕事でもOSS活動でもJIRAを使うことがよくあるのですが、JIRAのチケット番号を自動的にリンクにしてくれるChrome拡張を見つけたので試してみました。

chrome.google.com

リンク先のJIRAは以下のような画面で設定できます。

f:id:takezoe:20180918231942p:plain

簡単なものですが、GitHubGmailなど様々な場面でチケット番号がリンクになるのでなかなか便利です。ただ、リンク先のJIRAが1つしか設定できないので、プロジェクト毎にリンク先のJIRAを設定できるともっと便利になりそうです。

Docker/Kubernetes 実践コンテナ開発入門

職場では様々な用途にDockerが活用されていたり、今後はKubernetesを使いそうな気配もあるので予習しておこうと思い、最近出たばかりのこの書籍を購入してみました。

Docker/Kubernetes 実践コンテナ開発入門

Docker/Kubernetes 実践コンテナ開発入門

見た目は結構厚い本ですが、400ページほどなのでそこまで大ボリュームというわけではありませんが、DockerからDocker Compose、Docker Swarm、Kubernetes、そしてクラウドベースのマネージドサービスまでこの分量でよくまとまっています。ハウツー的な解説に加えて概念的な部分や、なぜこのような設定を行うのかといった部分がきっちり抑えられており、きちんと理解しつつ読み進めることができます。コラムも実践的で、著者の方の深い知識と実戦で培われたであろう経験に裏打ちされた良書だと思います。

唯一残念な点があるとすると索引でしょうか。索引を作ると言うのはなかなか面倒な作業で(大半は編集さんの仕事ですが)、自分が本を書くときは索引なんて適当で良いのではないかと思ってしまうこともあるのですが、読者の立場になってみると設定項目からページを引いたりと思いの外利用頻度が高かったりします。この本の索引はちょっと荒過ぎて使い物にならない感じです。ただ、電子書籍版であればテキスト検索でカバーできるかもしれません。

ともあれこれからDocker/Kubernetesを触ってみようという方はこれを読んでおけば間違いないのではないでしょうか。