ITANDI TECH BLOG

イタンジのスタッフブログです。イベントや技術情報などを発信しています。

digdag+embulkを使ってテーブル毎にカラムを設定してBigQueryに流し込んでみた

横澤です、平素より格別のご高配を賜り、厚く御礼申し上げます。

先日DWH系のネタについてAthenafluentdの二つを書いたのですが、結論としてdigdag+embulkという構成に落ち着いてしまったのでここにご報告させて頂きます。

ここに至った細かい経緯は本記事の最後にまとめておきますので、まずは設定ファイル例を公開しようと思います。なお、digdagやembulkの細かい使い方についてはWEB上に良記事があるので割愛します。

まずメインの呼び出し先となる.digファイルです、定期実行タイミングをscheduleで登録し、!includeを使って登録DBの接続情報を外に出してgitignoreで管理しています。digdagにはsecret機能もあるのですが、server利用でないと扱えないようなので取り敢えずこのような形でgithubに上がらないように逃しています。

timezone: UTC

schedule:
  daily>: 10:00:00

+main:
  !include : my-db.dig

step1では全カラムを転送するテーブルを並べています、embulk用のymlファイルについては後ほど解説します。

  +step1:
    for_each>:
      table: [
        users
        , products
      ]
    _do:
      embulk>: mysql_to_bigquery.yml

step2では全カラムではなく、特定のカラム指定や特定の行指定を行いたいテーブルをembulk側のqueryで設定しています。データ量が大して大きくないテーブルならば特に考えずに全カラム、全行を投げても良いのですが、データ量がそこそこ大きいテーブルで全部やってしまうと稼働するインスタンスのコストが増加してしまいます。重厚長大なテーブルについてはMECEにデータを指定する事で転送用のインスタンスコストを抑えつつ転送速度を確保させています。

  +step2:
    embulk>: mysql_to_bigquery_orders.yml

続いては接続設定用の.digファイルです、これはdigdagワークフロー内で変数を展開するexport機能を使って指定するだけです。

_export:
  host: 'localhost'
  user: 'me'
  password: 'my-pass'
  database: 'my-database'
  project_id: 'my-projectid'
  dataset: 'my-dataset'

最後にembulk用のymlファイルです、digdag側から変数設定されて呼び出すのでシンプルにワンファイルで住むのが魅力的ですね。またbigqueryの転送にはembulk-output-bigqueryを使っているのですが、テンポラリテーブルを作ってスキーマに合わせたloadをしてくれるので楽且つ安全で嬉しい感じです。

in:
  type: mysql
  host: ${host}
  user: ${user}
  password: '${password}'
  database: ${database}
  table: ${table}
out:
  type: bigquery
  mode: replace
  auth_method: json_key
  json_keyfile: ./my-bigquery.json
  project: ${project_id}
  dataset: ${dataset}
  auto_create_table: true
  table: ${table}
  allow_quoted_newlines: true

また.digで設定したstep2用のembulk設定ファイルを用意します。こちらでは前述したデータ範囲を指定する為にembulk-input-mysqlのqueryキーワードを使って取得カラムや範囲を指定しています。

in:
  type: mysql
  host: ${host}
  user: ${user}
  password: '${password}'
  database: ${database}
  query: |
    select id,user_id,product_id from orders
out:
  type: bigquery
  mode: replace
  auth_method: json_key
  json_keyfile: ./my-bigquery.json
  project: ${project_id}
  dataset: ${dataset}
  auto_create_table: true
  table: orders
  allow_quoted_newlines: true

embulk-input-mysqlにはselectやwhereといった設定キーワードもあるのでもっと綺麗な感じに出来ないか試行錯誤していたのですが、取り敢えず動かす事を目標に泥臭く書いています。embulkプラグインのオプションは豊富に用意されているのでよく読むと様々な使い方を発見できそうです。

そもそもAthenaは「使ってみた」という感じなのでDWHとしての本番採用は最初から考えておらず、この業界特有の雑多なCSVを良い感じにリレーショナルDB的に扱えるかどうか試してみる、という目的で使っていました。 fluentdについてはしばらく運用していたのですが、バッファリングサーバーの可用性や拡張性を維持するのが結構大変で、途中で運用を止めたりデータを減らしたりと思うような運用が出来ておりませんでした。 しかし、よくよく考えると今回のDWHはアドホックな分析に使われる目的のみを期待されているので、リアルタイムにデータ連携される必要はなく、fluentdによるストリーミング処理は廃止してembulkによるバッチ処理に切り替える事を決めました。 embulkをshellなりコマンドで呼んでも良いのですが、複数DBやテーブル毎に微妙にカラム構造を変えたい、という要件があったのでワークフローエンジンを使って上手い事出来ないかを調べたらdigdagがどうやら良さそうだったので、取り敢えずdigdag+embulkで構成を作ってみました。 現在のところはこの構成で上手く回っており、redashからもGBQに接続出来るようにしたので今まではエンジニアでなければ出来なかった(エンジニアでもかなり面倒でした)サービスを横断したデータ取得が出来るようになりQualityOfDataLifeが向上したような予感がしています。

イタンジでは多くのデータを活用してサービスのグロースにコミットするプロダクトエンジニアと、探索的にデータを活用して全く別の兆候を発見する機械学習エンジニアを募集しております!

AWS Data PipelineとAthenaを使ってお手軽DWHを3分クッキング

横澤です、いつもお世話になっております。

本当は5月に書こうと思っていたネタなのですが、最近は時空の歪みの影響を強く受ける事があり気づいたら6月になってしまいました。

本題です、本エントリではData Pipelineを使ってRDSデータをS3にエクスポートし、去年のre:inventで発表のあったAthenaを使ってDWHっぽいものを作ってみるエントリです。Athenaについては説明記事やチュートリアル記事が沢山あるので気になったらググって見て下さい。Data Pipelineはデータ処理に特化したワークフローエンジンの様なサービスです。名前の通りデータの抽出や加工、集約等についてAWS内部の様々なリソースを活用してワークフローを組み立てる事が出来ます。今回はRDS(MySQL)に入ってるデータをCSVファイルとしてS3に吐き出す部分をData Pipelineで実行し、Athenaで読み込めるようにしてみました。

【最初の1分:ジョブの登録】

RDSからS3にCSVを出力する処理はテンプレートが用意されているので簡単に作成できます。 テンプレートを選択したらDBへの接続情報、バッチスケジュール、S3のバケットを指定すれば初期設定は完了です。

このタイミングで幾つか注意点があります。

  • Athenaは現時点(2017-06-08)ではUSリージョンのみサービス提供となっているので、読み込み先となるS3バケットもUSリージョンに作成する必要があります。

  • Data Pipelineは「DataPipelineDefaultRole」と「DataPipelineDefaultResourceRole」というロールを使うのでこれらにS3やRDSへのアクセス権を付与する必要があります

  • データエクスポートを実行するEC2インスタンスについて、デフォルトだとt1.microが選ばれていますがt系インスタンスVPC上への作成がデフォなので接続先がVPCに置かれていない場合はm系のインスタンスを使うなどの工夫が必要です

【間の1分:ジョブの編集】

登録されたジョブはこのような感じでワークツリーで可視化されます、それぞれのタスクを選択すると右側でconfigを修正する事が出来ます。 RDSでsubnetを設定している場合はEc2Resourceタスクを開いてオプションでsubnet-idを設定しないとconnectionエラーになってしまいます。またEC2からRDSへ上手く接続出来ない時にはデバッグ目的でEC2にsshで入りたくなる事もあるのでキーペアも登録しておくと後々幸せになれるかもです。

【最後の1分:Athenaでクエリ発行】

これでジョブは登録されたのでスケジュールをon-demandに設定してactivateするとワークフローが実行され、成功するとS3にCSVが吐き出されます。ワークフローエンジンらしく各ジョブはこんな感じで結果成否やログが見れるのでハマった時も修正がかけやすいです。 最後にAthenaのQueryEditorを使ってHiveQL形式のCREATE TABLE文を発行すればDWHっぽいテーブルの完成です!一点ハマったポイントとしてLOCATIONは「s3://[バケット名]/[パス名]」と指定しなければならず、当初はリージョンURLを含めて指定していたせいでエラーが起きてました・・・

以上で3分クッキングは完了です、実際にはもっと時間かかりましたが本記事を読んで頂く事で3分くらいで作れるようになると嬉しい限りです。そしてここまで書いておいてなんですが、イタンジではDWHっぽい事を実現するツールとしてはGoogle Big Queryを使っており、最近はDigdagというワークフローエンジン経由でembulkを動作させてData Pipelineと似たような事をやっています。なので今回はあくまでもData PipelineとAthenaを実験する目的でやってみた的なネタなので実運用するとどうなるかは未知数だったりします。

イタンジ株式会社ではこのようなデータエンジニアリングにテンションが上がるエンジニアや、集約管理されたデータを使って探索的にデータ解析したいエンジニアを募集しております。

railsのactive jobでGoole Cloudのpub/subを使う

こんばんは、エンジニアの福崎です。 最近仕事でGCPを使ってます、AWSに比べて情報は少ないけど意外にドキュメント揃っているので ドキュメント読めば簡単なチュートリアルは動かせちゃいます。 まだネットに情報少ないので個人的には公式ドキュメントを読み漁るのが一番早かったです。 今日はrailsのactive jobでGAEのpub/subを使ってみたのでメモがてらブログに残します。

Pub/Subを使った背景

active job最初はいつもどおりsidekiqでやろうとしたんですがGCPにはフルマネージドなredisが無いみたいなので 自分でサーバー立てて管理するのはちょっと。。と思っていたらPub/Subに行き着きました。

Pub/Subとは

Cloud Pub/Sub はフルマネージドのリアルタイム メッセージング サービスで、個別のアプリケーション間でメッセージを送受信できます。

今回はこちらのメッセージングサービスにjobをキューイングしていきworkerでpullして処理します。

日本語チュートリアルもあるので読んでおくと良いと思います。

たこの内容はこちらの公式ドキュメントをベースにした内容になってます。

完成物

ここに置いてます。

前準備

pub/subを有効にする

GCPで新しいプロジェクトを作ってpub/subをapi managerから有効にしておいてください。(デフォが無効になってます)

rails環境の準備

今回はrails 5.1でやってます

認証ファイルの取得

GCPにログインして「API Manager」→「認証情報」→「認証情報を作成」→「サービスアカウントキー」→「GAEと選択」→「jsonを選択」→「作成」保存したjsonファイルを /config/pub-sub-sample-auth.jsonに設置してください。

project idの記述

/config/settings.ymlを作りproject_idにGCPのproject idを入れてください。

development:
  project_id: <your project_id>
  auth_file: pub-sub-sample-auth.json

実装

gemを入れる

Gem Fileにpub/sub用のgemを定義してbundleで入れておきます。

gem 'google-cloud-pubsub'

APIドキュメントがあるので目を通しておくと良いです。

application.rbの設定

/config/application.rbに以下の定義を追記します

# 今回作るadapterを使うよう定義します
config.active_job.queue_adapter = :pub_sub_queue
# libの下にadapterとか置くのでautoloadの対象にしておきます
config.autoload_paths << Rails.root.join('lib')
# GCP周りの設定をsettingファイルに定義するので
config.x.settings = Rails.application.config_for(:settings)
# GCPにログ吐くようにします
if Dir.exist?('/var/log/app_engine/custom_logs')
  config.logger = ActiveSupport::TaggedLogging.new Logger.new('/var/log/app_engine/custom_logs/application.log')
end

adapterの定義

今回の本丸です。 /lib/active_job/queue_adapters/pub_sub_queue_adapter.rbを以下の内容で作成します。

require 'json'
require 'google/cloud/pubsub'

module ActiveJob
  module QueueAdapters
    class PubSubQueueAdapter

      def enqueue(job)
        Rails.logger.info "[PubSubQueueAdapter] enqueue job #{job.inspect}"

        topic = PubSubQueueAdapter.pubsub.topic(job.queue_name, autocreate: true)

        topic.publish(job.class.name, arg: job.arguments)
      end

      class << self
        def pubsub
          @pubsub ||= begin
            project_id = Rails.application.config.x.settings['project_id']
            Google::Cloud::Pubsub.new(
              project: project_id,
              keyfile: "#{Rails.root.join('config')}/#{Rails.application.config.x.settings['auth_file']}"
            )
          end
        end

        def run_worker!(queue_name = 'default')
          p 'Running worker'

          topic        = pubsub.topic(queue_name, autocreate: true)
          subscription = topic.subscription("#{queue_name}_task")

          topic.subscribe("#{queue_name}_task") if subscription.blank?

          subscription.listen(autoack: true) do |message|
            message.data.constantize.send(:perform_now, *JSON.parse(message.attributes['arg']))
          end
        end
      end
    end
  end
end

workerタスクの作成

後はいつもどおりjobを定義します。 app/jobs/sample_job.rb

class SampleJob < ApplicationJob
  queue_as :default
  def perform(num)
    p '==================='
    p "#{num} sample job executed!"
    p '==================='
  end
end

そしてjobを発行するrakeタスクを用意 lib/tasks/sample_job.rake

desc 'issue job'
task issue_job: :environment do
  (1..5).each do |num|
    SampleJob.perform_later(num)
  end
end

早速キューイングしてみましょう

bundle exec rake issue_job

最後にworkerを起動するとキューイングしたjobが処理されていくのを見ることが出来ます

bundle exec rake run_worker

まとめ

いかがでしたか? sidekiq程高機能ではないので時間を指定しての実行などは出来ませんが 単純にworkerの機能がほしいのであればこれでも十分です。 GCPを使う際はpub/subを是非使ってみて下さい!

今回はlocalで動かしたので次回はこれをGAEにdeployする方法を書きたいと思います

WebpackをRailsに導入する方法を比較する: 後編

9a5b0608-318e-5e20-110c-d98fa941d784

こんにちは、エンジニアのケントです。

前編の解説により、webpackerが提供する方法論は

  • js環境構築がとても楽である
  • webpackerが提供する環境に開発がロックインされる

ということがわかりました。

後半ではWebpackとRailsを独立して使う方法を解説し、rubygemのWebpackerを使う方法との比較をしようかと思います。

WebpackとRailsを独立して使う

この方法を実装するにあたってはCookpadさんのブログを参考にさせていただきました。

まず、ルートディレクトリに frontend というディレクトリを作ります。

├── app
│   └── assets
│
├── lib
│   └── tasks
│
└── frontend

そして

├── app
│   └── assets
│
├── lib
│   └── tasks
│
├── frontend
│   ├── src
│   ├── .babelrc
│   ├── .eslintrc
│   ├── package.json
│   ├── webpack.config.js
│   ├── webpack.config.prod.js
│   └── yarn.lock

このように frontend ディレクトリにwebpackの開発に必要なコードを全て詰め込み、frontend/src ディレクトリに主なソースコードを書くスタイルです。

webpackerでは開発環境を定義するファイルが自動生成されましたが、今回の場合は開発環境を定義するファイルを .babelrc .eslintrc webpack.config.js webpack.config.prod.js などといったファイル群によしなに自作していく必要があります。

また、frontend ディレクトリにwebpackによるjsの環境を詰め込んだので、Railsのルートディレクトリからwebpackを起動するコマンドを用意したくなります。なので

├── app
│   └── assets
│
├── lib
│   └── tasks
│       └── webpack.rake
│
├── frontend

といったrakeコマンドを lib/tasks ディレクトリに追加してあげましょう コマンドの中身は

namespace :webpack do
  task build: :environment do
    begin
      result = exec('cd frontend && yarn run build')
    rescue
      print result
    end
  end
end

といった感じです。必要に応じてコマンドはよしなに生やしてください。

そして、最後にwebpackによる成果物の配信先を作る必要があります。

├── app
│   └── assets
│       └── javascripts
│           └── frontends
│
├── lib
│   └── tasks
│       └── webpack.rake
│
├── frontend

配信後は javascript_include_tag で任意のファイルを読み込んでください。

webpackでファイル圧縮をするのでアセットパイプラインは必要ないかもしれませんが、必要が生じた場合はマニフェストファイルも app/assets/javascripts の下に置きましょう。

あとは rails srake webpack:build コマンドをそれぞれ別コンソールから打てば開発をスタートすることができます。

必要に応じて webpack-dev-server をrakeコマンドから叩いてもいいですし、別々のコンソールを開くのが面倒な場合は foreman の導入も検討してください。

デプロイする場合は、capistrano

task :build do
  yarnでnode_modulesを作るコマンド
  webpackのコマンド
end

Rake::Task["assets:precompile"].enhance(%i(build))

と追記しておけば、precompile時にwebpackのコマンドをhookできます。 (この部分はこの方の記事を参考にしました)

環境によってはyarnが環境変数をうまく読んでくれないことがあるので、その場合は上記のコマンドに

'export NODENV_ROOT="nodenvの位置"',
'export NODENV_VERSION="nodeのバージョン"',
'export PATH="$NODENV_ROOT/bin:$PATH"',
'eval "$(nodenv init -)"',
'export PATH="$PATH:$(yarn global bin)"',

と行った環境変数を読むコマンドを泥臭く追記してください。

まとめ

Webpackerを使う方法とWebpackとRails別にする方法の pros & cons をまとめると

  • Webpacker
    • pros
      • 全自動でWebpack環境が作られる。今現在Webpackの知識が少なくても始められる
    • cons
      • Webpackerのレールから外れたくなると相当のWebpackの専門知識と覚悟が必要とされる
  • WebpackとRailsを独立して使う
    • pros
      • 自分のWebpackの知識の範囲内で環境を作れるので、後々のメンテナンスが楽になる
    • cons
      • 最初の段階でWebpackの知識がそれなりに要求される

といった印象でした。

WebpackをRailsのプロダクトに組み込みたい人の判断の助けになれば幸いです。

WebpackをRailsに導入する方法を比較する: 前編

Unknown

こんにちは、エンジニアのケントです。

2017年のフロントエンドの開発において、Webpackが大人気です。

イタンジのプロダクトはRailsで開発することが多いのですが、WebpackをRailsに導入することを考える場合、大きく分けて二つの選択肢が考えられます。

  • rubygemのWebpackerを使う
  • WebpackとRailsを独立して使う

直近で二つのイタンジのRailsプロダクトにWebpackを組み込むお仕事をし、上記の両方を試してみたので、それぞれの導入方法と雑感、pros and consをまとめてみようと思います。

rubygem webpackerを使う

導入方法

webpackerは、Rails5.1から標準で導入されるwebpackのラッパーです。 導入方法はwebpackerのreadmeにだいたい載ってるので、詳細はそちらにお任せしますが、結論だけ説明すると

  1. gem 'webpacker', github: 'rails/webpacker' をgemfileに追記する
  2. bundle install する
  3. bin/rails webpacker:install コマンドを打つ

たったこれだけでwebpackによる開発をスタートすることができます。

webpacker導入後、Railsディレクトリ構成が変わりますが、ざっくりいうと以下のような構成が追加されます。

├── app
│      └── javascript --①
├── config
│      └── webpack   --②
├── public
│      └── packs        --③

webpackerでは、①で主なソースコードを書き、②で開発環境を定義し、③に開発の成果物を配信する形になります。

雑感

しかし、この②の部分がなかなかの曲者でして、なかなか複雑なディレクトリ構成となっています。

├── config
│      └── webpack --②
│               ├── configuration.js
│               ├── development.js
│               ├── development.server.js
│               ├── development.server.yml
│               ├── loaders
│               │         ├── assets.js
│               │         ├── babel.js
│               │       ├── coffee.js
│               │        ├── erb.js
│               │         ├── react.js
│               │         └── sass.js
│               ├── paths.yml
│               ├── production.js
│               ├── shared.js
│               └── test.js

このように開発環境を定義するファイルが乱立しているため、より込み入ったフロントエンドの開発をしたくなったタイミング等で開発環境を変更しようとした際に苦労する印象です。

(後編で解説しますが、単純にWebpackによるjs開発環境を用意するだけならこのような複雑な構成にはなりません。) とはいえモダンなjs開発環境をgemを入れてコマンドを打つだけで用意できるのは大変素晴らしいと思いました。

後編はWebpackとRailsを独立して使う方法を解説し、rubygemのWebpackerを使う方法との比較をしようかと思います。

「AIは不動産業界の敵か味方か?」不動産テック最新事例セミナーVol.1を開催しました!

現在、「フィンテック」(金融×IT)の次は「不動産テック」と言われるなど、ビッグデータ解析、AI(人工知能)などを活用し不動産業界にイノベーションを起こそうとする「不動産テック」に対して、注目が集まっています。当社は、今後ますます不動産業界のIT活用が進み、業界全体が発展することを目的として、不動産仲介会社、不動産管理会社を対象とした「不動産テック」に関するセミナーを開催することとなりました。参加者の皆さまに、身近でリアルな事例を知ってもらいたいという思いのもと、記念すべき第1回には、株式会社S-FIT代表取締役社長、紫原 友規様にお越しいただきました。

その一部をご紹介します。

はじめてAIと出会ったのは10年ぐらい前、23歳の時だった?

 まずはじめに、代表の伊藤が登壇し、自身がAI(人工知能)に関心を抱いたきっかけについて語りました。 「10年ぐらい前、当時23歳だった私は、三井不動産レジデンシャルリースに勤めていました。当時は社内でもエクセルが得意な方だったということもあり、自分でマクロを組んで賃料査定をしていました。現在のテクノロジーとは全然レベルが違いますが、その頃から『こういった作業を人ではなく機械が出来るようになれば、効率化出来ることがたくさんあるのでは?』と感じていました。今では実際にAI技術が具体的に活用され効率化が進んでおりますが、当時から必ずしも人が作業をする必要なく、機械で効率化出来ることを考えていましたね。その後、仲介会社を立ち上げる中で再度、『物件の入力などがもし機械によって自動的になったら、不動産取引がもっと円滑になるのではないか』と思いました。これらの経験が、イタンジを立ち上げるきっかけにもなりました。」

 その後は国内外の不動産テックの事例について、自社サービスのデモも含めて紹介をしました。  「今は不動産取引のデータをクラウド上に蓄積する時代。そのデータを活用することでさまざまなことが自動化される、つまりAIが本当に活躍する時代が来る」という話で締めくくりました。

不動産テック勉強会伊藤

集客専門チームを作ることで1人あたり月間300反響の対応が可能に!

続いて、S-FIT代表の紫原様から「IT戦略や働き方改革」についてお話いただきました。「お陰さまでS-FITの業績は好調です。売上は14年連続で増収しております。このような状況でも課題なのは、優秀な人材を確保すること。どうずれば人を増やさずに業績を伸ばせるのか?悩んだ末の答えが仲介で一番大変な集客を営業から切り離す分業化スタイルでした。この時期にイタンジと出会い、そこにAIを導入することで、多い時では一人で月間300反響の対応が可能になりました。」 「現状はまだ、どこをAIでやるのかはっきりしていない。でも、本当に正社員がやるべきなのか?、パートに任せるのか?、それともAIなのかを考えるようにしている」等、興味深いお話をしていただきました。

不動産テック勉強会紫原様

AIにさせたい業務とは?将来どのような業務がAIに置き換わるのか?

 パネルディスカッションでは、「AIにさせたい業務とは?将来どのような業務がAIに置き換わるのか?」等の質問がなされました。伊藤は「身体を使うこと、課題発見以外はAIにやらせたい。特に管理の領域はデータが豊富なので、家賃査定、入居者の審査、入居者とのコミュニケーション等、可能性が非常にある。」等と答えました。  対して紫原様からは、「今でも、クロージングに遠いものはAIでもできる。繁忙期の業務量、残業減らすのに役立っている。でも最終意思決定に関わるものはやっぱり人。」等のお話をしていただきました。  その他、「今後、業界で活躍するのはどのような人材か?」、「システムを自社で持つべきか」等の質問に対して、非常に活発な議論が交わされました。

不動産テック勉強会

最後は、参加企業者の方々と懇親会を開催しました。ビールを飲みながら、今後の不動産業界の未来について、熱く語り合いました。

これからも「不動産テック最新事例セミナー」を定期的に開催していきます!

次回は4月25日(火)です!不動産会社の皆さまのご参加を心からお待ちしています!申込は、下記のページからお願いいたします。

http://www.itandi.co.jp/seminers

機械学習エンジニア育成プログラムを外部にも公開しました

こんにちは、エンジニアの建三です。

前回のブログ機械学習エンジニア社内育成プログラム(MLTP)のお話をしました。開始から一ヶ月程経ち、大分コンテンツが溜まってきました。社内のエンジニアからも分かり易いと好評です。

そこで今回MLTPを外部にも公開することにしました

僕が機械学習を勉強していた時はとても苦労しました。何故苦労したかというと、世に出てる機械学習のコースや本は大抵理論的過ぎるか理論を飛ばしています。

大学で使うような教科書は大まかなコンセプトを理解するには良いんですが、大抵数式で説明されているので、僕のように数式だけでなくコードを読んでアルゴリズムを理解するような人には中々辛いです。

逆に実践書だと理論をすっ飛ばしてコードを書くので、理解出来ないまま終わることが多いです。

MLTPは理論を数式で説明するだけでなく、コードを書きます。しかもただコードをコピペするのではなく問題形式になっているので理解が深まります。答えがちゃんと載っているので、もしつまづいても問題ありません。

研究者を目指すほどではないがある程度の理論は知っておきたいという方にオススメです。

もしやってみて質問やフィードバック等ありましたらgitterまでお願いします。