euphonictechnologies’s diary

Haskell超初心者の日記です。OCamlが好きです。

follow us in feedly

Google Cloud Datalabを使って未経験からKaggleのTitanicサブミットまで - その1 データラボを使えるようにする

Kaggleが、やりたいです

もうプログラマーである以上はいい加減Kaggleから逃げられないご時世になってきました。諦めてデータサイエンス覚えましょう。さもなくばクビです。

この記事はこんな人向けです

  • プログラミングはできる
  • Pythonは触ったことあるけどpythonのelse-if文の書き方忘れたなあ
  • Google Cloud Platformは触ったことねえ
  • Kaggleのアカウントだけ作った

なんでGoogle Cloud Platform?

セットアップが超簡単です。30分もあれば導入完了です。そのうち叩くコマンドは4つくらいです。簡単だね!

そして、BigQueryが使えます。今回は使わなかったけど。BigQuery使えるようになりたいですよね、deathよね。

あとAWS飽きた。

Kaggleのアカウント作りましょう

メールアドレスで登録するだけです。

Google Cloud Platformのアカウント有効にしましょう。

しましょう。

KaggleのTitanic問題に登録しましょう

f:id:euphonictechnologies:20180917121410p:plain

これです。

Google Cloud Platformで作業しましょう

プロジェクトを選ぶ

Google Cloud Platformのダッシュボードに移動して、プロジェクトを新規作成しましょう。

f:id:euphonictechnologies:20180917153924p:plain

左上に書いてあるやつが今ダッシュボードで作業中のプロジェクトです。名前をわかりやすくつけましょう。

ビリングを設定する

GCEは金かかりまっせ。支払い方法を設定しておきましょう。

支払い方法の追加、削除、更新  |  Cloud Billing のドキュメント  |  Google Cloud

APIをイネーブルする

APIをイネーブルします。APIを管理する画面を開いて、

f:id:euphonictechnologies:20180917154049p:plain

そこから追加画面を開きます。

f:id:euphonictechnologies:20180917154337p:plain

そこからAPIを選択できます。こんな感じ。

f:id:euphonictechnologies:20180917154443p:plain

最低限必要なのは

  • Compute Engine API

です。名前の通り必須です。他にも後々使いそうなのはBigQueryとかBigTableとか。いつでもこの画面で追加できます。必要な操作をやるとAPIが必要な場合はそういう風に怒ってくれるので必要になり次第API追加していきましょう。

GCE準備できたのでDatalabをはじめる。

f:id:euphonictechnologies:20180917154753p:plain

ここからコンソールを開きます。多分開いているページの下半分がコンソールになります。

f:id:euphonictechnologies:20180917155130p:plain

こんな感じ。

Datalabインスタンスを作成する。

簡単です。こんだけ。

datalab create --disk-size-gb 10 --machine-type f1-micro datascience-01a
  • --disk-size-gb : このVMインスタンスにくっつけるディスクのサイズです。20GBのディスクが自動でくっつくのでデータを保管する作業場所のサイズです。30GBまでならAlways Freeなはずなので、差し引き10GBを割り当てています。本当に無料になるかどうかは知りません。
  • --machine-type : f1-microを指定しています。これもAlways Free Eligibleなはず。本当に無料になるかどうかは(略)

datascience-01aはVMインスタンスの名前です。適当につけましょう。

途中でインスタンスをデプロイする場所を聞かれます。お金持ちは近い場所asiaとか、で、私のような貧乏人はus-のどこかAlways Free Eligibleな場所にしましょう。本当に(略)

最後に作ったDatalabを保護するためのSSHトンネリング用のパスワードを聞かれます。忘れないやつをつけましょう。

そんだけ!

Datalabでの作業を始める

開く

まずは開かないと話になりません。開きましょう。 2つ方法があって

  1. 表示されたURLをコピーしてブラウザにぶち込む
  2. ウェブプレビューボタンを活用する

私は最初1でやりました。それでいいと思いますが、2を覚えておくと便利です。

f:id:euphonictechnologies:20180917155830p:plain

このボタンを押して、開くと

f:id:euphonictechnologies:20180917155854p:plain

モーダルが出るので、8081に変更して開く。

f:id:euphonictechnologies:20180917155927p:plain

すると

f:id:euphonictechnologies:20180917160013p:plain

データラボじゃー!

フォルダとノートブックを作る

f:id:euphonictechnologies:20180917160259p:plain

この二つのボタンを使って作業場をこしらえます。私は既存のnotebooksフォルダの下にKaggleフォルダを作って、さらに問題フォルダを掘ってそこにノートブックを置きました。

f:id:euphonictechnologies:20180917160358p:plain

フォルダ構成は自由です。憲法にも保障されている基本的人権の一つです。適当にやりましょう。

フォルダはボタンを押したあとしばらくしてできあがります。反応無いからつって連打するとたくさんできます。我慢しましょう。フォルダは無名で作成されるのでRenameしましょう。

ノートブックを作ると空っぽのノートができるはずです。開きましょう。

ノートブックを開く

まあ、開けますよね。

問題を取り込む

kaggleツールをインストール

いろいろなやり方があると思いますが、私はクソめんどくさがりなのでノートブックから取り込みました。kaggleは専用ツールを公開していて、そいつを使うと問題データを簡単に取り込めます。kaggleの問題ページにこんな感じに表示されていると思います。

f:id:euphonictechnologies:20180917160738p:plain

これを使います。まずはツールをインストールしましょう。

f:id:euphonictechnologies:20180917160835p:plain

こんな感じでノート上でシェルコマンドが実行できます。

%%bash
pip install kaggle

実行しましょう。普通のpipコマンドが使えるので使いたいライブラリはこんな感じでどんどん足せます。

んで、このツールを使うにはツールが認証を突破できるようにしなければいけないので、kaggleのAPIトークンを発行しましょう。

kaggle APIトークンの取得

kaggleのページの右上に表示されているあなたのアバターの

f:id:euphonictechnologies:20180917161152p:plain

でアカウントページを開いて、

f:id:euphonictechnologies:20180917161253p:plain

ここからAPIトークンを取得します。トークンはユーザー名と暗号化トークンのふたつからなる辞書です。それをコピペしてDatalab上にファイルとしておいときます。

%%bash
echo '{"username":"oreno_user_name","key":"cdac546cacd78dc9ac7897dc....."}' > ~/.kaggle/kaggle.json

こんな感じ。

ファイルをダウンロード!

するとkaggleコマンドが使えるようになっているので、問題ページのやつをコピペしてシェルコマンドとして実行しましょう。

%%bash
kaggle competitions download -c titanic

これでノートブックと同じフォルダにファイルが置かれているはずです。

f:id:euphonictechnologies:20180917161549p:plain

こんな感じ!

中身を確認

少し中身をのぞいてみましょう。実際にこれから作業していくのはトレーニングデータtrain.csvです。

from google.datalab import Context
import google.datalab.bigquery as bq
import google.datalab.storage as storage
import pandas as pd
try:
  from StringIO import StringIO
except ImportError:
  from io import BytesIO as StringIO

train_data = pd.read_csv('train.csv')
train_data.head(4)

こんな感じでデータ、見えました?

f:id:euphonictechnologies:20180917161856p:plain

今回はここまで

長くなってきたのでここら辺で。次回は実際に読み込んだデータで問題に取り組みます。

Rails - administrateはどうやってナビゲーションバーを作っているのか

ごく

私的なメモです

ナビゲーションバーはどこへ?

./vendor/bundle/ruby/2.4.0/gems/administrate-0.8.1/app/views/administrate/application/_navigation.html.erb

どんなコード?

administrate/_navigation.html.erb at master · thoughtbot/administrate · GitHub

<nav class="navigation" role="navigation">
  <% Administrate::Namespace.new(namespace).resources.each do |resource| %>
    <%= link_to(
      display_resource_name(resource),
      [namespace, resource.path],
      class: "navigation__link navigation__link--#{nav_link_state(resource)}"
    ) %>
  <% end %>
</nav>

Administrate::Namespace.new(namespace).resourcesの中身は?

./vendor/bundle/ruby/2.4.0/gems/administrate-0.8.1/lib/administrate/namespace.rb

どんなコード?

administrate/namespace.rb at master · thoughtbot/administrate · GitHub

module Administrate
  class Namespace
...
    def routes
      @routes ||= all_routes.select do |controller, _action|
        controller.starts_with?(namespace.to_s)
      end.map do |controller, action|
        [controller.gsub(/^#{namespace}\//, ""), action]
      end
    end

で、resourcesroutesをResourcesオブジェクトに仕立てたもの。

つまり?

routes.rbに書いてあって名前が"admin"ではじまるコントローラのくっついているroutesをナビゲーションバーに表示するよ!

routes.rbから消したらエラーになるんだけど?

普通はroutes.rbのnamespace admin以下のresourcesを消せばそれはナビゲーションバーから見えなくなる。 ただ、rootが設定されている場合はそれがadminではじまるコントローラ扱いになってroutesに残ってresources消したからshowかなんかが見えなくなってpathとれなくてエラー吐く。

rootもあわせて変更すること!

RustでCのライブラリを頑張って呼ぶ

何がしたいの?

daala(https://github.com/xiph/daala)動画コーデックライブラリをRustにポートしたいのです。Rustで開発した方がきっと清潔でスピードも早いのでは!ではでは!

そこでまずは

daalaのライブラリに付属の例題であるところのexample_encode.cのRust版を作ってみましょう。これはコマンドラインで使えるエンコーダです。

開発環境を整える

MacにVSCodeプラグイン入れるのが快適です。素直にRustプラグインとRust(rls)プラグイン入れましょう。どっちも入れといて大丈夫っぽいです。 RustのインストールはRust公式の通りで大丈夫そうです。

daalaをGitHubからクローンしてmakeする

これも簡単です。書いてあるとおりにautogen.shして./configureしてmakeしましょう。

プロジェクト作る

> cargo new daara --bin

としましょう。以下、xiph.orgのCで書かれたライブラリをdaala、今から作るものをdaaraと呼びます。laとra(rはRustのrです)の違いです。la〜。シンガポールの人みたいですね。

daalaライブラリとかファイルを配置する

daalaのライブラリはsrcディレクトリの下にまるごとソフトリンクとして配置しちゃいましょう。私のMac上ではdaalaとdaaraは同じディレクトリの下にあります。なので../daala/srcと../daala/includeをsrc/daalaの下にソフトリンクしちゃいましょう。

さらにcargoでdaalaとdaara一緒にビルドするためにcc-rs(https://github.com/alexcrichton/cc-rs)を使うためにCargo.tomlをこんな感じに。

[package]
name = "daara"
version = "0.1.0"
authors = ["yoshi <ysnr.kdm@gmail.com>"]
build = "build.rs"
links = "libdaalaenc"

[dependencies]
libc = "0.2.0"

[build-dependencies]
cc = "1.0"

libdaalaencは今から我々がビルドするdaalaのライブラリのファイル名です。

ビルドのためにbuild.rsをまず実行して、libdaalaencを生成します。そのあとにRustのコードをコンパイルして最後にリンカが動きます。cc-rsがそのあたりを面倒見てくれます。build-dependenciesに書いてあるcc = "1.0"がそれです。

build.rsをまず見てみましょう。

extern crate cc;

fn main() {
    cc::Build::new()
        .file("src/daala/src/accounting.c")
        .file("src/daala/src/encode.c")
        .file("src/daala/src/generic_code.c")
        .file("src/daala/src/infoenc.c")
        .file("src/daala/src/laplace_tables.c")
        .file("src/daala/src/pvq.c")
        .file("src/daala/src/state.c")
        .file("src/daala/src/zigzag32.c")
        .file("src/daala/src/block_size_enc.c")
        .file("src/daala/src/entcode.c")
        .file("src/daala/src/generic_decoder.c")
        .file("src/daala/src/internal.c")
        .file("src/daala/src/logging.c")
        .file("src/daala/src/pvq_decoder.c")
        .file("src/daala/src/switch_table.c")
        .file("src/daala/src/zigzag4.c")
        .file("src/daala/src/dct.c")
        .file("src/daala/src/entdec.c")
        .file("src/daala/src/generic_encoder.c")
        .file("src/daala/src/intra.c")
        .file("src/daala/src/mc.c")
        .file("src/daala/src/pvq_encoder.c")
        .file("src/daala/src/tf.c")
        .file("src/daala/src/zigzag64.c")
        .file("src/daala/src/decode.c")
        .file("src/daala/src/entenc.c")
        .file("src/daala/src/info.c")
        .file("src/daala/src/laplace_decoder.c")
        .file("src/daala/src/mcenc.c")
        .file("src/daala/src/quantizer.c")
        .file("src/daala/src/util.c")
        .file("src/daala/src/zigzag8.c")
        .file("src/daala/src/dering.c")
        .file("src/daala/src/filter.c")
        .file("src/daala/src/infodec.c")
        .file("src/daala/src/laplace_encoder.c")
        .file("src/daala/src/partition.c")
        .file("src/daala/src/rate.c")
        .file("src/daala/src/zigzag16.c")
        .include("src/daala/include")
        .compile("libdaalaenc.a");   
}

力業ですね。src/daala/src/*.cを全部追加しました。.fileのファイルはすべてコンパイルされて.oファイルを吐かせます。最後の.includeでインクルードヘッダを読み込んで.compileでその名前のスタティックライブラリをどこか適当なところに生成します。

ここら辺全部終わるとディレクトリ構成は以下のような感じになっているはずです。

.
├── Cargo.lock
├── Cargo.toml
├── build.rs
├── src
│   ├── daala
│   │   ├── include -> ../../../daala/include
│   │   └── src -> ../../../daala/src
│   └── main.rs
└── target
    ├── ...

とりあえずdaala_infoの初期化だけでも

してみましょう。

extern crate libc;

pub enum daala_enc_ctx {}

#[repr(C)]
#[derive(Default, Debug)]
pub struct daala_plane_info {
  pub xdec: u8,
  pub ydec: u8,
}

#[repr(C)]
#[derive(Default, Debug)]
pub struct daala_info {
  pub version_major: u8,
  pub version_minor: u8,
  pub version_sub: u8,
  /** pic_width,_height form a region of interest to encode */
  pub pic_width: libc::int32_t,
  pub pic_height: libc::int32_t,
  pub pixel_aspect_numerator: libc::uint32_t,
  pub pixel_aspect_denominator: libc::uint32_t,
  pub timebase_numerator: libc::uint32_t,
  pub timebase_denominator: libc::uint32_t,
  pub frame_duration: libc::uint32_t,
  /**The amount to shift to extract the last keyframe number from the granule
   *  position. */
  pub keyframe_granule_shift: i32,
  /** bitdepth_mode is one of the three OD_BITDEPTH_MODE_X choices allowed
   * above. */
  pub bitdepth_mode: i32,
  /**FPR must be on for high-depth, including lossless high-depth.
     When FPR is on for 8-bit or 10-bit content, lossless frames are still
      stored in reference buffers (and input buffers) with 8 + OD_COEFF_SHIFT
      bit depth to allow streams with mixed lossy and lossless frames. Having a
      mix of reference buffers stored in 10-bit and 12-bit precisions would be
      a disaster, so we keep them all at 12-bit internally.
   */
  pub full_precision_references: i32,
  pub nplanes: i32,
  pub plane_info: [daala_plane_info; 4],
   /** key frame rate defined how often a key frame is emitted by encoder in
    * number of frames. So 10 means every 10th frame is a keyframe.  */
  pub keyframe_rate: i32,
}

#[link(name = "daalaenc", kind="static")]
extern {
    fn daala_encode_create(info: *mut daala_info) -> daala_enc_ctx;
    fn daala_info_init(info: *mut daala_info);
}

fn main() {
    println!("Hello, world!");
    let mut di = Box::new(Default::default());
    unsafe { daala_info_init(&mut *di); };
    println!("{:?}", di);
}

daala_encode_createとdaala_info_initはexample_encode.cにあります。daala_info_initはdaala_info構造体を初期化してくれます。これを使っていろいろ設定してdaala_encode_createにそれを渡すとdaala_enc_ctx(エンコード用のコンテキスト)を作ってもらえて、それを使うとエンコードできますね。

上のコードをビルドしてみますと

$ cargo build
   Compiling daara v0.1.0 (file:///hogehogehogehoge/codes/daara)
warning: src/daala/src/laplace_encoder.c:66:34: warning: for loop has empty body [-Wempty-body]
warning:     for (pos = 0; !y[pos]; pos++);
warning:                                  ^
warning: src/daala/src/laplace_encoder.c:66:34: note: put the semicolon on a separate line to silence this warning
warning: 1 warning generated.
warning: foreign function is never used: `daala_encode_create`
  --> src/main.rs:49:5
   |
49 |     fn daala_encode_create(info: *mut daala_info) -> daala_enc_ctx;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: #[warn(dead_code)] on by default

warning: type `daala_enc_ctx` should have a camel case name such as `DaalaEncCtx`
 --> src/main.rs:3:1
  |
3 | pub enum daala_enc_ctx {}
  | ^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: #[warn(non_camel_case_types)] on by default

    Finished dev [unoptimized + debuginfo] target(s) in 6.30 secs

ワーニングが出ますね。未使用ですからね。ビルドできたようです。6秒ちょっとかかりました。

main.rsのみどころ: お膳立て

main.rsでどのように使うのかをさらっと見ていきます。

extern crate libc;

libcクレートが必要です。クレートってめっちゃRustやってますな響きですね!

#[repr(C)]
#[derive(Default, Debug)]
pub struct daala_plane_info {
  pub xdec: u8,
  pub ydec: u8,
}

これはdaalaの中で定義されている構造体をRust側に引っ張り出してきたものです。daalaの本来のコードは

struct daala_plane_info {
  unsigned char xdec;
  unsigned char ydec;
};

ですね。まずunsigned charなのでpub ...: u8に適当に置き換えました。多分あってます。

#[repr(C)]はCの構造体とメモリ構造一緒にしてくれるおまじないです。まじないましょう。

#[derive(Default, Debug)]はDefaultとDebugのトレイトを追加します。Haskellでいう型クラスみたいなもんですね。厳密には違うと思うのですがここらの違いを語り出すとキモい言語オタクみたいになりますね。

Defaultは中身を適当に埋めてくれるトレイトです。空っぽのこいつを作るために毎回フィールド設定するのは面倒です。

Debugはプリティプリンタ用です。println!で{:?}フォーマットを指定すると、フィールドの内容を再帰的に羅列してくれます。再帰的にと言うのは、

#[repr(C)]
#[derive(Default, Debug)]
pub struct daala_info {
  pub version_major: u8,
  pub version_minor: u8,
  pub version_sub: u8,
  /** pic_width,_height form a region of interest to encode */
  pub pic_width: libc::int32_t,
  pub pic_height: libc::int32_t,
  pub pixel_aspect_numerator: libc::uint32_t,
  pub pixel_aspect_denominator: libc::uint32_t,
  pub timebase_numerator: libc::uint32_t,
  pub timebase_denominator: libc::uint32_t,
  pub frame_duration: libc::uint32_t,
  /**The amount to shift to extract the last keyframe number from the granule
   *  position. */
  pub keyframe_granule_shift: i32,
  /** bitdepth_mode is one of the three OD_BITDEPTH_MODE_X choices allowed
   * above. */
  pub bitdepth_mode: i32,
  /**FPR must be on for high-depth, including lossless high-depth.
     When FPR is on for 8-bit or 10-bit content, lossless frames are still
      stored in reference buffers (and input buffers) with 8 + OD_COEFF_SHIFT
      bit depth to allow streams with mixed lossy and lossless frames. Having a
      mix of reference buffers stored in 10-bit and 12-bit precisions would be
      a disaster, so we keep them all at 12-bit internally.
   */
  pub full_precision_references: i32,
  pub nplanes: i32,
  pub plane_info: [daala_plane_info; 4],
   /** key frame rate defined how often a key frame is emitted by encoder in
    * number of frames. So 10 means every 10th frame is a keyframe.  */
  pub keyframe_rate: i32,
}

daala_infoのコピペですね。これもcodec.hにあります。この中にdaala_plane_infoがあります。固定長配列4つ分ですね。トレイトは再帰的に使ってくれるのでDefaultとDebugを両方の構造体宣言につけてあります。

この2つの構造体を使いましょう。

main.rsのみどころ: 呼び出し

#[link(name = "daalaenc", kind="static")]
extern {
    fn daala_encode_create(info: *mut daala_info) -> daala_enc_ctx;
    fn daala_info_init(info: *mut daala_info);
}

fn main() {
    println!("Hello, world!");
    let mut di = Box::new(Default::default());
    unsafe { daala_info_init(&mut *di); };
    println!("{:?}", di);
}

#[link(name = "daalaenc", kind="static")]で、build.rsで生成したlibdaalaenc.aを使えるようにします。スタティックライブラリなのでkind="static"を指定します。libdaalaenc.aなのでlibと.aを除いた名前を指定します。libfoo.aならfooです。libと.aがついていない場合は知りません。変な名前はやめましょう。

externの中にlibdaalaenc.aの中の使いたい関数を書きましょう。とりあえず2つ。今回本当に使うのはdaala_info_initだけです。

main関数の中を見ましょう

Hello, worldは無視しましょう。そのあと

    let mut di = Box::new(Default::default());

として、daala_infoの受け皿を作りましょう。

    unsafe { daala_info_init(&mut *di); };

daala_info_initにさっきのdiのポインタを渡しましょう。当然unsafeで囲んで&mut で渡しましょう。&mut って最高にロックですね。Rustらしくなさ無限大です。

    println!("{:?}", di);

最後に結果を見ましょう。Debugトレイトが表示する中身を全部羅列してくれるので我々は{:?}でprintlnするだけです。

結果を見ましょう

$ cargo run
   Compiling daara v0.1.0 (file://munyamunyamunya/codes/daara)

...中略...

    Finished dev [unoptimized + debuginfo] target(s) in 7.4 secs
     Running `target/debug/daara`
Hello, world!
daala_info { version_major: 0, version_minor: 0, version_sub: 0, pic_width: 0, pic_height: 0, pixel_aspect_numerator: 0, pixel_aspect_denominator: 0, timebase_numerator: 0, timebase_denominator: 0, frame_duration: 0, keyframe_granule_shift: 31, bitdepth_mode: 1, full_precision_references: 0, nplanes: 0, plane_info: [daala_plane_info { xdec: 0, ydec: 0 }, daala_plane_info { xdec: 0, ydec: 0 }, daala_plane_info { xdec: 0, ydec: 0 }, daala_plane_info { xdec: 0, ydec: 0 }], keyframe_rate: 0 }

というわけで、中身は出てますね。daala_info_initでちゃんとセットされてるのか確認しましょう。daala_info_initはこんな感じのことをします。

// ...
internal.h:# define OD_VERSION_MAJOR (0)
internal.h:# define OD_VERSION_MINOR (0)
internal.h:# define OD_VERSION_SUB   (0)
// ...
#define OD_BITDEPTH_MODE_8 (1)
// ...

void daala_info_init(daala_info *_info) {
  OD_CLEAR(_info, 1);
  _info->version_major = OD_VERSION_MAJOR;
  _info->version_minor = OD_VERSION_MINOR;
  _info->version_sub = OD_VERSION_SUB;
  _info->keyframe_granule_shift = 31;
  _info->bitdepth_mode = OD_BITDEPTH_MODE_8;
  _info->full_precision_references = 0;
  /*TODO: Set other defaults.*/
}

て感じです。OD_CLEARはメモリクリアですね。というわけでkeyframe_granule_shift = 31とbitdepth_mode = 1ぐらいしかセットされませんね。あとは他全部がゼロクリアされていることを確認しましょう。

Hello, world!
daala_info { version_major: 0, version_minor: 0, version_sub: 0, pic_width: 0, pic_height: 0, pixel_aspect_numerator: 0, pixel_aspect_denominator: 0, timebase_numerator: 0, timebase_denominator: 0, frame_duration: 0, keyframe_granule_shift: 31, bitdepth_mode: 1, full_precision_references: 0, nplanes: 0, plane_info: [daala_plane_info { xdec: 0, ydec: 0 }, daala_plane_info { xdec: 0, ydec: 0 }, daala_plane_info { xdec: 0, ydec: 0 }, daala_plane_info { xdec: 0, ydec: 0 }], keyframe_rate: 0 }

ちゃんとなってますね。全部ゼロ、ただしkeygrame_granule_shiftとbitdepth_modeはセットされています。できた!

まとめ

今日はRustからCのライブラリを呼び出して構造体を初期化できました。これができればあとは物理で殴るだけですね!(意訳:インプリヘヴィですね!)

参考文献