FLINTERS Engineer's Blog

FLINTERSのエンジニアが綴る技術ブログ

BigQuery ML のモデル作成機能を使ってレポートの推移を予測してみた

TL;DR

本日はBigQuery MLの時系列モデルを用いてレポートデータの予測をしてみました。

時系列モデルを使ってモデルの作成からモデルの作成までをSQLライクにできて非常にかんたんでした。

今回はそちらのやり方・内容・料金面についてご紹介させていただきます。

完成したグラフはこちらです。

f:id:s_hayase:20210812124925p:plain

モデル作成してから利用するまでの流れ

今回はモデルを作成してから、100日後までの気温を予測しました。

-- モデルの作成
CREATE MODEL IF NOT EXISTS `sample_model` 
  OPTIONS(
      -- MODEL_TYPE: モデルのタイプを設定する。ここでは時系列データ。
      -- TIME_SERIES_TIMESTAMP_COL: トレーニングデータのタイムを指定しているカラム
      -- TIME_SERIES_DATA_COL: 予測する値のカラムを指定する。1つのデータしか予測できない。
      MODEL_TYPE = 'ARIMA_PLUS'
      , TIME_SERIES_TIMESTAMP_COL = 'date'
      , TIME_SERIES_DATA_COL = 'temperature'
) AS

-- トレーニングデータの指定
SELECT
    date,
    temperature
FROM
    `sample_reports`

下記のようにクエリ結果が出力されます。

処理時間は5.7 KiBで約12秒かかりました。

前に実験で100GBぐらいの処理を行ったときには約10分程かかりました。

f:id:s_hayase:20210812112310p:plain

モデルから予測値を取り出す方法は下記のようにFROM句にモデルを記載してあげるだけです。

-- 100日分の気温を予測する
SELECT
  date(time_series_timestamp) AS date,
  if(time_series_type = 'history', time_series_data, null) history_data,
  if(time_series_type = 'forecast', time_series_data, null) forcast_data,
  prediction_interval_lower_bound,
  prediction_interval_upper_bound
FROM
  ML.EXPLAIN_FORECAST( MODEL `sample_model`,
    STRUCT(100 AS horizon, 0.8 AS confidence_level) )

注意すべき点

クレンジングされたデータを使ったほうがいいです。

IDやタイムスタンプなどにNULLが入っているとモデル作成時にエラーが発生するので、モデルを作れませんでした。

モデル作成時のクレンジングに関してはKaggleのCause(英語)などが参考になると思います。

お金の話

BigQuery 料金をもとに計算しました。

  • CREATE MODEL
    • 時系列モデルの場合は $300.00 per TB。モデルタイプによって内容は変わってきます。
    • 無料枠は毎月 10 GB までの処理
  • 評価、検査、予測(すべてのモデルタイプ)
    • $6.00 per TB
    • 無料枠は毎月1TBまでの処理

またモデルや予測を実行すると下記のように、処理にかかるリソースの割合を示してくれます。

f:id:s_hayase:20210812112346p:plain
モデルの処理規模予測

今回の場合過去1年分の天気データを用いてモデルを作成した結果

  • CREATE MODEL: 5.7 KiB
  • 予測: 42.7 KiB

かかりました。

そのため、毎日1ヶ月モデルの再構築と予測をおこなった場合はおおよそ、

ぐらいで無料枠に収まりました。

逆に、数年分のデータ x IDの量が膨大になってくると料金は爆発的に伸びることが考えられるので、料金を落とすにはデータを集約したり不要なデータの排除を事前に行うことが必要になってくると思います。

感想

BigQuery MLに用意されたテンプレートの時系列モデルを活用することで、データの活用を効率的に行うことができました。

その予測値の精度に関しては確認していないので、ML.ARIMA_EVALUATEなどを用いて確認する部分が課題です。

セプテーニグループは大量のレポートデータを保持しているので、時系列モデルを用いたデータ活用に関しては非常にマッチしていると思いました。

3ヶ月の新人研修を終えて

こんにちは新卒の中澤です。これから入社するあなた、エントリーを検討しているあなたに向けて研修中どんなことを学んでいたのかを伝えられたらと思います。

目次

  • 目次
  • 自己紹介
  • 内定後〜入社前の様子
  • ついに始まった研修
    • Scalaは大学数学みたいだった
    • 顧客に対する考え方が変わった
    • コードを書くだけじゃない!
  • 研修でつけるべき今後も使える力
  • 自走できる人とできない人の違い
  • 最後に
続きを読む

2021年 新人研修の感想

 初めまして、2021年4月に新卒でFLINTERSに入社した服部です。

 この度3ヶ月の新人研修を終えたので、ざっと振り返って感想や学んだことを書いていこうと思います。

目次

続きを読む

3ヶ月のリモート研修を終えて

 こんにちは、2021年の4月から株式会社FLINTERSに入社した津布久です。自走できるエンジニアという目的のもと3ヶ月の研修を受けました。それについての感想を以下に述べていきたいと思います。

これまでの経験

 エンジニアとしての経歴についてですが、実務経験は全くありませんでした。プログラミングについてですが、大学3年の夏頃から自分で興味を持って勉強を始めました。入社時点での成果はRuby on railsぐるなびAPIを使って飲食店検索風サイトを作り、herokuでデプロイまで行ったことです。これについても理解をしてコードを書けたわけではなく、うまく噛み合わせて動かしたというものでした。それなのでプログラミング言語フレームワークは触ったことがあるが、AWSやDockerなどの技術を使ったことがない状態でした。

 研修前はあまり経験のない僕がこの先チームに貢献してプロジェクトを成長させる存在になれるか不安でした。

この記事について

 会社の企業サイトだけではなかなか見えづらい部分を伝えられたら良いなと思い、書きました。

続きを読む

業務アプリケーションのScala 3アップデートを試してみた

こんにちは。FLINTERSでTech Adviserをしています、OE(@OE_uia)です。

Scala 3.0.0が2021年5月14日にリリースされたことを受けて、いつアップデートするか検討中の方も多いかと思います。

実際、今FLINTERSではScala製プロダクトのScala 3.0.0へのバージョンアップを試みており、今回はその進捗をブログ記事化しました。

TL;DR

Play Frameworkplay-jsonを利用したプロジェクトでも、一部はScala 3.0.0にアップデート出来ることを確認しました。

Scala 3化を完遂するにはやや時期尚早な感があるものの、今回試してみてScala 2.13とScala 3の互換性に関する知見がたまり、OSSへの貢献もできました。

scala3-migrate-pluginで依存しているライブラリのScala 3対応状況について調査したり、社内ハッカソンなどでアップデートに取り組んでみるには良い頃合いではないでしょうか。

本時期の執筆にあたって

Scala 3開発の中心となったEPFLのScala 3 Team、Migration Guideやツールのサポートなどで中心的役割を果たしたScala Center、そしてScala 3.0.0対応に取り組んでいる、Lightbend社を含めた全てのFOSSメンテナ、コントリビュータの皆さんに感謝します。

そもそもの発端

2019/12/18 THE ROAD TO SCALA 3

2019年末記事では、「Scala 3が最もコミュニティから必要としているものは、皆さん一人一人に、ご自身のコードを移植してもらうことなのです。」(訳) *1」と言及されていました。しかし当時は、(以前実施した勉強会でも言及した通り)業務コードを移行するには、試す前に分かる大きな壁がありました。

2020/7/20 Scala 3 Migration Guide(continuation)

2020年夏頃に、Scala CenterからアナウンスされたScala 3 Migration GuideのMilestoneに「Scala CenterパートナーのEarly adapter企業がScala 3へ移行する。(訳)*2」という項目が盛り込まれました。

そろそろScala 3を業務プロジェクトで試しはじめる良い頃合いかな?と思いまして、2021年始め頃からFLINTERSの業務プロジェクトのScala 3移行を少しづつお試ししていました。

現在のステータス

現在FLINTERSでScala 3移行お試し中のプロダクトは、以下のようなサブプロジェクトで構成されています。

root -> application -> domain -> util

依存ライブラリのうち、Scala 3.0.0から利用可能なアーティファクトがpublishされてないものについていえば、rootがPlayScala Pluginを利用しており、applicationがplay-jsonに依存しています。

そのうえで現在のScala 3お試しステータスは、以下の条件付きですがほとんどのテストが通っています。

  • rootを除く、全てのサブプロジェクトのscalaVersionを3.0.0*3へアップデート
  • play-jsonJson.formatマクロを利用した一部テストの除外

説明のため、簡単にしたビルド定義ファイルは以下の通りです。*4

//project/plugins.sbt
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.8")
addSbtPlugin("ch.epfl.scala" % "sbt-scala3-migrate" % "0.4.4")
// sbt-scala3-migrate 0.4.4が依存するscalafixとsemanticdb-scalacプラグインのversionが古く、Scala 2.13.6をサポートしていないため
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.29")
#project/build.properties
sbt.version=1.5.4
val scala3Commons: Seq[Setting[_]] = Seq(
  scalaVersion := "3.0.0",
  scalacOptions += "-source:3.0-migration",
  libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.9" % Test
)

//build.sbt
lazy val root = project
  .enablePlugins(PlayScala)
  .settings(
    scalaVersion := "2.13.6",
    // sbt-scala3-migrate 0.4.4が依存するscalafixとsemanticdb-scalacプラグインのversionが古く、Scala 2.13.6をサポートしていないため
    semanticdbVersion := scalafixSemanticdb.revision,
    scalacOptions ++= Seq("-Ytasty-reader", "-Xsource:3", "-deprecation"),
    // 同一ライブラリの、Scala 3向けとScala 2.13向けアーティファクトを混ぜることはできないため、
    // Test/applicationに依存する場合は3向けアーティファクトを除く
    // libraryDependencies -= "org.scalatest" % "scalatest_3" % "3.2.9" % Test,
    libraryDependencies ++= Seq(
      "org.scalatest" %% "scalatest" % "3.2.9" % Test,
      "org.scalatestplus.play" %% "scalatestplus-play" % "5.1.0" % Test
    )
  )
  .dependsOn(application)

lazy val application = project
  .settings(scala3Commons)
  .settings(
    libraryDependencies ++= Seq(
      // `publishLocal` したplay-jsonのversion
      "com.typesafe.play" %% "play-json" % "2.9.2+118-f1769bd5-SNAPSHOT",
      ("com.typesafe.akka" %% "akka-stream" % "2.6.14")
        .cross(CrossVersion.for3Use2_13)
    )
  )
  .dependsOn(domain)

lazy val domain = project
  .settings(scala3Commons)
  .dependsOn(util)

lazy val util = project.settings(scala3Commons)

今回マイグレーションのためにやったこと

以下で、Scala 3 マイグレーションのためにやったことについて紹介します。

Scala 3 Migration Guideを読む

scala-lang公式サイトにて、Scala 3 Migration Guide が公開されています。

マイグレーションのために知っておくべき基礎知識がカバーされています。まずはざっとでもいいので、一読しましょう。ただし掲載されているScalaやsbt-plugin等のversionが最新ではない場合がありますので、そのときの最新版を使うとよいでしょう。

また、Scala 3 migrate pluginというsbt pluginを入れると、依存するライブラリのScala 3サポート状況や、互換性のビルド定義の変更方法の提示、ソースコードに含まれるScala 3非互換の構文のうち一部をScalafix ruleで修正する機能など、migrationに役立つ様々な機能が入っています。*5

Scala, sbt, Play Frameworkやその他ライブラリを、できるだけアップデートしておく

Scala 3開発に際し、TASTyという中間コードフォーマットが新規に開発されました。ファイル拡張子は .tastyで、Scala 3向けにビルドされたjarファイルの中に含まれ、配布されます。

このTASTyにより、Scala 3.0.0からScala 2.13.6、Scala 2.13.6からScala 3.0.0双方向について、マクロやScala 3の一部の新機能を除いたバイナリの互換性を達成しています。

このバイナリ互換性を実現するため、sbt(執筆時点で最新は1.5.4)は以下のような特別なハンドリングをしてくれるようになりました。

  • scalaVersionが "3.0.0" からはじまるときに、scala-libraryのartifactIdをscala3-libraryにして解決してくれる
  • scalaVersionが異なる(3.0.0と2.13.6など)サブプロジェクト間で依存(dependsOn)できる
  • Scala 3とScala 2.13の間で、バイナリを相互利用するためのイディオムが導入される
    • .cross(CrossVersion.for3Use2_13) を付与すると、Scala 3サブプロジェクトからでも、Scala 2.13のjarを解決してくれる

なおPlay Frameworkは以下のissueにより、Play 2.8.8以外はsbt 1.5.0以上でコンパイルすることができません。よってsbtを1.5.4に上げるには、Playも2.8.8に上げる必要があります。

https://github.com/sbt/sbt/issues/6400

以上の理由により、Scala 2.13.6, sbt 1.5.4, Play Framework 2.8.8にそれぞれアップデートすることは、Scala 3対応のためにも特に重要です。

その他のライブラリについても、Scala 3向けアーティファクトは最新のversionについてのみ提供されるケースがほとんどなので、あらかじめ最新近くまでアップデートしておくと良いでしょう。

サブプロジェクトの依存グラフの下流から、Scala 3にアップデートする

もともとScala 3.0.0はScala 2.13の構文の大部分は(警告が出るとしても)可能な限りそのままコンパイルが通るように設計されています。またScala 3には "-source:3.0-migration" というscalac optionが用意されており、このoptionをつけるとマイグレーションモードになって非互換な構文のほとんどはコンパイルエラーではなく警告対象となります。

そのため、業務プロジェクトのScala 3アップデートでコンパイルやテストを通すことを目指すなら、構文の非互換性よりもライブラリの対応状況の方がブロッカーになることが多いでしょう。つまり依存しているライブラリが少ないサブプロジェクトほど、アップデートが容易であると期待できます。

本プロダクトのサブプロジェクトの依存グラフは、以下の通りです。

(上流) root -> application -> domain -> util (下流)

そこで、サブプロジェクトの依存グラフの下流であるutilから、以下のように順番にScala 3へアップデートし、テストを通していきました。

サブプロジェクトごとに、依存ライブラリのScala 3対応状況について調べる

依存ライブラリのScala 3対応状況次第で、以下のような対応をしていきました。

  1. Scala 3対応のアーティファクトがある場合はそれを使う
  2. Scala 2.13対応アーティファクトがマクロを利用しており、Scala 3対応はmain branchで完了しているもののpublishされてないときは、local publishして使う
  3. Scala 2.13対応アーティファクトがマクロを利用していない場合は .cross(CrossVersion.for3Use2_13) を指定して使う
  4. Scala 2.13対応アーティファクトがマクロを利用しており、Scala 3対応が完了していない場合は、依存サブプロジェクトをScala 2.13のままにする。

Scala 3対応アーティファクトの有無、マクロの利用有無については、今はscala3-migrateプラグインmigrate-libs コマンドを利用するのが最も簡単だと思います。

sbt> migrate-libs application
[info] com.typesafe.play:play-json:2.9.2                   -> X : Contains Macros and is not yet published for Scala 3.
[info] com.typesafe.akka:akka-stream:2.6.14              -> "com.typesafe.akka" %% "akka-stream" % "2.6.14" cross CrossVersion.for3Use2_13 : It's only safe to use the 2.13 version if it's inside an application.
[info] "com.typesafe" % "config" % "1.3.3"               > Valid : Java libraries are compatible.

play-jsonの対応を保留する

play-jsonは、執筆時点でScala 3向けアーティファクトがpublishされていませんが、mainブランチはScala 3に対応済です。

ただし最新のmainブランチをlocal publishしたとしても、今回のようにScala 3と2.13を混ぜたプロジェクトでは Json.format を利用しづらい状態です。例えばplay-jsonJson.format 実装を利用したコードがあるとして:

case class A(a: String)
implicit val formatA: Format[A] = Json.format[A]

このA、formatA、そしてformatAを利用するコードがScala 3 / Scala 2.13の境界をまたぐと、以下の2つの理由により殆どのケースでコンパイルエラーに遭遇します。*6

  • Scala 3のcase classのコンパニオンオブジェクトのunapplyメソッドのシグネチャScala 2.13と異なること
  • Scala 2.13のJson.formatのマクロ実装が、コンパニオンオブジェクトのunapplyメソッドを利用すること
  • Scala 3マクロをScala 2.13から直接呼び出す、もしくはその逆はできないこと

もちろんJson.formatを利用しないコードに書きかえればテストを通すことも可能ですが、そもそも全サブプロジェクトをScala 3にアップデートすれば解決できる問題です。

今回は???-Yignore-scala2-macros*7などを利用してコンパイルだけ通し、テストを対応させることは保留しました。

マイグレーションモードでもコンパイルエラーとなる非互換の構文を直す

今回取り組んだプロジェクトでは、パッケージ名やメソッド名の一部に export という単語が使われていました。

Scala 3では export は予約語になっており、マイグレーションモードであってもコンパイルエラーとなりますので、いったん exports にrenameしました。

依存しているライブラリのバグを踏んだら報告する、もしくは直す

Scala 3.0.0へアップデートする際、Scalaやライブラリのバグに遭遇することもまだまだ多いです。

今回はScalaについていえば、-Ytasty-reader絡みの複数のバグ*8*9に遭遇したため、それぞれワークアラウンドのコードを追加しました。

また、ScalaTestのDiagramsのマクロ関連バグに遭遇し、以下のように報告や修正を行いました。修正版はまだpublishされていないため、今は with Diagramsコメントアウトしています。

github.com

github.com

総括

現在FLINTERSでは、一部の業務プロダクトのScala 3アップデートを試みていますが、道半ばです。

Scala 3へのmigrationは、そのプロジェクト依存しているライブラリやフレームワークによって難易度が変わりますが、総じて未解決のバグに遭遇することも多い状態だと思います。

今回のプロダクトのようなPlay Frameworkやplay-jsonを利用したプロジェクトでは、Scala 3.0.0移行を部分的にでも完遂するには、時期尚早な感がありました。しかしながら、今回migrationを試したことでScala 2.13 <-> Scala 3の互換性に関する知見がたまり、また幾つかのOSSへの貢献につながりました。

Scala 3.0.0-M1当初から比べてマイグレーションガイドやツールも充実してきましたので、scala3-migrate-pluginで依存ライブラリの対応状況を調査したり、社内ハッカソンなどでアップデートに取り組んでみるには丁度よい頃合いではないでしょうか。

そしてScala 3へアップデートするためには、Scala 2系、sbt、そしてPlay最新版へのアップデートはどのみち必要になります。今すぐ取り組むことができて、比較的簡単に恩恵を受けることができます。まだの方は今のうちにやっておきましょう。

*1:原文: "The biggest thing Scala 3 needs from the community is for everyone to begin porting their code."

*2:原文: "M4 - Early adopter company partnered with Scala Center" "The Scala Center will partner with companies that are willing to migrate to Scala 3."

*3:執筆時点の最新の3.0.1-RC2についても試しています

*4:実際には他にも様々なライブラリやpluginが入っていますが、今回のScala 3 migrationにはあまり関係ないため割愛します。

*5:今回のブログ執筆にあたっては、Scala 3 migrate pluginの開発が進む前に色々試してしまったので、実は自分の場合はあまり活用する機会に恵まれませんでした。しかし、これからScala 3 migrationを試す人にとっては大きな助けになると思います。

*6:例外として、Scala 2.13に定義されたクラスAに対して、Scala 3側でformatAを定義し、かつ利用するパターンなら使えます。

*7:

*8:https://github.com/scala/bug/issues/12369

*9:https://github.com/scala/bug/issues/12409