VS CodeでPython 3の競プロコードをデバッグ実行

AtCoderCodeforcesなどの競技プログラミングサイトでは、通常、標準入力から入力を受け取ります。例えばPythonで回答する場合、あなたの回答(例としてsolve.pyとします)は、入力が書かれたテキストファイルinput.txt

$ python solve.py < input.txt

のように与えられたとき、正しく出力することが求められます。

Visual Studio Codeでは<を使った入力や出力はdebugger/runtime specificということになっています (https://code.visualstudio.com/docs/editor/debugging#_redirect-inputoutput-tofrom-the-debug-target) 。私の理解が正しければ、Pythonを実行する処理系が<をうまく扱えない場合、Visual Studio Code<を使う諦めざるを得ないはずです。実際、Windows + Anaconda Pythonという組み合わせで試行錯誤しましたが、うまくいきませんでした。

このように<を使った入力や出力がうまくいかない処理系を使っている場合であっても、Visual Studio CodePythonデバッグができるような方法を考えました。

手順1. Pythonスクリプトに3行追加

Visual Studio Codeは、以下の形式であれば問題なく引数を受け取ってデバッグ可能です。

$ python solve.py input.txt

そこで、以下のどちらでも同じように入力を受け取れるようにPythonスクリプトを変更します。

$ python solve.py < input.txt
$ python solve.py input.txt

そのためには、Pythonスクリプトに以下の行を入れればOKです(参考: how to take a file as input stream in Python - Stack Overflow )。

import sys


if len(sys.argv) == 2:
    sys.stdin = open(sys.argv[1])

例えば ABC146のB問題ですと、回答は以下のようになります。

import sys


if len(sys.argv) == 2:
    sys.stdin = open(sys.argv[1])

N = int(input())
S = input()
T = ""
for ch in S:
    T += chr(ord('A') + (ord(ch) - ord('A') + N) % 26)
print(T)

手順2. Visual Studio Codeデバッグ実行

事前にPython拡張機能を入れて置く必要があります(多分Pythonで検索して最初に出てくるMicrosoftのものでOK)。

  • Pythonコードを右クリックしてVisual Studio Codeで開きます。

  • 左の方にあるデバッグのアイコンをクリックします。Ctrl + Shift + DでもOKです。

f:id:minus9d:20200723181922p:plain

  • "create a launch.json file." をクリックします。

f:id:minus9d:20200723182544p:plain

  • "Python"を選択します。

f:id:minus9d:20200723182626p:plain

  • "Python File"を選択します。

f:id:minus9d:20200723182700p:plain

  • 自動で作成される雛形に、"args": [/path/to/入力ファイルへのパス] を追加します。パスは、あなたのPythonスクリプトのあるディレクトリを基準とした相対パスでもよいようです。以下に例を示します。
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Current File",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal",
            "args": ["test/sample-1.in"]
        }
    ]
}
  • Pythonスクリプトのタブを選択して、行番号の左側をクリックしてBreakpointをはります。

f:id:minus9d:20200723183947p:plain

  • さっき作った設定が選ばれていることを確認して、再生ボタンを押します。

f:id:minus9d:20200723183414p:plain

  • Debugしましょう!

f:id:minus9d:20200723183548p:plain

ios_base::sync_with_stdio(false); cin.tie(0); の意味

競技プログラミングC++を使うときに、入出力を高速化する目的でおまじないのように書かれる

ios_base::sync_with_stdio(false);
cin.tie(0);

の意味、実はよくわかっていなかったので c++ - Significance of ios_base::sync_with_stdio(false); cin.tie(NULL); - Stack Overflow を主に参考として調べてみました。

sync_with_stdio(false);

C++の標準入出力ストリームがCの入出力と同期しないようにします。代償として、 std::cinscanf() を混ぜたり std::coutprintf() を混ぜたりすると破滅するようになります。

std::cinscanf()を混ぜて入力がおかしくなる例を示します。まず以下のようなテキストファイルを用意します。

100
0 1 2 3 (略) 98 99

これを、以下のようにstd::ios::sync_with_stdio(false); した上で、std::cinscanf()で交互に数値を読み込みます。

#include <iostream>
#include <cstdio>
#include <vector>

int main(void)
{
    // C++の標準入出力ストリームがCの入出力と同期しないようにする
    std::ios::sync_with_stdio(false);

    int N;
    std::cin >> N;

    // 標準入力からcinとscanfで交互に数字を読む
    std::vector<int> arr(N);
    for(int n = 0; n < N; ++n) {
        if (n % 2) std::cin >> arr[n];
        else scanf("%d", &arr[n]);
    }

    // 読み取った結果を表示
    for(int n = 0; n < N; ++n) {
        std::cout << arr[n] << " ";
    }
    std::cout << std::endl;

    return 0;
}

出力例は以下です。正しく数値を読み込めていないことがわかります。

0 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 0 10 0 11 0 12 0 13 0 14 0 15 0 16 0 17 0 18 0 19 0 20 0 21 0 22 0 23 0 24 0 25 0 26 0 27 0 28 0 29 0 30 0 31 0 32 0 33 0 34 0 35 0 36 0 37 0 38 0 39 0 40 0 41 0 42 0 43 0 44 0 45 0 46 0 47 0 48 0 49

コードは https://ideone.com/u6TBWZ で実行を試すことができます。

cin.tie(0)

std::cinstd::coutとの結合を解きます。例えば、std::coutで出力を要求する文字が全部出力される前に、std::cinによる入力待ち状態になることがありえるようになります。

#include <iostream>
int main(void)
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);

    int n;
    for(int rep = 0; rep < 3; ++rep) {
        std::cout << "Input number: ";
        std::cin >> n;
        std::cout << n << " is given." << std::endl;
    }

    return 0;
}

実行例は以下です。Input number: が表示される前に数値の入力が要求されてしまいました。

$ ./a.exe
100
Input number: 100 is given.
200
Input number: 200 is given.
300
Input number: 300 is given.

std::cout << "Input number: " << std::flush; とすればこの問題は解決可能です。

Oversized Pancake Choppers の解説

Google Code Jam 2020 Round 1Cの最終問題であるOversized Pancake Choppersの解説です。

この問題はTest Set1から3の3つから構成されます。本番ではTest Set 1のみ解けました。この記事ではTest Set2とTest Set3について解説します。

問題概要

N個のパンケーキが存在する。パンケーキのサイズはA = {A_1, ..., A_N}である。D人の客に、同じサイズのパンケーキを渡さなければいけない。これを達成するために必要な最小のカット数はいくらか。

Test Set 2

制約

  • 1 ≤ N ≤ 300
  • 2 ≤ D ≤ 50

考察

パンケーキは、「等分でカットする」または「まったくカットしない」のどちらかのときに、「得られるスライス数=カット数+1」が成立しカット数を節約できます。例えば、サイズ14のパンケーキからサイズ4のスライスを3個得るには3回カットしなければいけませんが、サイズ12のパンケーキからサイズ4のスライスを3個得るには2回のカットで済みます。

例としてN = 3, D = 4, A = {3, 5, 6}の場合を考えてみます。客に提供するスライスサイズは、サイズ3のパンケーキを1から4に等分した3, 3/2, 3/3, 3/4のどれかか、サイズ5のパンケーキを1から4に等分した5, 5/2, 5/3, 5/4のどれかか、サイズ6のパンケーキを1から4に等分した6, 6/2, 6/3, 6/4のどれかになります。

Test Set 2では、上に上げたスライスの候補すべてについて、以下を調べます。

  • (a) パンケーキ列Aから、サイズsのスライスをD個取れるか
    • 取れないとすると、このスライスサイズは大きすぎるのでダメ
  • (b) パンケーキ列Aから、サイズsのスライスを取るための最小回数

(a)は、パンケーキのサイズA = {A_1, ..., A_N}それぞれをsで割ってあまりを捨てたものの和をとれば求められます。

(b)は、パンケーキ列Aのうち、サイズsでちょうど割り切れるパンケーキから順に、その中でもカット回数が少なくてすむパンケーキから順に貪欲にカットしていけば求められます。例えば、5回カットして6つのスライスが得られるパンケーキより、2回カットして3つのスライスが得られるパンケーキを優先的にカットするほうが、常に得です。

以上で問題が解けました。時間計算量は  O(DN^2) です。

実装

有理数を扱うのは面倒なので、パンケーキのサイズ列 A = {A_1, ..., A_N} を、スライスサイズが整数になるように何倍かしておくと楽です。

私の実装を以下に示します。Python 3だとTLEしてしまったので、PyPy2で通ることを確認しました。GCJはPyPy3がないので不便です…。

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

"""
客に提供するスライスのサイズは、必ず「どれかのパンケーキを等分したもの」になることに着目。
つまり、スライスサイズは、A[i]/1, A[i]/2, ..., A[i]/(D-1) (i = 0..N-1) のどれかになる。
あるスライスサイズについて、全探索。

Python 3だとSet2でTLE。 PyPy2だとSet2までAC
"""

from __future__ import print_function

import sys


# Python 2, 3両対応
if sys.version_info[0] == 2:
    myinput = raw_input
elif sys.version_info[0] == 3:
    myinput = input


def solve():
    N, D = map(int, myinput().split())
    As = list(map(int, myinput().split()))

    # パンケーキは小さい順に並び替えておく
    As.sort()

    ans = 10 ** 10
    for d in range(1, D + 1):
        Bs = [a * d for a in As]

        for a in As:
            # パンケーキaをd-1回カットしてd個に等分割した場合を以下で考える
            # このときの基準サイズは a / d である。
            # 有理数は扱いにくいので、すべてをd倍する。
            # つまり、パンケーキ: B
            #         基準サイズ: a

            # カット可能か調べる
            avail = 0
            for b in Bs:
                avail += b // a
            # カット不能ならcontinue
            if avail < D:
                continue

            # パンケーキBsそれぞれについて、基準サイズaでちょうど割り切れる場合、
            # 何個のパンケーキを取れるかを数える
            just = []
            for b in Bs:
                if b % a == 0:
                    just.append(b // a)

            # お得なパンケーキから順番にとっていく
            # AsやBsはあらかじめ昇順にソートしてあるので、justも昇順にソートされている
            remain_num = D  # あと何個のスライスが必要か
            cut_num = 0  # カット回数
            for j in just:
                if remain_num >= j:
                    remain_num -= j
                    cut_num += j - 1
                else:
                    break

            # 足りない分は残りのパンケーキから取る
            cut_num += remain_num

            ans = min(ans, cut_num)

    print(ans)            


def main():
    T = int(myinput())
    for testcase in range(T):
        print("Case #{}: ".format(testcase+1), end="")
        solve()

main()

Test Set 3

制約

  • 21ケースは 9000 ≤ N ≤ 10000.
  • 残りのケースは 1 ≤ N ≤ 1000.
  • 2 ≤ D ≤ 50

考察

Test Set 2の解法はTLEするのでもうひと工夫必要です。

Test Set 2の解法では、パンケーキのそれぞれについて、A * D種類のスライスサイズで割り切れるかどうかを判定していたのが無駄でした。 サイズaのパンケーキがカット数節約の効果を得るのは、このパンケーキを1, 2, ..., D分割するときだけなので、この場合のみ考えればよいです。

この考察から、以下のような手続きで, keyが「スライスサイズ」、valueが「そのスライスサイズをちょうど取れる数を並べた配列」であるような辞書を作ると良いことがわかります。

    dict = {}
    for a in A[0], A[1], ..., A[N - 1]:
        for d in 1, 2, ..., D:
            # Fraction(a, d) は、a / dを表現する有理数クラス
            dict[Fraction(a, d)].push_back(d)

例としてN = 3, D = 4, A = {3, 5, 6}の場合を考えてみます。辞書は以下のようになります。

keyは約分された有理数で持つ必要があります。各keyについて、サイズsのスライスを取るための最小回数を求める方法は、Test Set 2で書いたとおりです。

残る問題は、パンケーキ列Aから、サイズsのスライスをD個取れるかの確認です。もし上記辞書のkeyのそれぞれについてこの確認をしてしまうと時間計算量はO(DN^2)となり時間が足りません。

そこで、単調性を利用した二分探索を使います。keyの値をソートすると、小さなスライスサイズのときは問題なくスライスD個をとることができて、スライスサイズを大きくしていくと、どこかのタイミングでスライスD個をとれなくなります。その境界、つまりぎりぎりD個のスライスを作れる最大のスライスサイズs_maxを二分探索で探しておきます。

そうすれば、スライスのサイズsがs_max以下であるかどうかをチェックするだけで、スライスをD個取れるかが分かります。

以上で問題が解けました。時間計算量は、二分探索が O(N \log DN)、各keyについてサイズsのスライスを取るための最小回数を求める部分が O(DN) だと思います。

実装

私の実装を以下に示します。これもPython 3だとTLE、PyPy2でACです。

また、Pythonの標準ライブラリで提供されているFractionクラスは遅すぎたので自力実装しています。カスタムクラスを辞書のkeyとして使うために__hash____eq__を実装しました(参考: python - Object of custom type as dictionary key - Stack Overflow )。カスタムクラスをソートするために普通は比較関数を実装すると思いますが、Python 2とPython 3で必要な比較関数が違う(参考: python - Object of custom type as dictionary key - Stack Overflow)のが面倒だったので、分子 / 分母を浮動小数点数でもっておいてそれをkeyにソートするようにしました。大小関係の逆転が起こると嫌だなと思いましたが一応ACしました。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
パンケーキが価値があるのは、ちょうど等分で割り切れるとき。
すべてのパンケーキについて、1等分 .. D等分を試して
(スライスのサイズ, 何個のスライスが取れるか) のタプルを得る

Python 3だとTLE
"""

from __future__ import print_function
from __future__ import division

import array
from bisect import *
from collections import *
import fractions
from fractions import Fraction
import heapq
from itertools import *
import math
import random
import re
import string
import sys

# Python 2, 3両対応
if sys.version_info[0] == 2:
    input = raw_input
    gcd = fractions.gcd
elif sys.version_info[0] == 3:
    gcd = math.gcd


class MyFraction:
    """fractions.Fractionがあまりに遅いので自分で実装"""
    def __init__(self, n, d):
        gcd_value = gcd(n, d)
        self.numerator = n // gcd_value
        self.denominator = d // gcd_value
        self.real = n / d

    # hashのkeyとして使うために__hash__, __eq__が必要

    def __hash__(self):
        hashed_value = hash((self.numerator, self.denominator))
        return hash((self.numerator, self.denominator))

    def __eq__(self, other):
        return (self.numerator, self.denominator) == \
        (other.numerator, other.denominator)

    def __repr__(self):
        return "{} / {} ({})".format(self.numerator, self.denominator, self.real)


def mydiv(n, fraction):
    """floor(n(整数) / fraction)を返す"""
    return (n * fraction.denominator // fraction.numerator)


def solve_set3(N, D, As):
    ans = 10 ** 10
    slice_size_to_slice_num_list = defaultdict(list)
    for a in As:
        for d in range(1, D + 1):

            # fractions.Fraction()は非常に遅い
            # slice_size = Fraction(a, d)

            # そのため、自作クラスを使う
            slice_size = MyFraction(a, d)

            slice_num = d
            slice_size_to_slice_num_list[slice_size].append(slice_num)

    # slice_sizeのうち、D個カットできる境目を二分探索する
    # これにより、後段でO(N)でスライスが可能かをチェックする必要がなくなる
    slice_size_list = list(slice_size_to_slice_num_list.keys())
    slice_size_list.sort(key=lambda obj: obj.real)
    lo = 0
    hi = len(slice_size_list)
    while hi - lo > 1:
        mid = (lo + hi) // 2

        # パンケーキからslice_sizeサイズのスライスを何個作れるか?
        slice_size = slice_size_list[mid]
        cuttable_slice_num = 0
        for a in As:
            cuttable_slice_num += mydiv(a, slice_size)
            if cuttable_slice_num >= D:
                break

        if cuttable_slice_num >= D:
            lo = mid
        else:
            hi = mid

    # カット可能なスライスの範囲のみを探索
    for slice_size in slice_size_list[:(lo + 1)]:
        slice_num_list = slice_size_to_slice_num_list[slice_size]

        # スライスの個数が小さいものから順番に
        slice_num_list.sort()
        cnt = 0
        profit = 0
        for slice_num in slice_num_list:
            cnt += slice_num
            if cnt <= D:
                profit += 1

        ans = min(ans, D - profit)

    return ans         


def solve():
    N, D = map(int, input().split())
    As = list(map(int, input().split()))
    print(solve_set3(N, D, As))


def main():
    T = int(input())
    for testcase in range(T):
        print("Case #{}: ".format(testcase+1), end="")
        solve()


main()

Codeforces Round #641 Orac and Mediansの解説

約1年ぶりにCodeforcesに出場しました。Div. 2の4問目、Orac and Medians が解けそうで解けませんでした。終了後、解説を読みながら考えをまとめました。

問題概要

数列 a_1, a_2, ..., a_Nが与えられる。この数列の任意区間を選び、その区間のすべての値を、その区間の中央値で置換することを好きな回数だけ行う。数列すべての値を値 Kにすることはできるか? できる場合は"yes", できない場合は"no"を出力。

ここで、数列sの中央値は、要素を小さい順に並べたときの ⌊|s|+1/2⌋ 番目の要素と定義。例えば数列{1, 7, 5, 8, 4}の中央値は、3番目に小さい5。数列{1, 7, 5, 8}の中央値は、2番めに小さい5。

考察

Kより小さい値、K、Kより大きい値の3カテゴリのみ考えればよい。以下では、Kより小さい値を0, Kを1, Kを2と置換した数列 b_1, b_2, ..., b_Nを考える。

以下のケースはすぐに分かる。

  • 数列 b_iに1個も1が存在しない場合は明らかに"no"。
  • 数列 b_iの長さが1の場合、 b_1が1の場合のみ"yes"。

今後、数列の長さは2以上、かつ少なくとも1個は1が存在するものとする。

以下のケースも少し考えると分かる。

  • 1と1が隣り合う箇所がある場合は"yes"。
    • "? 1 1" または "1 1 ?" どちらのパターンでも中央値は1なので、"1 1 1" と1が増殖する。これを続けると数列はすべて1になる。
  • 1と2が隣り合う箇所がある場合は"yes"。
    • "1 2" または "2 1" どちらのパターンでも中央値は1なので、"1 1"となる。あとは↑のパターンに帰着。
  • 2と2が隣り合う箇所がある場合は"yes"。
    • "2 2"の前後にある"0"をすべて"2"に変えていくと、いつかは"1"に接触する。
    • すると"1 2"または"2 1"のパターンが発生するので、"yes"になる。

さらに考えると、以下のパターンも成り立つことがわかる。

  • "1 0 1"がある場合は"yes"。
    • "1 0 1" の中央値は1なので、"1 1 1"を作れる。"1 1"のパターンを作れたので"yes"。
  • "1 0 2" または "2 0 1" がある場合は"yes"。
    • "1 0 2" または "2 0 1" どちらのパターンでも中央値は1なので、"1 1 1"を作れる。"1 1"のパターンを作れたので"yes"。
  • "2 0 2" がある場合は"yes"。
    • "2 0 2"の中央値は2なので、"2 2 2"を作れる。
    • "2 2"のパターンを作れたので"yes"。

解説によると"yes"のパターンとなるのは実は上記までで、上に書いたパターンのどれも持たない数列はすべて"no"になる。これを直感的に感じ取るのは私には難しかった。

上に書いたパターンにどれにも当てはまらないということは、数列の1や2の間には、少なくとも2つの0があるということ。例えば以下。

  • "0 1 0 0 1 0 0 2"
  • "2 0 0 2 0 0 1 0 0 2"

0の影響力は強くて、0を1や2に変えるには、1や2で挟む必要がある。しかし上記のようなパターンだと、1や2で挟めない。よって"no"となる。

回答例 (C++)

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
#define REP(i,n) for(int i = 0; i < (int)(n); ++i)
#define FOR(i,a,b) for(int i = (a); i < (int)(b); ++i)
#define ALL(c) (c).begin(), (c).end()
#define SIZE(v) ((int)v.size())
#define pb push_back
#define mp make_pair
#define mt make_tuple

string solve(int N, int K, vector<int>& As) {
    // Kが1個もなければno
    if (find(ALL(As), K) == As.end()) {
        return "no";
    }

    // 以下では少なくとも1個のKがあるとしてよい

    // 配列の長さ1
    if (N == 1) {
        return "yes";
    }

    // 以下では配列の長さが2以上としてよい

    // 隣り合う数同士にK以上があるならyes
    REP(i, N - 1) {
        if (As[i] >= K && As[i + 1] >= K) {
            return "yes";
        }
    }
    // 1個離れた位置にK以上があるならyes
    REP(i, N - 2) {
        if (As[i] >= K && As[i + 2] >= K) {
            return "yes";
        }
    }

    return "no";
}

int main(void)
{
    cin.sync_with_stdio(false);
    int T; cin >> T;
    REP(t, T) {
        int N; int K;
        cin >> N >> K;
        vector<int> As(N);
        REP(n, N) cin >> As[n];
        cout << solve(N, K, As) << endl;
    }

    return 0;
}

Google Kickstart 2020 Round 1B - Wandering Robot の解説

Google Kickstart 2020 Round 1Bの最後の問題、Wandering Robot についてやっと理解できたので解説を書きます。

問題概要

W*Hの盤面が与えられる。盤面には ある矩形1つ分の穴が空いている。ロボットは左上の(1, 1)を出発し、等確率で右か下に移動する。ただし盤面の右端に到達した場合は必ず下に、盤面の下端に到達した場合は必ず右に移動する。ロボットが穴に落ちずに盤面の右下(W, H)に到着できる確率を求めよ。

着想

例えば以下の盤面を考えます。■は穴を表します。

f:id:minus9d:20200430231500p:plain

★に着目してください。ロボットが右下に到達するためには、必ず★のどれか一つを通らなければいけません。かつ、★を2つ以上通ることはできません。なので、各★を通る確率を個別に求め、その和をとれば、答が求まりそうです。

各セルを通る確率は、以下のように求められます。さきほどの盤面の場合、3つの★の確率の和 1/16 + 4/16 + 1/8 = 7/16 = 0.4375 が答になります。

f:id:minus9d:20200430231821p:plain

上記の確率はいかにも二項分布で一般化できそうな形をしています。しかし、上図であえて空白にしている部分の確率はどうでしょうか。すべての確率を求めた図を以下に示します。ロボットが右端 or 下端に達したときは確率1で移動方向が決定するため、二項分布の法則が崩れます。

f:id:minus9d:20200430234324p:plain

なので、以下のような盤面が与えられたとき、★の部分の確率の和を求めるのは一筋縄ではいきません。

f:id:minus9d:20200430234337p:plain

工夫1: 仮想盤面の導入

本ラウンド1位のscotwu氏の回答 がそれへの解になります。この回答では、まるでロボットが右端や下端を無視して移動し続けられるかのような仮想盤面を考えます。

xxxf:id:minus9d:20200430234414p:plain

上の仮想盤面において☆の確率を足すと、ちょうど前の盤面の★の確率の和と同じになっていることが分かります!! 仮想盤面では二項分布の法則が崩れないので、確率の計算が容易です。

ただし、穴が盤面の下または右に接するコーナーケースに注意です。以下の例の場合、×印で示した★の確率を足してはいけません。

f:id:minus9d:20200501214850p:plain

工夫2: logの計算

本質的にはここまでの説明で解けたようなものですが、実際に確率を算出するためにはもうひと工夫必要です。

ロボットがマス(x, y)を通過する確率は C((x - 1) + (y - 1), (x - 1)) / 2^((x - 1) + (y - 1)) で書けます(ここで、C(n, k)は二項分布)。しかし、分子も分母も巨大な整数になるため愚直に計算すると計算時間がかかりすぎます。

そこで、分子、分母それぞれを底を2とするlogをとることで、計算時間を抑えることができます。C(n, k) = n! / k! (n - k)! なので、1!, 2!, 3!, ... それぞれについて底を2とするlogの値を前計算で求めておけばOKです。

コード

Python 3です。

#!/usr/bin/env python3

import math


# log2(n!)のテーブルを作成
log_table = [0] * 200001
log_value = 0
for i in range(1, 200001):
    log_value += math.log(i, 2)
    log_table[i] = log_value


def log_of_choose(n, k):
    """log C(n, k)を求める"""
    return log_table[n] - log_table[k] - log_table[n - k]


def calc_probability(x, y):
    """(1, 1)から(x, y)に到着する確率を求める
    """
    x -= 1
    y -= 1
    return log_of_choose(x + y, x) - (x + y)


def solve_fast(W, H, L, U, R, D):

    ans = 0.0

    # ◎の部分を下から順に
    # □□□□□◎
    # □□□□◎
    # □□□■□
    # □□□■□
    # □□□□□
    if R != W:
        x = R + 1
        y = U - 1
        while y >= 1:
            log2_of_prob = calc_probability(x, y)
            ans += pow(2, log2_of_prob)
            x += 1
            y -= 1

    # ◎の部分を右から順に
    # □□□□□
    # □□□□□
    # □□□■□
    # □□□■□
    # □□◎□□
    #  ◎
    # ◎
    if D != H:
        x = L - 1
        y = D + 1
        while x >= 1:
            log2_of_prob = calc_probability(x, y)
            ans += pow(2, log2_of_prob)
            x -= 1
            y += 1

    return ans


def solve():
    W, H, L, U, R, D = map(int, input().split())
    print("{:.20f}".format(solve_fast(W, H, L, U, R, D)))


T = int(input())
for testcase in range(T):
    print("Case #{}: ".format(testcase+1), end="")
    solve()

Dockerfileでkeras環境を作成する練習

Docker ImageからContainerを作るコマンドのまとめ - minus9d's diary の続きです。今回はKeras環境を備えるDocker Imageを作成する練習をしてみました。普通であればTensorflow公式のDocker Imageを使うのが正しいと思いますが、練習なので気にしないことにします。

この記事では私の試行錯誤の過程をそのまま記述しています。最終的なDockerfileは末尾に記載しています。

最初のDockerfile

まず、以下のようなDockerfileを作成します。

FROM nvidia/cuda:10.2-cudnn7-devel-ubuntu18.04

RUN apt-get update && apt-get install -y \
    python3-pip \
    wget

RUN pip3 install tensorflow-gpu && pip3 install keras

RUN wget https://raw.githubusercontent.com/keras-team/keras/master/examples/cifar10_cnn.py

CMD python3 cifar10_cnn.py

以下、Dockerfileの簡単な解説です。

一行目のFROM nvidia/cudaで、Docker Hub にあるNVIDIA公式のCUDAの環境をベースイメージとして指定します。

二行目のapt-getで、あとで必要になるバイナリの習得を行います。つい手癖でapt-get updateの後はapt-get upgradeしたくなりますが、Best practices for writing Dockerfilesによるとapt-get upgradeは避けるべきだそうです。また、apt-get updateと、apt-get install -y XXXは同じRUN命令で同時に実行すべきだそうです。

三行目でtensorflow-gpuとkerasをインストールします。

四行目でkerasのサンプルスクリプトをダウンロードします。

次に、このDockerfileをもとにDocker Imageを作成します。このDockerfileと同じディレクトリにて

$ docker build -t mykerasimage .

とすることで、mykerasimageという名前のDocker Imageを作成します。

次に、このDocker Imageから、コンテナを作成して実行します。

$ docker run -it mykerasimage

とすると、Dockerfileの最終行に書いたpython3 cifar10_cnn.pyがコンテナ内で実行されます。

以下はそのときのログです。

$ docker run -it mykerasimage
Using TensorFlow backend.
/usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/dtypes.py:516: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  _np_qint8 = np.dtype([("qint8", np.int8, 1)])
/usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/dtypes.py:517: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  _np_quint8 = np.dtype([("quint8", np.uint8, 1)])
/usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/dtypes.py:518: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  _np_qint16 = np.dtype([("qint16", np.int16, 1)])
/usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/dtypes.py:519: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  _np_quint16 = np.dtype([("quint16", np.uint16, 1)])
/usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/dtypes.py:520: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  _np_qint32 = np.dtype([("qint32", np.int32, 1)])
/usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/dtypes.py:525: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  np_resource = np.dtype([("resource", np.ubyte, 1)])
/usr/local/lib/python3.6/dist-packages/tensorboard/compat/tensorflow_stub/dtypes.py:541: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  _np_qint8 = np.dtype([("qint8", np.int8, 1)])
/usr/local/lib/python3.6/dist-packages/tensorboard/compat/tensorflow_stub/dtypes.py:542: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  _np_quint8 = np.dtype([("quint8", np.uint8, 1)])
/usr/local/lib/python3.6/dist-packages/tensorboard/compat/tensorflow_stub/dtypes.py:543: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  _np_qint16 = np.dtype([("qint16", np.int16, 1)])
/usr/local/lib/python3.6/dist-packages/tensorboard/compat/tensorflow_stub/dtypes.py:544: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  _np_quint16 = np.dtype([("quint16", np.uint16, 1)])
/usr/local/lib/python3.6/dist-packages/tensorboard/compat/tensorflow_stub/dtypes.py:545: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  _np_qint32 = np.dtype([("qint32", np.int32, 1)])
/usr/local/lib/python3.6/dist-packages/tensorboard/compat/tensorflow_stub/dtypes.py:550: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  np_resource = np.dtype([("resource", np.ubyte, 1)])
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170500096/170498071 [==============================] - 130s 1us/step
x_train shape: (50000, 32, 32, 3)
50000 train samples
10000 test samples
WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:4070: The name tf.nn.max_pool is deprecated. Please use tf.nn.max_pool2d instead.

Using real-time data augmentation.
2020-04-07 14:16:02.853936: I tensorflow/core/platform/cpu_feature_guard.cc:142] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2020-04-07 14:16:02.857300: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Could not dlopen library 'libcuda.so.1'; dlerror: libcuda.so.1: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /usr/local/nvidia/lib:/usr/local/nvidia/lib64
2020-04-07 14:16:02.857318: E tensorflow/stream_executor/cuda/cuda_driver.cc:318] failed call to cuInit: UNKNOWN ERROR (303)
2020-04-07 14:16:02.857334: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:163] no NVIDIA GPU device is present: /dev/nvidia0 does not exist
2020-04-07 14:16:02.875675: I tensorflow/core/platform/profile_utils/cpu_utils.cc:94] CPU Frequency: 3600000000 Hz
2020-04-07 14:16:02.876032: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x453cdb0 executing computations on platform Host. Devices:
2020-04-07 14:16:02.876049: I tensorflow/compiler/xla/service/service.cc:175]   StreamExecutor device (0): <undefined>, <undefined>
2020-04-07 14:16:02.915462: W tensorflow/compiler/jit/mark_for_compilation_pass.cc:1412] (One-time warning): Not using XLA:CPU for cluster because envvar TF_XLA_FLAGS=--tf_xla_cpu_global_jit was not set.  If you want XLA:CPU, either set that envvar, or use experimental_jit_scope to enable XLA:CPU.  To confirm that XLA is active, pass --vmodule=xla_compilation_cache=1 (as a proper command-line flag, not via TF_XLA_FLAGS) or set the envvar XLA_FLAGS=--xla_hlo_profile.
WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:422: The name tf.global_variables is deprecated. Please use tf.compat.v1.global_variables instead.

Epoch 1/100
1563/1563 [==============================] - 63s 40ms/step - loss: 1.8523 - accuracy: 0.3178 - val_loss: 1.5431 - val_accuracy: 0.4363
Epoch 2/100
1563/1563 [==============================] - 63s 40ms/step - loss: 1.5708 - accuracy: 0.4255 - val_loss: 1.3986 - val_accuracy: 0.4933
Epoch 3/100
1563/1563 [==============================] - 62s 40ms/step - loss: 1.4585 - accuracy: 0.4721 - val_loss: 1.3288 - val_accuracy: 0.5212
Epoch 4/100
1563/1563 [==============================] - 62s 40ms/step - loss: 1.3764 - accuracy: 0.5028 - val_loss: 1.2310 - val_accuracy: 0.5604
(以下略)

このログをよく見ると、Could not dlopen library 'libcuda.so.1' failed call to cuInit: UNKNOWN ERROR (303) などの不穏なエラーが並んでいることがわかります。これは、私が NVIDIA Docker の環境を作らなかったことが原因だと思われます。ただ、これらのエラーが出るにもかかわらずサンプルファイルは実行できているようにも見え、謎です…。

NVIDIA Dockerの導入

NVIDIA Dockerの導入は、NVIDIAの中の人が記した NVIDIA Docker って今どうなってるの? (19.11版) - Qiita が歴史的経緯も含めてまとめられていてわかりやすいです。今回は「Docker 19.03 以降の環境で前だけを見て生きる場合」に従い、NVIDIA Dockerの追加インストールを行いました。

そして今度は、コンテナ内で使用するGPUを指定するオプション--gpuを付与して、改めてコンテナを作ります。

$ docker run --gpus all -it mykerasimage 

以下はその実行結果です。さきほどの不穏なエラーは消えましたが、代わりにlibcudart.so.10.0が見つからないというエラーなどが出ました。

(略)
Using real-time data augmentation.
2020-04-07 14:30:25.187308: I tensorflow/core/platform/cpu_feature_guard.cc:142] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2020-04-07 14:30:25.191128: I tensorflow/stream_executor/platform/default/dso_loader.cc:42] Successfully opened dynamic library libcuda.so.1
2020-04-07 14:30:25.281694: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:1005] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-04-07 14:30:25.282141: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x34f5f90 executing computations on platform CUDA. Devices:
2020-04-07 14:30:25.282156: I tensorflow/compiler/xla/service/service.cc:175]   StreamExecutor device (0): GeForce GTX 1070, Compute Capability 6.1
2020-04-07 14:30:25.303626: I tensorflow/core/platform/profile_utils/cpu_utils.cc:94] CPU Frequency: 3600000000 Hz
2020-04-07 14:30:25.303940: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x3e90720 executing computations on platform Host. Devices:
2020-04-07 14:30:25.303972: I tensorflow/compiler/xla/service/service.cc:175]   StreamExecutor device (0): <undefined>, <undefined>
2020-04-07 14:30:25.304181: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:1005] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-04-07 14:30:25.304464: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1640] Found device 0 with properties: 
name: GeForce GTX 1070 major: 6 minor: 1 memoryClockRate(GHz): 1.683
pciBusID: 0000:01:00.0
2020-04-07 14:30:25.304599: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Could not dlopen library 'libcudart.so.10.0'; dlerror: libcudart.so.10.0: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /usr/local/nvidia/lib:/usr/local/nvidia/lib64
2020-04-07 14:30:25.304673: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Could not dlopen library 'libcublas.so.10.0'; dlerror: libcublas.so.10.0: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /usr/local/nvidia/lib:/usr/local/nvidia/lib64
2020-04-07 14:30:25.304742: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Could not dlopen library 'libcufft.so.10.0'; dlerror: libcufft.so.10.0: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /usr/local/nvidia/lib:/usr/local/nvidia/lib64
2020-04-07 14:30:25.304810: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Could not dlopen library 'libcurand.so.10.0'; dlerror: libcurand.so.10.0: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /usr/local/nvidia/lib:/usr/local/nvidia/lib64
2020-04-07 14:30:25.304880: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Could not dlopen library 'libcusolver.so.10.0'; dlerror: libcusolver.so.10.0: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /usr/local/nvidia/lib:/usr/local/nvidia/lib64
2020-04-07 14:30:25.304950: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Could not dlopen library 'libcusparse.so.10.0'; dlerror: libcusparse.so.10.0: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /usr/local/nvidia/lib:/usr/local/nvidia/lib64
2020-04-07 14:30:25.395108: I tensorflow/stream_executor/platform/default/dso_loader.cc:42] Successfully opened dynamic library libcudnn.so.7
2020-04-07 14:30:25.395193: W tensorflow/core/common_runtime/gpu/gpu_device.cc:1663] Cannot dlopen some GPU libraries. Skipping registering GPU devices...
2020-04-07 14:30:25.395243: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1181] Device interconnect StreamExecutor with strength 1 edge matrix:
2020-04-07 14:30:25.395275: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1187]      0 
2020-04-07 14:30:25.395302: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1200] 0:   N 
2020-04-07 14:30:25.472855: W tensorflow/compiler/jit/mark_for_compilation_pass.cc:1412] (One-time warning): Not using XLA:CPU for cluster because envvar TF_XLA_FLAGS=--tf_xla_cpu_global_jit was not set.  If you want XLA:CPU, either set that envvar, or use experimental_jit_scope to enable XLA:CPU.  To confirm that XLA is active, pass --vmodule=xla_compilation_cache=1 (as a proper command-line flag, not via TF_XLA_FLAGS) or set the envvar XLA_FLAGS=--xla_hlo_profile.
WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:422: The name tf.global_variables is deprecated. Please use tf.compat.v1.global_variables instead.

Epoch 1/100
1563/1563 [==============================] - 63s 40ms/step - loss: 1.9029 - accuracy: 0.3008 - val_loss: 1.5870 - val_accuracy: 0.4238
Epoch 2/100
1563/1563 [==============================] - 63s 40ms/step - loss: 1.5875 - accuracy: 0.4180 - val_loss: 1.3819 - val_accuracy: 0.4944

$ docker run --gpus all -it mykerasimage bashでコンテナの中を探ってみると、libcudart.so.10.0は存在せず、かわりに/usr/local/cuda-10.2/targets/x86_64-linux/lib/libcudart.so.10.2があるのみでした。また、 tensorflow-gpuのバージョンが1.14.0と古いことに気づきました。tensorflow-gpu 1.14.0はCUDA 10.0を要求するにもかかわらず、CUDA 10.2のベースイメージを用意してしまったのが失敗でした。

ベースイメージの見直しとpip3の更新

この反省をもとに、現時点で最新である tensorflow-gpu 2.1 と、これが要求するCUDA 10.1の組み合わせを実現するDockerfileを書き直すことにしました。

書き直したDockerfileを以下に示します。ベースイメージをCUDA 10.1に変更し、かつ、pip3を最新版に更新(現時点で20.0.2)してから、tensorflow-gpuとkerasをバージョン指定で入れるようにしました。

FROM nvidia/cuda:10.1-cudnn7-devel-ubuntu18.04

RUN apt-get update && apt-get install -y \
    python3-pip \
    wget

RUN pip3 install --upgrade pip

RUN pip3 install tensorflow-gpu && pip3 install keras

RUN wget https://raw.githubusercontent.com/keras-team/keras/master/examples/cifar10_cnn.py

CMD python3 cifar10_cnn.py

このDockerfileをもとにDocker Imageを再作成し、docker run --gpus all -it mykerasimageしたときのログを以下に示します。まだlibnvinfer.so.6, libnvinfer_plugin.so.6 などがロードできない旨のエラーが出ていますが、それ以外は正しく動いていそうです。

$ docker run --gpus all -it mykerasimage     
Using TensorFlow backend.
2020-04-08 01:11:33.204351: W tensorflow/stream_executor/platform/default/dso_loader.cc:55] Could not load dynamic library 'libnvinfer.so.6'; dlerror: libnvinfer.so.6: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /usr/local/nvidia/lib:/usr/local/nvidia/lib64
2020-04-08 01:11:33.204485: W tensorflow/stream_executor/platform/default/dso_loader.cc:55] Could not load dynamic library 'libnvinfer_plugin.so.6'; dlerror: libnvinfer_plugin.so.6: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /usr/local/nvidia/lib:/usr/local/nvidia/lib64
2020-04-08 01:11:33.204496: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:30] Cannot dlopen some TensorRT libraries. If you would like to use Nvidia GPU with TensorRT, please make sure the missing libraries mentioned above are installed properly.
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170500096/170498071 [==============================] - 91s 1us/step
x_train shape: (50000, 32, 32, 3)
50000 train samples
10000 test samples
2020-04-08 01:13:07.198910: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcuda.so.1
2020-04-08 01:13:07.217257: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-04-08 01:13:07.217949: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1555] Found device 0 with properties: 
pciBusID: 0000:01:00.0 name: GeForce GTX 1070 computeCapability: 6.1
coreClock: 1.683GHz coreCount: 15 deviceMemorySize: 7.93GiB deviceMemoryBandwidth: 238.66GiB/s
2020-04-08 01:13:07.219716: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudart.so.10.1
2020-04-08 01:13:07.273601: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcublas.so.10
2020-04-08 01:13:07.308268: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcufft.so.10
2020-04-08 01:13:07.317546: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcurand.so.10
2020-04-08 01:13:07.384937: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcusolver.so.10
2020-04-08 01:13:07.393726: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcusparse.so.10
2020-04-08 01:13:07.493477: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudnn.so.7
2020-04-08 01:13:07.493865: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-04-08 01:13:07.495793: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-04-08 01:13:07.497123: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1697] Adding visible gpu devices: 0
2020-04-08 01:13:07.497893: I tensorflow/core/platform/cpu_feature_guard.cc:142] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2020-04-08 01:13:07.538725: I tensorflow/core/platform/profile_utils/cpu_utils.cc:94] CPU Frequency: 3600000000 Hz
2020-04-08 01:13:07.539661: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x40f4d80 initialized for platform Host (this does not guarantee that XLA will be used). Devices:
2020-04-08 01:13:07.539710: I tensorflow/compiler/xla/service/service.cc:176]   StreamExecutor device (0): Host, Default Version
2020-04-08 01:13:07.637951: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-04-08 01:13:07.638329: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x417adc0 initialized for platform CUDA (this does not guarantee that XLA will be used). Devices:
2020-04-08 01:13:07.638342: I tensorflow/compiler/xla/service/service.cc:176]   StreamExecutor device (0): GeForce GTX 1070, Compute Capability 6.1
2020-04-08 01:13:07.638458: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-04-08 01:13:07.638711: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1555] Found device 0 with properties: 
pciBusID: 0000:01:00.0 name: GeForce GTX 1070 computeCapability: 6.1
coreClock: 1.683GHz coreCount: 15 deviceMemorySize: 7.93GiB deviceMemoryBandwidth: 238.66GiB/s
2020-04-08 01:13:07.638738: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudart.so.10.1
2020-04-08 01:13:07.638750: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcublas.so.10
2020-04-08 01:13:07.638759: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcufft.so.10
2020-04-08 01:13:07.638770: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcurand.so.10
2020-04-08 01:13:07.638785: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcusolver.so.10
2020-04-08 01:13:07.638808: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcusparse.so.10
2020-04-08 01:13:07.638827: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudnn.so.7
2020-04-08 01:13:07.638878: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-04-08 01:13:07.639220: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-04-08 01:13:07.639473: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1697] Adding visible gpu devices: 0
2020-04-08 01:13:07.639501: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudart.so.10.1
2020-04-08 01:13:07.641096: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1096] Device interconnect StreamExecutor with strength 1 edge matrix:
2020-04-08 01:13:07.641107: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1102]      0 
2020-04-08 01:13:07.641113: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1115] 0:   N 
2020-04-08 01:13:07.641218: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-04-08 01:13:07.641555: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-04-08 01:13:07.641833: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1241] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 7011 MB memory) -> physical GPU (device: 0, name: GeForce GTX 1070, pci bus id: 0000:01:00.0, compute capability: 6.1)
Using real-time data augmentation.
Epoch 1/100
2020-04-08 01:13:11.232517: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcublas.so.10
2020-04-08 01:13:11.745019: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudnn.so.7
1563/1563 [==============================] - 19s 12ms/step - loss: 1.8952 - accuracy: 0.3029 - val_loss: 1.5868 - val_accuracy: 0.4233
Epoch 2/100
1563/1563 [==============================] - 17s 11ms/step - loss: 1.6196 - accuracy: 0.4084 - val_loss: 1.4305 - val_accuracy: 0.4763
Epoch 3/100
1563/1563 [==============================] - 17s 11ms/step - loss: 1.4915 - accuracy: 0.4593 - val_loss: 1.3402 - val_accuracy: 0.5222

Python3でエラー発生時にデバッガを起動する

Python3のスクリプトを数時間動かしたあとにエラーで落ちるという経験はないでしょうか。エラーが発生したら pdb と呼ばれるデバッガが起動するようにしておくと、エラーの究明に役立ちます。

例えば、以下のような script.py があったとします。

def main():
    a = 10
    b = 0
    print(a / b)


main()

このスクリプト$ python3 script.py と普通に実行すると、以下のようにゼロ除算エラーが発生し、異常終了してしまいます。

$ python script.py
Traceback (most recent call last):
  File "script.py", line 7, in <module>
    main()
  File "script.py", line 4, in main
    print(a / b)
ZeroDivisionError: integer division or modulo by zero

ここで、

$ python3 -m pdb -c continue script.py

というふうに、-m pdb -c continue をつけてスクリプトを実行すると、以下のように、エラーが発生した地点でデバッガが起動します。

$ python3 -m pdb -c continue script.py
Traceback (most recent call last):
  File "/usr/lib/python3.6/pdb.py", line 1667, in main
    pdb._runscript(mainpyfile)
  File "/usr/lib/python3.6/pdb.py", line 1548, in _runscript
    self.run(statement)
  File "/usr/lib/python3.6/bdb.py", line 434, in run
    exec(cmd, globals, locals)
  File "<string>", line 1, in <module>
  File "/home/user/script.py", line 1, in <module>
    def main():
  File "/home/user/script.py", line 4, in main
    print(a / b)
ZeroDivisionError: division by zero
Uncaught exception. Entering post mortem debugging
Running 'cont' or 'step' will restart the program
> /home/user/script.py(4)main()
-> print(a / b)
(Pdb) 

あとは、pdbを使って自由に原因究明できます。

(Pdb) print(a)
10
(Pdb) print(b)
0