需要のないページ

プログラミングや趣味や。

Land of Lispを読んでいたのになぜかC++のコードを書いていた

前書き

悟りを得たくて、神の言語Lispに手を付けてみた。
もちろんここで紐解くのは、言わずと知れた聖典Land of Lispである。

Land of Lisp

Land of Lisp

 

この本はなかなか痛快で、他の言語のユーザを痛烈に皮肉っている。*1
Lispのシンプルさ*2の主張として、次のような例が取り上げられている(p.20)。

((foo<bar>)*(g++)).baz(!&qux::zip->ding())

 このコードはC++シンタックスに従っている。意図を強調するために、C++の中でも特に他の言語とは違う変てこりんなシンタックスをてんこ盛りにしてみた。
 [中略]
 もちろんこのC++プログラムは意味を持っている。このコードをC++のプログラムに埋め込めば、それはコンピュータに何か動作をさせるだろう。

...ほんとにこれ動くの?というわけで、試してみる。

まずは解析

どこがどう結合するのか、適当な結合表を見ながら考えてみる。
そのうち、今回の式に登場する演算子だけをまとめると次のようになる。

優先順位 演算子 名称 結合規則
1 :: スコープ解決演算子 左から右
2 a++ 後置インクリメント演算子
a() 関数呼び出し演算子
. メンバアクセス演算子(ドット演算子)
-> メンバアクセス演算子(アロー演算子)
3 ! 論理否定演算子 右から左
&a 単項アドレス演算子
5 a*b 乗算演算子 左から右

項目は C++ Operator Precedence - cppreference.com を、
日本語名称は C++ の演算子、優先順位と結合規則 を参照した。

さて、この表に基づくと、次の二式は同値である。
((foo<bar>)*(g++)).baz(!&qux::zip->ding())
((foo<bar>)*(g++)).baz(!(&(((qux::zip)->ding()))))
後者めっちゃくちゃ汚いな。まあ、これで検証に踏むべき手順は見えた。

実際に書いてみる:part1

まずは、bazの実引数部分について考えてみよう。

以下のコードは、全てG++でコンパイルが通る。*3
ただし、-Wunused-value(評価されない値)ワーニングは出るけどね。*4

quxスコープの中に変数zipを作る

zipの型は何でもいいので、空のクラスを作った。


    class Zip{};

    namespace qux {
        Zip zip;
    }
    
    int main(void) {
        qux::zip;
    }
    
zipからアロー演算子でdingを参照する

アロー演算子を適用出来るということは、zipがポインタであることを意味する。
それを踏まえて、先ほどのコードから少し宣言部分も修正してある。


    class Zip{
    public:
        void ding(void) {
        }
    } zip_;

    namespace qux {
        Zip *zip = &zip_;
    }
    
    int main(void) {
        qux::zip->ding();
    }
    
dingの戻り値のアドレスを解決する

dingは実体を持つ何らかのオブジェクトを返す必要がある。
手を抜くとしたら自分自身の参照を返すのが一番楽だろう。


    class Zip{
    public:
        Zip& ding(void) {
            return *this;
        }
    } zip_;

    namespace qux {
        Zip *zip = &zip_;
    }
    
    int main(void) {
        &qux::zip->ding();
    }
    
得られたアドレスの論理否定を取る

これに関しては特に書き換えるべき箇所はない。
また、zip_のアドレスがヌルであり得ない以上、必ず式の値はfalseになる。


    class Zip{
    public:
        Zip& ding(void) {
            return *this;
        }
    } zip_;

    namespace qux {
        Zip *zip = &zip_;
    }
    
    int main(void) {
        !&qux::zip->ding();
    }
    


実際に書いてみる:part2

次は、bazをメンバ関数として持っているオブジェクトを書く。
面倒なので、クラスZipをここでも利用してやろう。

テンプレート変数を用意する

テンプレートクラス/関数はよく見かけるが、テンプレート変数はあまり見ない。
なぜなら、この要素はC++14以降に追加されたからである。

変に説明するのも面倒なので、有益なリンクを貼っておく。
変数テンプレート - cpprefjp C++日本語リファレンス

これを踏まえると、次のように表現できるだろう。


    class Zip{
    };
    
    using bar = Zip;
    template<typename T> T foo;
    
    int main(void) {
        foo<bar>;
    }
    

エイリアスとは言え、クラス名がパスカルケースでないのは大分気持ち悪い。
まあ、次のように書いてもいいのだが。これはこれはなんか嫌だ。


    class Zip{
    };
    
    constexpr int bar = 0;
    template<int B> Zip foo;
    
    int main(void) {
        foo<bar>;
    }
    
gを定義する

説明不要だろう... と言いたいが。せっかくだ、gもZip型にしてしまおう。
この場合、後置インクリメントを自前でオーバーロードしないといけない。

今回初めて知ったのだが、こいつを定義する場合、ダミーの引数が必要だと言う。
C++マニアック - オペレータのオーバーロード | C++入門 C++言語講座

これは言語仕様上の欠陥じゃないか?C++ともあろう人が...
とりあえず組む。実用は度外視しているので、自身の参照を返せばよいだろう。

コンマ演算子はどんな状況でも使えるので、こういうときは重宝する。


    class Zip{
    public:
        Zip& operator++(int) {
            return *this;
        }
    } g;
    
    using bar = Zip;
    template<typename T> T foo;
    
    int main(void) {
        foo<bar>, g++;
    }
    
アスタリスク二項演算子を定義する

直前の内容を踏まえれば、今度こそ説明不要だろう。


    class Zip{
    public:
        Zip& operator++(int) {
            return *this;
        }
        Zip& operator*(Zip&) {
            return *this;
        }
    } g;
    
    using bar = Zip;
    template<typename T> T foo;
    
    int main(void) {
        (foo<bar>)*(g++);
    }
    


実際に書いてみる:マージ

part1とpart2の流れを汲むと、次のように問題が実現可能であることがわかる。
ただし、Zipに論理値を引数に取るメンバ関数bazを追加する必要がある。


    class Zip{
    public:
        Zip& ding(void) {
            return *this;
        }
        void baz(bool) {
        }
    
        Zip& operator++(int) {
            return *this;
        }
        Zip& operator*(Zip&) {
            return *this;
        }
    } zip_, g;
    
    namespace qux {
        Zip *zip = &zip_;
    }
    
    using bar = Zip;
    template<typename T> T foo;
    
    int main(void) {
        ((foo<bar>)*(g++)).baz(!&qux::zip->ding());
    }
    

bazの中身が空だと少し悲しい。
せっかく引数を取っているのだから、華の出力部分を任せよう。


    #include <iostream>
    
    class Zip{
    public:
        Zip& ding(void) {
            return *this;
        }
        void baz(bool arg) {
            if(arg) std::cout << "Lispは神の言語\n";
        }
    
        Zip& operator++(int) {
            return *this;
        }
        Zip& operator*(Zip&) {
            return *this;
        }
    } zip_, g;
    
    namespace qux {
        Zip *zip = &zip_;
    }
    
    using bar = Zip;
    template<typename T> T foo;
    
    int main(void) {
        ((foo<bar>)*(g++)).baz(!&qux::zip->ding());
    }
    

いざ動かしてみると... 何も表示されない。
そりゃそうだ。bazの引数は絶対にfalseになるんだから。

結論

前述の式は、確かに記述可能であったし、警告も何もない。
ただし、有用に利用出来ることは断じてないだろう。

それでも俺は無理矢理動かす

bazの中で論理値を反転させてしまうのが早いのだが。
せっかくなのでオーバーロードを悪用してみる。


    #include <iostream>
    
    class FooBar {
    public:
        bool operator&(void) {
            return false;
        }
    } foobar;
    
    class Zip{
    public:
        FooBar& ding(void) {
            return foobar;
        }
        void baz(bool arg) {
            if(arg) std::cout << "Lispは神の言語\n";
        }
    
        Zip& operator++(int) {
            return *this;
        }
        Zip& operator*(Zip&) {
            return *this;
        }
    } zip_, g;
    
    namespace qux {
        Zip *zip = &zip_;
    }
    
    using bar = Zip;
    template<typename T> T foo;
    
    int main(void) {
        ((foo<bar>)*(g++)).baz(!&qux::zip->ding());
    }
    

これでLispを礼賛できるぞ!... C++への冒瀆によってだが。

*1:例えば、Lisper以外のことをクロマニヨン人と称し、同様にLisp以外の言語をクロマニヨン言語と呼んでいる。

*2:すべての構文がリスト。これ以上ないほどシンプルなシンタックスを持っている。

*3:main関数にreturn文が存在しないが、これはC++の規格上一切問題ない。C++では、main関数に限ってはreturn 0が補完される。ただ、非常に気持ち悪い。

*4:main関数内の式の値を用いていないからで、これはしょうがない。

Pythonのモジュールキャッシュ作成/使用条件を確認してみる

前書き

Pythonをいじっているとにょきにょき発生する__pychache__。
内部にはコンパイル済みモジュール.pycが格納される。

リファレンスには、次のような記述がある。

Python は2つの場合にキャッシュのチェックを行いません。ひとつは、コマンドラインから直接モジュールが読み込まれた場合で、常に再コンパイルされ、結果を保存することはありません。2つめは、ソース・モジュールのない場合で、キャッシュの確認を行いません。ソースのない (コンパイル済みのもののみの) 配布をサポートするには、コンパイル済みモジュールはソース・ディレクトリになくてはならず、ソース・ディレクトリにソース・モジュールがあってはいけません。

ちょっと変形すると、次のような感じだ。(引用表記してるけどかなり改変してるよ)

Pythonがキャッシュをチェックしない場合

  1. コマンドラインから直接モジュールが読み込まれた場合
    常に再コンパイルされ、結果を保存することはない。
  2. ソース・モジュールのない場合
    コンパイル済みモジュールはソース・ディレクトリになくてはならない。

これを実際に試してみるだけの記事。

Windows前提の記事だけど、Macユーザもなんとなくわかるよね?
(逆のことをMacユーザに言われたらキレる自信がある)

本文

次のように意味のないコードを書いた。これでも立派なモジュールである。


    print('hogee')
    

そしてディレクトリ階層は次のとおり。


    hoge_py/
      └ hoge.py
    

階層と呼ぶのもおこがましいが、ここらへんは明確にしないといけない。

コマンドラインから直接モジュールが読み込まれた場合        

つまりこういうこと。もちろん見やすいように成形はしてあるが。


    C:\...\hoge_py>dir
        2017/10/30  17:09    <dir>          .
        2017/10/30  17:09    <dir>          ..
        2017/10/30  17:20                14 hoge.py
                   
    C:\...\hoge_py>python hoge.py
        hogee
    
    C:\...\hoge_py>dir 
        2017/10/30  17:09    <dir>          . 
        2017/10/30  17:09    <dir>          .. 
        2017/10/30  17:20                14 hoge.py 
                       

見てわかるように、実行前後での変化はない。

逆に、コマンドラインから直接モジュールが読み込まれない場合とは?
こういうことである。もちろんこちらも成形済み。


    C:\...\hoge_py>dir
        2017/10/30  17:09    <dir>          .
        2017/10/30  17:09    <dir>          .. 
        2017/10/30  17:20                14 hoge.py
    
    C:\...\hoge_py>python
        >>> import hoge 
        hogee 
        >>> exit()
        
    C:\...\hoge_py>dir 
        2017/10/30  17:41    <dir>          .
        2017/10/30  17:41    <dir>          ..
        2017/10/30  17:20                14 hoge.py 
        2017/10/30  17:41    <dir>          __pycache__ 
        
    C:\Users\...\hoge_py>dir __pycache__ 
        2017/10/30  17:41    <dir>          .
        2017/10/30  17:41    <dir>          ..
        2017/10/30  17:41               151 hoge.cpython-36.pyc
        

確かにキャッシュファイルが作られているのがわかる。
また、ファイル名からコンパイル環境がわかるのも便利だ。

ソース・モジュールのない場合        

さきほどの実験の続き。hoge.pyを消してやると...


    C:\...\hoge_py>del hoge.py
    
    C:\...\hoge_py>dir
        2017/10/30  17:41    <dir>          .
        2017/10/30  17:41    <dir>          ..
        2017/10/30  17:41    <dir>          __pycache__

    C:\...\hoge_py>python
        >>> import hoge
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        ModuleNotFoundError: No module named 'hoge'
        >>> exit()
    
    C:\...\hoge_py>dir
        2017/10/30  17:41    <dir>          .
        2017/10/30  17:41    <dir>          ..
        2017/10/30  17:41    <dir>          __pycache__
        

当然ながら、hogeモジュールをインポートすることは出来ない。
ここで、先ほど生成されたキャッシュを次の手順で利用してみよう。

  1. キャッシュファイルをhoge_pyに移動する。
  2. キャッシュファイルの名前をhoge.pycにする。

このように、モジュールをインポート出来るようになる。


    C:\...\hoge_py>python
        >>> import hoge
        hogee
        

バージョンがファイル名に含まれる利点が失われているのが少々残念である。

なお、このときのディレクトリ階層は次のようになっている。


    hoge_py/
      └ hoge.pyc
    

...うん。階層と呼ぶのも難だが、説明を明確にするには必要なのだ。

 

おまけ - キャッシュを利用可能にするバッチ

最後は簡単な説明で済ませてしまったが、これをCUIで書くと次のようになる。


    rem rm *.py
    
    move .\__pycache__\* .\
    rd .\__pycache__ /q
    ren *.pyc ????????????????.pyc
                       

remはコメントアウトの意味だ。
一行目は完全に実装を隠したいデストロイヤー向けなのである。

なお、リネームに随分回りくどい方法を用いているのは理由がある。
説明するのが面倒なので、サンプルとリンクだけ置いておく。

上手くいきそうでいかない例


    C:\...\hoge_py\__pycache__>dir
        2017/10/30  20:03    <dir>          .
        2017/10/30  20:03    <dir>          ..
        2017/10/30  19:55               151 hoge.cpython-36.pyc

    C:\...\hoge_py\__pycache__>dir *.cpython-36.pyc
        2017/10/30  19:55               151 hoge.cpython-36.pyc

    C:\...\hoge_py\__pycache__>ren *.cpython-36.pyc *.pyc
    
    C:\...\hoge_py\__pycache__>dir
        2017/10/30  20:03    <dir>          .
        2017/10/30  20:03    <dir>          ..
        2017/10/30  19:55               151 hoge.cpython-36.pyc
        

とりあえず思い通りの結果が出る例


    C:\...\hoge_py>dir
        2017/10/30  20:03    <dir>          .
        2017/10/30  20:03    <dir>          ..
        2017/10/30  19:55               132 hoge.cpython-36.pyc
    
    C:\...\hoge_py>ren *.cpython-36.pyc ????????????????.pyc
    
    C:\...\hoge_py>dir
        2017/10/30  21:56    <dir>          .
        2017/10/30  21:56    <dir>          ..
        2017/10/30  19:55               132 hoge.pyc
        
12436288584_94d6bc46d2_b.jpg
コマンドプロンプトに慣れているなら当然のように使っていると思います。いまさら何の説明がいるのかというくらい基本的なものですけど、このコマンドが備えているほんの少しの便利な機能と、それを帳消しにするクソ仕様を知っていますか?

 

おまけ - ソースを置き換えるスクリプト

配布目的ならsetup.pyを書けばよいので、これはただの嫌がらせである。
むしゃくしゃしたときに実装をすべて隠蔽してやろう。


    import os, sys, glob, importlib, shutil
    from distutils import dir_util
    
    # 自分以外の.pyファイルをすべて取得
    py_files = glob.glob('*.py')
    py_files.remove(sys.argv[0])
    
    # 片っ端からインポートして消す
    for py_file in py_files:
        importlib.__import__(
            os.path.splitext(os.path.basename(py_file))[0]
        )
        os.remove(py_file)    
    
    # pycacheの中身を今のディレクトリに移す
    dir_util.copy_tree('./__pycache__', './')
    shutil.rmtree('./__pycache__')
    
    # pycの中身をリネーム
    pyc_files = glob.glob('*.pyc')
    for pyc_file in pyc_files:
        basename, ext = os.path.splitext(os.path.basename(pyc_file))  
        os.rename(pyc_file, basename.split('.')[0] + ext)
        

書き捨てなのでぐちゃぐちゃなコードだ。特にインポートの多さ。*1
とにかく、これを使うと次のように嫌がらせが出来る。


    C:\...\hoge_py>dir
        2017/10/30  21:46    <dir>          .
        2017/10/30  21:46    <dir>          ..
        2017/10/30  21:39                 0 fuga.py
        2017/10/30  21:39                 0 hoge.py
        2017/10/30  21:39                 0 piyo.py
        2017/10/30  21:42               726 replace_source_to_cache.py
    
    C:\...\hoge_py>python replace_source_to_cache.py
    
    C:\...\hoge_py>dir
        2017/10/30  21:46    <dir>          .
        2017/10/30  21:46    <dir>          ..
        2017/10/30  21:46               132 fuga.pyc
        2017/10/30  21:46               132 hoge.pyc
        2017/10/30  21:46               132 piyo.pyc
        2017/10/30  21:42               726 replace_source_to_cache.py
        

追記:組み込みモジュールpy_compileを使うと、もっと簡単に書ける。


    import py_compile
    py_compile.compile('hoge.py', cfile='hoge.pyc')
                       

.pycファイルの移動をサボれるだけだが。

*1:Python3.4以降ならpathlib使うべき

クソみたいなPythonワンライナーを書いてみた

前書き

あるときふと思った。クソコードを書きたい。

コードと動作

コードは次の通り。素因数を表示するものだ。
一応Python3だが、print関数をprint文に置き換えればPython2でも動作する。


    print((lambda f: (lambda n: f(f, [], n, 2)))(lambda f, r, n, i: r if n == 1 else (r.append(i) or f(f, r, n/i, i)) if n % i == 0 else f(f, r, n, i+1))(int(input())))     
    

実行とその結果は次のようである。


    >python -c "print((lambda f: (lambda n: f(f, [], n, 2)))(lambda f, r, n, i: r if n == 1 else (r.append(i) or f(f, r, n/i, i)) if n % i == 0 else f(f, r, n, i+1))(int(input())))"     
    100
    [2, 2, 5, 5]   
    

我ながら満足のクソっぷりである。
改善したい点を挙げるとしたら、リストを使いまわしているところだろうか。
ラムダ式を上手くジェネレータのように活用できないだろうか。

一応解説

解説というか、見やすく分解しただけだが。


    print(
        # 再帰可能にする工夫と、高階関数としての役割
        (lambda func: 
            (lambda num:
                func(func, [], num, 2)
            )
        )
        # 素因数分解をつかさどる部分
        (lambda func, result, num, i:
            # 終了条件と返り値
            result if num == 1
            # i が num の約数のとき 
            else (result.append(i) or func(func, result, num/i, i)) if num % i == 0    
            # i が num の約数でないとき
            else                      func(func, result, num, i+1)
        )
        # 実際に関数に与える引数
        (int(input()))
    )  
    

ワンラインの拘りなしにこれを書くとこのようになる。


    def higher_order(func):
        return lambda num: func(func, [], num, 2)    
    
    @higher_order
    def ret_factor(func, result, num, i):
        if num == 1:
            return result
    
        if num % i == 0:
            result.append(i)
            return func(func, result, num/i, i)
        else:
            return func(func, result, num, i+1)
    
    print(
        ret_factor(int(input()))
    )
    

そもそもラムダ式がわからん。デコレータも知らん。という方はこちら。


    def ret_factor(func, result, num, i):
        if num == 1:
            return result
    
        if num % i == 0:
            result.append(i)
            return func(func, result, num/i, i)
        else:
            return func(func, result, num, i+1)
    
    print(
        ret_factor(func=ret_factor, result=[], num=int(input()), i=2)    
    )
    

普通に書くなら

ラムダ式での再帰のために、第一引数に自身を取る冗長な構造にしている。
こちらのページが非常に参考になった。

普通に書くなら、次のようになる。


    def ret_factor(result, num, i):
        if num == 1:
            return result
    
        if num % i == 0:
            result.append(i)
            return ret_factor(result, num/i, i)    
        else:
            return ret_factor(result, num, i+1)
    

また、リストに値を格納して最後に返すのは、大抵無駄だ。
このようなときには、ジェネレータを使うべきである。


    def ret_factor(num, i):
        if num == 1:
            return
    
        if num % i == 0:
            yield i
            yield from ret_factor(num/i, i)
        else:
            yield from ret_factor(num, i+1)    
    

本当はこれをワンラインにしたいのだが...
いかんせん終了時の処理を上手く表現できないのだ。

その後の足掻き


    print(
        list(
            # 再帰可能にする工夫と、高階関数としての役割
            (lambda func: 
                (lambda num:
                    func(func, num, 2)
                )
            )
            # 素因数分解をつかさどる部分
            (lambda func, num, i:
                # 終了条件
                None if num == 1
                # i が num の約数のとき 
                else ((yield i), (yield from func(func, num/i, i))) if num % i == 0    
                # i が num の約数でないとき
                else             (yield from func(func, num, i+1))
            )
            # 実際に関数に与える引数
            (int(input()))
        )
    )
    

このようにしたら出来た。達成感より先に気持ち悪さがあるが。
これはつまり、次のように書けるということなのだ...


    >>> def func(num):
    ...     ((yield num), (yield from func(num-1))) if num > 0 else None    
    ...
    >>> list(func(10))
    [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
    

なんとも恐ろしい。

さらに改造

例えば、次のように改造できる。


    print(
        (lambda max: [m for m in range(2, max) if len(
            # 再帰可能にする工夫と、高階関数としての役割
            (lambda func: 
                (lambda num:
                    func(func, [], num, 2)
                )
            )
            # 素因数分解をつかさどる部分
            (lambda func, result, num, i:
                # 終了条件と返り値
                result if num == 1
                # i が num の約数のとき 
                else (result.append(i) or func(func, result, num/i, i)) if num % i == 0    
                # i が num の約数でないとき
                else                      func(func, result, num, i+1)
            )
            # 実際に関数に与える引数
            (m)
        # 約数(1を除く)が1つだったら素数
        ) == 1])
        # 求める素数の上限
        (int(input()))
    ) 
    

改行と無駄な空白を潰すとこんな感じ。


    print((lambda max: [m for m in range(2, max) if len((lambda func: (lambda num: func(func, [], num, 2)))(lambda func, result, num, i: result if num == 1 else (result.append(i) or func(func, result, num/i, i)) if num % i == 0 else func(func, result, num, i+1))(m)) == 1])(int(input())))    
    

入力された数値未満の素数リストを出力するワンライナーだ。
しかし、1000ほどの大きな値を入れるとRecursionErrorで落ちる。
末尾再帰最適化で解消できるはずだが、まあそこまではしなくてよいだろう。

一応参考までに。
さすがにこれも込みでワンライナーにするのは骨だ。

 

晒してみた

ImportError: cannot import nameにいじめられた話

前書き

ImportErrorの中で、頻出し、即座に解消出来るのはこのようなものかと思う。


>>> import hoge Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named 'hoge'

おおかたモジュール名を間違えたか、階層が異なるかのいずれかだろう。

 

しかし、今回私が遭遇したのは次のようなエラーだった。


>>> import sys >>> sys.path.append('hoge/') >>> from fuga import piyo Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: cannot import name 'piyo'

私はミスタイプもしていないし、何度確認しても階層hoge/fuga/piyoであった。

いったいなぜエラーが出たのか?どのようにして解決できるのか?

(熟練したPythonistaにとってはどっちもつまらないエラーかもしれないが) 

 

具体的なコードと不可解な解決

「多分一般的なエラーだよ」と言いたいがために、hogeとかを使ってみた。 

...が。もし一般的じゃなかったら恥ずかしいので、実際のコードを貼っておく。


>>> import sys >>> sys.path.append('C:/[中略]/TensorFlow/models/') >>> from object_detection.protos import hyperparams_pb2 Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: cannot import name 'hyperparams_pb2'

こんなコードだ。また、次のように書いたらエラーは解消された。


>>> import sys >>> sys.path.append('C:/[中略]/TensorFlow/') >>> from models.object_detection.protos import hyperparams_pb2

ダメ元で試してみたのに成功してしまったパターンの典型例である。

しかし、「よくわからんが解決」というのが一番気に食わないので粘った*1

 

根本的原因 (推測だけど)

piyo.__path__を使えば、importしたパッケージpiyoのパスを知ることが出来る。

これを用いて、パッケージが本当はどこにあるのか調査してみよう。


# 上手くいくとき >>> import sys >>> sys.path.append('C:/[中略]/TensorFlow/') >>> from models import object_detection >>> object_detection.__path__ ['C:/[中略]\\models\\object_detection]

# 上手くいかないとき >>> import sys >>> sys.path.append('C:/[中略]/TensorFlow/models/') >>> import object_detection >>> object_detection.__path__ ['C:\\[中略]\\object_detection-0.1-py3.5.egg\\object_detection']

どうやら参照先が異なっているようだ。

このミスリードを招いているディレクトリを覗いてみたいと思う。

 

eggに関する解説は読み飛ばしてしまったが、その本質はzipであるらしい。

拡張子をzipにリネームすると、普通に解凍することが出来た。

そして肝心の中身がこちら。

f:id:LouiS:20170701142351p:plain

どうやら、前なんとなくビルドしたデータがゴミになって残っていたらしい。

 

後書き

インポート系のエラーに遭遇したときは、その実態を覗いてみるとよさそうだ。

また、当たり前のことなのかもしれないが...

なんとなくビルドするのはやめよう

クソみたいな結論に至ってしまった。

 

追記:2017/07/05

なんか、タイムリーに似たような情報があがっていた。

*1:こういうのを許容できないから作業が遅い

TensorFlowのrestoreで、NotFoundErrorにいじめられた話

前書き

TensorFlowで学習済みモデルを使って遊ぼうとしたときに躓いたのでメモ。

ここに学習済みパラメータと、その他情報が置いてある。

 

生じた問題

次のコードで問題が生じた。


    import tensorflow as tf
    from tensorflow.contrib import slim
    from tensorflow.contrib.slim.nets import resnet_v1

    #
    # Construct Network
    inputs = tf.placeholder(tf.float32, shape=[None, 224, 224, 3]) 
    net, end_points = resnet_v1.resnet_v1_50(inputs=inputs)

    #
    # Read ckpt File
    ckpt_filename = "C:/[中略]/resnet_v1_50.ckpt"

    with tf.Session() as sess:
        saver = tf.train.Saver()
        saver.restore(sess, ckpt_filename)
    

 

エラーは次の通りである。

tensorflow.python.framework.errors_impl.NotFoundError: Tensor name "resnet_v1_50/conv1/biases" not found in checkpoint files C:/[中略]/resnet_v1_50.ckpt
NotFoundError (see above for traceback): Tensor name "resnet_v1_50/conv1/biases" not found in checkpoint files C:/[中略]/resnet_v1_50.ckpt

かなり省略してあるが、結局必要なデータが見つからんというエラーである。

なお、上記エラーが発生したときも、ckptファイル自体は読み込めている。

 

かなりの人が躓いているらしく、ネット上でも悲痛な声が散見された。

 

エラーの原因

Githubのイシューを漁った結果、次のようなスレッドが見つかった。

sugada氏のコード片を見る限り、arg_scopeが解決の鍵のようだ。

 

arg_scopeはcontrib/framework/python/ops/arg_scope.pyで定義されていて、

Allows one to define models much more compactly by eliminating boilerplate code. This is accomplished through the use of argument scoping (arg_scope).

と書かれている。(たぶん)モジュール性を高めるための工夫らしい。

 

解決した方法

さきほどのコードを、次のように改変する。


    import tensorflow as tf
    from tensorflow.contrib import slim
    from tensorflow.contrib.slim.nets import resnet_v1
    
    #
    # Construct Network
    inputs = tf.placeholder(tf.float32, shape=[None, 224, 224, 3])
    
    arg_scope = resnet_v1.resnet_arg_scope()
    with slim.arg_scope(arg_scope):
        net, end_points = resnet_v1.resnet_v1_50(inputs=inputs)
    
    #
    # Read ckpt File
    ckpt_filename = "C:/[中略]/resnet_v1_50.ckpt"
    
    with tf.Session() as sess:
        saver = tf.train.Saver()
        saver.restore(sess, ckpt_filename)

ネットワークモデルを読み取る際に、このような工夫が必要だった。

 

ここではResNetを用いたが、他のモデルも同様である。

実際のソースコードを見ると、それっぽい関数はすぐ見つかる。

 

追記 (2017/07/19)

次のページに、この記事へのリンクが張られていた。

TensorFlowのセッション内でrestoreする際に起こるエラーについて | teratail

ここで『どうも、管理人です』と颯爽と回答できればかっこよかったなぁ...

結局、よくわからないままに解消できたらしい。

Jupyter上でやっているのですが、全てを1つのセルにまとめて実行していたことが原因のようでした。

私もJupyterにいじめられたことがあるので... 苦手意識が更に高まる。

TensorFlow/models/Mask R-CNNのクラス構造と関数の引数

Pull Request #1561によるコードとコメントをまとめたもの。

Apache Licenseなので、問題はない... はず。

 

MaskRCNNBoxPredictor (BoxPredictor)
Mask R-CNN Box Predictor. See Mask R-CNN: He, K., Gkioxari, G., Dollar, P., & Girshick, R. (2017). Mask R-CNN. arXiv preprint arXiv:1703.06870.
This is used for the second stage of the Mask R-CNN detector where proposals cropped from an image are arranged along the batch dimension of the input image_features tensor. Notice that locations are *not* shared across classes, thus for each anchor, a separate prediction is made for each class. In addition to predicting boxes and classes, optionally this class allows predicting masks and/or keypoints inside detection boxes. Currently this box predictor makes per-class predictions; that is, each anchor makes a separate box prediction for each class.

  • __init__
    • self
    • is_training
      Indicates whether the BoxPredictor is in training mode.
    • num_classes
      Number of classes. Note that num_classes *does not* include the background category, so if groundtruth labels take values in {0, 1, .., K-1}, num_classes=K (and not K+1, even though the assigned classification targets can range from {0,... K}).
    • fc_hyperparams
      Slim arg_scope with hyperparameters for fully connected ops.
    • use_dropout
      Option to use dropout or not. Note that a single dropout op is applied here prior to both box and class predictions, which stands in contrast to the ConvolutionalBoxPredictor below.
    • dropout_keep_prob
      Keep probability for dropout. This is only used if use_dropout is True.
    • box_code_size
      Size of encoding for each box.
    • conv_hyperparams=None
      Slim arg_scope with hyperparameters for convolution ops.
    • predict_instance_masks=False
      Whether to predict object masks inside detection boxes.
    • mask_prediction_conv_depth=256
      コメントなし
    • predict_keypoints=False
      Whether to predict keypoints inside detection boxes.
  • num_classes (@property)
    • self
  • _predict
    Computes encoded object locations and corresponding confidences. Flattens image_features and applies fully connected ops (with no non-linearity) to predict box encodings and class predictions. In this setting, anchors are not spatially arranged in any way and are assumed to have been folded into the batch dimension. Thus we output 1 for the anchors dimension.
    • self
    • image_features
      A float tensor of shape [batch_size, height, width, channels] containing features for a batch of images.
    • num_predictions_per_location
      An integer representing the number of box predictions to be made per spatial location in the feature map. Currently, this must be set to 1, or an error will be raised.
    • Return Values
      A dictionary containing the following tensors.
      • box_encodings
        [batch_size, 1, num_classes, code_size] representing the location of the objects.
      • class_predications_with_background
        [batch_size, 1, num_classes + 1] representing the class predictions for the proposals.
      • instance_masks (When predict_mask is True)
        A float tensor of shape [batch_size, 1, num_classes, image_height, image_width]
      • keypoints (When predict_keypoints is True)
        [batch_size, 1, num_keypoints, 2]

SSD-TensorFlowをWindows10で試してみる

前書き

そろそろ物体検出のアルゴリズムを試してみないと、と思い立った。*1

使ってみるのはSSDと呼ばれるもの。ぶっちゃけ内容は理解していない。

 

SlideShareにも参考になりそうなスライドがある。

 

環境

  • Windows10 Home x64
  • Miniconda 4.3.21
  • CUDA 8.0
  • PyCharm 171.4424.42

Pythonのパッケージは下記のとおり。

 

実験用のPython仮想環境を用意

今までと同様、Minicondaを用いてpython3.5環境を用意した。

とりあえず導入したパッケージとバージョンなどは次のとおり。

numpy 1.13.0 py35_0
scipy 0.19.0 np113py35_0
scikit-learn 0.18.1 np113py35_1
matplotlib 2.0.2 np113py35_0
pandas 0.20.2 np113py35_0

ここらへんはすべて、conda install で叩き込める。

 

後になって必要になったパッケージとバージョンは次のとおり。

opencv-python 3.2.0.7 <pip>
pillow 4.1.1 py35_0
jupyter 1.0.0 py35_3

OpenCVはpip*2、それ以外はcondaでインストールできる。

 

これに加えて、TensorFlowも当然導入する。

公式サイトに従うと、導入コマンドは次のとおり(GPU版)*3


pip install --ignore-installed --upgrade https://storage.googleapis.com/tensorflow/windows/gpu/tensorflow_gpu-1.1.0-cp35-cp35m-win_amd64.whl

 

とりあえず動かそうとしてドツボにはまる

見出しから落ちは見えているが、とりあえず失敗談 (=大事な経験) をば。

 

今回動かしてみる実装は以下のとおり。

READMEによると、ダウンロード後にssd_300_vgg.ckpt.zipを解凍すれば、

notebookディレクトリのssd_notebook.ipynbで動作確認できるらしい。

 

PythonもDNNもろくにいじったことがなく、これらが何か調べる必要があった。

ssd_300_vgg.ckpt
TensorFlowで学習済みのパラメータのことらしい。
解凍すると .index と .data-00000-of-00001 が出てきた。

ssd_notebook.ipynb
Jupyter notebookで用いるファイル形式らしい。
Pythonのコードの中でMarkDownコメントを使えて便利。
...結果的にはこいつにはめられた。

READMEに従って.ckptを解凍し、PyCharmでプロジェクトファイルを開く。

[TerminalIPythonApp] WARNING | Subcommand `ipython notebook` is deprecated and will be removed in future versions.
[TerminalIPythonApp] WARNING | You likely want to use `jupyter notebook` in the future

いきなり怒られた。

ipythonから改名したんだよ!と主張しているだけなので、これは無視する。

 

In[6]で怒涛のワーニング & Tracebackが発生した。以下抜粋。

2017-06-09 19:05:48.187022: W c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\framework\op_kernel.cc:1152] Invalid argument: Unsuccessful Tenso[IPKernelApp] ERROR | Exception in message handler:
tensorflow.python.framework.errors_impl.InvalidArgumentError: Unsuccessful TensorSliceReader constructor: Failed to get matching files on ../checkpoints/ssd_300_vgg.ckpt: Not found: FindFirstFile failed for: ../checkpoints : \udc8ew\udc92\u80b3\udc82\ua0bd\udc83p\udc83X\udc82\udcaa\udc8c\udca9\udc82\x82\udca9\udc82\udce8\udc82\u0702\udcb9\udc82\udcf1\udc81B
tensorflow.python.framework.errors_impl.InvalidArgumentError: Unsuccessful TensorSliceReader constructor: Failed to get matching files on ../checkpoints/ssd_300_vgg.ckpt: Not found: FindFirstFile failed for: ../checkpoints : \udc8ew\udc92\u80b3\udc82\ua0bd\udc83p\udc83X\udc82\udcaa\udc8c\udca9\udc82\x82\udca9\udc82\udce8\udc82\u0702\udcb9\udc82\udcf1\udc81B
InvalidArgumentError (see above for traceback): Unsuccessful TensorSliceReader constructor: Failed to get matching files on ../checkpoints/ssd_300_vgg.ckpt: Not found: FindFirstFile failed for: ../checkpoints : \udc8ew\udc92\u80b3\udc82\ua0bd\udc83p\udc83X\udc82\udcaa\udc8c\udca9\udc82\x82\udca9\udc82\udce8\udc82\u0702\udcb9\udc82\udcf1\udc81B
UnicodeEncodeError: 'utf-8' codec can't encode character '\udc8e' in position 174: surrogates not allowed
UnicodeEncodeError: 'utf-8' codec can't encode character '\udc8e' in position 3281: surrogates not allowed

どうやら、一番最初のWarningに全てのエラーが引きずられているようだ。

Jupyter Notebookをブラウザで開いてみると...

さっき解凍した.index と .data-00000-of-00001がアップロードされていない!

f:id:LouiS:20170610114559p:plain

 

ここでの解決篇: .ipynbを.pyに変換する

もっとスマートな方法がある気もするが、ここでは.ipynbに別れを告げる。

.ipynbのmd形式*4で書かれている部分を消してやれば.pyと同じだ。

ただ、%matplotlib inline を消してやる必要がある。

 

実は相対パスをすべて絶対パスに置き換えればなんとでもなるっちゃなる。

しかし、新たな非常に厄介な問題に遭遇したのでここでは避ける。

 

実行結果(の代わり)

実行結果を撮って置くのを忘れ、しかしまたリポジトリを落とす気にもなれず。

ネットから持ってきた画像を貼る。(当たり前だが)同じ結果だった。

f:id:LouiS:20170625004743p:plain

 

もしかして

上記のサイトによると、もっとスマートに.ipynb.pyに変換できるようだ。


    > jupyter nbconvert --to python ssd_notebook.ipynb
    

ももうjupyterなんてシラネ。

*1:この記事は二週間前から下書きに埋もれていたので、時系列的に内容は古い

*2:野良ビルドを用いることは可能

*3:必要に応じて、CUDAなどの環境を整える必要がある。

*4:Mark Down形式

TensorFlowの学習済みモデルをいじってみる(1)

能書き

TensorFlowの学習済みモデルで遊ぼうと思ったら、いろいろとハマった。

同じようにハマることもあるだろう、記録は残しておいた方がよい。

 

環境

  • Windows 10 64bit
  • PyCharm 2017.1.3
  • Miniconda 3.6
  • Python 3.5
  • Tensorflow-gpu 1.1.0 <pip>

 

チュートリアルの実行

object_detectionのチュートリアルを試してみる。

 

私は実行ファイルを移動させてしまったので、sys.pathをいじる必要があった。

元のコードを次のように書き換える。


    # sys.path.append("..")
    LIBRARY_PATH = '[ダウンロード先のパス]/models'
    sys.path.append(LIBRARY_PATH)
    sys.path.append(LIBRARY_PATH + '/object_detection')

さらに、コードに散らばる相対パス絶対パスに置き換えていく。

これを怠ると、 次のようなメッセージを含んだエラーが吐かれる。

tensorflow.python.framework.errors_impl.NotFoundError: NewRandomAccessFile failed to Create/Open: data\mscoco_label_map.pbtxt : \udc8ew\udc92?\udc82?\udc83p\udc83X\udc82\udcaa\udc8c\udca9\udc82?\udca9\udc82\udce8\udc82?\udcb9\udc82\udcf1\udc81B
FileNotFoundError: [Errno 2] No such file or directory: 'test_images\\image1.jpg'

二回出たエラーを繋げて書いてみた。両方同時に出るわけではない。

 

パス問題を解消したが... 今度は次のエラーに泣かされた。

Traceback (most recent call last):
File "[LIBRARY_PATH]/models/ObjectDetection/objTest.py", line 18, in <module>
from utils import label_map_util
File "[LIBRARY_PATH]/models/object_detection\utils\label_map_util.py", line 22, in <module>
from object_detection.protos import string_int_label_map_pb2
ImportError: cannot import name 'string_int_label_map_pb2'

ディレクトリを見ると、.protoファイルはあっても.pyファイルがない。

 

.protoファイルから、.pyファイルを生成しなければいけないようだ。

 

このページの最新の圧縮ファイルのうち、win32.zipとついているものを落とす。

win64版が見当たらずにしばらく探したが、結局win32で妥協した。

Windows以外のOSを用いている場合、自力でビルドする必要があるらしい。

 

解凍して得られるprotoc.exeをmodelsに移し、次のコマンドをたたく。


    > protoc object_detection/protos/*.proto --python_out=.
    

そうすると、確かに.pyファイルがディレクトリに追加されていた。

 

実行結果

f:id:LouiS:20170624175046j:plain

もう一つ、海辺の画像が表示されるが、そのページは有名なので端折る。

OpenCVのクラスの前方宣言をまとめてみる

 次のサイトがものすごい参考になる。

 

cv::Mat


    namespace cv {
        class Mat;
    }

cv::Point


    namespace cv {
        template< typename > class Point_;
        typedef Point_< int > Point;
    }

cv::Range


    namespace cv {
        class Range;
    }

cv::Rect


    namespace cv {
        template< typename > class Rect_;
        typedef Rect_< int > Rect;
    }

cv::Scalar


    namespace cv {
        template< typename > class Scalar_;
        typedef Scalar_< double > Scalar;
    }

cv::Size


    namespace cv {
        template< typename > class Size_;
        typedef Size_< int > Size;
    }

cv::String


    namespace cv {
        class String;
    }

 

間違いのご指摘、(答えるとは限りませんが)リクエストある場合はご連絡下さい。

本稿はOpenCV3.1のソースを基に書いています。

Condaを使ってTensorFlowの野良ビルドを導入する

前書き

前回の記事の続きっちゃ続き。環境は、Windows10の64bit機だ。

ただし、この記事ではCPU版のインストールだけ実践する。

 

TensorFlowは、深層学習によく用いられるフレームワークである。

公式サイトは、次のように説明している。

TensorFlow™ is an open source software library for numerical computation using data flow graphs.

使いづらいと揶揄されるが、あくまで本質は計算ライブラリのようだ。

 

TensorFlowのインストール (公式による)

公式サイトが簡潔なインフォメーションを出している。

適当にまとめてみる。(稚拙な和訳/強引な意訳)

  • CPUのみをサポートするものと、GPUも利用するものとの二種類がある
  • GPUが利用できる環境は次の通り
    • CUDA Toolkit 8.0が入っていて、パスが通っている
    • 上記CUDAに対応するNVIDIAドライバが搭載されている
    • cuDNN v5.1が入っていて、パスが通っている
    • GPUのCompute Capabilityが3.0以上である
  • GPUが利用できる場合も、まずはCPU版を試してみることを勧める

 

  • インストールの際は、次の二通りの方法がある
    • "native" pipを利用するもの (推奨)
      • pip3 install --upgrade tensorflow
      • pip3 install --upgrade tensorflow-gpu
    • Anacondaを利用するもの (公式のサポート外)
      • pip install --ignore-installed --upgrade https://storage.googleapis.com/tensorflow/windows/cpu/tensorflow-1.1.0-cp35-cp35m-win_amd64.whl
      • pip install --ignore-installed --upgrade https://storage.googleapis.com/tensorflow/windows/gpu/tensorflow_gpu-1.1.0-cp35-cp35m-win_amd64.whl

 

このインフォは親切だが、condaを用いたインストールは出来ないのだろうか?

 

TensorFlowのインストール (condaを用いる)

予め確認しておくが、この方法は公式に保証されたものではない。

こだわりなくインストールしたいのなら、native pipを用いるのが確実だ。

 

野良ビルドリポジトリは、Anaconda Cloudで検索できる*1

適合する条件をちょいちょい入力すると、次の結果が表示された。

f:id:LouiS:20170606164033p:plain

投稿現在(2017/06)、4件のリポジトリが表示される。リンクはこちら

 

この中で最もダウンロード数の多い、一番上の候補を選んだ。

緑色のリンクをクリックすると、インストールコマンドが表示される。

conda install -c conda-forge tensorflow=1.1.0

こいつをAnacondaプロンプトに叩き込んでやればよい。

 

以下、実際のプロンプト画面。

前もって、Python3.5の仮想環境をactivateしていることに注意されたい。

f:id:LouiS:20170606165401p:plain

 

PyCharmで試してみる

公式インフォQiitaの投稿を参考に、次のテストコードを実行してみた。

 

ほぼほぼパクリだが、先頭二行のコードで警告レベルを変更した。

当初この二行を除いて実行した結果、怒涛のWarningに襲われたからである。

C:\Users\[UserName]\AppData\Local\conda\conda\envs\python35\python.exe C:/Users/[UserName]/temp/py35test/test.py
2017-06-06 17:18:42.845745: W c:\tf_jenkins\home\workspace\release-win\device\cpu\os\windows\tensorflow\core\platform\cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE instructions, but these are available on your machine and could speed up CPU computations.
2017-06-06 17:18:42.863110: W c:\tf_jenkins\home\workspace\release-win\device\cpu\os\windows\tensorflow\core\platform\cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE2 instructions, but these are available on your machine and could speed up CPU computations.
2017-06-06 17:18:42.863465: W c:\tf_jenkins\home\workspace\release-win\device\cpu\os\windows\tensorflow\core\platform\cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE3 instructions, but these are available on your machine and could speed up CPU computations.
2017-06-06 17:18:42.863802: W c:\tf_jenkins\home\workspace\release-win\device\cpu\os\windows\tensorflow\core\platform\cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.1 instructions, but these are available on your machine and could speed up CPU computations.
2017-06-06 17:18:42.864145: W c:\tf_jenkins\home\workspace\release-win\device\cpu\os\windows\tensorflow\core\platform\cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.2 instructions, but these are available on your machine and could speed up CPU computations.
2017-06-06 17:18:42.864488: W c:\tf_jenkins\home\workspace\release-win\device\cpu\os\windows\tensorflow\core\platform\cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX instructions, but these are available on your machine and could speed up CPU computations.
2017-06-06 17:18:42.864843: W c:\tf_jenkins\home\workspace\release-win\device\cpu\os\windows\tensorflow\core\platform\cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX2 instructions, but these are available on your machine and could speed up CPU computations.
2017-06-06 17:18:42.865197: W c:\tf_jenkins\home\workspace\release-win\device\cpu\os\windows\tensorflow\core\platform\cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use FMA instructions, but these are available on your machine and could speed up CPU computations.
b'Hello, TensorFlow!'
32
Process finished with exit code 0

適切に実行されているようであるが、警告がなんにせようざったい。

 

適宜検索検討して、これらの警告をにぎりつぶすことに決めた。

  1. これらの警告は、CPUの拡張命令をフルに活かせていないことを指摘するもので、自前でビルドしなおすことで解消される。

  2.  ビルドしなおすと確かに速くなるが、その効果は限定的で、それだったらGPUを使える環境を整える方が圧倒的に理にかなっている。

  3. 頑張ってCPUの実行速度を最適化しても、GPUの実行速度には影響しない。(GPUの方が圧倒的に貢献度が高い状況だと、CPUに合わせてチューニングしても意味が薄い)

 

警告レベルを変えれば、(当たり前だが)表面上はすっきり実行される。

C:\Users\[UserName]\AppData\Local\conda\conda\envs\python35\python.exe C:/Users/[UserName]/temp/py35test/test.py
b'Hello, TensorFlow!'
32

Process finished with exit code 0

 

せっかくなので

せっかくなのでKerasも叩き込んだ。

conda install -c conda-forge keras=2.0.2

f:id:LouiS:20170607162451p:plain

まだ試していないが。

 

後書き

上記の例では、TensowFlowの機能の1%も使えていないだろう。

GitHubからTensorFlowを活用したリポジトリを落として、解析しなければ。

なお、ボトムアップ的(?)な学習には、以下の公式ページが参考になりそうだ。

あと、GPU試す。これ大事。

*1:サインインしなくても普通に使える