需要のないページ

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

OpenCVでイメージウィンドウを閉じられるようにする

能書き

OpenCVでイメージを表示した際に、面倒な落とし穴がある。
クローズボタンを押してしまうと処理がハングアップする場合があることだ。

上記回答ではプロパティを確認する方法が紹介されている。
ただ、毎度似たような処理を書くのは面倒に思える。

記事の目的
  • ウィンドウの可視性プロパティを取得する際の挙動を追うこと
  • もっと簡単かつ安全にウィンドウを表示する方法がないか検討すること

ドキュメントにはどのように書いてある?

この手の問題は普通はドキュメントに明記してあるものだ。

12436288584_94d6bc46d2_b.jpg
Provides parameters of a window. The function getWindowProperty returns properties of a window.
Parameters ...
See also setWindowProperty

書いてねぇ。
setWindowPropertyにもWindowPropertyFlagsにも目的の情報はなかった。

仕方ないので実装を覗く

cv::getWindowProperty @highgui/src/window.cpp#L90
cvGetWindowPropertyに主だった処理を委託している。

cvGetWindowProperty @highgui/src/window.cpp#L236
関連した部分を引用する。


  /* return -1 if error */
  CV_IMPL double cvGetWindowProperty(const char* name, int prop_id)
  {
      if (!name)
          return -1;
  
      switch(prop_id)
      {
      ...
  
      case CV_WND_PROP_VISIBLE:
          #if defined (HAVE_QT)
              return cvGetPropVisible_QT(name);
          #else
              return -1;
          #endif
      break;
      default:
          return -1;
      }
  }
    

Qtに委託しているのか。そういうことをドキュメントに書いておけよ。

cvGetPropVisible_QT @highgui/src/window_QT.cpp#L141


  double cvGetPropVisible_QT(const char* name) {
      if (!guiMainThread)
          CV_Error( CV_StsNullPtr, "NULL guiReceiver (please create a window)" );
  
      double result = 0;
  
      QMetaObject::invokeMethod(guiMainThread,
          "getWindowVisible",
          autoBlockingConnection(),
          Q_RETURN_ARG(double, result),
          Q_ARG(QString, QString(name)));
  
      return result;
  }
    

まずGUIスレッドが起動していないといけないようだ。
こいつについても調査したのだが、話がそれるのでここでは省略する。*1

少なくとも画像を表示しているときはスレッドは起動している。
よって例外が生じたときは『画像は閉じられている』と認識しても良い。

次に調査すべきは getWindowVisible だ。*2

GuiReceiver::getWindowVisible @highgui/src/window_Qt.cpp#L924
このコードは随分シンプルだ。


  double GuiReceiver::getWindowVisible(QString name)
  {
      QPointer w = icvFindWindowByName(name);
  
      if (!w)
          return 0;
  
      return (double) w->isVisible();
  }
    

ここで isVisible の正体は QWidget::isVisible で、返り値は当然boolである。

可視性を調べたときの挙動まとめ

  • バックエンドがQtでなく、調査不能である際は -1 を返す。
  • guiMainThreadが起動されていないときは、CVError を投げる。
  • 該当する名前のウィンドウがないときは、0 を返す。
  • 該当する名前のウィンドウがある場合
    • 開いているときは 1 を返す。
    • 閉じているときは 0 を返す。

使いやすいラッパー関数を考える

単純に返り値や例外を制御するだけならば、次のようになるだろう。


  import cv2
  
  BackendError = type('BackendError', (Exception,), {})
  def is_visible(winname):
      try:
          ret = cv2.getWindowProperty(
              winname, cv2.WND_PROP_VISIBLE
          )
  
          if ret == -1:
              raise BackendError('Use Qt as backend to check whether window is visible or not.')
  
          return bool(ret)
  
      except cv2.error:
          return False
    

動作を確認するなかで、次の事実に気付いた。

  • 明示的にデストロイしないと、ウィンドウは『見える』扱い。
  • namedWindowを呼び出しただけでも『見える』扱い。

よって、is_visibleを単体で使うメリットは薄い。人間の直感に反する。

本当に使いやすいラッパー関数を考える

結局imshowをラップするのが分かりやすいのだろう。


  import cv2
  
  BackendError = type('BackendError', (Exception,), {})
  def _is_visible(winname):
      try:
          ret = cv2.getWindowProperty(
              winname, cv2.WND_PROP_VISIBLE
          )
  
          if ret == -1:
              raise BackendError('Use Qt as backend to check whether window is visible or not.')
  
          return bool(ret)
  
      except cv2.error:
          return False
  
  
  ORD_ESCAPE = 0x1b
  def closeable_imshow(winname, img, *, break_key=ORD_ESCAPE):
      while True:
          cv2.imshow(winname, img)
          key = cv2.waitKey(10)
  
          if key == break_key:
              break
          if not _is_visible(winname):
              break
      
      cv2.destroyWindow(winname)
    

良い感じ。

成果物

ブレークするための条件をもうちょっと詳細に設定できるようにしてみた。

  • closeable_imshow(winname, img, *, break_keycode=0x1b)
  • Parameters
    • winname: str
    • img: numpy.ndarray
    • break_keycode (optional keyword)

      ウィンドウを閉じるためのアスキーコードを指定する。コードの指定方法は整数値でも文字でも良いが、リストやタプルを用いて複数指定する場合はアスキーコードでなくてはならない。また'all'(全てのキー入力に反応)や'nothing'(全てのキータイプを無視)を指定することも出来る。

  • Returns
    • None
  • Raises
    • BackendError
      調査に充分なバックエンド(Qt)がないとき
    • ValueError
      break_keycodeを文字列で指定したが、その構成が誤っているとき
    • TypeError
      break_keycodeの指定が誤っているとき

ソースコードはGistに上げておいた。cv_util.py · GitHub

*1:後日記事に起こすかもしれないし、そうでないかもしれない。

*2:getWindowVisibleを励起する記法はQt独特だが、結局ユーザから見た動作は result = guiMainThread.getWindowVisible(name); と同じだ。

Pythonを起動しただけでUnicodeDecodeError

能書き

表題のとおり。
何の気なしに対話環境を起動したら、次のようなエラーに見舞われた。


    >python
    Python 3.6.2 |Continuum Analytics, Inc.| (default, Jul 20 2017, 12:30:02) [MSC v.1900 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.

    Failed calling sys.__interactivehook__
    Traceback (most recent call last):
      File "C:\...\site.py", line 418, in register_readline
        readline.read_history_file(history)
      File "C:\...\rlmain.py", line 166, in read_history_file
        self.mode._history.read_history_file(filename)
      File "C:\...\history.py", line 82, in read_history_file
        for line in open(filename, 'r'):
    UnicodeDecodeError: 'cp932' codec can't decode byte 0x99 in position 2111: illegal multibyte sequence

    

なにこれ?

解消

どうやら C:\Users\MyName\.python_history が影響しているようだ。
中身を覗いてみたところ、本当にただのヒストリだったので消してやった*1

直前に minty で遊んでいたのが原因な気がする。

.python_historyって?

12436288584_94d6bc46d2_b.jpg
デフォルトの設定ではあなたのユーザーディレクトリの .python_history という名前のファイルに履歴を保存します。 履歴は次の対話的なインタプリタのセッションで再び利用することができます。

普通に使う分には気にしなくてよさそう。

*1:ゴリラ的解決。まあキャッシュのクリアって言い換えればいいのか。

Androidで長押し可能なカスタムViewを作る

能書き

ノリでAndroid開発を始めて二日目。
それっぽい挙動をするViewを作れたので記録しておく。

f:id:LouiS:20180314175444g:plain

コードだけコピりたい人はこちら: Holdable button sample. · GitHub
なお、ド素人が書いたコードであると免責しておく*1*2

.javaファイル


    public class HoldableButton extends AppCompatButton {
        private int delayMsec;
        private int intervalMsec;
    
        private MainTask mainTask;
        private final Handler handler = new Handler();
        private Runnable runnableCode;
    
        //
        //
        public HoldableButton(Context context) {
            this(context, null);
        }
        public HoldableButton(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
        public HoldableButton(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initAttrs(
                context.obtainStyledAttributes(
                    attrs, R.styleable.HoldableButton, defStyleAttr, 0
                )
            );
            setMainTask(() -> {});
        }
        private void initAttrs(TypedArray typedArray) {
            delayMsec = typedArray.getInteger(
                R.styleable.HoldableButton_delay_msec, 400
            );
            intervalMsec = typedArray.getInteger(
                R.styleable.HoldableButton_interval_msec, 100
            );
            typedArray.recycle();
        }
        private void initListener() {
            setOnClickListener(view -> mainTask.doTask());
    
            //
            runnableCode = () -> {
                mainTask.doTask();
                handler.postDelayed(runnableCode, intervalMsec);
            };
            setOnTouchListener(
                (view, motionEvent) -> {
                    switch(motionEvent.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        handler.postDelayed(runnableCode, delayMsec);
                        break;
    
                    case MotionEvent.ACTION_UP:
                        handler.removeCallbacks(runnableCode);
                        view.performClick();
                        break;
    
                    default:
                        break;
                    }
    
                    return true;
                }
            );
        }
    
        //
        public void setMainTask(MainTask mainTask) {
            this.mainTask = mainTask;
            initListener();
        }
    
        //
        //
        @FunctionalInterface
        public interface MainTask {
            void doTask();
        }
    }

    

果たすべきメインタスクを文字通りmainTaskとして保持している。

  • タップが短いとき → mainTask.doTaskを一度だけ呼び出す。
  • タップが長いとき → mainTask.doTaskをintervalMsecおきに呼び出す。

そしてタップが『長い』と判断する閾値がdelayMsecなのである。
これらのパラメータを扱いやすくするため、属性とすることにした。

.xmlファイル


    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="HoldableButton">
            <attr name="delay_msec" format="integer" />
            <attr name="interval_msec" format="integer" />
        </declare-styleable>
    </resources>
    

正直今の段階では『なんとなく感』が強いので解説は避ける。

また、このままだとボタンが潰れて表示される
三時間粘った末、解決策が発見できた。

You should explicitly set android:theme. That theme should be derived from Widget.AppCompat.Button theme.

先人は偉大だ。

むすび

試行錯誤しながら、主に次のリンク先の情報を元に完成させることが出来た。

12436288584_94d6bc46d2_b.jpg
背景 View上で長押しを判定したい時、通常はGestureDetectorクラスのOnGestureListenerをimplementsしてonLongPressを実装する。 [中略] しかしonLongPressは長押し判定の時間を指定できない。

Runnable#runメソッドを再帰的に呼び出す方法は目から鱗であった。

*1:参考書も買わず、ググりながら適当に書いている。さすがにコピペはしてないけど。

*2:実際、絶対もっと簡単に書けるよね。こういうUIよく見るもん。

Python3.xで『スライドするイテレータ』を書いてみる (重複あり/なし)

能書き

次のようなイテレータが欲しくなったことはないだろうか。


    >>> for e in ideal_it_A(range(6), 3):
    ...     print(e)
    ...
    (0, 1, 2)
    (3, 4, 5)
    >>>
    >>> for e in ideal_it_B(range(6), 3):
    ...     print(e)
    ...
    (0, 1, 2)
    (1, 2, 3)
    (2, 3, 4)
    (3, 4, 5)
    

これらを書いてみただけの話。Gist

重複のないイテレータ

出落ちのようだが、これについては公式リファレンスに言及がある。
引用元: 2. 組み込み関数 — Python 3.6.3 ドキュメント (太字は引用者)

zip(*iterables)
それぞれのイテラブルから要素を集めたイテレータを作ります。
(中略)
イテラブルの左から右への評価順序は保証されています。そのため zip(*[iter(s)]*n) を使ってデータ系列を長さ n のグループにクラスタリングするイディオムが使えます。これは、各出力タプルがイテレータを n 回呼び出した結果となるよう、 同じ イテレータを n 回繰り返します。これは入力を長さ n のチャンクに分割する効果があります。

よって、これをそのまま用いて次のように書けばよい。


    def split_each(iterable, n):
        return zip(
            * [iter(iterable)]*n
        )
    

重複のあるイテレータ

一方こちらのイテレータについては、リファレンスに言及がない。
最初は単純に考え、次のようなコードを書いてみた。


    def slide_each_simple(iterable, n):
        sequence = tuple(iterable)
        return zip(
            * [sequence[i:] for i in range(n)]
        )
    

しかしこの方法は、与えられた全てのイテレータをタプルに置き換えてしまう。
意味合い的には分かりやすいが、実体化する時間的なコストがある

そこで、次のような二つの代替関数を用意した。*1


    from copy import copy
    def slide_each_faster_A(iterable, n):
        iterator = iter(iterable)
        return zip(
            * [
                [copy(iterator), next(iterator)][0] 
                for _ in range(n)
            ]
        )
    
    def slide_each_faster_B(iterable, n):
        def _func(iterator, n):
            for _ in range(n):
                yield copy(iterator)
                next(iterator)
    
        return zip(
            *_func(iter(iterable), n)
        )
    

後述するとおり上記の代替関数のパフォーマンスはさして変わらない。
好きな方を使うと良いだろう。後者の方が素直だが。*2

実行速度の計測/比較

ideal_it_Bを満たす三関数について、実行速度を比較してみた。*3

Gist Iterator Sequence
List Tuple
simple 23.01130 10.26225 7.27913
faster_A 14.45048 13.33324 13.42435
faster_B 14.48402 13.39973 13.36088

結果を見ると、必ずしもfaster_Xが速いというわけではないことがわかる。
simpleのボトルネックは、やはりイテレータのタプルへの置き換えのようだ。

次のようにエイリアスを決めておくと、ストレスなく利用できるかもしれない。


    slide_each = slide_each_faster_A
    slide_each_gen = slide_each_faster_A
    slide_each_seq = slide_each_simple
    

悲しいオチ

書いた記事が間違っていると恥ずかしい。従って、必死で調べる。
その結果、自らのコーディングが車輪の再発明に過ぎないことを知るのだ。

12436288584_94d6bc46d2_b.jpg
Windowing
These tools yield windows of items from an iterable. Return a sliding window of width n over the given iterable.

    >>> from more_itertools import windowed
    >>>
    >>> for e in windowed(range(5), 3):
    ...     print(e)
    ...
    (0, 1, 2)
    (1, 2, 3)
    (2, 3, 4)
    >>>
    >>> for e in windowed(range(5), 3, step=2):
    ...     print(e)
    ...
    (0, 1, 2)
    (2, 3, 4)
    

わあ便利。みなさんはこっちを使いましょう。pipで入るよ。
私のコードに優位性を見出すとしたら、ミニマムなことくらいだと思う。*4

追記:2018/02/01

イムリーにも、同様の記事がQiitaにアップされていた。

このコードをちょいと書き換えて、more-itertoolsを含め再計測してみる。

Gist Iterator Sequence
List Tuple
simple 22.97496 10.17278 7.31506
faster_A 14.70850 13.49781 13.45389
faster_B 14.71984 13.70904 14.27782
quanon 3.06136 2.77098 3.70850
windowed 0.67819 0.38008 0.53994

まじか。5~6倍の違いは覚悟していたが、まさか数十倍の差があるとは。
逆に言えば高速化に関する良い題材となり得るので、今後解析しようと思う。

*1:collections.abc.Iteratorを継承したクラスを作っても良いだろうが、却って状態が複雑になりそうなので今回は避けた。

*2:ただし、私はクソコード大好き人間なので熱烈に前者を支持する。

*3:実行環境はWindows10 64bit機, Miniconda実装のPython3.6.0である。

*4:more_itertools.windowedの実装は、docstringを除いても30行を超える。

OpenCVの内部関数partitionをPython3.xで焼き直す

能書き

OpenCVについて以前調べているとき、内部関数partitionの存在を知った。
簡易なクラスタリングを行う汎用関数のようだ。

個人的には、こいつはとんでもなく使い勝手が良い関数だと思う。
ただし残念なことに、この関数はユーザに開放されていないのだ。*1

そいつを、自力で焼き直して解決してみた話。

こんなときにお勧め

  • クラスタリングではあるが、問題が簡単なのでサクッと書きたい
  • 同属条件がルールベースで表現でき、学習を求めていない
  • クラスタ数が事前に予測できない (でもx-meansを使うほどでもない)
  • ラムダ式を覚えたばかりで使いたくてたまらない

How to Use

大抵の読者にとって、『どのように実現するか』は関心の外にあるだろう。

差が1以内の要素を同一グループにする
連続する要素を同一のクラスタに振り分ける。


    from partition import partition
    
    my_list = [i for i in range(20)]
    del my_list[3:6]
    del my_list[4:8]

    print(my_list)  # [0, 1, 2, 6, 11, 12, 13, 14, 15, 16, 17, 18, 19]

    part1 = partition(my_list, lambda x, y: abs(x-y) <= 1)
    print(part1)    # [0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2]
    

偶奇をグループ分けする
偶数/奇数にそれぞれ0/1のラベルを与える。


    part2 = partition(my_list, lambda x, y: x % 2 == y % 2)
    print(part2)    # [0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]   
    

結果に応じて分割する
意外と煩雑な処理が必要なので、関数gen_groupを用意しておいた。


    from partition import gen_group
    
    for group in gen_group(my_list, part1):
        print(group)
    
    """出力
    [0, 1, 2]
    [6]
    [11, 12, 13, 14, 15, 16, 17, 18, 19]
    """
    
    for group in gen_group(my_list, part2):
    print(group)

    """出力
    [0, 2, 6, 12, 14, 16, 18]
    [1, 11, 13, 15, 17, 19]
    """
    

実装

本家のpartitionに倣って、UnionFind木を利用する。

12436288584_94d6bc46d2_b.jpg
素集合データ構造(そしゅうごうデータこうぞう、英: disjoint-set data structure)は、データの集合を素集合(互いにオーバーラップしない集合)に分割して保持するデータ構造。

自力で組むのも良い勉強になるだろうが...
ここではパフォーマンスを優先して既存のコードを利用する。*2

12436288584_94d6bc46d2_b.jpg
SonechkaGodovykh/union_find.py forked from tnoda/union_find.py Union-Find in Python

今回書いたpartition.pyは次のとおり。Gist


    from itertools import product, groupby
    import operator
    
    from UnionFind import UnionFind
    
    def _renumber(id_list):
        id_set = set(id_list)
        replace_dict = dict(zip(
            sorted(list(id_set)),
            [i for i, _ in enumerate(id_set)]
        ))
        return [replace_dict[elem] for elem in id_list]
    
    def partition(src_list, predicate_func):
        src_len = len(src_list)
        uf = UnionFind(src_len)
    
        loop_obj = product(src_list, src_list)
        for ij, (ei, ej) in enumerate(loop_obj):
            i, j = divmod(ij, src_len)
    
            if ei is ej:
                continue
            if not predicate_func(ei, ej):
                continue
    
            uf.union(i, j)
    
        return _renumber(uf._id)
    
    def gen_group(src_list, cluster_list):
        groups = groupby(
            sorted(
                zip(src_list, cluster_list),
                key=operator.itemgetter(-1)
            ),
            key=operator.itemgetter(-1)
        )
        return (
            [e[0] for e in elems] for _, elems in groups
        )

    

この中で強いて解説するなら_renumber関数だろう。
この関数は野放図に振られたクラスタIDを、0からの連番に変更する。


    >>> from partition import _renumber
    >>> _renumber([0, 2, 4, 4, 6])
    [0, 1, 2, 2, 3]
    

日頃行っているパズルのようなコーディングも、案外役に立つものだ。

おまけ - このモジュールを書いた経緯

もともと『近い点を同一視』する処理を実現するために書いた。
このように新たな発見があるものだから、teratailは面白い。*3

そこそこ便利なので、PyPIにアップする練習に使っても良いかもしれない。
どうせ既に似たようなモジュールがあるんだろうけど。

追記:2018/01/26

more_itertools.partitionという同様の関数が存在することが発覚。
ただし、これは2クラス分類しか出来ないようだ。セーフセーフ。

more_itertools.partition(pred, iterable)
Returns a 2-tuple of iterables derived from the input iterable. The first yields the items that have pred(item) == False. The second yields the items that have pred(item) == True.

*1:C++ならば、operations.hppをインクルードすれば利用可能かもしれない。しかし、Pythonの場合その手段は選べない。あくまで提供されているのはバインディングだからだ。

*2:2018/1/17時点でStarが6つも付いていれば信用していいんじゃないかな。たぶん。Godovykh女史に感謝。

*3:記事の最初の方にさりげなくリンクを張ってあるが、OpenCVのpartitionを知ったのも回答するための調査の過程であった。

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にいじめられたことがあるので... 苦手意識が更に高まる。

/* コードブロック */