まひろ量子のハックログ

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

文字と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でのシェア、「読者になる」ボタンのクリックをお願いします。 それではまたー!

Pythonのリストでキューを実現する方法

f:id:twx:20190317202506p:plain
画像出典:https://1000ya.isis.ne.jp/1532.html

単純な方法(appendとpop)

Pythonのリストを使ってキューを実現するにはどうすれば良いでしょうか? 単純な方法として、リストのappendとpopを使う方法が考えられます。

append(x)でリストの末尾にxを追加、pop(0)でリストの先頭を取得できます。

>>> q = []
>>> q.append(1)
>>> q
[1]
>>> q.append(2)
>>> q
[1, 2]
>>> x = q.pop(0)
>>> x
1
>>> q
[2]
>>> x = q.pop(0)
>>> x
2
>>> q
[]

しかし、実はこの pop(0) はメチャメチャ遅いのです。 先頭の要素をリストから消すとき、先頭から2個目以降の要素を1個ずつ前にズラさなくてはならないためです。 大きなリストを扱うときは特に処理時間に大きく影響します。

より望ましい方法 (dequeの利用)

では、どうすれば良いでしょうか? 実は、キューを使うには、リストの代わりにdequeというオブジェクトを利用したほうがベターです。これは、上述のpopの問題が回避された実装になっています。

>>> from collections import deque
>>> q = deque([])
>>> q.append(1)
>>> q
deque([1])
>>> q.append(2)
>>> q
deque([1, 2])
>>> x = q.popleft()
>>> x
1
>>> q
deque([2])
>>>
>>> x = q.popleft()
>>> x
2
>>> q
deque([])

大きなリストで実行時間を比較!

では大きなリストを扱うタスクで速度を比較してみます。「ラウンドロビンスケジューリング」という問題について考えます。

ラウンドロビンスケジューリングとは以下のような問題です。

  • 1度に同じ仕事を10個こなせる人(Xさん)がいます。
  • 今ここに仕事Aが20個、仕事Bが5個、仕事Cが15個あります。
  • Xさんは、並んでいる順に仕事をこなしていくのですが、1度にこなせなかった分は後回しにします。

以上の条件で、Xさんはどのような順番で仕事をこなしていくことになるでしょうか。

順番に考えていきます。

  1. 仕事が A:20, B:5, C:15 と並んでいる
  2. Xさんは仕事A 20個を受け取るが、そのうち10個だけこなして残りの10個は後回し。(残り B:5, C:15, A:10)
  3. Xさんは仕事B 5個を受け取り、全てをこなす。(残り C:15, A:10)
  4. Xさんは仕事C 15個を受け取るが、そのうち10個だけこなして残りの5個は後回し。(残り A:10, C:5)
  5. Xさんは仕事A 10個を受け取り、全てをこなす。(残り C:5)
  6. Xさんは仕事C 5個を受け取り、全てをこなす。(残り なし)

というわけで、A10→B5→C10→A10→C5 というのが答えとなります。

このように、こなせなかった仕事を後回しにしつつ、仕事を順番にこなしていく方法をラウンドロビンスケジューリングといいます。 これをキューを使ってシミュレートしてみます。

以下では、1度にこなせる仕事量(quantum)を30、仕事の種類が100000個、各仕事の個数が1個〜1000個ランダムにあるとして、リストを使ったキューと、dequeを使ったキューで速度を比較してみました。

# リストを使ったキュー。pop(0)とappend()で、キューに値を出し入れする。
def rrs_simple(tasks, quantum):
    elapsed = 0
    while 1:
        task = tasks.pop(0)
        if task['cost'] <= quantum:
            elapsed += task['cost']
            print(task['name'], elapsed)
        else:
            elapsed += quantum
            task['cost'] -= quantum
            tasks.append(task)
        if len(tasks) == 0:
            return None

# dequeを使ったキュー。popleft()とappend()で、キューに値を出し入れする。
def rrs_using_lib(tasks, quantum):
    from collections import deque
    tasks = deque(tasks)
    elapsed = 0
    while 1:
        task = tasks.popleft()
        if task['cost'] <= quantum:
            elapsed += task['cost']
            print(task['name'], elapsed)
        else:
            elapsed += quantum
            task['cost'] -= quantum
            tasks.append(task)
        if len(tasks) == 0:
            return None


if __name__ == '__main__':
    import time
    import random
    import copy

    # 1度に仕事を30個処理できる。
    quantum = 30
    tasks = []
    # 仕事の種類は100000種類ある。
    for i in range(100000):
        # 各仕事は1個〜1000個ある。(ランダム)
        cost = random.randint(1,1000)
        task = {'name': 'task' + str(i), 'cost': cost}
        tasks.append(task)

    # 前者の方法でラウンドロビンスケジューリング問題を解いてみて、実行時間を表示する
    copied_tasks = copy.deepcopy(tasks)
    start = time.time()
    rrs_simple(copied_tasks, quantum)
    elapsed_time = time.time() - start
    print('-------')
    print('simple:', elapsed_time)
    print('-------')

    # 後者の方法でラウンドロビンスケジューリング問題を解いてみて、実行時間を表示する
    copied_tasks = copy.deepcopy(tasks)
    start = time.time()
    rrs_using_lib(copied_tasks, quantum)
    elapsed_time = time.time() - start
    print('-------')
    print('library:', elapsed_time)
    print('-------')

実行してみます。

python round_robin_scheduling.py | grep ":"

simple: 32.75466179847717
library: 0.7376770973205566

32秒の差が出ました。 前者の方法はかなり遅いことがわかります。

スクラッチ実装でdequeと戦ってみた

pop(0)を回避しつつ、リストを使った方法でdequeと戦ってみました。実装のポイントはリストで「リングバッファ」を作るという点です。リングバッファとは、固定長の1次元リストのお尻と頭が繋がったリストです。

f:id:twx:20190317201536p:plain
リングバッファの概念図

headとtailという、リストのインデックスを指すポインタを2つ用意し、キューに値を入れるときはheadが指している場所に値を代入し、キューから値を取り出すときはtailが指している場所を参照します。値を代入したときはheadを、値を参照した時はtailを、それぞれ1だけインクリメントします。ただし、headもしくはtailがリストのお尻を指している時に限り、インクリメントするのではなくリストの頭を指すようにします。こうすることで、pop(0)のように要素の並び替えをすることなくキューを実現できます。

では、先程と同様に速度比較してみましょう。

# スクラッチ実装
def rrs_from_scratch(tasks, quantum):
    tasks.append(None)
    elapsed = 0
    head = len(tasks) -1
    tail = 0
    max_idx = len(tasks) -1

    while 1:
        task = tasks[tail]
        tasks[tail] = None
        tail += 1
        if tail > max_idx:
            tail = 0

        if task['cost'] <= quantum:
            elapsed += task['cost']
            print(task['name'], elapsed)
        else:
            elapsed += quantum
            task['cost'] -= quantum
            tasks[head] = task
            head += 1
            if head > max_idx:
                head = 0

        if head == tail:
            return None

if __name__ == '__main__':
    import time
    import random
    import copy

    # 1度に仕事を30個処理できる。
    quantum = 30
    tasks = []
    # 仕事の種類は100000種類ある。
    for i in range(100000):
        # 各仕事は1個〜1000個ある。(ランダム)
        cost = random.randint(1,1000)
        task = {'name': 'task' + str(i), 'cost': cost}
        tasks.append(task)

    # 単純なリスト方式
    copied_tasks = copy.deepcopy(tasks)
    start = time.time()
    rrs_simple(copied_tasks, quantum)
    elapsed_time = time.time() - start
    print('-------')
    print('simple:', elapsed_time)
    print('-------')

    # dequeを使用
    copied_tasks = copy.deepcopy(tasks)
    start = time.time()
    rrs_using_lib(copied_tasks, quantum)
    elapsed_time = time.time() - start
    print('-------')
    print('library:', elapsed_time)
    print('-------')

    # スクラッチ実装(リングバッファ)
    copied_tasks = copy.deepcopy(tasks)
    start = time.time()
    rrs_from_scratch(copied_tasks, quantum)
    elapsed_time = time.time() - start
    print('-------')
    print('scratch:', elapsed_time)
    print('-------')

実行してみます。

python round_robin_scheduling.py | grep ":"

simple: 40.73896789550781
library: 0.7772879600524902
scratch: 0.8062808513641357
アルゴリズム 時間(秒)
単純リスト 40.73
deque 0.77
スクラッチ(リングバッファ) 0.80

という結果となりました。やはり単純なリストは遅いです。スクラッチは、dequeには勝てませんでしたがほぼ同速となりました。

以上です。

本日は「Pythonのリストでキューを実現する方法」をご紹介しました。pop(0)は遅いため、代わりにdequeやスクラッチで実装するほうがベターです。良い記事だと思っていただいた方は、以下の「★+」ボタンのクリック、SNSでのシェア、「読者になる」ボタンのクリックをお願いします。 それではまたー!

【数学パズル】末尾に同じ数が777個連続する階乗は存在するか

f:id:twx:20190317183144p:plain
画像提供:https://human-illustration.com/01-emotion-picture/043-free-silhouette.html

先日、いろんな数の階乗について調べていて「とある面白い性質」に気づいたので数学の問題にしてみました。難易度的には高校数学ですが、中学生でも鋭い子なら解けるかと思います。制限時間10分ほどで考えてみてください。なお、中学生向けに補足しておくと「階乗」というのは「\( 1 \times 2 \times 3 \times 4 \times 5 \)」のように1個ずつ階段のように数を掛け算していく計算のことです。この場合、1から5までの階段を掛けているので「5の階乗」と言い、「 \( 5!\) 」と書きます。

問題

任意の自然数 \(n\) に対して、「 \(n\) の1桁目から、同一の数が最も多く連続するのは\( f(n) \)桁目までである」という条件を満たす関数\( f(n) \)を定義する。 例えば、

$$ f(1234) = 1\\ f(12344) = 2\\ f(123444) = 3 $$

である。

このとき、

$$ f(N!) = 777 $$

となる自然数 \(N\) は存在するか。

解答

以下に解答を載せます。自力で解きたい方はスクロールするのを我慢してください。













まず、色々な数の階乗を見てみましょう。

N N!
1 1
2 2
3 6
4 24
5 120
6 720
7 5040
8 40320
9 362880
10 3628800
11 39916800
12 479001600
13 6227020800
14 87178291200
15 1307674368000
16 20922789888000
17 355687428096000
18 6402373705728000
19 121645100408832000
20 2432902008176640000
21 51090942171709440000
22 1124000727777607680000
23 25852016738884976640000
24 620448401733239439360000
25 15511210043330985984000000
26 403291461126605635584000000
27 10888869450418352160768000000
28 304888344611713860501504000000
29 8841761993739701954543616000000
30 265252859812191058636308480000000
31 8222838654177922817725562880000000
32 263130836933693530167218012160000000
33 8683317618811886495518194401280000000
34 295232799039604140847618609643520000000
35 10333147966386144929666651337523200000000
36 371993326789901217467999448150835200000000
37 13763753091226345046315979581580902400000000
38 523022617466601111760007224100074291200000000
39 20397882081197443358640281739902897356800000000
40 815915283247897734345611269596115894272000000000
41 33452526613163807108170062053440751665152000000000
42 1405006117752879898543142606244511569936384000000000
43 60415263063373835637355132068513997507264512000000000
44 2658271574788448768043625811014615890319638528000000000
45 119622220865480194561963161495657715064383733760000000000

\(5!\) 以降は全て、ゼロが末尾に連続しています。これは、\(5!\) 以降の数は必ず \(10\) を因数とした因数分解が可能であるからです。

具体的に言うと、\( 420 = 42 \times 10 \) のように10を因数に1個含む数は末尾に0が1個並びます。また、\( 4200 = 42 \times 10 \times 10 \) のように10を因数に2個含む数は末尾に0が2個並びます。

更に、もうちょっと踏み込んで考えてみます。\( 10 \) を素因数分解すると \( 2 \times 5 \) なので、「ある数の因数に \( 10 \) を何個含むことができるか」という問いは「ある数を素因数分解したときに2と5のペアが何組含まれているか」という問いと同じであることがわかります。また、階乗というのは「 \( 1 \times 2 \times 3 \times 4 \times 5 \times \cdots \times N \) 」のように1から \(N\) に至るまでの全ての自然数を因数に持っています。すると、階乗の因数には2が大量に存在していることがわかります。なぜなら、「 \( 1 \times 2 \times 3 \times 4 \times 5 \times \cdots \times N \) 」の各掛け算のうち2回に1回は2の倍数が登場するためです。同様に、階乗の因数には5もたくさん存在していますが、2ほどは多くないこともわかります。 「 \( 1 \times 2 \times 3 \times 4 \times 5 \times \cdots \times N \) 」の各掛け算のうち5の倍数が登場するのは5回に1回であるためです。

そのため、素因数分解して2と5のペアを作るときに、「5が余っているのに2が足りない…」と2の不足を心配する必要はないということがわかります。実際は「2が余っているのに5が足りない…」となるわけです。したがって、「階乗を素因数分解したときに2と5のペアが何組含まれているか」という問いは、「階乗を素因数分解したときに5が何個含まれているか」という問いと同じであることがわかります。

では、\( N! \) を素因数分解したときに \( 5 \) が幾つ含まれているのかを考えていきます。先ほど述べたように、「 \( 1 \times 2 \times 3 \times 4 \times 5 \times \cdots \times N \) 」の各掛け算のうち5の倍数が登場するのは5回に1回ですので、\( N \) を5で割ったときの商を計算すれば良さそうです。しかし、もう少し注意深く考えてみると、「 \( 1 \times 2 \times 3 \times 4 \times 5 \times \cdots \times N \) 」の各掛け算のうち25回に1回の頻度で25の倍数が登場します。25の倍数は素因数として5を2つ含んでいるため、これもカウントしてあげなくてはなりません。同様に、125回に1回の頻度で \( 5^{3} \) の倍数、625回に1回の頻度で \( 5^{4} \) の倍数が登場するため、これらもカウントしていきます。

結局、Nが5以上のときは、\( f(N!) \) つまり「 \( N! \) を素因数分解したときに \( 5 \) が幾つ含まれているのか」という問いの答えは、以下の計算式で求めることができます。

$$ f(N!) = \left[ \frac{ N }{ 5 } \right] + \left[ \frac{ N }{ 5^{2} } \right] + \left[ \frac{ N }{ 5^{3} } \right] + \left[ \frac{ N }{ 5^{4} } \right] + \cdots $$

ただし、ここでは、\( \left[ Z \right] \) は \( Z \) を超えない最大の整数(整数部分)を表します。 この和を見ると色々な性質がわかってきます。たとえば、\( f(N!) \geq f((N-1)!) \) ということがわかります。この不等式は、上述の右辺同士を比較してみるとすぐに得られます。・・・(1)

この和の計算方法について考えてみます。 もし第一項が100ならNは500〜504です。(もしNが505以上なら第一項は101以上になってしまいますし、もしNが499以下なら第一項は99になってしまいますので。) したがって、第二項は \( 500/5^{2} = 20 \) と計算可能です。同様に、第三項は \( 500/5^{3} = 4 \) 、第四項以降はゼロになります。以上の方法で、和は \( 100 + 20 + 4 = 124 \)と計算できることがわかります。

第一項は第二項以降に比べて比較的大きいため、\( f(N!) \) の値は第一項とかなり近いということがわかります。実際、上の例では第一項が100のときに \( f(N!) = 124 \) でした。

以上を踏まえて問題文を見てみましょう。

$$ f(N!) = 777 $$

を満たす \( N \) の値に、おおよその見当をつけていきます。

和の第一項を625とすると、第二項は125、第三項は25、第四項は5、第五項は1となり、\( f(N!) = 625 + 125 + 25 + 5 + 1 = 781 \) となります。言い換えると、素因数に5が781個含まれているということになります。目的の777個と比べると少しだけ多いですね。では、少しだけ素因数の5の個数を減らして777個にできるかどうかを考えてみます。さきほどのNの値は3125〜3129でした(第一項 \( 625 \times 5 = 3125 \) であるため)。このNから1だけ小さい \( N=3124 \)を考えてみます。計算すると、\( f(3124!) = 624 + 124 + 24 + 4 = 776 \)となります。

以上から、

$$ f(3125!) = 781\\ f(3124!) = 776 $$

となりました。 ここで、不等式(1)より、\( f(N!) \) は、\( f(N!) \geq f((N-1)!) \) という性質があるため、N>3125の範囲に\( f(N!) = 777 \) となるNはありませんし、N<3124の範囲にも\( f(N!) = 777 \) となるNはありません。

したがって、

$$ f(N!) = 777 $$

となる自然数 \(N\) は存在しないと言えます。

 

以上です。

本日は「末尾に同じ数が777個連続する階乗は存在するか」という問いを解いてみました。いかがでしたでしょうか。5以上の階乗には、末尾にゼロが連続するという面白い性質を使った問題でした。

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

Watson APIで{"code":401, "error": "Unauthorized"}

エラー内容

2019年1月27日現在、Watson APIで次のようにHTTPリクエストを投げたら401エラーが返ってきます。

curl -X POST --form "images_file=@xxxx.jpg" "https://gateway.watsonplatform.net/visual-recognition/api/v3/classify?api_key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&version=2016-05-20"

ただし、xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxの部分はご自身のAPI KEYに置き換えてください。

エラー内容

{"code":401, "error": "Unauthorized"}

解決策

以下のようにしたら解決しました。

curl -X POST --form "images_file=@xxxx.jpg" -u "apikey:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "https://gateway.watsonplatform.net/visual-recognition/api/v3/classify?api_key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&version=2016-05-20"

やったこと

  • -uオプションを追加

これで、2019年1月現在、動きます!

その他のエラー

Failed to connect to gateway-a.watsonplatform.net port 443: Operation timed out

というエラーが起きる方は以下も合わせて御覧ください。 www.mahirokazuko.com

Watson APIでcurl: (7) Failed to connect to gateway-a.watsonplatform.net port 443: Operation timed out

エラー内容

2019年1月27日現在、Watson APIで次のようにHTTPリクエストを投げたら443エラーが返ってきます。

curl -X POST --form "images_file=@xxxx.jpg" "https://gateway-a.watsonplatform.net/visual-recognition/api/v3/classify?api_key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&version=2016-05-20"

ただし、xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxの部分はご自身のAPI KEYに置き換えてください。

エラー内容

curl: (7) Failed to connect to gateway-a.watsonplatform.net port 443: Operation timed out

解決策

以下のようにしたら解決しました。

curl -X POST --form "images_file=@xxxx.jpg" -u "apikey:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "https://gateway.watsonplatform.net/visual-recognition/api/v3/classify?api_key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&version=2016-05-20"

やったこと

  • -uオプションを追加
  • APIのエンドポイントをgateway.watsonplatform.net/visual-recognition/apiに変更

これで、2019年1月現在、動きます!

その他のエラー

{"code":401, "error": "Unauthorized"}

というエラーが起きる方は以下も合わせてご覧下さい

www.mahirokazuko.com

GANを使って簡単に架空アイドル画像を自動生成(Progressive Growing of GANs)

f:id:twx:20181215154156p:plain
Artificial Idol
この記事で紹介する方法で、このような画像が作れるようになります。

最近趣味でやってる画像生成系のDNNについて簡単にレポートします。

1. Progressive Growing of GANsとは

Paperはこちら。 [1710.10196] Progressive Growing of GANs for Improved Quality, Stability, and Variation

Githubはこちらです。 https://github.com/tkarras/progressive_growing_of_gans

提案されているテクニックは、簡単にいうとGANの学習をする際に「小さいネットワーク」から段階的に「大きいネットワーク」に転移させていくことで、大きな画像においても安定した学習を可能にする、というものです。論文では、4x4の小さい画像から始めて1024x1024の大きな画像を生成することに成功したと述べられています。

f:id:twx:20181215151701p:plain
引用元:Figure 1; PROGRESSIVE GROWING OF GANS FOR IMPROVED QUALITY, STABILITY, AND VARIATION

また、1024x1024の学習に必要となる高画質な学習用画像を得るために様々な工夫がされています。簡単に言うと、顔の位置を揃える操作、超解像、背景のぼかしです。元となる顔画像のデータセットに対して、両目の位置を検出しその座標を起点としてトリミングすることで顔の位置をすべてのデータで合わせます。更に、超解像技術を用いて512x512の画像を1024x1024に高解像度化します。最後に、背景にブロー処理をかけてぼかします。

かなりの手間をかけたこのようなデータのクリーニングは、ハイクオリティな画像を生成するのに必須だと言われています。

今回はこの論文の再現実験として、実在しない架空のアイドルの顔画像を生成してみました。アイドルの生成には既に先駆者がいて、1年ほど前にかなりバズったのを覚えていらっしゃる方もいると思います。なので、目新しさは無いです。

2. データの準備

アイドル画像をひたすらクローリングしまくります。クローリング対象のURLを公開することは迷惑行為になってしまうので、すみませんが非公開とさせてください。ここでは、クローリングを行うコードをいくつか載せるに留めておきます。

2.1 google画像検索結果を保存するコード

Google検索で画像を手に入れる方法です。 google-images-download というpipモジュールを使います。このモジュールはコマンドライン上で pip コマンドを使ってインストールします。

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

詳しい使用方法は以下のページが詳しいです。

co.bsnws.net

さて、これを使ってアイドル画像をダウンロードします。以下のXXXXXXXXXXXの部分を、任意の検索クエリに書き換えて実行すると大量の画像が手に入ります。 XXXXXXXXXXXには、アイドルの名前を入れると良いです。

  • get_images1.sh *
googleimagesdownload --keywords "XXXXXXXXXXX" --size large
googleimagesdownload --keywords "XXXXXXXXXXX" --size large
・
・
・
googleimagesdownload --keywords "XXXXXXXXXXX" --size large

2.2 アイドル画像が掲載されている特定ページをクローリング

soupなどの一般的なクロール技術を使って、Google検索ではなく、アイドルの写真をたくさん載せているサイトからクローリングします。迷惑行為になりかねないので、手順の公開は控えさせていただきます。

2.3 顔画像のトリミング

上の2.1と2.2の方法で数万枚オーダーの画像を集め終わったら、今度は写真の中から顔画像を検出して適切なサイズにトリミングします。これには以下のツールを使いました。

https://github.com/deepfakes/faceswap

これは元々、Faceswapという、2人の人物の顔を互いに入れ替えるタスクで有名なツールです。このタスクも、事前に学習データ(顔画像)に対して前処理を行う必要があり、顔をトリミングする機能をもつコードも含まれています。これを利用しましょう。

デフォルトでは以下のコマンドを実行すると、srcフォルダの中の全ての画像に対して顔検出を行い、両目の位置がx軸と平行になるように回転補正をかけたうえで、目の位置を起点に正方形の画像をトリミングしてくれます。

run python faceswap.py extract

しかし、問題がいくつかあります。 トリミング後の画像サイズは256x256で固定なので、高解像度画像が必要なPGGANsで使うには少し小さすぎます。また、顔がややズームアップされた状態でトリミングされるため、髪や服装があまり写らないという欠点もあります。更に、回転補正して正方形に切り出すため、もしも顔が画面端で斜めに検出されてしまうと、正方形に切り出した際に四隅にデッドスペースができてしまいます(以下のように)。 これらの点をなんとかして改善する必要があります。

f:id:twx:20181215173339p:plain
回転時に四隅が消えてしまう失敗例

まずは四隅のデッドスペースをなんとかします。これには、元論文にも書かれていますが、元画像の端っこに鏡像反転させた余白を付与するという手法を適用します。

f:id:twx:20181215175059p:plain
境界ミラーリングの例(引用元:PROGRESSIVE GROWING OF GANS FOR IMPROVED QUALITY, STABILITY, AND VARIATION)

以下のコマンドで、元画像が保存されているフォルダに対して、全ての画像の上下左右に10%のマージンを付与します。

import cv2
import numpy as np
from matplotlib import pyplot as plt
import glob

def mirror_padding(img_path):
    img1 = cv2.imread(img_path)
    padding_y = img1.shape[0] // 10
    padding_x = img1.shape[1] // 10
    img2 = cv2.copyMakeBorder(img1, padding_y, padding_y, padding_x, padding_x, cv2.BORDER_REFLECT_101)
    return img2    

image_paths = glob.glob('/Path/To/Src/Images/*')
for image_path in image_paths:
    img_name = image_path.split('/')[-1]
    img = mirror_padding(image_path)
    cv2.imwrite('/Path/To/Output/' + img_name, img)

こうすることで、もしも顔が画面端にあったとしても、ある程度回転角に余裕をもたせられます。

次に、顔がズームアップされてしまう件と、画像サイズが小さい問題を解決します。これは、Faceswapのソースを改造すればOKです。

以下の2箇所をこのように書き換えましょう。

# faceswap/plugins/Extract_Align.py
12c12
<         extracted = self.transform(image, alignment, size, 48)
---
>         extracted = self.transform(image, alignment, size, 48*3)
# faceswap/scripts/extract.py
129c129
<             256,
---
>             512,

これで、うまく顔のトップから、肩くらいまでがトリミングされます。

2.4 良質な画像の選別

数万枚の画像を実際に目で見て、良い画像と悪い画像に分けます。ここは、どうしても気合と根性が必要です。「正面を向いている」「見切れていない」「暗くない」「手で顔が隠れていない」といった条件に満たすものを選別します。

ただ、完全に手作業だとかなり辛いので、以下のような画像仕分け効率化ツールを作りました。

  • 仕分けツール.html *
<!DOCTYPE html>
<html>
<head>
  <title>仕分けツール</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
</head>
<body>
<script>
  var numFiles = 0;
  var files = new Array();
  var fileNames = new Array();
  var cursor = 0;
  var prevImageName, currImageName, nextImageName;

  var classA = new Array();
  var classB = new Array();
  var chache = new Array();

  function drawImageOnCanvas(file){
    var image = new Image();
    var reader = new FileReader();
    var canvas = $('#cur_canvas');
    var ctx = canvas[0].getContext('2d');
    reader.onload = function(evt) {
      image.onload = function() {
        ctx.clearRect(0, 0, 300, 300);
        ctx.drawImage(image, 0, 0, 300, 300);
      }
      image.src = evt.target.result;
    }
    reader.readAsDataURL(file);
  }

  function fileListDirectory(_files) {
    for (i=0; i<_files.length; i++) {
      var fileType = _files[i].type;
        if (fileType == 'image/jpeg' || fileType == 'image/png' ) {
          files.push(_files[i])
          fileNames.push(_files[i].name );
          numFiles ++;
        }
    }
    resetImage(cursor);
  }

  function resetImage(cursor) {
    prevImageName = (cursor == 0 ? 'なし' : fileNames[cursor-1]);
    currImageName = fileNames[cursor];
    nextImageName = (cursor == numFiles - 1 ? 'なし' : fileNames[cursor+1]);
    document.getElementById('previous').innerHTML = prevImageName;
    document.getElementById('current').innerHTML = currImageName;
    document.getElementById('next').innerHTML = nextImageName;
    drawImageOnCanvas(files[cursor]);
    document.getElementById('progress').innerHTML = (cursor+1) + '/' + numFiles;
  }

  function previous(){
    cursor --;
    if( cursor < 0 ) {
      cursor = 0;
    }
    resetImage(cursor);
  }
    
  function next(){
    cursor ++;
    if( cursor > numFiles-1 ) {
      cursor = numFiles-1;
    }
    resetImage(cursor);
  }

  function undo() {
    if(cursor > 0){
      var which = chache[cursor];
      if(which == 'A') {
        classA.pop();
      } else if (which == 'B') {
        classB.pop();
      }
      chache.pop();
      previous();
      document.getElementById('classA').innerHTML = classA.length;
      document.getElementById('classB').innerHTML = classB.length;
    }
  }

  function downloadData() {
    var hiddenElement = document.createElement('a');
    hiddenElement.href = 'data:attachment/text,' + encodeURI(classA);
    hiddenElement.target = '_blank';
    hiddenElement.download = 'NG.txt';
    hiddenElement.click();
    var hiddenElement = document.createElement('a');
    hiddenElement.href = 'data:attachment/text,' + encodeURI(classB);
    hiddenElement.target = '_blank';
    hiddenElement.download = 'OK.txt';
    hiddenElement.click();
  }

  window.onload = function() {
    function onKeyUp(e) {
      if(e.code=='KeyF') {
        classA.push( fileNames[cursor] );
        chache.push('A');
        next();
        document.getElementById('classA').innerHTML = classA.length;
      } else if(e.code=='KeyJ') {
        classB.push( fileNames[cursor] );
        chache.push('B');
        next();
        document.getElementById('classB').innerHTML = classB.length;
      }
      e.preventDefault();
    };

    // Set up key event handlers
    window.addEventListener('keyup', onKeyUp);
  };

</script>

<div class="container">
  <div class="row">
    <div class="col-sm-12 mt-5">
      <div class="btn btn-success p-0">
        <input class="p-1" type="file" webkitdirectory directory onChange="fileListDirectory(this.files)">
      </div>
      <div id=progress></div>
      <div style="display: none;">前の画像:<span id="previous">結果がここに表示されます。</span></div>
      <div style="display: none;">今の画像:<span id="current">結果がここに表示されます。</span></div>
      <div style="display: none;">次の画像:<span id="next">結果がここに表示されます。</span></div>

      <div>
        <canvas id="cur_canvas" width="300" height="300"></canvas>
      </div>
      <button class="btn btn-success" onclick="undo()">1つ戻る</button-->
      <button class="btn btn-success" onclick="downloadData()">ダウンロード</button-->
    </div>
    <div class="col-sm-6 mt-5">
      <h2>不良データ</h2>
      <div id="classA">
      </div>
    </div>
    <div class="col-sm-6 mt-5">
      <h2>優良データ</h2>
      <div id="classB">
      </div>
    </div>

  </div>
</div>

</body>
</html>

このhtmlをローカルに保存してChromeで開いてください。

f:id:twx:20181215182633g:plain
仕分けツール

フォルダを選択できるボタンがあるので、これまで準備を進めてきた「トリミング済みの顔画像が大量に保存されているフォルダ」を選択してください。すると、画面中央に画像が出現しますので、画像にフォーカスをあてたうえで「F」キーと「J」キーで、「NG」か「OK」かを仕分けしてください。要は、キーボード操作でスピーディーに仕分けができるというツールです。

3秒で1枚をさばけると仮定すると、8時間強で1万枚さばけます。

最後に、「ダウンロードボタン」を押すと、「OK」に仕分けられた画像の名前が列挙されたテキストファイルを得ることができます。あとは、この「OKと判定した画像」だけを別のフォルダにコピーするなりしてください。

こうして、良質な学習データが手に入りました! 私はこれで1万3000枚ほど集めました。

f:id:twx:20181215183848p:plain
あつめた画像たち

3. 学習する

学習にはGPUが必要です。Google ColaboratoryならGPUを無料で使えます(2018年12月現在)。

Google Colaboratoryは、Jupyter notebook風にブラウザ上でコードを実行できるGoogleのサービスです。詳しくは以下からどうぞ。

https://colab.research.google.com

Google ColaboratoryはGoogle Driveと連携できます。つまり、自作の学習データをGoogle Driveに置いておくと、それをGoogle Colaboratoryから読み込むことができます。まず、先程作った画像をDriveにアップロードします。フォルダをzipで圧縮してから送ります。先程の「OKと判定した画像」が大量に入っているフォルダをzip化し、OK_idol.zipとします。

f:id:twx:20181215184709p:plain
Google Driveの画面

私は、Google Driveのルート直下に、dataというフォルダを作り、その中にOK_idol.zipを保存しました。

また、学習済モデルや、生成画像を保存したりするのに使う作業用のフォルダも作っておきます。 Google Driveのルート直下に、workというフォルダを作り、その中にPGGANというフォルダを作っておきます。

f:id:twx:20181215192307p:plain
Google Driveの画面2

ここから、Colaboratory上での操作になります。

まず、Google DriveをColaboratoryにマウントします。そして、PGGANsのソースをColaboratory環境上にクローンします。

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

%cd /content/drive/My\ Drive/work/PGGAN
!git clone https://github.com/tkarras/progressive_growing_of_gans.git

クローンが完了すると、Drive上で work/PGGAN/progressive_growing_of_gansの中に、config.pyというファイルが見つかります。

このconfigを以下のように編集します。

class EasyDict(dict):
    def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs)
    def __getattr__(self, name): return self[name]
    def __setattr__(self, name, value): self[name] = value
    def __delattr__(self, name): del self[name]

#----------------------------------------------------------------------------
# Paths.

data_dir = '/content/my_dataset'
result_dir = 'results'

#----------------------------------------------------------------------------
# TensorFlow options.

tf_config = EasyDict()  # TensorFlow session config, set by tfutil.init_tf().
env = EasyDict()        # Environment variables, set by the main program in train.py.

tf_config['graph_options.place_pruned_graph']   = True      # False (default) = Check that all ops are available on the designated device. True = Skip the check for ops that are not used.
env.TF_CPP_MIN_LOG_LEVEL                        = '1'       # 0 (default) = Print all available debug info from TensorFlow. 1 = Print warnings and errors, but disable debug info.

#----------------------------------------------------------------------------
# Official training configs, targeted mainly for CelebA-HQ.
# To run, comment/uncomment the lines as appropriate and launch train.py.

desc        = 'pgan'                                        # Description string included in result subdir name.
random_seed = 1000                                          # Global random seed.
dataset     = EasyDict()                                    # Options for dataset.load_dataset().
train       = EasyDict(func='train.train_progressive_gan')  # Options for main training func.
G           = EasyDict(func='networks.G_paper')             # Options for generator network.
D           = EasyDict(func='networks.D_paper')             # Options for discriminator network.
G_opt       = EasyDict(beta1=0.0, beta2=0.99, epsilon=1e-8) # Options for generator optimizer.
D_opt       = EasyDict(beta1=0.0, beta2=0.99, epsilon=1e-8) # Options for discriminator optimizer.
G_loss      = EasyDict(func='loss.G_wgan_acgan')            # Options for generator loss.
D_loss      = EasyDict(func='loss.D_wgangp_acgan')          # Options for discriminator loss.
sched       = EasyDict()                                    # Options for train.TrainingSchedule.
grid        = EasyDict(size='1080p', layout='random')       # Options for train.setup_snapshot_image_grid().

# Dataset (choose one).
desc += '-idol512';               dataset = EasyDict(tfrecord_dir='OK_idol_for_PGGAN'); train.network_snapshot_ticks = 1; train.mirror_augment = True

# Resume
#train.resume_run_id = '/content/drive/My Drive/work/PGGAN/progressive_growing_of_gans/results/032-pgan-idol512-preset-v2-1gpu-fp32/network-snapshot-xxxxxx.pkl';
#train.resume_kimg = 0

# Config presets (choose one).
desc += '-preset-v2-1gpu'; num_gpus = 1; sched.minibatch_base = 4; sched.minibatch_dict = {4: 128, 8: 128, 16: 128, 32: 64, 64: 32, 128: 16, 256: 8, 512: 4}; sched.G_lrate_dict = {1024: 0.0015}; sched.D_lrate_dict = EasyDict(sched.G_lrate_dict); train.total_kimg = 12000

# Numerical precision (choose one).
desc += '-fp32'; sched.max_minibatch_per_gpu = {256: 16, 512: 8, 1024: 4}
#----------------------------------------------------------------------------

コンフィグの各行の意味を詳しく知りたい方はオリジナルのgithubのページをご確認ください。ここで重要なのは、データセットのパスと、resumeの設定です。resumeとは、学習がある程度進んで保存したモデルから、学習を再開することを言います。1番最初に学習を開始するときはresumeは関係ありません。

まず、データセットのパスに注意してください。 data_dir = '/content/my_dataset'のように指定しています。さきほど、Google Driveに保存したので、データは/content/drive/My\ Drive/data/OK_idol.zipに格納されているはずです。ここでzipを展開すれば良い気がしますが、実はこれは良くありません。Google driveは書き込みが非常に遅いため、zipを展開して大量の画像を書き込むのに長時間かかってしまいます。一方、Colaboratory上での展開は高速です。なので、以下のようにしてColaboratory上にディレクトリを作り、そこに画像を展開してください。

!mkdir /content/my_dataset
!unzip /content/drive/My\ Drive/data/OK_idol.zip -d /content/my_dataset > /dev/null 2>&1 & 

展開には数分かかります。以下のコマンドで、画像が何枚展開されたかをカウントできます。

!echo /content/my_dataset/OK_idol/* | xargs ls | wc

全て展開できたことを確認したら、以下のコマンドを実行します。詳しくはPGGANのREADMEに書いてありますが、自作のデータセットを、PGGANが読み込める形に変形するコマンドです。

!python dataset_tool.py create_from_images /content/my_dataset/OK_idol_for_PGGAN /content/my_dataset/OK_idol

以上のコマンドが完了したら、次のコマンドを実行します。これで学習が開始します。

!python train.py

Colaboratoryは、ある条件を満たすと環境が丸ごと削除されてしまいます。「ブラウザのセッションが切れて90分以上経過する」「連続稼働時間が12時間を上回る」のどちらか一方でも満たすと削除されてしまいます。したがって、ブラウザを起動してPCを放置していても、12時間しか学習を回せません。私は、毎晩0時頃、夜寝る前と、12時頃にお昼ご飯を食べるときに、resume機能を使って学習を再開させています。

学習を再開させる方法は、上のコンフィグで、次の行を書き換えればOKです。

# Resume
train.resume_run_id = '/content/drive/My Drive/work/PGGAN/progressive_growing_of_gans/results/032-pgan-idol512-preset-v2-1gpu-fp32/network-snapshot-xxxxxx.pkl';
train.resume_kimg = xxxxxxx

最初に学習を始めるときは、これらの行はコメントアウトされていました。resumeで2回目以降の学習を行うときはコメントを外して、032-pgan-idol512-preset-v2-1gpu-fp32/network-snapshot-xxxxxx.pklの部分を、ご自身のGoogle Driveに保存されている学習済みモデルの名前にリネームしてください。これが、resume時に使用するモデルとなります。

また、

train.resume_kimg = xxxxxxx

の右辺には、network-snapshot-xxxxxx.pkl のxxxxxと同じ値を整数で指定してください。

学習は、12時間×2セットを毎日行い、3週間ほど続ける必要があります。

3週間の学習の末、得られたモデルを使ってアイドル画像を生成してみました。以下がその結果です。

f:id:twx:20181215204301g:plain
生成されたアイドルが変形していく様子

こんな感じで動画も作れます。

いい感じのショットを選んで4x2にアペンドしてみました。

f:id:twx:20181215154156p:plain
Artificial Idol

なかなかの出来ですね!

以上、今回はProgressive Growing of GANsを使って簡単にアイドル画像を自動生成してみました。良い記事だと思っていただいた方は、SNSでのシェア、ブログからのリンク、「読者になる」ボタンのクリック、「★」ボタンのクリック、よろしくお願いします! ではまた次の記事でお会いしましょう!

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でのシェア、「読者になる」ボタンのクリックをお願いいたします。 それではまた〜

Falconを使い超簡単なAPIを作る 【所要時間たったの3分】

Falconとは

f:id:twx:20180908114858p:plain

Falcon - Bare-metal web API framework for Python

Falcon を使えばRESTful な Web API をサクッと作ることができる。業務で簡単なWeb API を用意する必要があったり、ハッカソンなどの短期間開発で疎結合なアークテクチャを作らなくてはならないときに重宝する。Falcon自体はpythonのモジュールだが、肝心のAPI部分は好きな言語で作っても全然構わない (pythonから呼べるようにしておけば良いというだけの話だ)。

この記事では、例として「文を単語分割する」という機能のWeb APIを作ってみようと思う。所要時間はタイトル通り3分だ。

準備 【1分】

「文を単語分割する」とは何か

たとえば、「今日も1日頑張るぞい」という文があったとき、これを単語にスペースで区切ってみると以下のようになる。

今日  も  1  日  頑張る  ぞい

このように、文を単語ごとに区切ることを単語分割という。このような処理は形態素解析ツールによって実現できる。有名なツールに、mecabというものがある。今回はこれを使う。

mecabのインストール

Ubuntuであれば、以下のようにapt-getでインストールできる。

sudo apt-get install mecab libmecab-dev mecab-ipadic
sudo apt-get install mecab-ipadic-utf8
echo "本日は晴天なり" | mecab # => 形態素解析できるかテストしてみよう

falconなどのpipモジュールの準備

以下のようにpipで必要なモジュールをインストールする。ここでは、falcon, falcon-multipart, gunicornをインストールする。

falcon-multipartは、マルチパート形式のPOSTメソッドでパラメータを受け取ることができるようにするモジュールだ。 gunicornは簡易的なWEBサーバだ。

pip install falcon
pip install falcon-multipart
pip gunicorn

サーバ側のコード【1分】

server.py

import falcon
import json
import subprocess
from falcon_multipart.middleware import MultipartMiddleware

# クロスオリジンを許可すする
class CORSMiddleware:
    def process_request(self, req, resp):
        resp.set_header('Access-Control-Allow-Origin', '*')

# shelコマンドを実行する関数
def do_command(command):
    proc = subprocess.Popen(
        command,
        shell  = True,
        stdin  = subprocess.PIPE,
        stdout = subprocess.PIPE,
        stderr = subprocess.PIPE
    )
    return proc.communicate()

class Mecab:

  def on_post(self, req, res):
    # POSTメソッドを受け付ける
    # sentenseという名前のパラメータを受け取る
    sentence = req.get_param('sentence')

    # 'echo hogehoge| mecab -Owakati'というコマンドをたたく。-Owakatiオプションでmecabをたたけば単語分割ができる。
    stdout, stderr = do_command('echo {} | mecab -Owakati'.format(sentence))

    # データを整形し、クライアントにレスポンスを返す
    resp = {
      'stdout': stdout.decode('utf-8').strip(),
      'stderr': stderr.decode('utf-8').strip()
    }
    res.body = json.dumps(resp)

# ミドルウェアを2つ設定している。前者はCORSを許可するために必要だ。これがないと異なるオリジンからのアクセスが許可されない。
api = falcon.API(middleware=[CORSMiddleware(), MultipartMiddleware()])

# /hogeなど任意のエンドポイントを設定できる。今回は / としている。
api.add_route('/', Mecab())

上のコードをサーバとして起動させよう。サーバ側で以下のコマンドを実行する。

gunicorn server:api

これで、デフォルトではhttp://127.0.0.1:8000でサーバが立ち上がる。

IPを指定したい場合は、以下のようにすれば良い。

gunicorn server:api -b xxx.xxx.xxx.xxx

クライアント側のコード【1分】

client.html

<html>
<body>

<p>単語に分割したい文を入力してください</p>
<input type="text" id="sentence"/>
<button id="send">送信</button>

<p>結果</p>
<span id="result"></span>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>
$(document).ready(function () {

  $('#send').click(function (event) {
    var fd = new FormData();
    fd.append('sentence', $('#sentence').val());
    $.ajax({
      url: 'http://127.0.0.1:8000',
      type: 'POST',
      dataType: 'json',
      data: fd,
      processData: false,
      contentType: false
    })
    .done(function( res, textStatus, jqXHR ) {
      $('#result').text(res.stdout)
    });
  });
});
</script>

</body>
</html>

実行結果

f:id:twx:20180909013202p:plain

 ↓えいや

f:id:twx:20180909013210p:plain

できた!

以上、falconでAPIを簡単に作る方法をご紹介しました。

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

Google Colaboratory のTips集その1 (GoogleDriveマウント、セッション継続、TensorBoard接続)

Google Colaboratory、すごいです。 誰でも、Tesla K80のGPUを無料ですぐに使うことができる。Tensorflow環境もすぐに手に入れることができる。

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

この記事では、Google Colaboratoryを使って深層学習をまわすときのちょっとしたTipsを紹介する。

Google Colaboratoryの時間制限ルール

2018年9月7日時点で、以下のようなルールとなっている

  • インスタンスの連続稼働時間は12時間
  • セッションが途切れて90分立つとセッション初期化

長時間の学習で12時間を超えてしまう場合、途中の学習結果をGoogle 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

途中で2回、Googleにログインするのためのコード入力が要求される。 無事、ログインできれば、driveというディレクトリにgoogle driveがマウントされる。

わたしはいつも、Google driveでプロジェクトごとにディレクトリを分けている。

%cd /content/drive/work/project_A

こんな感じで、ディレクトリ移動できるようになる。

モデルを保存するときは、上述のようにディレクトリを移動して普通にカレントディレクトリに保存するか、モデルの保存先として/content/drive/work/project_AなどのGoogleDriveマウント先のディレクトリを指定すればOK。

セッションが途切れないように定期アクセス

クライアントPCで定期的にアクセスする。以下の例では、Macのopenコマンドで定期的にColaboratoryを開いている。

for i in `seq 0 12`
do
  open https://colab.research.google.com/drive/xxxxxxxxxxxxxxxxxxxxx
  sleep 100
done

AWS等から定期的にアクセスする方法も、のちほど記事化したい。乞うご期待。

Google ColaboratoryでTensorBoardを使う。

ノートブックに以下のコードを追加する。

# ngrokというツールをもちいて、Colaboratoryのローカル環境に立つサーバにインターネットからアクセスできるようにする。
! wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip 
! unzip ngrok-stable-linux-amd64.zip 

get_ipython().system_raw('./ngrok http 6006 &') 
get_ipython().system_raw('tensorboard --logdir=logs --host 0.0.0.0 --port 6006 &')
# ↑ --logdir=logsの右辺にTensoBoardのログの名前を書くこと。

! curl -s http://localhost:4040/api/tunnels | python3 -c "import sys, json; print(json.load(sys.stdin)['tunnels'][0]['public_url'])" 

# 表示されるURLにアクセスするとTensorBoardの画面が見れる。

以上、Google ColaboratoryのTipsその1でした。その2では、AWSをもちいたセッション継続や、GPU使用状況の確認などについて書こうと思います。

この記事が役に立ったという方は、下の「★+」ボタンのクリック、SNSでのシェア、「読者になる」ボタンのクリックをお願いします!!

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