まひろ量子のハックログ

プログラミングや機械学習などの知識を記録・共有します

OpenCV3をインポートするときcv3ではなくcv2なのは何故?

f:id:twx:20180917152722p:plain

ふと気になったので調べてみました。

こちらに答えがありました。↓

answers.opencv.org

cv2convert toの略なんじゃないか?という意見もあるみたいですが、実際は、「2はバージョンを表しているのではなく、C APIをcvというプレフィックスで表し、C++ APIをcv2というプレフィックスで表すようにしている」だけのようです。

Google ColaboratoryのCUDAを9.0にアップグレードする方法【失敗】

f:id:twx:20180915171037p:plain

結論

失敗しました。CUDA9をインストールしても、Nvidiaドライバとの互換性を合わせられなかったり、pipでtensorflowをアップグレードできなかったりと、色々ハマります。Googleが公式でCUDA9をサポートしてくれるのを待つしかなさそうだという結論に至りました。

やったこと

現時点(2018年9月15日)でGoogle ColaroratoryにインストールされているCUDAのバージョンは8です。CUDA9を前提としているプログラムが動かず困っていたのですが、色々試した結果CUDA9.0にアップグレードする方法を見つけました。この記事では、その方法を簡単にご紹介します。

CUDA9.0をインストール

こちらのページを参考にしました。 stackoverflow.com

Colab上で以下を実行します。

!wget https://developer.nvidia.com/compute/cuda/9.0/Prod/local_installers/cuda-repo-ubuntu1604-9-0-local_9.0.176-1_amd64-deb
!dpkg -i cuda-repo-ubuntu1604-9-0-local_9.0.176-1_amd64-deb
!apt-key add /var/cuda-repo-9-0-local/7fa2af80.pub
!apt-get update
!apt-get install cuda

cudnn7をインストール

こちらのページを参考にしました。

qiita.com

Colab上で以下を実行します。

!wget http://developer.download.nvidia.com/compute/redist/cudnn/v7.1.4/cudnn-9.2-linux-x64-v7.1.tgz
!tar -xvzf cudnn-9.2-linux-x64-v7.1.tgz
!cp cuda/lib64/* /usr/local/cuda/lib64/
!cp cuda/include/* /usr/local/cuda/include/

環境変数をセット

Colab上で以下を実行します。

import os 
os.environ["CUDA_HOME"]="/usr/local/cuda"
os.environ["LD_LIBRARY_PATH"]="/usr/local/cuda/lib64"

これで、CUDA9.0、cudnn7.1がインストールされます。

追記 (2018.9.15 21:30)

ドライバのバージョンが合ってないと怒られました。対策を検討中です。

!nvidia-smi

Failed to initialize NVML: Driver/library

追記 (2018.9.16 10:20)

結局、Nvidiaドライバを切り替えることができず断念しました。。。Googleが公式でCUDA9をサポートしてくれる日を待ちましょう。

以上です。 今回は「Google ColaboratoryのCUDAを9.0にアップグレードする方法【失敗】」をご紹介しました。

良い記事だと思っていただいた方は以下の「★+」ボタンのクリック、SNSでのシェア、コメント、「読者になる」ボタンのクリックをお願いします!

それではまたー

Image Inpaintingをやってみた【中間報告編】

f:id:twx:20180913152447p:plain

画像出典: https://arxiv.org/pdf/1804.07723.pdf

Image Inpaintingとは

不定形に塗りつぶされた画像を修復させるというタスクだ。NVIDAが発表した論文とデモ動画が、その精度の高さで話題となっている。

元論文

元論文はこちら。 Image Inpainting for Irregular Holes Using Partial Convolutions https://arxiv.org/pdf/1804.07723.pdf

実装

サードパーティー製の実装がGithubにある。こちらを使わせていただこう。

GitHub - MathiasGruber/PConv-Keras: Keras implementation of "Image Inpainting for Irregular Holes Using Partial Convolutions"

画像の準備

画像データは自分で用意した。 ImageNet画像を全部で約2万9000枚ほど用意し、train, test ,validationという名前のディレクトリに振り分けた。

trainに約2万7000枚、trainvalidationにそれぞれ約1000枚という配分にした。

これらをzipで固めてGoogle Driveに送り、以下の要領でColaboratory環境に移した。

www.mahirokazuko.com

学習

はじめ、コードをそのまま実行したところサイズが512x512x3の画像を入力として学習がはじまった。しかし、1エポック進むのにかなりの長時間を要しそうだということが分かったため、入力サイズを256x256x3に変更した。

model = PConvUnet(img_rows=256, img_cols=256, weight_filepath='data/logs/')

また、試しに動作確認をしたかったので、現実時間で待てるレベルまでエポック数を下げた。

model.fit(
    train_generator, 
    steps_per_epoch=100,
    validation_data=val_generator,
    validation_steps=50,
    epochs=50,        
    plot_callback=plot_callback,
    callbacks=[
        TensorBoard(log_dir='data/logs/initial_training', write_graph=False)
    ]
)

90分くらいで20エポックまで来た。

Epoch 1/1
Found 27308 images belonging to 1 classes.
 99/100 [============================>.] - ETA: 1s - loss: 1192959.7986Found 1000 images belonging to 1 classes.
100/100 [==============================] - 149s 1s/step - loss: 1192393.0381 - val_loss: 1140785.5613
Epoch 2/2
100/100 [==============================] - 145s 1s/step - loss: 929772.3394 - val_loss: 1083900.0400
Epoch 3/3
100/100 [==============================] - 139s 1s/step - loss: 896846.2562 - val_loss: 918737.5038
Epoch 4/4
100/100 [==============================] - 141s 1s/step - loss: 832707.7262 - val_loss: 912742.8137
Epoch 5/5
100/100 [==============================] - 144s 1s/step - loss: 817528.7878 - val_loss: 907044.2275
Epoch 6/6
100/100 [==============================] - 143s 1s/step - loss: 765230.2653 - val_loss: 918365.0200
Epoch 7/7
100/100 [==============================] - 148s 1s/step - loss: 748308.9103 - val_loss: 863127.1031
Epoch 8/8
100/100 [==============================] - 149s 1s/step - loss: 753139.3806 - val_loss: 886118.9925
Epoch 9/9
100/100 [==============================] - 146s 1s/step - loss: 733665.1872 - val_loss: 820381.9600
Epoch 10/10
100/100 [==============================] - 148s 1s/step - loss: 724513.2947 - val_loss: 807128.7750
Epoch 11/11
100/100 [==============================] - 150s 1s/step - loss: 718573.7362 - val_loss: 842021.4556
Epoch 12/12
100/100 [==============================] - 147s 1s/step - loss: 687009.6784 - val_loss: 792984.8875
Epoch 13/13
100/100 [==============================] - 146s 1s/step - loss: 717200.5312 - val_loss: 793620.3700
Epoch 14/14
100/100 [==============================] - 141s 1s/step - loss: 671126.3444 - val_loss: 784162.4425
Epoch 15/15
100/100 [==============================] - 141s 1s/step - loss: 664130.9928 - val_loss: 768478.3525
Epoch 16/16
100/100 [==============================] - 141s 1s/step - loss: 641558.0719 - val_loss: 734417.9025
Epoch 17/17
100/100 [==============================] - 150s 2s/step - loss: 654648.5581 - val_loss: 778414.4363
Epoch 18/18
100/100 [==============================] - 145s 1s/step - loss: 643995.8372 - val_loss: 727236.1906
Epoch 19/19
100/100 [==============================] - 146s 1s/step - loss: 633081.0981 - val_loss: 703399.7200
Epoch 20/20
100/100 [==============================] - 146s 1s/step - loss: 630721.5150 - val_loss: 742294.0931
Epoch 21/21
100/100 [==============================] - 147s 1s/step - loss: 626138.9844 - val_loss: 715688.0587
Epoch 22/22
100/100 [==============================] - 146s 1s/step - loss: 606827.5503 - val_loss: 690054.2956

ロスのオーダーが105なのが気になる…

TensorBoardで見てみるとこんな感じ↓。

f:id:twx:20180913153940p:plain

20エポック時点でのテスト結果を見てみるとこんな感じ↓。

f:id:twx:20180913154136p:plain f:id:twx:20180913154150p:plain f:id:twx:20180913154201p:plain

いい感じではある。

論文によると、「10日間学習させる必要があった」と書かれているので、もう少し経過を待ってみようと思う。

追記 (50エポック終了)

約3時間で50エポックが終了しました。

f:id:twx:20180913180646p:plain

まだまだロスは下がりそうです。

テストデータでの結果は以下の通り。

f:id:twx:20180913180842p:plain f:id:twx:20180913180850p:plain f:id:twx:20180913180859p:plain

さらに学習は継続していくので、続報をお楽しみに。

以上、本日は「Image Inpaintingをやってみた」ということで、Image InpaintingをGoogle Colaboratoryで回してみました。最終的な結果は後日追ってご報告します。

良い記事だと思っていただいた方は、以下の「★+」ボタンのクリック、SNSでのシェア、「読者になる」ボタンのクリックをお願いします。 それではまたー!

Google Colaboratory に大量データをアップロードする方法

Google Driveをマウントする

Colab上で以下を実行。/content/driveというディレクトリにGoogle Driveがマウントされる。

!apt-get install -y -qq software-properties-common python-software-properties module-init-tools
!add-apt-repository -y ppa:alessandro-strada/ppa 2>&1 > /dev/null
!apt-get update -qq 2>&1 > /dev/null
!apt-get -y install -qq google-drive-ocamlfuse fuse

from google.colab import auth
from oauth2client.client import GoogleCredentials
import getpass
auth.authenticate_user()
creds = GoogleCredentials.get_application_default()

!google-drive-ocamlfuse -headless -id={creds.client_id} -secret={creds.client_secret} < /dev/null 2>&1 | grep URL
vcode = getpass.getpass()
!echo {vcode} | google-drive-ocamlfuse -headless -id={creds.client_id} -secret={creds.client_secret}

!mkdir -p drive
!google-drive-ocamlfuse drive

Google Driveにアップロードする

xxxxx.zipのようにzipで圧縮してGoogle Driveに普通にアップロードする。

Google DriveからGoogle Colaboratoryにファイルを移動し展開

Colab上で以下を実行。/content/dataというディレクトリに大量データが展開される。

!mkdir /content/data
!unzip /content/drive/train.zip -d /content/data > /dev/null 2>&1 &

ポイントは、> /dev/null 2>&1 &で、標準出力を捨てているところだ。

そうしないと、unzipで大量データを展開したときに大量の標準出力が表示されてしまい、Chromeがクラッシュして落ちてしまうことがある。

解凍後のファイル数を確認するには以下を実行する。

!echo /content/data/train/* | xargs ls | wc

出力例。27308個のファイルが展開されていることがわかる。

  27308   27308 1119628

以上、本日は「Google Colaboratory に大量データをアップロードする方法」を紹介しました。 良い記事だと思っていただいた方は下の「★+」ボタンのクリック、SNSでのシェア、「読者になる」ボタンのクリックをお願いします! それではまたー

RailsアプリをAWS EC2で公開する超簡単な手順 【独自ドメイン/HTTPS対応】

f:id:twx:20180910173108p:plain

やりたいこと

  • Railsアプリをインターネットに公開したい
  • AWS EC2で公開したい
  • 独自ドメインで公開したい
  • HTTPSで公開したい
  • 単一のEC2で複数のアプリを公開したい

この記事では、独自ドメインやHTTPSにも対応した形でEC2でアプリを公開する手順を紹介する。

なお、独自ドメインの発行およびAWSの運用には料金がかかる。 筆者はだいたい月に3000円くらいかかっている。(AWS: 3000円/月くらい, ドメイン: 10円/月くらい)

この記事に書かれていることを実践し何らかの不利益を被ったとしても自己責任であり、筆者は責任を負わない旨をご了承いただきたい。

また、「EC2インスタンスを立ち上げる」「Route53でDNSレコードを登録する」などといった基本的な作業の手順はここには載せない。ググれば易しく解説してくれている記事がたくさん見つかるので、それらを参照していただきたい。

1. AWS EC2の環境を整える

まずはAWSコンソールにログインしよう。

https://ap-northeast-1.console.aws.amazon.com/console/

EC2インスタンスを1台立ち上げる。本格運用しないのであれば安いインスタンスで構わない。 この記事ではOSがUbuntuのイメージを使用したという前提で話を進める。

1.1 ユーザ作成

EC2にログインしたら、まずアプリごとにLinuxユーザを作ろう。 まず、デフォルトユーザ(ユーザ名ubuntu)でログインする。

以下のコマンドを実行してユーザを作っていく。ここではユーザ名はbananaとして話を進めていく。

ubuntuユーザとして実行

sudo useradd -m banana
sudo gpasswd -a banana sudo
sudo passwd banana

パスワードを聞かれれるので任意の文字列を打ち込む。これは今後、bananaユーザのパスワードとなるので覚えておこう。

次に、ログイン時のシェルを指定する。

sudo chsh banana

シェル名を聞かれるので、/bin/bashと打ち込もう。

1.2 sshでログインできるようにする

ubuntuユーザのキーをコピーして使うことにする。

sudo su banana
cd /home/banana
mkdir .ssh
sudo cp ../ubuntu/.ssh/authorized_keys .ssh/
sudo chown banana .ssh/authorized_keys
sudo chgrp banana .ssh/authorized_keys
exit

これで、bananaユーザとしてsshログインできるようになる。いったんEC2からログアウトし、bananaユーザとして入り直そう。以下の操作は全てbananaユーザとして行う。

1.3 もろもろ環境設定

ここは、各自好きなように環境設定すれば良い。

## 誤ってrmコマンドで削除してしまうのを防止する
echo "alias rm='rm -i'" >> ~/.bashrc
exec $SHELL

## sshが切れないようにする
sudo vi /etc/ssh/sshd_config
ClientAliveInterval 30 # 末尾に追加
sudo /etc/init.d/ssh restart

## OS最新化
sudo apt-get update -y
sudo apt-get upgrade -y

## IPv6サポートをoffにする
sudo vi /etc/default/ufw 
IPV6=no # yesからnoに変更


## ポート22, 80, 3000番を開けファイアウォールを有効化
sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 3000
sudo ufw enable
sudo ufw status

## よく使うコマンドのインストール (自身の好みに合わせてこの辺は自由にインストールしてください)
sudo apt-get install -y pwgen zip unzip nkf screen imagemagick
sudo apt-get install mecab libmecab-dev mecab-ipadic # Mecab
sudo apt-get install mecab-ipadic-utf8
echo "本日は晴天なり" | mecab # => 形態素解析のテスト

## 開発ツールのインストール
sudo apt-get install -y build-essential automake libssl-dev libreadline-dev libyaml-dev libpq-dev libbz2-dev libsqlite3-dev
sudo apt-get upgrade 

## pythonのインストール
git clone https://github.com/pyenv/pyenv.git ~/.pyenv
echo '' >> ~/.bashrc
echo '# pyenv' >> ~/.bashrc
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(pyenv init -)"' >> ~/.bashrc
exec $SHELL

pyenv install 2.7.11
pyenv global 2.7.11
python --version

## rubyのインストール
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo '' >> ~/.bashrc
echo '# rbenv' >> ~/.bashrc
echo 'export RBENV_ROOT="$HOME/.rbenv"' >> ~/.bashrc
echo 'export PATH="$RBENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
exec $SHELL

git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
rbenv install --list
rbenv install 2.4.1
rbenv global 2.4.1
ruby --version

## nodeのインストール
curl -L git.io/nodebrew | perl - setup
echo '' >> ~/.bashrc
echo '# nodebrew' >> ~/.bashrc
echo 'export NB_ROOT="$HOME/.nodebrew/current"' >> ~/.bashrc
echo 'export PATH="$NB_ROOT/bin:$PATH"' >> ~/.bashrc
exec $SHELL

nodebrew ls-remote
mkdir .nodebrew/default/src
nodebrew install-binary v8.1.2
nodebrew use v8.1.2
node --version

## 各種パッケージ最新化
pip install --upgrade pip
gem update --system
npm install -g npm

## よく使うgemをグローバルインストール
gem install bundler
gem install unicorn
gem install execjs

2. データベースの設定

PostgreSQLを使用する。

2.1 PostgreSQLのインストール・設定

### (参考) https://www.postgresql.org/download/linux/ubuntu/
sudo vi /etc/apt/sources.list.d/pgdg.list
deb http://apt.postgresql.org/pub/repos/apt/ xenial-pgdg main # この行を追加

wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo apt-get update
sudo apt-get -y install postgresql-9.4
sudo apt autoremove

sudo locale-gen ja_JP.UTF-8
sudo service postgresql restart


## PostgreSQLの設定
sudo vi /etc/postgresql/9.4/main/postgresql.conf # PostgreSQL設定ファイル
shared_buffers = 512MB # パフォーマンス向上のためサイズ拡大(物理メモリの1/4程度が望ましい)
sudo service postgresql restart

DBを作る

アプリごとにDBを作ろう。

ここでは、bananaという名前のPostgreSQLユーザ、banana_productionという名前のデータベースを例として話を進める。 パスワードはpwgenで生成したものから適当に選んで使う。

pwgen  # => Nohwee3k
sudo su - postgres
psql

CREATE DATABASE banana_production WITH TEMPLATE template0 ENCODING = 'UTF-8' LC_COLLATE = 'ja_JP.UTF-8' LC_CTYPE = 'ja_JP.UTF-8';
CREATE USER banana WITH LOGIN PASSWORD 'Nohwee3k';
ALTER USER banana CREATEDB;
GRANT ALL PRIVILEGES ON DATABASE banana_production TO banana;

3. ソースコードをEC2に持ってくる

ローカルで開発したアプリのコードをEC2上に持ってこよう。これはGitリポジトリで経由で行う。 わたしはBitBucketをよく使う。BitBucketは少人数開発であれば無料でプライベートリポジトリを複数作ることができるので便利だ。

bitbucket.org

ただ、事前にBitBucketにSSHキーを登録しておく必要があるので注意。ローカルPCとEC2の両方で、以下のようにしてキーをBitBucketに登録する必要がある。

cd ~/.ssh
ssh-keygen -t rsa -C your_mail_address@example.com
mv id_rsa bitbucket_rsa # わかりやすくするためリネーム
mv id_rsa.pub bitbucket_rsa.pub # わかりやすくするためリネーム
chmod 600 bitbucket_rsa
vi ~/.ssh/config

### 以下を追記
Host bitbucket.org
  HostName bitbucket.org
  IdentityFile ~/.ssh/bitbucket_rsa
  User git
### ここまで

cat ~/.ssh/bitbucket_rsa.pub # ここで表示される文字列をクリップボードへコピーしておく

コピーした公開鍵情報をBitBucketの「SSH鍵」に登録する。 以下のURL (2018.9.10現在)の画面から登録する。

https://bitbucket.org/account/user/YOUR_USER_NAME/ssh-keys/

SSH鍵を登録できたら、BitBucketにリポジトリを作り、ここにローカルアプリをプッシュしよう。

# ローカル等の開発環境で実行
git init
git add .
git commit -m "First commit"
git remote add origin git@bitbucket.org:YOUR_NAME/banana_app.git
git push -u origin master 

EC2の/home/banana上で、リポジトリをクローンする。

# EC2で実行
cd /home/banana
git clone git@bitbucket.org:YOUR_NAME/banana_app.git

4. Production環境で動かす

本番環境でRailsアプリを動かすために、環境変数やデータベース接続の設定を行おう。

4.1 本番用GEMのインストール

unicornとpostgresqlを使うのでGemfileに以下を追加しておくこと。

gem 'pg'
gem 'unicorn'

まずgemをインストールする。

bundle install --path vendor/bundle --without development test # 本番環境依存のパッケージをローカルインストール

インストール時にこんなエラーが出ることがある

Important: You may need to add a javascript runtime to your Gemfile in order for bootstrap's LESS files to compile to CSS.

**********************************************

ExecJS supports these runtimes:

therubyracer - Google V8 embedded within Ruby

therubyrhino - Mozilla Rhino embedded within JRuby

Node.js

**********************************************

対処方法は以下。

gem install execjs

4.2 データベースの接続設定

vi config/database.yml

### 以下を追記
production:
  adapter: postgresql
  encoding: unicode
  database: banana_production
  pool: 5
  username: banana
  password: Nohwee3k
  min_messages: WARNING
### ここまで

ここで、databaseusernamepasswordは先ほどPostgreSQLの設定で使った値と同じものを使うこと。

4.3 環境変数の設定

## 本番環境であることを表す環境変数をセットする
echo "export SECRET_KEY_BASE='production'" >> ~/.bashrc
rake secret
echo "export SECRET_KEY_BASE='435c1b5517e10640b8d661e361cac2682c6dc0c4690ad4c952d6af84098c02538d0a5acb138e34bb313a420b2a4dd97743d182a6307cea12ace817d8db844e6d'" >> ~/.bashrc
exec $SHELL
RAILS_ENV=production bundle exec rake db:setup # DBをproduction環境でセットアップする

## アセットコンパイルを行う
RAILS_ENV=production bundle exec rails assets:precompile

## 起動確認
bundle exec rails s -e production # ブラウザでhttp://xxx.xxx.xxx.xxx:3000を開いて表示されるか確認してみる

ここまで行えば、http://xxx.xxx.xxx.xxx:3000 という風にIPとポート番号でアクセスできるようになる。

次からは独自ドメイン、HTTPSで公開する方法を説明する。

5. Unicorn/Nginxの設定

5.1 Unicornの設定

Unicornというミドルウェアを使用する。Unicornは、次に紹介するNginxというWebサーバとRailsアプリの間を橋渡ししてくれる。

# アプリのルートディレクトリに入って実行
vi config/unicorn.rb # 新規作成

### 以下を追加
# set lets
$app_dir = "/home/banana/banana_app" # Railsアプリケーションのドキュメントルート
$worker  = 2 # HTTPリクエストを処理するプロセスの数. 最低でもCPUコア数以上とすること. コア数は cat /proc/cpuinfo コマンドで確認できる.
$timeout = 30
$listen  = File.expand_path 'tmp/sockets/.unicorn.sock', $app_dir # 任意のパスで良い. Nginxが参照可能な, UNIXドメインソケットのパス
$pid     = File.expand_path 'tmp/pids/unicorn.pid', $app_dir # 同じく任意のパスで良い.
$std_out = File.expand_path 'log/unicorn.stdout.log', $app_dir # 任意のパス. ログの出力先
$std_err = File.expand_path 'log/unicorn.stderr.log', $app_dir # 任意のパス. エラーログの出力先

# set config
working_directory $app_dir
worker_processes  $worker
stdout_path $std_out
stderr_path $std_err
timeout $timeout
listen  $listen
pid $pid

# loading booster
preload_app true

# before starting processes
before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      Process.kill "QUIT", File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end

# after finishing processes
after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end
### ここまで

ここで、上部のこのあたりは各自の環境に合わせて変えていただきたい。その他の部分はオマジナイだと思って、そっくりそのままコピペで構わない。

$app_dir = "/home/banana/banana_app" # Railsアプリケーションのドキュメントルート
$worker  = 2 # HTTPリクエストを処理するプロセスの数. 最低でもCPUコア数以上とすること. コア数は cat /proc/cpuinfo コマンドで確認できる.
$timeout = 30

次に、

sudo vi /etc/init.d/banana

### 以下を追加
#!/bin/sh

# BEGIN INIT INFO
# Provides:          banana
# Required-Start:    $local_fs $remote_fs $network $syslog
# Required-Stop:     $local_fs $remote_fs $network $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts banana (a rails app)
# Description:       starts banana (a rails app) using start-stop-daemon
# END INIT INFO

USER=banana
APP_ROOT=/home/banana/banana_app
RAILS_ENV=production
PID_FILE=$APP_ROOT/tmp/pids/unicorn.pid
CONFIG_FILE=$APP_ROOT/config/unicorn.rb
CMD="/home/banana/.rbenv/shims/bundle exec /home/banana/.rbenv/shims/unicorn_rails"
ARGS="-c $CONFIG_FILE -D -E $RAILS_ENV"

export RAILS_SERVE_STATIC_FILES=1
export SECRET_KEY_BASE='435c1b5517e10640b8d661e361cac2682c6dc0c4690ad4c952d6af84098c02538d0a5acb138e34bb313a420b2a4dd97743d182a6307cea12ace817d8db844e6d''
export PATH=/home/banana/.rbenv/shims/:$PATH

case $1 in
  start)
    start-stop-daemon --start --chuid $USER --chdir $APP_ROOT --exec $CMD -- $ARGS || true
    ;;
  stop)
    start-stop-daemon --stop --signal QUIT --pidfile $PID_FILE || true
    ;;
  restart|force-reload)
    start-stop-daemon --stop --signal USR2 --pidfile $PID_FILE || true
    ;;
  status)
    status_of_proc -p $PID_FILE "$CMD" banana && exit 0 || exit $?
    ;;
  *)
    echo >&2 "Usage: $0 <start|stop|restart|force-reload|status>"
    exit 1
    ;;
esac
### ここまで

ここで、banana_appbananaと書かれている箇所だけ、自身の環境に合わせればOK。たとえば、mikanというユーザでmikan_appというアプリを作る場合は以下のように置換すれば良い。

cat  | perl -pe  "s/banana_app/mikan_app/g" | perl -pe "s/banana/mikan/g"

また、Railsアプリ内でENV["HOGE_HOGE"]のように環境変数を使っている場合は、このファイルに環境変数を書いておく必要がある。たとえば、ツイッターのAPIを使っていて、ツイッターのAPI KEYとAPI SECRETを環境変数から呼び出したいときは

# Twitter
export TWITTER_KEY='xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
export TWITTER_SECRET='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

みたいに書いておこう。

ここまで出来たら有効化する。

# 有効化
sudo chmod 755 /etc/init.d/banana
sudo update-rc.d banana defaults

### bananaアプリの動作確認
sudo service banana start
sudo service banana stop
sudo service banana restart
sudo service banana status

# ※ statusがrunningになっているかどうか確認する。

5.2 Nginxの設定

Nginxがインストールされていなければ、まずインストールするところから。

### (1) nginxサイトが配布するPGPキーを追加
curl http://nginx.org/keys/nginx_signing.key | sudo apt-key add -

### (2) リポジトリを一覧に追加
sudo sh -c "echo 'deb http://nginx.org/packages/ubuntu/ xenial nginx' >> /etc/apt/sources.list"
sudo sh -c "echo 'deb-src http://nginx.org/packages/ubuntu/ xenial nginx' >> /etc/apt/sources.list"

### (3) アップデート後、nginxをインストール
sudo apt-get update
sudo apt-get install nginx

インストールできたら、設定ファイルをいじっていく。

## バーチャルホストの設定
cd /etc/nginx
sudo rm koi-utf koi-win win-utf # これらはロシア語の文字コード返還テーブル.  ただ邪魔なだけなので削除しておく.

sudo vi nginx.conf

### 以下のように変更
user  nginx;
worker_processes  1; # CPUコア数と同じ値が推奨

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024; # 同時接続数
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    keepalive_timeout  65;
    include /etc/nginx/sites-enabled/*.conf;
}
### ここまで

ここで、最後の行include /etc/nginx/sites-enabled/*.conf;が重要な役割を担っている。sites-enabledディレクトリの中に入っている*.confというコンフィグファイルがアルファベット順に実行される。

ただし、Ngingの作法としては、sites-enabledに直接コンフィグファイルを置かず、代わりにsites-availableというディレクトリにコンフィグファイルを置くという風にされている。sites-available内に作ったコンフィグファイルに対してシンボリックリンクを張り、そのリンクをsites-enabledに置くというやり方をする。

sudo mkdir sites-available # コンフィグファイルの置き場を作る
sudo mkdir sites-enabled # コンフィグファイルに張るシンボリックリンクを置く場所を作る
sudo cp conf.d/default.conf sites-available/ # デフォルトのコンフィグファイルをコンフィグ置き場にコピーする

デフォルトのコンフィグを以下のように修正する。

vi sites-available/default.conf

###
server {
    listen       80;
    server_name  localhost;

    # 適当なランダムな文字列↓
    location /_LJoilLNe5KJHIy84LL4lKJEZ {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
### ここまで

ここで_LJoilLNe5KJHIy84LL4lKJEZというのは適当なランダムな文字列だ。これは、後ほど紹介するEC2ロードバランサのヘルスチェック用として使う。

以下のヘルスチェック用のページを用意しよう。

sudo vi /usr/share/nginx/html/_rQhFwH9PWgMXZyUtPrrfi2JQ9nVMfPUeyInYcdEZ/index.html

### 以下を追加
<!DOCTYPE html>
<html>
<head>
<title>Check Page</title>
</head>
<body>
Check Page
</body>
</html>
###

次に、banana用のコンフィルファイルを作る。

sudo vi sites-available/banana.conf # 新しいコンフィグ新規作成

### ここから
limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;
limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=1r/s;

upstream app_server {
  server unix:/home/banana/banana_app/tmp/sockets/.unicorn.sock;
}

server {
  listen 80;
  server_name example.com; # このドメインでアプリを公開する

  # 静的コンテンツ(public/****)の場所
  root /home/banana/banana_app/public;
  error_log  /home/banana/banana_app/log/nginx.error.log;
  access_log /home/banana/banana_App/log/nginx.access.log;

  # 接続制限関連
  keepalive_timeout 5; # 接続を保つ秒数 (国内であれば5-10でOK)
  client_max_body_size 256k; # クライアントからのリクエストボディは256KBまで許容する.
  client_header_buffer_size 128k; # クライアントからのリクエストヘッダは128KBまで許容する.
  large_client_header_buffers 4 128k; # クライアントからのリクエストヘッダは128KBまで許容する.
  limit_conn conn_limit_per_ip 10; # 1つのIPに対する同時接続は10本まで許容する.
  limit_req zone=req_limit_per_ip burst=5; # リクエスト数/秒の制限にかかったリクエストは5件まで許容する

  # Railsアプリへのルーティング
  try_files $uri/index.html $uri.html $uri @app;
  location @app {
    # HTTP headers
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://app_server;
  }

  # Railsエラーページ
  error_page 500 502 503 504 /500.html;
  location = /500.html {
    root /home/banana/banana_app/public;
  }
}
### ここまで

sudo ln -s /etc/nginx/sites-available/default.conf /etc/nginx/sites-enabled/000_default.conf # シンボリックリンクを張る
sudo ln -s /etc/nginx/sites-available/banana.conf /etc/nginx/sites-enabled/ # シンボリックリンクを張る

ここで、例としてexample.comというドメインでアプリを公開するという体で話を進める。ここは、自分が使いたいドメイン名で置き換えていただきたい。

最後に、これまで作ったNginxのファイルが間違っていないかチェックする。

sudo nginx -t # コンフィグが正常かどうかテスト

# syntax ok と出ればOK.
# okと出ない場合は、どこかタイピングミスしていると思われる。

最後に、Nginxを有効化する。

sudo service nginx restart
sudo service nginx status

# ※ statusがrunningになっているかどうか確認する。

6. 独自ドメイン/HTTPSで公開

6.1 お名前.comでドメインを買う

www.onamae.com

6.2 お名前.comでネームサーバをRoute53に設定する

AWSのRoute53で、新たにHosted zoneを作り、先程購入したドメインと同じ名前のhosted zoneを作る。新しく割り当てられた4のネームサーバ (nsと書かれている)をコピーしておく。 https://console.aws.amazon.com/route53/

f:id:twx:20180911105543p:plain

コピーしたネームサーバを、お名前.comのネームサーバに設定する。

https://www.onamae.com/domain/navi/ns_update/input

このページで、「他のネームサーバを利用」というタブををクリックし、先程コピーしておいた4つのNSを入力する。

6.3 AWS Certificate ManagerでSSL証明書を取得する

https://ap-northeast-1.console.aws.amazon.com/acm/home

「証明書のリクエスト」を押して、先程取得したドメイン名と同じドメインの証明書を取得する。その際、「Route53にレコード(CNAME)を追加する」という旨のボタンを押しておく。(AWSのアップデートで、ボタン名の表現は頻繁に変わるので一言一句同じボタンは無いかもしれない。ニュアンスで判断していただきたい。)

※ 10分ほど待つと有効になる。有効にならないと、次の処理が失敗するので必ず有効になっているかどうか確認すること。

6.4 クラシックロードバランサを立てる

https://ap-northeast-1.console.aws.amazon.com/ec2/v2/home#LoadBalancers

クラシックロードバランサはドメインごとに立てる。(アプリを2つ公開したい場合は2つのドメイン、2つのロードバランサを立てる。) クラシックロードバランサを立てるとき、設定画面で「HTTPS:443をHTTP:80にリダイレクトする」ように設定する。また、SSL証明書として、上記で取得したACMの証明書を指定する。また、紐付けるインスタンスは当然、本番運用するEC2とする。

ロードバランサが出来たら、ヘルスチェックの設定を行う。

これは、定期的にロードバランサがEC2の死活をチェックするという機能だ。特定のURLでアクセスしEC2から反応があれば「生きている」と判断される。ここで、「特定のURL」というのが、先程作成した適当な名前(_LJoilLNe5KJHIy84LL4lKJEZとした)のサイトだ。

「ヘルスチェックの編集」画面で以下の様に設定する。

f:id:twx:20180911111315p:plain

Aレコードでロードバランサのエイリアスを指定

Route53で、Aレコードを作成する。 AWSでは、Aレコードを選んだときに「エイリアス」というものを指定できる。

f:id:twx:20180911111722p:plain

関連付け対象に、先程作ったロードバランサを指定する。

以上で準備は全て整った。

7. 確認

https://YOUR_DOMAIN

でアプリが見れるようになった。

以上、今回はRailsアプリをAWS EC2で公開する超簡単な手順 【独自ドメイン/HTTPS対応】を紹介しました。 この記事が役にたったという方は以下の「★+」ボタンのクリック、SNSでのシェア、「読者になる」ボタンのクリックをお願いいたします。 ではまた〜

深層学習の「超解像」でモザイクは除去できるのか (Tensorflow + Keras)

f:id:twx:20180909143610p:plain

画像出典: http://mmlab.ie.cuhk.edu.hk/projects/SRCNN.html

結論から言うと

うまくいかなかった。

あくまで実験記録として記事を書くが、ここにあるコードを真似してもうまく高解像度化できないので注意していただきたい。

超解像をやってみた。

モザイク画像から元画像を推定するAutoEncoderを作ってみた。深層学習フレームワークはTensorflow+Kerasを使用した。 参考にした論文はこれだ。

Learning a Deep Convolutional Network for Image Super-Resolution

SRCNNという、3層で構成されたシンプルなネットワークだ。任意のshapeの画像を受け取り、画像サイズを変えないようにpaddingを付与してCNNを3回繰り返すという処理を行う。活性化関数はReLuとした。

# ネットワークの定義
model = Sequential()
model.add(Conv2D(
    filters=64,
    kernel_size=9,
    padding='same',
    activation='relu',
    input_shape=(None, None, 3)
))
model.add(Conv2D(
    filters=32,
    kernel_size=1,
    padding='same',
    activation='relu'
))
model.add(Conv2D(
    filters=3,
    kernel_size=5,
    padding='same'
))

低解像度画像を作る

元画像を10%ほど粗くしたモザイク画像を作り、モザイク画像から元画像を推定させる。画像をモザイク化する関数は以下の記事を参考にしてOpenCVを用いて実装した。

note.nkmk.me

# 受け取ったnp arrayにモザイクをかけnp arrayとしてreturnする
def drop_resolution(arr, ratio=0.1):    
    tmp = cv2.resize(arr, None, fx=ratio, fy=ratio, interpolation=cv2.INTER_NEAREST)
    new_img = cv2.resize(tmp, arr.shape[:2][::-1], interpolation=cv2.INTER_NEAREST)
    new_arr = img_to_array(new_img)
    
    return new_arr

試しにモザイク化してみた。こんな感じになった。

f:id:twx:20180909144802p:plain

学習しよう

コード全文を以下に掲載する。Google ColaboratoryでGPUを使って回すと、だいたい30分〜40分くらいで終わる。

Google Colaboratoryで学習するには、画像データをGoogle Driveにアップロードしておき、ColaboratoryでGoogle Driveにアクセスできるように設定しておく必要がある。詳しくは以下の記事を参照していただきたい。

www.mahirokazuko.com

# 各種モジュールのインポート
import os
import glob
import math
import random
import cv2
import numpy as np
from tensorflow.python import keras
from tensorflow.python.keras import backend as K
from tensorflow.python.keras.models import Model, Sequential
from tensorflow.python.keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array, array_to_img
from tensorflow.python.keras.layers import Add, Input, Conv2D, Conv2DTranspose, Dense, Input, MaxPooling2D, UpSampling2D, Lambda

# 受け取ったnp arrayにモザイクをかけnp arrayとしてreturnする
def mosaic(arr, ratio=0.1):    
    tmp = cv2.resize(arr, None, fx=ratio, fy=ratio, interpolation=cv2.INTER_NEAREST)
    new_img = cv2.resize(tmp, arr.shape[:2][::-1], interpolation=cv2.INTER_NEAREST)
    new_arr = img_to_array(new_img)
    return new_arr

# yieldをもちいてミニバッチをreturnするジェネレータ
def data_generator(data_dir, mode, mosaic_ratio=0.1, target_size=(128, 128), batch_size=32, shuffle=True):
    for imgs in ImageDataGenerator().flow_from_directory(
        directory=data_dir,
        classes=[mode],
        class_mode=None,
        color_mode='rgb',
        target_size=target_size,
        batch_size=batch_size,
        shuffle=shuffle
    ):
        x = np.array([mosaic(img_to_array(img), mosaic_ratio) for img in imgs])
        yield x/255., imgs/255.

        
DATA_DIR = 'data/'
BATCH_SIZE = 100

# 上で定義したジェネレータをもちいて学習データとテストデータをロード
train_data_generator = data_generator(DATA_DIR, 'train', batch_size=BATCH_SIZE, shuffle=True)
test_data_generator = data_generator(DATA_DIR, 'test', batch_size=BATCH_SIZE, shuffle=False)


# ネットワークの定義
model = Sequential()
model.add(Conv2D(
    filters=64,
    kernel_size=9,
    padding='same',
    activation='relu',
    input_shape=(None, None, 3)
))
model.add(Conv2D(
    filters=32,
    kernel_size=1,
    padding='same',
    activation='relu'
))
model.add(Conv2D(
    filters=3,
    kernel_size=5,
    padding='same'
))

# ピーク信号対雑音比(PSNR)を「解像度の高さ」の指標とする。この値が20以上くらいになると比較的、解像度が良いとされている。
def psnr(y_true, y_pred):
    return -10*K.log(
        K.mean(K.flatten((y_true - y_pred))**2)
    )/np.log(10)

# モデルをコンパイルする。損失関数は二乗誤差。最適化アルゴリズムはAdam。評価指標は上で定義したpsnr。
model.compile(
    loss='mean_squared_error', 
    optimizer='adam', 
    metrics=[psnr]
)

# 学習させる
model.fit_generator(
    train_data_generator,
    validation_data=test_data_generator,
    validation_steps=1,
    steps_per_epoch=100,
    epochs=50
)

結果を見てみる

# 未知のデータに対してモザイク除去を試してみる

from IPython.display import display_png

unknown_img_t = img_to_array( load_img('pingpong.jpg') )
unknown_img_x = mosaic(unknown_img_t)
unknown_img_y = model.predict(unknown_img_x.reshape(1,128,128,3))[0]

display_png( array_to_img( unknown_img_t ) )
display_png( array_to_img( unknown_img_x ) )
display_png( array_to_img( unknown_img_y ) )

以下のようになった。上から順に、オリジナル画像、モザイク画像、モザイク除去画像だ。

f:id:twx:20180909163941p:plain

確かに、少しはマシになっているのかもしれないが、モザイクが除去できているとは言えない結果となった。

冒頭の元論文では、「少しぼやけた画像」に対して高解像度化の効果が得られたと書かれていた。さすがに、今回のモザイクのようにかなり粗い画像に対しては期待できる効果は得られないようだ。

GAN等を使ったほうが、良い結果になるかもしれない。

以上、今回は超解像を試してみました。 良い記事だと思っていただけた方は下の「★+」ボタンのクリック、SNSでのシェア、「読者になる」ボタンのクリックをお願いいたします。 それではまた〜

Kozuko Mahiro's Hacklog ―― Copyright © 2018 Mahiro Kazuko