まひろ量子のハックログ

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

簡単!EC-CUBE4で独自ドメインのメールをGmail経由で送る方法

f:id:twx:20190515193702p:plain

EC-CUBEで独自ドメインのメールアドレスからメールを送れるようにしたいと思ったことはありませんか?

この記事では、EC-CUBE4を対象に、独自ドメインのメールをGmail経由で送れるようにする方法をご説明します。

必要な費用はEC-CUBE4のサーバ台とドメイン代のみです。無料のGmailを使うのでメールサーバの料金などが新たに発生することはありません。

1. 独自ドメインの取得

まずは以下の『お名前.com』というサイトで独自ドメインを購入しましょう。費用は年間で数百円〜数千円です。

以下では、example.com というドメインを取得したという想定で説明を続けます。

ドメインを購入したら、自動的にxxxxxx@example.com というメールアドレスが使えるようになります。xxxxxxの部分は任意の文字列です。support@example.cominfo@example.com など、お好きなメールアドレスが使えると思っていただいてOKです。ドメインを1つに対してメールアドレスは2つ以上作ってもOKです。

以下では、example@example.comというメールアドレスをEC-CUBE4で運用するという想定で話を続けます。

2. Gmailの設定

Gmailのアカウントを持っていない方は新規作成してください。

mail.google.com

hogehoge@gmail.comというGmailアドレスを持っているとします。

Googleのセキュリティ設定用ページにアクセスしhogehoge@gmail.comでログインしてください。ログインすると、パスワードの発行ページが現れます。「アプリを選択」→「その他」→任意の名前を付けて生成ボタンを押してください。するとパスワードが1回だけ生成されます。このパスワードページを、消さずにそのまま保持しておいてください。

f:id:twx:20190515233828p:plain
パスワード発行画面

3. EC-CUBE4の設定

管理者画面で、送信元メールアドレス(From)をexample@example.comにしてください。

f:id:twx:20190515232827p:plain
EC-CUBE4設定画面

次に、設定ファイル .env を開いて25行目付近のMAILER_URLを以下のように書き換えてください。

MAILER_URL=smtp://smtp.gmail.com:587?encryption=tls&auth_mode=login&username=[XXX]&password=[YYY]

ただし、[XXX]はGmailアカウントの@gmail.comより前の文字列、つまり、hogehoge@gmail.comであればhogehogeとしてください。また、[YYY]の部分は先ほどのパスワード発行画面で得たパスワード(スペースは不要)に置き換えてください。

以上で、EC-CUBE4からメールを送れるようになりました。試しに、新規会員登録をしてみてください。example@example.comというメールアドレスから会員登録の旨のメールが届きます。

以上、本日はEC-CUBE4で独自ドメインのメールをGmail経由で送る方法をご紹介しました。良い記事だと思っていただいた方は、SNSでのシェア、ブログからのリンク、「読者になる」ボタンのクリック、「★」ボタンのクリック、よろしくお願いします!

htopの緑、水色、黄色、赤が表す意味

f:id:twx:20190506190123p:plain
htop

htopコマンドでCPUやメモリ使用量を見ることができます。その時、使用量を表すバーが緑や水色や黄色になっているのですが、これがどういう意味か調べて見ました。

以下に神回答がありました。

serverfault.com

CPU:

  • 青 = 優先度【低】のスレッド
  • 緑 = 優先度【中】のスレッド
  • 赤 = カーネルスレッド

Memory:

  • 緑 = 使用中
  • 青 = バッファ
  • 黄 = キャッシュ

だそうです。

【1分でわかる】ufwのルールを追加・確認・削除する方法

f:id:twx:20190506172312p:plain
ファイアーウォール

ルールの追加

sudo ufw allow 8080

ルールの確認

sudo ufw status

Status: active

To                         Action      From
--                         ------      ----
22                         ALLOW       Anywhere
80                         ALLOW       Anywhere
18080                      ALLOW       Anywhere
3000                       ALLOW       Anywhere
8080                       ALLOW       Anywhere

ルールの削除

まず、以下のようにしてルールのIDを確認します。

sudo ufw status numbered

Status: active

     To                         Action      From
     --                         ------      ----
[ 1] 22                         ALLOW IN    Anywhere
[ 2] 80                         ALLOW IN    Anywhere
[ 3] 18080                      ALLOW IN    Anywhere
[ 4] 3000                       ALLOW IN    Anywhere
[ 5] 8080                       ALLOW IN    Anywhere

この番号を指定して削除します。3番のruleを削除したければ

sudo ufw delete 3

と入力します。確認を求められるのでy Enterを入力します。

もう一度確認して見ると、[ 3] 18080 が削除できていることがわかります。

sudo ufw status

Status: active

To                         Action      From
--                         ------      ----
22                         ALLOW       Anywhere
80                         ALLOW       Anywhere
3000                       ALLOW       Anywhere
8080                       ALLOW       Anywhere

以上、ufwのルールを追加・確認・削除する方法をご紹介しました。良い記事だと思っていただいた方は、SNSでのシェア、ブログからのリンク、「読者になる」ボタンのクリック、「★」ボタンのクリック、よろしくお願いします!

【1分でわかる】Linuxに新たな秘密鍵でSSHログインする方法

f:id:twx:20190506150008p:plain
SSH鍵

以下ではUbuntuを前提として話を進めます。

接続先Ubuntuに新規ユーザを作る

まず、適当なアカウントで接続先Ubuntuにログインします。 新たなユーザを作るコマンドを実行します。以下、hogeという名前のユーザを作成するという体で説明します。

# 新規ユーザ作成
sudo useradd -m hoge

# sudoに追加
sudo gpasswd -a hoge sudo

# パスワードを設定
sudo passwd hoge # 任意のパスワードを設定

# 使用するシェルを設定
sudo chsh hoge # /bin/bash と打ってEnter

以上でユーザが作成されました。

SSH鍵の作成

接続元の端末で以下のコマンドを実行して公開鍵・秘密鍵を作ります。

ssh-keygen -t rsa

Generating public/private rsa key pair.
Enter file in which to save the key (/Users/hogehoge/.ssh/id_rsa): # ← 分かりやすい名前をつけること。例: ./aws-ec2-user-hoge
Enter passphrase (empty for no passphrase): # ← 任意のパスワードを設定できる。空欄でも良い。
Enter same passphrase again: # パスワードの確認

Enter file in which to save the keyの項目で例えば ./aws-ec2-user-hoge と入力した場合、カレントディレクトリにaws-ec2-user-hogeaws-ec2-user-hoge.pubという2つのファイルが生成されます。お尻に.pubと付いている方が公開鍵で、もう一方が秘密鍵です。

公開鍵の転送

以下では、接続先Ubuntuにhogeという名前のユーザでログインしたいとして説明します。

接続先Ubuntuの

/home/hoge/.ssh/authorized_keys

というファイルに、先程の公開鍵(※秘密鍵ではない)をコピー&ペーストします。このフォルダやファイルが存在しない場合は新規作成してください。

次に、このファイルの所有者とパーミッションを変更します。

sudo chown -R hoge /home/hoge/.ssh
sudo chgrp -R hoge /home/hoge/.ssh
sudo chmod 0700 /home/hoge/.ssh
sudo chmod 0600 /home/hoge/.ssh/authorized_keys

以上で、SSHログインできるようになりました。

接続テスト

接続元端末で以下を実行してください。ログインできるようになっているはずです。

ssh hoge@xxx.xxx.xxx.xxx -i aws-ec2-user-hoge

以上、今回はUbuntuに新たなユーザを作成し秘密鍵でSSHログインできるようにしてみました。良い記事だと思っていただいた方は、SNSでのシェア、ブログからのリンク、「読者になる」ボタンのクリック、「★」ボタンのクリック、よろしくお願いします!

pythonのloggingでSUCCESSレベルを追加

f:id:twx:20190504174817j:plain
logging

Pythonでログを出力する際にloggingを使っている方は多いと思います。loggingを使えば、単にログを書き出すだけではなくINFODEBUGWARNINGERRORCRITICALといった「重要度のレベル」を添えてログを出力することができます。

また、どの程度のレベルまでを出力するかを指定することもできます。 たとえばERRORよりも深刻なログだけを表示すると指定した場合、ERRORCRITICALのログだけが出力されます。

このように、非常に便利なloggingですが、デフォルトでは上述の5つのレベルしかありません。

ここでは、SUCCESSという新たなログレベルを追加してみようと思います。WARNINGINFOの間のレベルとします。

import logging
from logging import Formatter, getLogger, StreamHandler, FileHandler, DEBUG
import sys
import traceback

formatter = Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler = StreamHandler(sys.stdout)
handler.setLevel(DEBUG)
handler.setFormatter(formatter)
logger = getLogger(__name__)
logger.addHandler(handler)
logger.setLevel(DEBUG)

# SUCCESSを追加
logging.SUCCESS = 25 # WARNINGとINFOの間
logging.addLevelName(logging.SUCCESS, 'SUCCESS')
setattr(logger, 'success', lambda message, *args: logger._log(logging.SUCCESS, message, args))

# テストしてみます。
logger.debug('this is debug message')
logger.info('this is info message')
logger.success('this is success message')
logger.warning('this is warning message')
logger.error('this is error message')
logger.critical('this is critical message')

for i in range(10):
    try:
        a = 10/i
        if i == 5:
            i + 'hello world'
        logger.success('結果: {}'.format(a))
    except Exception as e:
        logger.error(traceback.format_exc())

上記のコードの実行結果は以下のようになります。

$ python logging_test.py
2019-05-04 08:43:07,513 - DEBUG - this is debug message
2019-05-04 08:43:07,514 - INFO - this is info message
2019-05-04 08:43:07,514 - SUCCESS - this is success message
2019-05-04 08:43:07,514 - WARNING - this is warning message
2019-05-04 08:43:07,514 - ERROR - this is error message
2019-05-04 08:43:07,514 - CRITICAL - this is critical message
2019-05-04 08:43:07,514 - ERROR - Traceback (most recent call last):
  File "logging_test.py", line 27, in <module>
    a = 10/i
ZeroDivisionError: division by zero

2019-05-04 08:43:07,514 - SUCCESS - 結果: 10.0
2019-05-04 08:43:07,515 - SUCCESS - 結果: 5.0
2019-05-04 08:43:07,515 - SUCCESS - 結果: 3.3333333333333335
2019-05-04 08:43:07,515 - SUCCESS - 結果: 2.5
2019-05-04 08:43:07,515 - ERROR - Traceback (most recent call last):
  File "logging_test.py", line 29, in <module>
    i + 'hello world'
TypeError: unsupported operand type(s) for +: 'int' and 'str'

2019-05-04 08:43:07,515 - SUCCESS - 結果: 1.6666666666666667
2019-05-04 08:43:07,515 - SUCCESS - 結果: 1.4285714285714286
2019-05-04 08:43:07,515 - SUCCESS - 結果: 1.25
2019-05-04 08:43:07,515 - SUCCESS - 結果: 1.1111111111111112

うまくSUCCESSが表示されていることがわかります。 以上、今回はloggingにSUCCESSレベルを追加してみました。良い記事だと思っていただいた方は、SNSでのシェア、ブログからのリンク、「読者になる」ボタンのクリック、「★」ボタンのクリック、よろしくお願いします!

Pythonでハイフンやマイナスなど類似文字の正規化

f:id:twx:20190502170113p:plain
類似文字の文字コード

類似文字問題

ハイフンとダッシュのように、別の文字ではあるが、見た目が全く同じ文字というものが存在しています。以下がその例です。

# ハイフンに見える文字と、その文字コード
- 0x2d0x20110x20120x20130xff0d0x25000x30fc 

# 波線に見える文字と、その文字コード0x301c0xff5e

# 中点に見える文字と、その文字コード
· 0xb70x22c5

見た目では区別が付きませんね。

これは結構困ります。キーボードで全角マイナスを打つときに、WindowsとMacでは別の文字が入力されるとさえ言われています。 https://www.ilovex.co.jp/Division/ITD/archives/2008/02/windowsmac.html

このような「見た目が同じでも実は別の文字」は、自然言語処理のあらゆる場面でも問題となってきます。たとえば、正規表現で電話番号をマッチさせたい時、上述の全てのハイフンを考慮しなくてはなりません。 これは大変面倒なので、事前に正規化することで特定のハイフンだけを考慮すれば良いというようにしましょう。

Pythonで正規化するコードを書いてみました。 なお、似た文字のリストはこちらを参考にさせていただきました。

Unicodeの似た文字を整理してみた - y-kawazの日記

def normalize_wavedash(text, show_conversion_rule=False):
    convert_from = [
        u'\u007e',
        u'\u223c',
        u'\u223e',
        u'\u301c',
        u'\u3030',
        u'\uff5e'
    ]
    convert_to = u'\u301c'
    for char in convert_from:
        text = text.replace(char, convert_to)
    if not show_conversion_rule:
        return text
    print('rule of wavedash normalization')
    for char in convert_from:
        print(char, hex(ord(char)), '->', convert_to, hex(ord(convert_to)))
    return text

def normalize_hyphen(text, show_conversion_rule=False):
    convert_from = [
        u'\u002d',
        u'\u00ad',
        u'\u2010',
        u'\u2011',
        u'\u2012',
        u'\u2013',
        u'\u2043',
        u'\ufe63',
        u'\u2212',
        u'\u207b',
        u'\u208b',
        u'\uff0d',
        u'\u2500',
        u'\u2501',
        u'\u30fc'
    ]
    convert_to = u'\u30fc'
    for char in convert_from:
        text = text.replace(char, convert_to)
    if not show_conversion_rule:
        return text
    print('rule of hyphen normalization')
    for char in convert_from:
        print(char, hex(ord(char)), '->', convert_to, hex(ord(convert_to)))
    return text

def normalize_3dots(text, show_conversion_rule=False):
    convert_from = [
        u'\u2026',
        u'\u22ef'
    ]
    convert_to = u'\u2026'
    for char in convert_from:
        text = text.replace(char, convert_to)
    if not show_conversion_rule:
        return text
    print('rule of 3 dots normalization')
    for char in convert_from:
        print(char, hex(ord(char)), '->', convert_to, hex(ord(convert_to)))
    return text

def normalize_dot(text, show_conversion_rule=False):
    convert_from = [
        u'\u00b7',
        u'\u2022',
        u'\u2219',
        u'\u22c5',
        u'\u30fb',
        u'\uff65'
    ]
    convert_to = u'\u30fb'
    for char in convert_from:
        text = text.replace(char, convert_to)
    if not show_conversion_rule:
        return text
    print('rule of dot normalization')
    for char in convert_from:
        print(char, hex(ord(char)), '->', convert_to, hex(ord(convert_to)))
    return text

if __name__ == '__main__':
    print(normalize_wavedash('', show_conversion_rule=True))
    print(normalize_hyphen('', show_conversion_rule=True))
    print(normalize_3dots('', show_conversion_rule=True))
    print(normalize_dot('', show_conversion_rule=True))

    text = 'やっほ〰 4月1日~4月2日 15:00∼16:00開催'
    print('変換前:', text)
    print('変換後:', normalize_wavedash(text))
    print()

    text = 'やっほ‒ 4月1日-4月2日 15:00─16:00開催'
    print('変換前:', text)
    print('変換後:', normalize_hyphen(text))
    print()

    text = '日時…4月1日-4月2日 時間⋯15:00─16:00'
    print('変換前:', text)
    print('変換後:', normalize_3dots(text))
    print()

    text = '日時・4月1日-4月2日 時間・15:00─16:00'
    print('変換前:', text)
    print('変換後:', normalize_dot(text))
    print()

上のコードを実行してみた結果。

rule of wavedash normalization
~ 0x7e -> 〜 0x301c
∼ 0x223c -> 〜 0x301c
∾ 0x223e -> 〜 0x301c
〜 0x301c -> 〜 0x301c
〰 0x3030 -> 〜 0x301c
~ 0xff5e -> 〜 0x301c

rule of hyphen normalization
- 0x2d -> ー 0x30fc
­ 0xad -> ー 0x30fc
‐ 0x2010 -> ー 0x30fc
‑ 0x2011 -> ー 0x30fc
‒ 0x2012 -> ー 0x30fc
– 0x2013 -> ー 0x30fc
⁃ 0x2043 -> ー 0x30fc
﹣ 0xfe63 -> ー 0x30fc
− 0x2212 -> ー 0x30fc
⁻ 0x207b -> ー 0x30fc
₋ 0x208b -> ー 0x30fc
- 0xff0d -> ー 0x30fc
─ 0x2500 -> ー 0x30fc
━ 0x2501 -> ー 0x30fc
ー 0x30fc -> ー 0x30fc

rule of 3 dots normalization
… 0x2026 -> … 0x2026
⋯ 0x22ef -> … 0x2026

rule of dot normalization
· 0xb7 -> ・ 0x30fb
• 0x2022 -> ・ 0x30fb
∙ 0x2219 -> ・ 0x30fb
⋅ 0x22c5 -> ・ 0x30fb
・ 0x30fb -> ・ 0x30fb
・ 0xff65 -> ・ 0x30fb

変換前: やっほ〰 4月1日~4月2日 15:00∼16:00開催
変換後: やっほ〜 4月1日〜4月2日 15:00〜16:00開催

変換前: やっほ‒ 4月1日-4月2日 15:00─16:00開催
変換後: やっほー 4月1日ー4月2日 15:00ー16:00開催

変換前: 日時…4月1日-4月2日 時間⋯15:00─16:00
変換後: 日時…4月1日-4月2日 時間…15:00─16:00

変換前: 日時・4月1日-4月2日 時間・15:00─16:00
変換後: 日時・4月1日-4月2日 時間・15:00─16:00

以上、今回はPythonでハイフンやマイナスなど類似文字を正規化してみました。 良い記事だと思っていただいた方は、SNSでのシェア、ブログからのリンク、「読者になる」ボタンのクリック、「★」ボタンのクリック、よろしくお願いします!

ターミナルに色がつかなくなった時の対処法

f:id:twx:20190322004925j:plain
ターミナルに色がつかなくなった…

ターミナルの文字に色を付ける設定は、通常、~/.bashrcの以下の行に書かれています。

以下の例は、AWS EC2のUbuntu18.04の~/.bashrcからの抜粋しました。

# uncomment for a colored prompt, if the terminal has the capability; turned
# off by default to not distract the user: the focus in a terminal window
# should be on the output of commands, not on the prompt
# force_color_prompt=yes

if [ -n "$force_color_prompt" ]; then
    if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
        # We have color support; assume it's compliant with Ecma-48
        # (ISO/IEC-6429). (Lack of such support is extremely rare, and such
        # a case would tend to support setf rather than setaf.)
        color_prompt=yes
    else
        color_prompt=
    fi
fi

if [ "$color_prompt" = yes ]; then
    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
unset color_prompt force_color_prompt

このような記述があればターミナルの文字に色が付きます。

しかし、以前、仕事中に突然ターミナルの色が消えてしまう(白黒になる)ことがありました。 ~/.bashrcはターミナル起動後に毎回実行されるはずなので、再起動したら直るかと思いましたが、無駄でした。

原因を探っていくと、実は「~/.bashrcはターミナル起動後に毎回実行されるわけではない」ということがわかりました。 ~/.bashrcが実行されるかどうかは、~/.profileの中に書かれています。

~/.profileの中を見てみましょう。

# ~/.profile: executed by the command interpreter for login shells.
# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
# exists.
# see /usr/share/doc/bash/examples/startup-files for examples.
# the files are located in the bash-doc package.

# the default umask is set in /etc/profile; for setting the umask
# for ssh logins, install and configure the libpam-umask package.
#umask 022

# if running bash
if [ -n "$BASH_VERSION" ]; then
    # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
    . "$HOME/.bashrc"
    fi
fi

# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
    PATH="$HOME/bin:$PATH"
fi

# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/.local/bin" ] ; then
    PATH="$HOME/.local/bin:$PATH"
fi

これを見てみると、中央あたりで.bashrcが実行されていることがわかります。また、1行目のコメントを見てみると、 ~/.bash_profile~/.bash_loginというファイルが存在する場合、.profileは読み込まれないと書かれています。

原因はこれでした!

ホームディレクトリを見てみると、~/.bash_profileが存在しました。どうやら、マシンの環境設定中に、本来は~/.bashrcを編集すべきところ誤って~/.bash_profileを作ってしまったのだと思います。~/.bash_profileを削除したら無事、ターミナルが色を取り戻しました。

~/.bash_profile には重要な情報が書かれていることがあります。~/.bash_profileを削除して何らかの被害が生じても、本記事および本記事の執筆者は責任を負えません。ご自身の判断と責任のもとで削除してください。

以上、ターミナルの色が付かない問題の解決方法でした。

Docker初心者が最低限抑えておくべき10のコマンド

f:id:twx:20190429153732p:plain
Docker

Dockerのチュートリアル

Dockerそのものの解説は以下の資料が非常にわかりやすかったです。

www.slideshare.net

qiita.com

初心者がよく使うコマンド

Dockerの解説書を見るたびに「Dokcerコマンド多すぎィ!」という感情を抱くのですが、私(Docker歴2ヶ月の初心者)がよく使うコマンドは、実は10個くらいしかありません。そこで、毎回コマンドをコピペできるようにパターン化してみました。

f:id:twx:20190429163855j:plain
Dockerでよく使うコマンド

1. pull: イメージをダウンロードする

DockerHubからイメージをダウンロードするコマンドです。後述のrunで代用できるので、pull自体は実はあまり使いません。

docker pull [image_name]

2. build: イメージを作る

Dockerfileという、イメージのレシピを使ってイメージを作成するコマンドです。-tで、任意のイメージ名をつけられます。最後の引数 . はDockerfileがあるディレクトリのパスを意味しています。

docker build -t [image_name:tag] .

3. イメージの一覧を見る

イメージの一覧を見ます。

docker images

4. コンテナ化

イメージからコンテナを作成しコマンドを実行します。 --nameでコンテナに名前をつけられます。最後の引数は、コンテナ上で実行するコマンドです。コマンドを省略することもできます。その場合は、イメージごとにデフォルトで指定されているコマンドが実行されます。

docker run --name [container_name] [image_name] [command]

最後の引数はコンテナ上で実行するコマンドである、と上述しました。つまり、/bin/bashを与えれば、bashが実行されます。その際、-itオプションを付けることで、いま開いているターミナルを用いて標準入力を与えたり標準出力を得たりすることができます。(本当は違うのですが)-itオプションは、「コンテナでInTeractiveな操作ができる」というニュアンスで捉えています。

docker run -it --name [container_name] [image_name] /bin/bash


バックグラウンドで動かしたい時は-dオプションを付けます。例えば、何らかのサーバとして動かす場合です。このサーバにポート指定でアクセスしたい場合は、-pオプションでポートの設定をする必要があります。[ホスト側のポート番号:コンテナのポート番号の形式を-pオプションの引数に渡すことで、ホスト側のポートからコンテナのポートへポートフォワーディングできるようになります。

docker run -d --name [container_name] -p 8080:80 [image_name] [command]

上の例では8080を80にポートフォワーディングしています。 この仕組みを使って、DockerコンテナにSSHでログインすることもできるようになります(後述)

5. コンテナから脱出する

上述のdocker run -itを実行した結果、ターミナルでコンテナを操作できる状態になります。この状態から脱出し、元のホスト側の環境に戻るには以下の操作をします。

Ctrl p, Ctrl q

コントロールを押しながら「p」を押し、続けてコントロールを押しながら「q」を押します。

一方、SSHログイン先などから抜けるために使うexitコマンドを使うと、コンテナが落ちてしまいます。

6. コンテナに再度入る

上の操作でコンテナから抜けた後、もう一度コンテナに入るには以下のようにします。

docker attach [container_name]

7. コンテナの一覧を見る

コンテナの一覧を見ます。

docker ps

落ちているコンテナも含めてすべて見る場合は以下のようにします。

docker ps -a

8. コンテナを消す

コンテナを消します。

docker rm -f [container_id]

9. イメージを消す

イメージを消します。

docker rmi [image_name]

以上、よく使うコマンドした。commitpushなんかは、Dockerに慣れてきて自分でDockerイメージを作ったりDockerHubに投稿したりする中級者向けなので、今回は省略させていただきました。

10. おまけ: DockerコンテナにSSHログイン

DockerfileにSSHサーバを22番ポート立ち上げるコマンドを記述しておき、これでイメージを作ります。runするときに、適当なポート番号を22番にポートフォワーディングすれば、コンテナにSSHログインできるようになります。

Dockerfile

FROM ubuntu:16.04

RUN DEBIAN_FRONTEND=noninteractive apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y openssh-server
RUN mkdir /var/run/sshd
RUN echo 'root:hogehoge' | chpasswd
RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]

コンテナ化

docker run -d --name ssh-test -p 20022:22 ssh-test-image

ログイン

ssh root@xxx.xxx.xxx.xxx -p 20022

xxx.xxx.xxx.xxxはホスト側のIPアドレスと同じになります。

以上、今回はよく使うDockerコマンドをまとめてみました。

良い記事だと思っていただいた方は、SNSでのシェア、ブログからのリンク、「読者になる」ボタンのクリック、「★」ボタンのクリック、よろしくお願いします!

M2DetをGoogle Colaboratoryで動かしてみた

f:id:twx:20190427142047j:plain
最強の物体検出器M2Detを動かしてみた

M2Detとは

2019年4月現在、最強の物体検出器と呼ばれているモデルです。2017〜2018年頃にSOTAだったYOLOやSSDを引き離し高い精度を叩き出しています。 多段階特徴ピラミッドネットワーク(MLFPN)と呼ばれる方法を提案し、異なるスケールの物体を検出するためのより効果的な仕組みを取り入れています。

元論文はこちらです。 arxiv.org

ソースコードや学習済モデルも公開されているので、今回はこれをGoogle Colaboratory上で動かして遊んでみます。

github.com

動かしてみた

今回、私がやった手順は以下で公開しています。

colab.research.google.com

まず、M2Detをインストールします。

!pip install torch torchvision
!pip install opencv-python tqdm addict
!git clone https://github.com/qijiezhao/M2Det.git
%cd M2Det/
!sh make.sh

次に、学習済みモデルをダウンロードします。Google Driveからサイズの大きいファイルをダウンロードするには、すこし工夫が必要です。詳しくは以下の記事をご覧ください。

www.mahirokazuko.com

モデルがダウンロードできたら、Mw2Detを動かしてみます。

!python demo.py -c=configs/m2det512_vgg.py -m=m2det512_vgg.pth

結果を表示します。まずは、デフォルトで用意されているトラックの画像を表示してみます。

import cv2
import matplotlib.pyplot as plt
plt.figure(figsize=(5,5),dpi=200)
img=cv2.imread('imgs/ss2_m2det.jpg')
show_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(show_img)

f:id:twx:20190427143556p:plain
実行結果:トラック

車が全て検出できておらず「ん?」って感じはしますが、まぁ、とりあえず動かすことには成功しました。精度については、大規模なデータで再学習して向上させるなど、もうひと工夫必要そうです。

手持ちの画像でもやってみた

次に、手持ちの画像でも試してみました。試したのは以下の画像です。

f:id:twx:20190427143940j:plain
フルーツ
f:id:twx:20190427143949j:plain
アヒル

それぞれ、以下のサイトから入手しました。

フルーツ:https://food.foto.ne.jp/

アヒル:https://pixabay.com/

画像が格納されている自身のGoogle DriveをColaboratoryにマウントし、カレントディレクトリに画像をコピーします。そして、M2Detを再度実行します。

from google.colab import drive
drive.mount('/content/drive')
!cp /content/drive/My\ Drive/work/M2Det/*.jpg ./imgs
!python demo.py -c=configs/m2det512_vgg.py -m=m2det512_vgg.pth

表示してみましょう。

plt.figure(figsize=(5,5),dpi=200)
img=cv2.imread('imgs/fruits_m2det.jpg')
show_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(show_img)

f:id:twx:20190427144432p:plain
実行結果:フルーツ

plt.figure(figsize=(5,5),dpi=200)
img=cv2.imread('imgs/goose_m2det.jpg')
show_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(show_img)

f:id:twx:20190427144515p:plain
実行結果:アヒル

まぁまぁの出来ですね。やはり、精度面で、まだまだ完璧とは言えないので再学習したいところです。

以上、今回はM2Detを動かしてみました。使うだけなら簡単なので皆さんもやってみては?

良い記事だと思っていただいた方は、SNSでのシェア、ブログからのリンク、「読者になる」ボタンのクリック、「★」ボタンのクリック、よろしくお願いします! ではまた次の記事でお会いしましょう!

Google Driveから巨大ファイルをダウンロードするコマンド

f:id:twx:20190427132449p:plain
サイズの大きなファイルのダウンロードには確認画面がある

機械学習モデルや、動画ファイルなどサイズの大きなファイルを、他人のGoogle Driveからダウンロードしようとしたとき、上のような確認画面に遷移します。ダウンロードボタンをクリックすれば普通にダウンロードできるのですが、Linuxサーバやjupyter notebookなどのCUI環境でダウンロードしたいときは「ボタンをクリックする」ことができないので困ります。

ダウンロードボタンを右クリックして「リンクのアドレスをコピー」をしても、そのアドレスでは残念ながらwgetコマンドなどでダウンロードできません。

f:id:twx:20190427132824p:plain
右クリックして、アドレスをコピー
f:id:twx:20190427133117p:plain
wgetコマンドを試してみるが失敗…

以下のようなエラーが出てダウンロードできません。

--2019-04-27 04:20:32--  https://drive.google.com/uc?export=download
Resolving drive.google.com (drive.google.com)... 108.177.96.138, 108.177.96.102, 108.177.96.101, ...
Connecting to drive.google.com (drive.google.com)|108.177.96.138|:443... connected.
HTTP request sent, awaiting response... 400 Bad Request
2019-04-27 04:20:38 ERROR 400: Bad Request.

よく見ると、コピーされたアドレスにはconfirm=xxxxというパラメータが付与されています。この4桁の値が毎回変わるので、HTTPリクエストのセッションが変わると弾かれるみたいですね。

pythonでゴリ押す

この問題について、こちらで解決策が提示されていました。

Download-Large-File-From-Google-Drive-Using-Python/Download-Large-File-from-Google-Drive.ipynb at master · nsadawi/Download-Large-File-From-Google-Drive-Using-Python · GitHub

以下の関数download_file_from_google_driveを使えば、コマンドでダウンロードできるようになります。

import requests

def download_file_from_google_drive(id, destination):
    URL = "https://docs.google.com/uc?export=download"

    session = requests.Session()

    response = session.get(URL, params = { 'id' : id }, stream = True)
    token = get_confirm_token(response)

    if token:
        params = { 'id' : id, 'confirm' : token }
        response = session.get(URL, params = params, stream = True)

    save_response_content(response, destination)    

def get_confirm_token(response):
    for key, value in response.cookies.items():
        if key.startswith('download_warning'):
            return value

    return None

def save_response_content(response, destination):
    CHUNK_SIZE = 32768

    with open(destination, "wb") as f:
        for chunk in response.iter_content(CHUNK_SIZE):
            if chunk: # filter out keep-alive new chunks
                f.write(chunk)
file_id = '0B1fGSuBXAh1IeEpzajRISkNHckU'
destination = '/home/myusername/work/myfile.ext'
download_file_from_google_drive(file_id, destination)

試しにやってみた

Google Colaboratory (Googleが提供しているjupyter notebook風のアプリケーション)で、この関数を動かしてみました。

f:id:twx:20190427134037p:plain
ダウンロード成功!!

500MBのファイルがダウンロードできていることがわかります。成功です。

以上、本日はコマンドでGoogle Driveから大きなファイルをダウンロードする方法をご紹介しました。良い記事だと思っていただいた方は、SNSでのシェア、ブログからのリンク、「読者になる」ボタンのクリック、「★」ボタンのクリック、よろしくお願いします! ではまた次の記事でお会いしましょう!

EC2インスタンス変更後にPostgreSQLやRailsが動かない時の対処法

f:id:twx:20190427125137p:plain
AWS EC2変更後にPostgreSQLが動かない…?

EC2インスタンスの変更方法

インスタンスが起動中であれば、まず停止します。 その後、「アクション」→「インスタンスの設定」→「インスタンスタイプの変更」をクリックすればOK。簡単すぎてびっくりします。 変更が完了したら起動してください。

f:id:twx:20190427125511p:plain
インスタンスの停止
f:id:twx:20190427125531p:plain
インスタンスタイプの変更

変更後のインスタンスでRailsが動いていない…?

Railsアプリをデーモンで起動しようとしたところ、うまくいっていない模様。

$ sudo service hogehoge status
● hogehoge.service - LSB: starts hogehoge (a rails app)
   Loaded: loaded (/etc/init.d/hogehoge; bad; vendor preset: enabled)
   Active: active (exited) since Sat 2019-04-27 10:18:14 JST; 10s ago
     Docs: man:systemd-sysv-generator(8)
  Process: 2490 ExecStop=/etc/init.d/hogehoge stop (code=exited, status=0/SUCCESS)
  Process: 2494 ExecStart=/etc/init.d/hogehoge start (code=exited, status=0/SUCCESS)

Apr 27 10:18:12 ip-172-31-11-246 systemd[1]: Stopped LSB: starts hogehoge (a rails app).
Apr 27 10:18:12 ip-172-31-11-246 systemd[1]: Starting LSB: starts hogehoge (a rails app)...
Apr 27 10:18:14 ip-172-31-11-246 hogehoge[2494]: master failed to start, check stderr log for details
Apr 27 10:18:14 ip-172-31-11-246 systemd[1]: Started LSB: starts hogehoge (a rails app).

ふむ、master failed to start, check stderr log for detailsとのこと。しかし、ログを見ても原因はわからずでした。困った…

しばらく原因を調査したところ、メモリ不足のせいで落ちていると分かりました。 インスタンス変更時にこれまでよりもメモリが少ないタイプに変更していたのです。

PostgreSQLの設定ファイルをいじって、使用するメモリの量を以下のように変更しました。 これまでT2.largeインスタンス(8GB)だったので、shared_buffersに2GBを割り当てていたのですが、今回T2.micro(1GB)に変更したのでshared_buffersは256MBにしました。

sudo vi /etc/postgresql/9.4/main/postgresql.conf 
# - Memory -

shared_buffers = 256MB                  # min 128kB

PostgreSQLを再起動すると、今度はうまくいきました。

$ sudo service hogehoge status
● hogehoge.service - LSB: starts hogehoge (a rails app)
   Loaded: loaded (/etc/init.d/hogehoge; bad; vendor preset: enabled)
   Active: active (running) since Sat 2019-04-27 10:34:23 JST; 2h 31min ago
     Docs: man:systemd-sysv-generator(8)
  Process: 3628 ExecStop=/etc/init.d/hogehoge stop (code=exited, status=0/SUCCESS)
  Process: 3632 ExecStart=/etc/init.d/hogehoge start (code=exited, status=0/SUCCESS)
    Tasks: 5
   Memory: 130.3M
      CPU: 4.987s
   CGroup: /system.slice/hogehoge.service
           ├─3695 unicorn_rails master -c /home/hogehoge/hogehoge-atsume/config/unicorn.rb -D -E production
           └─3700 unicorn_rails worker[0] -c /home/hogehoge/hogehoge-atsume/config/unicorn.rb -D -E production

Apr 27 10:34:20 ip-172-31-11-246 systemd[1]: Starting LSB: starts hogehoge (a rails app)...
Apr 27 10:34:23 ip-172-31-11-246 systemd[1]: Started LSB: starts hogehoge (a rails app).

めでたし、めでたし。

インスタンスタイプを変更してメモリが少なくなったら、これまで動いていたアプリケーションの使用メモリも小さくすべきというお話でした。

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

illust2vecを使って類似イラストを検索する

f:id:twx:20190414173122p:plain
類似画像検索

1. illust2vecとは

東北大学の齋藤真樹氏と東京大学の松井勇佑氏によって提案された「イラストの意味をベクトル化する」手法。 単語の意味をベクトル化するword2vecというものがありますが、それのイラスト版です。

ベクトル化することで、以下のようなことができるようになります。

  • 2つのベクトル間での、足し算や引き算
  • 2つのベクトルの類似度の計算

Githubでソースコードや学習済みモデルが公開されています。神かよ…

ソース github.com

モデル github.com

2. とりあえず動かしてみる

Google Colaboratory上で動かしてみます。

https://colab.research.google.com/

この記事に登場するコードは全てGoogle Colabで公開しています。 https://colab.research.google.com/drive/1uF5SgtKpluoBJLYBP0_Ta-FZMh9XChpE

まずはillust2vecをインストールします。

# illust2vecをインストール

!git clone https://github.com/rezoo/illustration2vec.git
%cd illustration2vec/
!pip install -r requirements.txt

!wget https://github.com/rezoo/illustration2vec/releases/download/v2.0.0/illust2vec_tag_ver200.caffemodel
!wget https://github.com/rezoo/illustration2vec/releases/download/v2.0.0/illust2vec_ver200.caffemodel
!wget https://github.com/rezoo/illustration2vec/releases/download/v2.0.0/tag_list.json.gz
!gzip -d tag_list.json.gz

初音ミクのイラストを入力して、タグを予測してみます。

# 初音ミクのイラストを入力とし、タグを予測してみる

import i2v
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np

illust2vec = i2v.make_i2v_with_chainer(
    "illust2vec_tag_ver200.caffemodel", "tag_list.json")
img = Image.open("images/miku.jpg")
plt.imshow(np.array(img))
illust2vec.estimate_plausible_tags([img], threshold=0.5)

f:id:twx:20190414165312p:plain
実行結果

'1girl', 'twintails', 'solo' といったタグが得られました。

3. 類似イラスト検索の仕組みを作ってみる

次に、類似イラスト検索の仕組みを作ってみようと思います。

ステップとしては、以下のようになります。

  1. 大量の画像をベクトル化したデータベースを作る
  2. 検索クエリとなる画像と、大量画像との類似度を計算する
  3. 類似度の高い画像を表示する

3.1 大量画像の準備

Google Colaboratoryから一度離れて、ローカル環境に戻ります。 ローカルPCで、Google画像検索で大量のアニメ画像を集めましょう。 google-images-download というpipモジュールを使うことで効率的に画像を集めることができます。

# pipコマンドでgoogle_images_downloadをインストール
pip install google_images_download

# google_images_downloadで画像を保存。ファイル形式はjpgのみ。
googleimagesdownload --keywords "アニメキャラ" -t jpg
googleimagesdownload --keywords "緑髪キャラ" -t jpg
googleimagesdownload --keywords "赤髪キャラ" -t jpg
googleimagesdownload --keywords "青髪キャラ" -t jpg

それぞれのフォルダに100枚、合計で400枚の画像が保存されます。たまに変な画像もあるので、そういう画像は手作業で削除してください。これらを1つのフォルダにまとめ、連番でリネームしておきます。リネームのコマンドは以下です。

ls *.jpg |  awk '{ printf "mv %s image_%03d.jpg\n", $0, NR }' | sh

f:id:twx:20190414171440p:plain
大量のイラスト画像

大量の画像が格納されたこのフォルダをillusts.zipという名前のZIPファイルにし、Google Driveにアップロードします。

Google Colaboratoryに戻り、Google Driveをマウントして先程のillusts.zipをGoogle Colaboratory上にコピーします。

from google.colab import drive
drive.mount('/content/drive')

!cp /content/drive/My\ Drive/illusts.zip ./
!unzip illusts.zip

f:id:twx:20190414171836p:plain
ColabにGoogle Driveをマウント

Colaboratory上に展開した全ての画像をベクトル化します。ここで、i2vは、イラストのパスをkeyに持ち、 イラストのベクトルをvalueに持つ辞書オブジェクトです。

from glob import glob
illusts = glob('illusts/img_*.jpg')
i2v = {}
for illust in illusts:
    img = Image.open(illust)
    v = illust2vec.extract_feature([img])[0]
    v = v / np.linalg.norm(v)
    i2v[illust] = v

3.2 検索クエリとなる画像と、大量画像との類似度を計算する

ベクトルの類似度の計算方法は色々ありますが、ここではコサイン類似度を用います。コサイン類似度とは、簡単に言うとベクトルの内積を正規化したものです。

def cos_sim(v1, v2):
    return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))

先程作ったi2vに格納されている全てのベクトルと、初音ミクの画像の類似度を計算していきます。ここで、i2csは、イラストのパスをkeyに持ち、 初音ミクとの類似度をvalueに持つ辞書オブジェクトです。

i2cs = {}

img = Image.open("images/miku.jpg")
target_vec = illust2vec.extract_feature([img])[0]
target_vec = target_vec / np.linalg.norm(target_vec)

for illust, vec in i2v.items():
      cs =  cos_sim(target_vec, vec)
      i2cs[illust] = cs

3.3 類似度の高い画像を表示する

コサイン類似度が高い順にソートし、上位5件を表示してみます。

sorted_i2cs = sorted(i2cs.items(), key=lambda x: -x[1])

plt.figure()
plt.imshow(np.array(img))

for illust, sim in sorted_i2cs[:5]:
    img = Image.open(illust)
    plt.figure()
    print(sim)
    plt.imshow(np.array(img))

f:id:twx:20190414173122p:plain
類似イラスト検索結果

上の1枚が検索クエリとなる画像、下の5枚が類似画像と判定された画像です。左から上位1, 2, 3, 4, 5位の順です。 確かに、似ている画像が検索できていることがわかります。

他の例も見てみましょう。

f:id:twx:20190414175044p:plain
類似イラスト検索結果その2

ピンポイントにラムちゃんを検索できたり、水着や緑髪などの特徴のあるイラストが検索できています。

以上です。この記事に登場するコードは全てGoogle Colabで公開しています。 https://colab.research.google.com/drive/1uF5SgtKpluoBJLYBP0_Ta-FZMh9XChpE

本日は「illust2vecを使って類似イラストを検索する」ことをやってみました。良い記事だと思っていただいた方は、SNSでのシェア、ブログからのリンク、「読者になる」ボタンのクリック、「★」ボタンのクリック、よろしくお願いします! ではまた!

PythonでFlatten

Flattenとは

「入れ子になっているリスト」を「入れ子になっていないリスト」にすることです。

こんな感じです。

[1,[2,3]]  → [1,2,3]

[1,[2,[3]]] → [1,2,3]

[1,[2,{'foo': 3}]] → [1,2,{'foo':3}]

[1,[2,'foo']] → [1,2,'foo']

PythonでFlatten

ほい。

flatten.py

def flatten(xs):
    result = []
    for x in xs:
        if isinstance(x, collections.Iterable) and not isinstance(x, dict)and not isinstance(x, str):
            result.extend(flatten(x))
        else:
            result.append(x)
    return result

テストしてみます。

>>> from flatten import flatten as f
>>> f([1,[2,3]])
[1, 2, 3]
>>> f([1,[2,[3]]])
[1, 2, 3]
>>> f([1,[2,{'foo':3}]])
[1, 2, {'foo': 3}]
>>> f([1,[2,'foo']])
[1, 2, 'foo']

できました!!

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

pythonで色付きのprintをする方法

f:id:twx:20190322004925j:plain
色付きの出力をしよう

ここでは、pythonで色付きの出力をする方法をご紹介します。

以下のような色付きprint関数cprintを作りました。

class PrintColor:
    END = '\033[0m'
    BLACK = '\033[30m'
    RED = '\033[31m'
    GREEN = '\033[32m'
    YELLOW = '\033[33m'
    BLUE = '\033[34m'
    PURPLE = '\033[35m'
    CYAN = '\033[36m'
    WHITE = '\033[37m'
    DEFAULT = '\033[39m'

def cprint(*args, color='DEFAULT'):
    if color == 'BLACK':
        print(PrintColor.BLACK, end='')
    elif color == 'RED':
        print(PrintColor.RED, end='')
    elif color == 'GREEN':
        print(PrintColor.GREEN, end='')
    elif color == 'YELLOW':
        print(PrintColor.YELLOW, end='')
    elif color == 'BLUE':
        print(PrintColor.BLUE, end='')
    elif color == 'PURPLE':
        print(PrintColor.PURPLE, end='')
    elif color == 'CYAN':
        print(PrintColor.CYAN, end='')
    elif color == 'WHITE':
        print(PrintColor.WHITE, end='')
    elif color == 'DEFAULT':
        print(PrintColor.DEFAULT, end='')
    print(*args, end='')
    print(PrintColor.END)

if __name__ == '__main__':
    import numpy as np
    cprint('The only way to do great work is to love what you do.', color='DEFAULT')
    cprint('If you haven’t found it yet, keep looking.', color='BLACK')
    cprint('Don’t settle.', color='RED')
    cprint(1, 2, 'Fizz', 4, 'Buzz', 'Fizz', 7, 8, color='GREEN')
    cprint([[1,2],[3,4]], color='YELLOW')
    cprint(list, dict, str, color='BLUE')
    cprint(np.array([[1,2],[3,4]]), color='PURPLE')
    cprint('好了那我该走了', color='CYAN')
    cprint({'foo': 42}, color='WHITE')

試しに実行してみます

f:id:twx:20190322005113p:plain
出力結果!

文字列もタプルもnumpy配列も綺麗に色付きで出力できました! 本日は「pythonで色付きのprintをする方法」をご紹介しました。良い記事だと思っていただいた方は、以下の「★+」ボタンのクリック、SNSでのシェア、「読者になる」ボタンのクリックをお願いします。 それではまたー!

文字とUTF-8, UTF-16, SJIS, EUCの相互変換【Python】

f:id:twx:20190318192518p:plain

UTF-8をはじめとする各種文字コードと実際の文字を相互変換する方法について説明します。

文字コードの対応表はこちらのサイトを参考にしました。なお、UTF-16はUTF-16ビッグエンディアンを表しています。 ash.jp

今回使う文字は「亜」です。各種文字コードは以下の通りです。

文字 SJIS EUC UTF-8 UTF-16
889F B0A1 E4BA9C 4E9C

UTF-16 ⇔ 文字

hex(ord('亜'))
# '0x4e9c'

chr(int('4e9c', 16))
# '亜'

UTF-8 ⇔ 文字

'亜'.encode('utf-8')
# b'\xe4\xba\x9c'

b'\xe4\xba\x9c'.decode('utf-8')
# '亜'

EUC ⇔ 文字

'亜'.encode('euc-jp')
# b'\xb0\xa1'

b'\xb0\xa1'.decode('euc-jp')
# '亜'

SJIS ⇔ 文字

'亜'.encode('sjis')
# b'\x88\x9f'

b'\x88\x9f'.decode('sjis')
# '亜'

こんな感じです。

本日は「PythonでUTF-8と文字の相互変換」をご紹介しました。良い記事だと思っていただいた方は、以下の「★+」ボタンのクリック、SNSでのシェア、「読者になる」ボタンのクリックをお願いします。 それではまたー!

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