オンラインPOGドラフト用のツール

今年度の仲間うちでのPOGドラフト会議は3密回避のためリモート開催となりました。

ローカルルール

  • 1~3位は入札方式。競合した場合はあみだくじで抽選
  • 4位以降はウェイバー方式で順繰りに指名

入札フェーズ: GoogleFormsの利用

GoogleFormsを利用しました。 以下のようなアンケートフォームを順位ごとに予め作成しておき、URLを参加者に共有し入力してもらいました。

f:id:sanshonoki:20200616052534p:plain

競合が出たときは、LINEのあみだくじ機能を使って決定しました。 グループのトークルーム上で幹事があみだくじを作って抽選します。

あみだくじ機能の使い方はコチラ guide.line.me

ウェイバーフェーズ: 独自開発アプリの利用

Vue.js + Firebaseの練習用として指名馬入力ツールを作りました。

参考にしたもの

グループの作成(オーナーの登録)

f:id:sanshonoki:20200618055434p:plain

馬名の選択

f:id:sanshonoki:20200618055509p:plain

部分一致で検索するので入力の手間はだいぶ楽でした。 すでに指名された馬は候補で出てこないように重複チェックも入れてます。

馬のマスターデータベースはnetkeiba.comからスクレイピングして(競走馬検索から年齢条件を2歳~2歳にして検索)、jsonファイルとしてもっておきそれを読み込みます。

const horse_catalogue = require("../assets/horse_catalogue.json")

指名馬のオーナーへの割当て

f:id:sanshonoki:20200618055817p:plain

割当て後 f:id:sanshonoki:20200618060040p:plain

登録結果のダウンロード

f:id:sanshonoki:20200618060551p:plain CSVでリストをダウンロードできます

downloadCSV: function() {
  let csv = '\ufeff' + 'order_no,owner_name,name,sire,mare,id\n'

  const sorted_horses = this.selected_horses.sort(function(a, b) {
    ...
  })

  sorted_horses.forEach(el => {
    const line = `${el['po_order_no']},${el['po_name']},${el['name']},${el['sire']},${el['mare']},${el['id']}\n`
    csv += line
  })
  const blob = new Blob([csv], { type: 'text/csv' })
  let link = document.createElement('a')
  link.href = window.URL.createObjectURL(blob)
  link.download = 'pog_result.csv'
  link.click()
},

ドラフトで使ってみた結果

今年度に関してはプロトタイプということでスマホ対応も含めてデザインがアレすぎますが機能面に関しては今のところ、1点を除いて問題なく使えました。

見つかった問題点(デザイン以外の改善点)
  • スマホ環境で事前にテストしていたとき、通信環境の問題からか Firebaseに同じ馬がなぜか重複して登録されることがあった..

来年度は Vuetify とか使ってデザインを改善しようと思います。 Vue.jsがオワコンになってなければよいけど。。

ちなみに、アプリは コチラ からアクセスできます。

自由に試せるのでもし興味ある方がいれば使ってみてください。 そのうちクローズしますがしばらくはopenしていると思います

ruby1.9.3でseleniumを動かす

超レガシー環境のruby1.9.3で動かしていたスクレイピングseleniumを使ったものに対応させる必要性があり、その時のメモです。

Dockerfile

FROM ruby:1.9.3

RUN apt-get update
RUN apt-get install -y ttf-freefont chromium unzip libgconf-2-4

ADD Gemfile .
ADD Gemfile.lock .
RUN bundle install

RUN wget https://chromedriver.storage.googleapis.com/2.28/chromedriver_linux64.zip && unzip chromedriver_linux64.zip
RUN rm -f /usr/local/bundle/bin/chromedriver && mv chromedriver /usr/local/bin/ && chmod 755 /usr/local/bin/chromedriver

RUN mkdir /noto
ADD https://noto-website.storage.googleapis.com/pkgs/NotoSansCJKjp-hinted.zip /noto
WORKDIR /noto
RUN unzip NotoSansCJKjp-hinted.zip && \
    mkdir -p /usr/share/fonts/noto && \
    cp *.otf /usr/share/fonts/noto && \
    chmod 644 -R /usr/share/fonts/noto/ && \
    fc-cache -fv
RUN rm -rf /noto

docker-compose.yml

version: '3'
services:
  ruby_selenium:
    build: .
    image: ruby_selenium
    container_name: ruby_selenium
    working_dir: /root/src
    volumes:
      - ./src/:/root/src/
    tty: true

src/scrape.rb

# coding: utf-8

require 'selenium-webdriver'

driver = Selenium::WebDriver.for :chrome, switches: ["--headless", "--no-sandbox"]

driver.navigate.to 'https://www.yahoo.co.jp/'
driver.save_screenshot './screenshot.png'

動かし方

コンテナを起動し、コンテナ内のシェルに入ります。

$ docker-compose build
$ docker-compose up -d
$ docker-compose exec ruby_selenium sh

シェルの中でサンプルスクリプトを動かすと

# ruby scrape.rb

次のように yahoo.co.jp のスクリーンショットが撮れます。

f:id:sanshonoki:20200213220552p:plain

手こずった点

ruby1.9.3だと selenium-webdriver の バージョンを指定する必要がある

selenium-webdriver のバージョン 2.53.4 じゃないとruby1.9 にはインストールできません

selenium-webdriver 2.53.4 だと Selenium::WebDriver::Chrome::Options がない

rubyで headless chrome を動かすコードをWebで調べると以下のようなサンプルコードが簡単に見つかるのですが、

options = Selenium::WebDriver::Chrome::Options.new
options.add_argument('--headless')

driver = Selenium::WebDriver.for :chrome, options: options

selenium-webdriver 2.53.4 ではこのコードは動きません..

How to use Chrome Options in Ruby Selenium? - Stack Overflow

に助けられました

chromiumのバージョンが古い

apt-get でインストールした chromiumブラウザ はかなりバージョンが古いです。

# /usr/bin/chromium --version
Chromium 57.0.2987.98 Built on 8.7, running on Debian 8.0

このバージョンに適合する chromedriver を探してくるのに苦労しました。

chromedriverのダウンロードページ だと ChromeDriver 2.41、Supports Chrome v67-69 までの情報しかのってなくv57 は見つかりません..

いろいろググっていたところ以下の記事が見つかり、助けられました。

javascript - Which ChromeDriver version is compatible with which Chrome Browser version? - Stack Overflow

この表から v57 に適合するのは 2.28 と分かり、対応するchromedriveをインストールできました

# /usr/local/bin/chromedriver --version
ChromeDriver 2.28.455506 (18f6627e265f442aeec9b6661a49fe819aeeea1f)

今回のコード

今回の記事のコードはこちらにあります github.com

参考にした記事

Heroku Schedulerの怪しい挙動

WebアプリでHeroku Schedulerでバッチ処理をしていたのですがたまに実行されないことが幾度かありました。

あれ?と思いつつも自分しか使わないサービスだし、たまにだったので手動再実行で対応していました。。

が、やはり気持ち悪いので重い腰をあげて今回調べてみることにしました

エラーは出てないので怪しいと思ったのは同じ時刻に2つのタスクが登録されていたこと

ということで、実験で再現するか調べます

実験

(1) 同じ時間に3タスク f:id:sanshonoki:20190929215716p:plain

(2) 30分ずつずらす f:id:sanshonoki:20190929215735p:plain

タスクの内容はslackに文字列を送るというタスクです (tsk1であれば "tsk1"というメッセージを送る)

交互に7日間ずつ実行しました

結果

(1)のときは 以下のように 2タスク分しか実行されないときが 2日(/7日)ありました

f:id:sanshonoki:20190929223207p:plain

結論

やはり同時刻に複数タスクが登録されていると実行されない場合があるようです

letter_opener_webをRails3.2+ruby1.9.3の環境で使う

送信メール確認用にdevelopment環境ではletter_openerを使っていたのですが dockerへ移行したので動かなくなりました。。

そこで、Webインターフェースで送信メールを確認できるletter_opener_webに乗り換えました。 ただ、Rails3.2+ruby1.9.3という古い環境では簡単にはいかなかったのでメモを残します。

結論

以下の環境で無事動きました

Gemfile

group :development do
  gem 'public_suffix', '~> 1.4.6' 
  gem "letter_opener", '1.4.1' 
  gem 'letter_opener_web', '1.2.2' 
end

docker-compose.yml

services:
  web:
    # 他は省略
    environment:
      - BROWSER=echo

動くようになるまでの道筋

おまけです。

public_suffixのインストールに失敗

依存gemの1つ、public_suffixのインストールで失敗しました。

public_suffix requires Ruby version >= 2.0.

そこで、ruby1.9.3をサポートしているバージョン '1.4.6' にダウングレードします。

参考にしたページ

Ruby 1.9.3 build broken due to public_suffix gem not compatible + Selenium Error · Issue #6885 · travis-ci/travis-ci · GitHub

/letter_openerを開くとエラー

/usr/local/bundle/gems/letter_opener_web-1.3.4/app/controllers/letter_opener_web/letters_controller.rb:8: unknown type of %string
    before_action :load_letter, only: %i[show attachment destroy]
  • %i の記法は ruby2.0からのサポート
  • before_action は Rails4からの機能

このため、letter_opener_web を '1.2.2' にダウングレードしました

Launchy::CommandNotFoundError のエラーが出る

このエラーは letter_opener_web の バージョン1.3.3 から rescue節でハンドリングすることで出ないようになっているのですが 1.2.2 にダウングレードしているためエラーが出てしまいます..

該当のコミット

https://github.com/fgrehm/letter_opener_web/commit/661ef92c9c83afa47e913b5ddad264b39f22ad09#diff-b83fce5786125deb6a48e47051caa2fe

環境変数 BROWSER をセットすることで回避します。 ブラウザでなくともコマンドにURLを渡すことさえできればエラーは出ないので 環境変数 BROWSER=echo をセットします。

メール送信時に undefined method `html_part'

letter_opener がエラーを出しています。 以下のページによると バージョン1.4.xではエラーが出ないようなので 1.4.1 にダウングレードします。

undefined method `html_part' for #<String:0x00007f5b16161520> · Issue #147 · ryanb/letter_opener · GitHub

Heroku上のRails3アプリの延命対策

プライベートユースでHeroku上で運用しているWebアプリ(Rails3.2 + ruby1.9.3)があるのですが

Cedar-14 stack end-of-life window begins 1 May 2019 | Heroku Dev Center

のアナウンスがあり、来年5月からビルドができなくなってしまうとのこと

ならば、アップグレードしておくかと

アップグレードガイド Upgrading to the Latest Stack | Heroku Dev Center に従って

$ heroku stack:set heroku-18
$ git commit --allow-empty -m "Upgrading to heroku-18"
$ git push heroku master

とheroku-18 stackベースでデプロイしてみたところ、buildエラーに...。

remote:  !     An error occurred while installing ruby-1.9.3-p551
remote:  !
remote:  !     Heroku recommends you use the latest supported Ruby version listed here:
remote:  !     https://devcenter.heroku.com/articles/ruby-support#supported-runtimes
remote:  !
remote:  !     For more information on syntax for declaring a Ruby version see:
remote:  !     https://devcenter.heroku.com/articles/ruby-versions

なんと..

こりぁアカン..

どうやらruby-1.9.3は新しいstackでサポートされてないようです..

ということでcontainerでデプロイするように作戦変更しました。

Rails/rubyの段階的アップグレードはとうの昔に放棄)

containerベースへの移行方法

Dockerfileを作ってローカル環境にてdocker上で動くようにできたら、

あとは

$ heroku container:login
$ heroku container:push web   # コンテナイメージのpush
$ heroku container:release web   # pushしたイメージのリリース

です。

既存のCedar-14 stackでのアプリケーションが稼働中であっても問題なく上書き?でデプロイできました。

コードの修正点は

config/database.yml

production:
  <<: *default
  url: <%= ENV['DATABASE_URL'] %>

それ以外としては、

.dockerignore

tmp/pids/server.pid

を追加しておくとよいと思います。

トラブルシューティング

やはりというかいくつか問題が発生したのでその対策です。 もっとスマートな方法はあるはず。。

トラブル1: Boot timeoutエラー

Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 60 seconds of launch

どうやらアプリケーションがPORTにアクセスできてないようです

Qiitaの記事 DockerでRailsの環境構築してHerokuへデプロイする - Qiita では

CMD ["rails", "server", "-b", "0.0.0.0"]

でサーバー起動していますが同様にしたら上記のエラーになってしまいました

どうやら自分の環境ではWebrickを使っているからのようです。。 (Rails5だとデフォルトサーバーのpumaの設定ファイル内で環境変数PORTにアクセスしているから問題ない)

本来ならばpumaを使うようにするべきだと思いますが、とりあえず次のように修正して解決しました。

CMD bundle exec rails server -b 0.0.0.0 -p $PORT

ただ、

配列形式の

CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0", "-p", "$PORT"]

にすると

2019-07-17T13:43:33.652404+00:00 heroku[web.1]: Starting process with command `bundle exec rails s -p \8224 -b 0.0.0.0

というエラーになりNGでした...

謎。。

トラブル2: assets precompileされていなくアプリケーションエラー

An ActionView::Template::Error occurred in page#index:

  application.css isn't precompiled

が出ます

assets precompileが必要です。(今まではHerokuデプロイ時に自動でやってくれていた)

Dockerfile に以下の行を追加して解決しました

RUN bundle exec rake assets:precompile

Rakefile を ADD/COPY する前だと No Rakefile found (looking for: rakefile, Rakefile, rakefile.rb, Rakefile.rb) が出るので要注意です

トラブル3: コンパイルしたassetsにアクセスできない

ActionController::RoutingError (No route matches [GET] "/assets/application-bf61d1c6485bd814e710bb58a3622bea.css"):

コンパイルされたCSSにアクセスできず、レイアウトが崩れてしまいました

Rails 4+ Asset Pipeline on Heroku | Heroku Dev Center を参考に

Gemfile に

gem 'rails_12factor', group: :production

を追加して解決しました

トラブル4: development環境用にインストールしたgemが悪さをする

development環境用のgemがインストールされていてproduction環境で悪さをしているということがありました

production環境用のgemのみインストールするのは

# RUN bundle install  # を
RUN bundle install --without test development #  に変更

でよいのですがこれだとローカルの開発環境でdockerビルドしたときに開発用のgemがインストールされません

ARGを使って使い分けるようにしました

Dockerfile

ARG bundle_install_opt="--without test development"
RUN bundle install ${bundle_install_opt}

docker-compose.yml

version: '2'

services:
  web:
    build:
      context: .
      args:
        - bundle_install_opt

というようにすると、ローカル環境では docker-compose build とすれば開発用のgemがインストールされたイメージをビルドできます


とりあえず、引き続きメンテナンスできそうで一安心です

Azureデータサイエンス仮想マシンでハマったこと

東大松尾研のDL4USのLesson7 Section4は学習時間が相当かかるのでGoogle Colab上での実行が困難です。 (実際に40時間以上かかりました。。)

このため、このSectionだけは Azureにインスタンスを作って動かしました。

そのときのメモです。

一言でいうと、インスタンスの作成時の認証は SSH公開鍵キー」 ではなく 「パスワード」 にしたほうが良いかも? という話です。

この画面です

f:id:sanshonoki:20190704214732p:plain

Azureデータサイエンス仮想マシンのイメージには機械学習に必要なツール、ライブラリが一通りインストールされていて環境構築の手間が省けてとても助かります。

作成方法は 公式ページ の通りですが、イメージ選択で「Data Science Virtual Machine for Linux (Ubuntu)」を選択します。

なぜパスワード認証がよい?

この仮想マシンイメージには JupyterHub が最初からインストールされていて自動起動します。

なので、https://<仮想マシンのIPアドレス>:8000 で JupyterHub にアクセスできるのですが SSH公開鍵認証の場合は、そのパスフレーズで JupyterHubにログインできないのです... (UNIXパスワードをもつユーザーでないとログインできない)

f:id:sanshonoki:20190704212742p:plain:w400

パスワード認証でインスタンス作成するとUNIXパスワードをもつユーザーが作成されるのでそのパスワードでJupyterHubにログインできます。

なお、パスワード認証でインスタンス作成してSSH認証したいときはインスタンス作成後、~/.ssh/authorized_keys に公開鍵を置けばオーケーです。

ローカルPCからだと次の1行で登録できます。

$ cat 公開鍵のパス | ssh ユーザー名@IPアドレス 'cat >> .ssh/authorized_keys'

ユーザー名はパスワード認証にしたときのユーザー名です

SSH公開鍵認証でインスタンスを作ってしまった場合

ご安心あれ。Jupyter notebookの設定ファイルを作って Jupyter notebookを起動すれば大丈夫です。

通常通りの手順ですね。

  1. Jupyter Notebookの設定ファイルを作る

    $ jupyter notebook --generate-config
    
  2. .jupyter/jupyter_notebook_config.py を編集する

    1. パスワードのハッシュを作成する

      $ python -c 'from notebook.auth import passwd; print(passwd())'
      
    2. パスワードを設定する

      c.NotebookApp.password = 'sha1:XXX'  # 上で生成したハッシュ
      

あとは jupyter notebook で起動し、ポート8888にアクセスすればオーケー

ただし、このままだと暗号化されていないhttp通信になるので sshポートフォワーディングでログインしてからjupyter起動するのがよいです。

$ ssh -i 秘密鍵へのパス -L ローカルのポート番号:localhost:8888 ユーザー名@インスタンスのIPアドレス

そして http://localhost:ポート番号 にアクセスすればjupyterにアクセスできます。

リスクを承知でポート転送しないときは追加で

  • 設定ファイルで c.NotebookApp.ip = '0.0.0.0' を設定する
  • インスタンスの 受信ポートの規則 で 8888 の TCP を追加する

が必要です

東大松尾研のDL4USをやってみた感想

東大松尾研が公開しているエンジニア向けDeep Learning講座、DL4USの評判が良さそうなので一通りやってみました。

やってみての感想

これまでディープラーニングのオンライン講座は fast.ai が一番だと思ってました。

ただ、

  • オリジナルのライブラリで抽象化されすぎていて実装がよくわからん(概念は理解できるけど、、特にv2 !)
  • モデルの学習でGPUを使って数時間以上〜がちょいちょいある(という記憶)
  • YouTubeの教材をまじめに聴くとそれなりに長い(英語の勉強にもなっていいんだけど)

と、学習時間がそこそこ必要という不満もありました、個人的に。

(v1とv2を一通りやりました。今はv3のようですがv3のことはよくわかりません。。)


それに比べて、DL4USは

  • GANや強化学習まで含めて一通り網羅されていて
  • Google Colab上でサクっと動かせ、1セクションにかかる所要時間も短い
  • また、コードがシンプルなので自分で実装するときに参考になる


素晴らしい !! その気になれば数日で終わらせることもできる

基本的に以下のとても参考になる記事に従っていれば問題なく進められます。

qiita.com

うまくいかなかった箇所

MNISTの学習でなぜか発散する(Lesson1 Section1、Lesson2 Section2、Lesson4 Section2等)

もともとのコードでは最適化アルゴリズムoptimizer='sgd' になっていますがそのままでは自分の環境ではなぜかAccuracyが0.11となって学習が発散してしまいました。。

optimizer='adam'optimizer='rmsprop' にしてやるとAccuracyも98%ぐらいになってうまくいきました。

MS-COCOをunzipするときのTips (Lesson5 Section1)

Colab上でMS-COCOのデータを解凍するとファイル数が多すぎるため標準出力に大量にメッセージ出力され、 それに伴いMacのファンが唸り始めて一向に処理が終わる気配がありませんでした..

以下のように標準出力を出さないようにスクリプトを書き換えてやると問題なく解凍できました

! sed -i -e 's/-d download\//-d download 1>\/dev\/null/' download_mscoco.sh
! sed -i -e 's/N_TRAIN_DATA = 50000/N_TRAIN_DATA = 30000/' preprocess_mscoco.py

その他参考になったこと

パラメータの初期化 (Lesson1 Section3)

特にGlorotの初期化法は活性化関数が全て線形な場合の解析結果であり、中央付近が線形とみなせるsigmoid関数やtanh関数に適していると言えます。 また、Heの初期化法は活性化関数がReLUであるときに適しています。

巷のKerasのコードでは活性化関数がReLuであってもデフォルト値であるGlorot(Xavier)の初期化になっている場合が多いですが、ちゃんとHeの初期化になっていて感心しました

ちなみに、normal と uniform の使い分けはどうなの? と気になって調べてみたのですが 基本的には差はないようですね。。

【参考記事】

によると

  • 基本的には personal choice である
  • Bengioは uniform を好んで使っていた、一方、Hintonは normal を使っていた
  • ResNet では he_normal が使われている
  • DLライブラリは uniform を使っているのが多い (のでKerasもそれをデフォルトとしたとCholletさんが言っている)

そうです。

BatchNormalization (Lesson2 Section3)

⋆ 後続の研究で、Batch Normalizationは内部共変量シフトを軽減させる効果は実はほとんどないことが実験的に示されています。 一方でBatch Normalizationにより損失関数・勾配の形状が緩やかになり、勾配法などによる最適化が容易になることが実験的に・一部理論的に示されています。 出典: S. Santurkar et al. "How Does Batch Normalization Help Optimization? (No, It Is Not About Internal Covariate Shift)". arXiv. 2018

うーん、そうだったのか、、これは知らなかった、、

論文読んでみたいと思います