20211129のPythonに関する記事は24件です。

Python でテキストデータの会話文を1行に成型する

テキストデータを自然言語処理する際に、会話文を抽出したかったが、テキストデータにおいて、1行に'「」'の対が複数あったり、逆に'「」'の対が複数行にまたがって存在していたりしており、成型がめんどうであった。 「」の対が1要素に収まっているリストを返す関数をメモ代わりに記載する。 # テキストファイルのパス path = 'hogehoge.txt' # 「」の対が1要素に収まっているリストを返す関数 def preprocessing(path): text = [] with open(path, mode='r', encoding='utf-8') as f: for line in f.readlines(): # text の list の要素に'「', '」'がそれぞれ1つ以下になるよう加工する line = line.replace('「','\n「') line = line.replace('」','」\n') if '\n' in line: line = line.split('\n') line = [l for l in line if l != ''] else: line = [line] text.extend(line) # 「」の対が複数要素にまたがっているものを1つの要素に結合する document = [] counter = 0 stack = [] for line in text: stack.append(line) if '「' in line: counter += 1 if '」' in line: counter -= 1 if counter == 0: stack = ''.join(stack) document.append(stack) stack = [] return document おそらくもう少し賢い書き方があるに違いないし、すべての場合に対応可能であるわけでは当然ないけれども、とりあえず目的は達成できたのでよしとする。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

平仮名<==>カタカナ 変換

応用 chr(ord("き") + (ord("ア") - ord("あ"))) # 'キ' chr(ord("バ") - (ord("ア") - ord("あ"))) # 'ば'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FARM_FINGERPRINTをPythonで再現する

tl; dr pip install pyfarmhash で PyFarmHash をインストールしたうえで、 import struct import farmhash def farm_fingerprint(value: str) -> int: hash_unsigned = farmhash.fingerprint64(value) hash_binary = struct.pack("Q", hash_unsigned) return struct.unpack("q", hash_binary)[0] はじめに:FARM_FINGERPRINTとは BigQueryやCloud Spannerでは、引数の値からハッシュ値を作る FARM_FINGERPRINT という関数が用意されている。 SHA256やSHA512といったおなじみのハッシュ関数も用意されているものの、 FARM_FINGERPRINT は戻り値がINT64なので値をそのままカラムに突っ込むことができ、重宝している。 (他のハッシュ関数は戻り値がBYTE型なので変換の手間によってちょっと敬遠してしまう。) さて、今回BigQuery上で行っていた計算処理を外出しし、Python上で自前で行うように改修を加えることになった。 具体的にはBigQuery上のデータを一度外部にエクスポートし、外部のサーバーのPython上で計算してから、計算結果をBigQueryにインポートする、というものだ。 特に計算処理の過程では FARM_FINGERPRINT 関数を使っていたため FARM_FINGERPRINT も自前で実装しなければならなくなった。 実行環境 Python 3.9.7 PyFarmHash 0.2.2 PythonでFARM_FINGERPRINTを再現する Pythonの場合、コアとなるロジックは PyFarmHash パッケージとして提供されているため、これを使う。 ライブラリのインストールは pip install pyfarmhash で行えばよい。 ハマりポイントは戻り値の符号だった。 PyFarmHashが用意しているハッシュ値計算は farmhash.fingerprint64 という関数で行われるのだが、この farmhash.fingerprint64 の戻り値の範囲は、0から(2^64)-1である。 つまり 符号なし64ビット整数 の範囲なのだ。 一方、BigQueryのINT64型は 符号あり64ビット整数 (-(2^63)〜(2^63)-1)だ。 したがって単純に farmhash.fingerprint64 で算出した値をBigQueryにインポートしようとすると範囲エラーによって取り込めない。 したがって符号なし64ビット整数を符号あり64ビット整数に変換する処理が必要になってくる。 Pythonではこのような変換のためにstructというパッケージを使うことができる。 structを使えば、符号あり/なしnビット整数とバイト配列の相互変換を行うことができる。 今回の場合、farmhash.fingerprint64 の出力である符号なし64ビット整数をバイト配列化し、そのバイト配列を符号あり64ビット整数に再変換すればよい。 import struct import farmhash # ハッシュ値を取得(戻り値は符号なし64bit整数の範囲) hash_unsigned = farmhash.fingerprint64(value) # hash_unsignedをバイナリー化する("Q"はhash_unsignedが符号なし64bit整数であることを指定している) hash_binary = struct.pack("Q", hash_unsigned) # hash_binaryを符号あり64bit整数化する("q"は符号あり64bit整数に変換することを指定している) hash_signed = struct.unpack("q", hash_binary)[0] struct.pack がバイト配列への変換関数、 struct.unpack がバイト配列からの変換関数だ。 それぞれの関数の第1引数は、入力/出力となる整数の符号あり/なしとビット数を指定するフォーマット文字だ。 公式のドキュメントの 書式指定文字 という項に一覧があるが、C言語の型事情に精通していることを前提とした書き方になっていて分かりづらいので、翻訳した表を載せておく(整数型のみ)。 フォーマット 厳密な整数型 b 符号あり8bit B 符号なし8bit h 符号あり16bit H 符号なし16bit l 符号あり32bit L 符号なし32bit q 符号あり64bit Q 符号なし64bit この一連の処理を関数化したものが、冒頭に挙げた farm_fingerprint となる。 おわりに BigQuery、Cloud Spanner互換のfingerprint64処理が書くことができた。 structの話(というかC言語の型の話)はだいぶ端折ったので不明点があればコメントにお願いします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Matplotlibでマウス操作でAlphaZeroとオセロする

この記事は BrainPad Advent Calendar 2021 2日目の記事です。 はじめに データサイエンティストです。普段は記事を書かないROM専ですが、たまにはアドベントカレンダーなるお祭り騒ぎに参加してみようと思います。 この記事では、データサイエンティストになじみの深い Matplotlib を用いて、ゲームを作ります。 データサイエンティストは、Jupyter Notebook や Matplotlib が大好きです。 また、データサイエンティストは、白黒はっきりさせることが大好きです。 つまり、データサイエンティストであれば、Jupyter Notebook 上で Matplotlib を用いてオセロができると聞いたら、胸のドキドキが止まらないことでしょう! ※ 個人の感想です。好みには個人差があります。 ※ 発言は個人の偏見であって、所属組織を代表するものではありません。 完成系 最初に完成系を見せておきましょう。 石を置くために、マスをクリックしています。 では、作っていきます。 Matplotlib によるインタラクティブな可視化 Jupyter Notebook 上で Matplotlib を使う場合には、ほとんどの人は以下を実行すると思います。 通常のコマンド %matplotlib inline Jupyter Notebook 上で Matplotlib によるインタラクティブな可視化を行うには、以下を実行しましょう。 インタラクティブな可視化を行うためのコマンド %matplotlib notebook さて、Matplotlib でクリックやキーボードを用いたインタラクティブな操作を受け付けるには、connectメソッドを用いて、グラフにイベント発生時の処理を結びつけます。公式ドキュメントを見て頂くのが一番だとは思いますが、下記に簡単な例を載せておきます。 マウスクリックに反応するプロットの例 import matplotlib.pyplot as plt import numpy as np xy1 = [] fig = plt.figure() ax1 = fig.add_subplot(111) sc1 = ax1.scatter([], [], color='blue') def on_click(event): xy1.append((event.xdata, event.ydata)) # クリック位置の座標(グラフのx軸とy軸のこと)をxy1に追加 sc1.set_offsets(xy1) # 散布図にxy1を設定 plt.draw() # グラフの再描画 plt.connect('button_press_event', on_click) # eventを引数に取る関数on_clickをbutton_press_eventと紐づける plt.show() button_press_eventはマウスのクリックに対応するイベントの指定で、どのボタン(LEFT, MIDDLE, RIGHT)が押されたかや、座標の情報などの情報を得ることができます。 インタラクティブモードでは、plt.drawでグラフを再描画することが可能です。 キーの押下に反応するプロットの例 import matplotlib.pyplot as plt import numpy as np fig = plt.figure() ax1 = fig.add_subplot(111) ax1.set_xlim([-1, 1]) ax1.set_ylim([-1, 1]) sc1 = ax1.scatter([], [], color='blue') x = np.linspace(start=-1, stop=1, num=100) def on_key(event): ax1.set_title('"{}" was pressed'.format(event.key)) # 押されたキーを表示 try: k = int(event.key) y = np.sin(k * x) sc1.set_offsets(np.array([x, y]).T) plt.draw() except ValueError: pass plt.connect('key_press_event', on_key) # eventを引数に取る関数on_keyをkey_press_eventと紐づける plt.show() このように、キー入力に対応して処理を行うことも可能です。 インタラクティブなプロットが作れることを知っておくと、簡易的なアノテーションツールの作成やシミュレーションの可視化など、データサイエンス業務やAI開発業務でも役立つシチュエーションがありますので、できるということだけでも知っておくと良いでしょう。 ちなみに、%matplotlib notebook として普通にプロットを作成するだけでも、ズームインができるなど多少インタラクティブな要素が得られますが、単純なインタラクティブなデータ可視化であれば、bokehなどの別ライブラリを使った方が良いでしょう。 また、ゲームとして第三者に提供するのであれば Dash と Plotly を使った方が良いのではないかと言う意見はごもっともですが、今回は「Jupyter Notebook 上で Matplotlib を用いてオセロができる」ということ自体がデータサイエンティストのハートをがっちり掴んで離さないと考え、今回はmatplotlibを、オセロをプレイするためのUIとして利用します。 AlphaZeroオセロモデルの準備 オセロをプレイできるようにするために、オセロAIを用意しましょう。 ルールベースで自作しても良いですし、機械学習で自作しても良いです。 今回は手っ取り早く、MIT-Licenceで公開されているAlpha Zero Generalから、学習済みモデルとコードを使わせて頂きます。 !git clone https://github.com/suragnair/alpha-zero-general %cd alpha-zero-general AlphaZero についてここでは説明しませんが、概要に興味がある方は、BrainPad の Platinum Data Blog の下記記事をお読みいただければ、雰囲気は掴めるのではないかと思います。 強化学習入門 Part3 - AlphaGoZeroでも重要な技術要素! モンテカルロ木探索の入門 - オセロをプレイできるようにする 先ほど、matplotlibで人がクリックした座標を取得する方法や、データを更新する方法を説明しました。 あとは、alpha-zero-general のコードと組み合わせれば完成です。 組み合わせる際に、ちょっと変な書き方してしまっている部分がありますが、愛嬌ということで。 GameUIクラス # 元コード(https://github.com/suragnair/alpha-zero-general)のpit.py, Arena.py, OthelloPlayers.pyを参考に作成 from matplotlib import pylab as plt from MCTS import MCTS from othello.OthelloGame import OthelloGame from othello.pytorch.NNet import NNetWrapper as NNet from utils import dotdict import numpy as np class GameUI(object): def __init__(self, is_human_first=True): """ Args: is_human_first (bool) Trueなら人間が先手、Falseなら人間が後手 """ self.game = OthelloGame(8) self.curPlayer = 1 # human self.player1 = lambda x: 'Human' # AlphaZero nnet = NNet(self.game) nnet.load_checkpoint('./pretrained_models/othello/pytorch/', '8x8_100checkpoints_best.pth.tar') args = dotdict({'numMCTSSims': 50, 'cpuct':1.0}) mcts = MCTS(self.game, nnet, args) self.player2 = lambda x: np.argmax(mcts.getActionProb(x, temp=1.0)) # game情報の初期化 if is_human_first: self.players = {-1: self.player2, 1:self.player1} self.player_names = {-1:'AlphaZero', 1:'You'} else: self.players = {-1: self.player1, 1:self.player2} self.player_names = {-1:'You', 1:'AlphaZero'} self.board = self.game.getInitBoard() self.board_size = self.board.shape self.it = 0 self._init_plot() self.fig.show() self._play() def _play(self): while self.game.getGameEnded(self.board, self.curPlayer) == 0: self.it += 1 self._make_plot_wo_draw() self.fig.canvas.draw() action = self.players[self.curPlayer](self.game.getCanonicalForm(self.board, self.curPlayer)) if action == 'Human': # human valids = self.game.getValidMoves(self.game.getCanonicalForm(self.board, self.curPlayer), 1) if valids[self.game.n ** 2] == 1: # パスしかできないとき action = self.game.n ** 2 self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) else: self.cid = self.fig.canvas.mpl_connect('button_press_event', self.onclick) # クリック待ち break else: # AlphaZero self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) if self.game.getGameEnded(self.board, self.curPlayer): win_player = self.game.getGameEnded(self.board, 1) self._make_plot_wo_draw() self.ax.set_title('Game over: Turn {} {} Win!'.format(self.it, self.player_names[win_player])) self.fig.canvas.draw() def onclick(self, event): x = int(event.xdata) y = int(event.ydata) action = self.game.n * x + y if x != -1 else self.game.n ** 2 valids = self.game.getValidMoves(self.game.getCanonicalForm(self.board, self.curPlayer), 1) if valids[action] == 1: self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) self.fig.canvas.mpl_disconnect(self.cid) self._play() else: pass def _make_plot_wo_draw(self): x, y = np.where(self.board == 1) self.sc1.set_offsets(np.array([x, y]).T + 0.5) x, y = np.where(self.board == -1) self.sc2.set_offsets(np.array([x, y]).T + 0.5) self.ax.set_title('Turn {}: {}'.format(self.it, self.player_names[self.curPlayer])) def _init_plot(self): self.fig = plt.figure(figsize=(4, 4), dpi=100) self.ax = self.fig.add_subplot(111) self.ax.set_xlim([0, self.board_size[1]]) self.ax.set_ylim([self.board_size[0], 0]) self.ax.axes.set_xticks(np.arange(0, self.board_size[0]+1)) self.ax.axes.set_yticks(np.arange(0, self.board_size[1]+1)) self.ax.tick_params(length=0) self.ax.set_xticklabels([]) self.ax.set_yticklabels([]) self.ax.grid() self.ax.set_facecolor('#2e8b57') self.sc1 = self.ax.scatter([], [], c='black', marker='o', s=400) self.sc2 = self.ax.scatter([], [], c='white', marker='o', s=400) 簡単にコードの補足をします。 _init_plot でグラフの書式を初期化しています。 背景色を緑色にし、grid を用いて8×8のオセロ盤面を表現しています。 軸の線やラベルを残しておくと Matplotlib らしさが出て、Matplotlib 愛好者はむしろその方が好みかもしれませんが、今回はそれらを消し、見た目をすっきりさせておきました。 player1 は黒色の丸型マーカー、player2 は白色の丸型マーカーの散布図によって表現しています。 _play では、人間の手番かゲーム終了になるまで、ゲームを進めています。 人間の手番になったら、クリックイベントの発生を待ちます。 onclick では、クリックされた位置を取得し、それが着手可能なマスであればゲームを進めます。 以上、オセロゲームの中身は Alpha Zero General のコードを活用させて頂き、可視化と入力は Matplotlib を利用することで、 とっても簡単にオセロゲームが遊べるUIを作ることができました。 実行 GameUI(is_human_first=True) 対局の振り返り さて、オセロが遊べるようになったのは良いのですが、 困ったことに、全然楽しくありません。 Matplotlib で作ったゲーム画面を派手にするとか、 効果音を再生するとか、 キャラクターの顔画像やセリフを表示するとか。 どうすれば楽しくなるのか考えましたが、結局のところ、全然勝てないから楽しくないのだと気づきました。 オセロで勝てるようになるにはどうすれば良いか。元将棋部の私なりに出した結論は、定石や基本の手筋を学ぶことと、対局の振り返りをすることです。 定石や基本の手筋はググることにして、ここでは、対局の振り返り機能を追加してみましょう。 まずは先ほどの GameUI クラスに history というメンバ変数を追加し、各手番で選択された action を保存するように修正します。 GameUIクラス_履歴を保存するようにしたバージョン # 元コード(https://github.com/suragnair/alpha-zero-general)のpit.py, Arena.py, OthelloPlayers.pyを参考に作成 from matplotlib import pylab as plt from MCTS import MCTS from othello.OthelloGame import OthelloGame from othello.pytorch.NNet import NNetWrapper as NNet from utils import dotdict import numpy as np class GameUI(object): def __init__(self, is_human_first=True): """ Args: is_human_first (bool) Trueなら人間が先手、Falseなら人間が後手 """ self.game = OthelloGame(8) self.curPlayer = 1 # human self.player1 = lambda x: 'Human' # AlphaZero nnet = NNet(self.game) nnet.load_checkpoint('./pretrained_models/othello/pytorch/', '8x8_100checkpoints_best.pth.tar') args = dotdict({'numMCTSSims': 50, 'cpuct':1.0}) mcts = MCTS(self.game, nnet, args) self.player2 = lambda x: np.argmax(mcts.getActionProb(x, temp=1.0)) # game情報の初期化 if is_human_first: self.players = {-1: self.player2, 1:self.player1} self.player_names = {-1:'AlphaZero', 1:'You'} else: self.players = {-1: self.player1, 1:self.player2} self.player_names = {-1:'You', 1:'AlphaZero'} self.board = self.game.getInitBoard() self.board_size = self.board.shape self.it = 0 self.history = [] self._init_plot() self.fig.show() self._play() def _play(self): while self.game.getGameEnded(self.board, self.curPlayer) == 0: self.it += 1 self._make_plot_wo_draw() self.fig.canvas.draw() action = self.players[self.curPlayer](self.game.getCanonicalForm(self.board, self.curPlayer)) if action == 'Human': # human valids = self.game.getValidMoves(self.game.getCanonicalForm(self.board, self.curPlayer), 1) if valids[self.game.n ** 2] == 1: # パスしかできないとき action = self.game.n ** 2 self.history.append(action) self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) else: self.cid = self.fig.canvas.mpl_connect('button_press_event', self.onclick) # クリック待ち break else: # AlphaZero self.history.append(action) self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) if self.game.getGameEnded(self.board, self.curPlayer): win_player = self.game.getGameEnded(self.board, 1) self._make_plot_wo_draw() self.ax.set_title('Game over: Turn {} {} Win!'.format(self.it, self.player_names[win_player])) self.fig.canvas.draw() def onclick(self, event): x = int(event.xdata) y = int(event.ydata) action = self.game.n * x + y if x != -1 else self.game.n ** 2 valids = self.game.getValidMoves(self.game.getCanonicalForm(self.board, self.curPlayer), 1) if valids[action] == 1: self.history.append(action) self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) self.fig.canvas.mpl_disconnect(self.cid) self._play() else: pass def _make_plot_wo_draw(self): x, y = np.where(self.board == 1) self.sc1.set_offsets(np.array([x, y]).T + 0.5) x, y = np.where(self.board == -1) self.sc2.set_offsets(np.array([x, y]).T + 0.5) self.ax.set_title('Turn {}: {}'.format(self.it, self.player_names[self.curPlayer])) def _init_plot(self): self.fig = plt.figure(figsize=(4, 4), dpi=100) self.ax = self.fig.add_subplot(111) self.ax.set_xlim([0, self.board_size[1]]) self.ax.set_ylim([self.board_size[0], 0]) self.ax.axes.set_xticks(np.arange(0, self.board_size[0]+1)) self.ax.axes.set_yticks(np.arange(0, self.board_size[1]+1)) self.ax.tick_params(length=0) self.ax.set_xticklabels([]) self.ax.set_yticklabels([]) self.ax.grid() self.ax.set_facecolor('#2e8b57') self.sc1 = self.ax.scatter([], [], c='black', edgecolors='gray', marker='o', s=400) self.sc2 = self.ax.scatter([], [], c='white', edgecolors='gray', marker='o', s=400) 実行 gameui = GameUI(is_human_first=True) では、対局の振り返りができるようにします。 同じクラスに機能を追加しても良いのですが、今回は別クラスとして作成しました。 GameViewerクラス # 元コード(https://github.com/suragnair/alpha-zero-general)のpit.py, Arena.py, OthelloPlayers.pyを参考に作成 from matplotlib import pylab as plt from MCTS import MCTS from othello.OthelloGame import OthelloGame from othello.pytorch.NNet import NNetWrapper as NNet from utils import dotdict import numpy as np class GameViewer(object): def __init__(self, history, numMCTSSims=50): self.game = OthelloGame(8) self.curPlayer = 1 self.history = history # AlphaZero nnet = NNet(self.game) nnet.load_checkpoint('./pretrained_models/othello/pytorch/', '8x8_100checkpoints_best.pth.tar') args = dotdict({'numMCTSSims': numMCTSSims, 'cpuct':1.0}) self.mcts = MCTS(self.game, nnet, args) # game情報の初期化 self.player_names = {-1:'White', 1:'Black'} self.board = self.game.getInitBoard() self.board_size = self.board.shape self.it = 0 self.fig = plt.figure(figsize=(4, 4), dpi=100) self.ax = self.fig.add_subplot(111) self.it += 1 self._init_plot() self.fig.show() self._make_plot_wo_draw() self.fig.canvas.draw() self.cid = self.fig.canvas.mpl_connect('button_press_event', self.onclick) # クリック待ち def _play(self): action = self.history[self.it - 1] self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) self._make_plot_wo_draw() self.fig.canvas.draw() if self.game.getGameEnded(self.board, self.curPlayer): win_player = self.game.getGameEnded(self.board, 1) self.ax.set_title('Game over: Turn {} {} Win!'.format(self.it, self.player_names[win_player])) self.fig.canvas.draw() self.fig.canvas.mpl_disconnect(self.cid) else: self.it+=1 def onclick(self, event): if event.button == 1: self._play() def _make_plot_wo_draw(self): x, y = np.where(self.board == 1) self.sc1.set_offsets(np.array([x, y]).T + 0.5) x, y = np.where(self.board == -1) self.sc2.set_offsets(np.array([x, y]).T + 0.5) obs = self.game.getCanonicalForm(self.board, self.curPlayer) if self.game.getGameEnded(self.board, self.curPlayer) == 0: policy = self.mcts.getActionProb(obs, temp=1.0) self.pcf.set_data(np.array(policy[:-1]).reshape(self.board_size).T) else: self.pcf.set_data(np.zeros(self.board_size)) self.ax.set_title('Turn {}: {}'.format(self.it, self.player_names[self.curPlayer])) def _init_plot(self): self.ax.set_xlim([0, self.board_size[1]]) self.ax.set_ylim([self.board_size[0], 0]) self.ax.axes.set_xticks(np.arange(0, self.board_size[0]+1)) self.ax.axes.set_yticks(np.arange(0, self.board_size[1]+1)) self.ax.tick_params(length=0) self.ax.set_xticklabels([]) self.ax.set_yticklabels([]) self.pcf = self.ax.pcolorfast(np.zeros(self.board_size), cmap=plt.cm.Greens, vmin=0, vmax=0.5) self.ax.grid() self.sc1 = self.ax.scatter([], [], c='black', edgecolors='gray', marker='o', s=400) self.sc2 = self.ax.scatter([], [], c='white', edgecolors='gray', marker='o', s=400) 簡単にコードの補足をします。 基本的には、左クリックで着手を再現して振り返るだけの機能です。 __init__ の中で今回も MCTS のインスタンスを用意しています。これは、各局面でどの手が良かったかを教えてくれる AI として使います。 _init_plot で AI が各局面でどの手が良いと判断しているかを表示する機能を追加しています。具体的には pcolorfast により、マスの色の濃淡で表現しています。そうすると player2 が白色の丸型マーカーだと背景色に埋もれてしまうため、灰色の線で囲むようにしました。 これで、対局を振り返りながら、どうすれば良かったのかを教えてもらえるようになりました。 振り返りの実行 tmp = GameViewer(gameui.history) ヒントモード これで対局の振り返りができるようになり、どうすれば良かったのかをAlphaZeroに教えてもらえるようになりました! しかし、私が強くなるまでには時間がかかります。アドベントカレンダーの記事の公開日も迫っています。 こうなったら! どうすれば良かったのかをAlphaZeroに対局の振り返りで教えてもらうのではなく、対局中に教えてもらいましょう! 常にAlphaZeroが教えてくれている状態だとゲームの体を成さないので、裏ワザ的に、hキーを押すとAlphaZeroが良い手を教えてくれるようにします。ヒントモードの搭載です! GameUIクラス_ヒントモードを搭載したバージョン # 元コード(https://github.com/suragnair/alpha-zero-general)のpit.py, Arena.py, OthelloPlayers.pyを参考に作成 from matplotlib import pylab as plt from MCTS import MCTS from othello.OthelloGame import OthelloGame from othello.pytorch.NNet import NNetWrapper as NNet from utils import dotdict import numpy as np class GameUI(object): def __init__(self, is_human_first=True): """ Args: is_human_first (bool) Trueなら人間が先手、Falseなら人間が後手 """ self.game = OthelloGame(8) self.curPlayer = 1 # human self.player1 = lambda x: 'Human' # AlphaZero nnet = NNet(self.game) nnet.load_checkpoint('./pretrained_models/othello/pytorch/', '8x8_100checkpoints_best.pth.tar') args = dotdict({'numMCTSSims': 50, 'cpuct':1.0}) mcts = MCTS(self.game, nnet, args) self.player2 = lambda x: np.argmax(mcts.getActionProb(x, temp=1.0)) # ForHintMode args = dotdict({'numMCTSSims': 100, 'cpuct':1.0}) self.mcts0 = MCTS(self.game, nnet, args) # game情報の初期化 if is_human_first: self.players = {-1: self.player2, 1:self.player1} self.player_names = {-1:'AlphaZero', 1:'You'} else: self.players = {-1: self.player1, 1:self.player2} self.player_names = {-1:'You', 1:'AlphaZero'} self.board = self.game.getInitBoard() self.board_size = self.board.shape self.it = 0 self.history = [] self._init_plot() self.fig.show() self._play() def _play(self): while self.game.getGameEnded(self.board, self.curPlayer) == 0: self.it += 1 self._make_plot_wo_draw() self.fig.canvas.draw() action = self.players[self.curPlayer](self.game.getCanonicalForm(self.board, self.curPlayer)) if action == 'Human': # human valids = self.game.getValidMoves(self.game.getCanonicalForm(self.board, self.curPlayer), 1) if valids[self.game.n ** 2] == 1: # パスしかできないとき action = self.game.n ** 2 self.history.append(action) self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) else: self.cid = self.fig.canvas.mpl_connect('button_press_event', self.onclick) # クリック待ち self.cid_k = self.fig.canvas.mpl_connect('key_press_event', self.onkey) break else: # AlphaZero self.history.append(action) self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) if self.game.getGameEnded(self.board, self.curPlayer): win_player = self.game.getGameEnded(self.board, 1) self._make_plot_wo_draw() self.ax.set_title('Game over: Turn {} {} Win!'.format(self.it, self.player_names[win_player])) self.fig.canvas.draw() def onclick(self, event): x = int(event.xdata) y = int(event.ydata) action = self.game.n * x + y if x != -1 else self.game.n ** 2 valids = self.game.getValidMoves(self.game.getCanonicalForm(self.board, self.curPlayer), 1) if valids[action] == 1: self.history.append(action) self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) self.pcf.set_data(np.ones(self.board_size)*0.34) self.fig.canvas.mpl_disconnect(self.cid) self.fig.canvas.mpl_disconnect(self.cid_k) self._play() else: pass def onkey(self, event): if event.key == 'h': obs = self.game.getCanonicalForm(self.board, self.curPlayer) policy = self.mcts0.getActionProb(obs, temp=1.0) self.pcf.set_data(np.array(policy[:-1]).reshape(self.board_size).T) def _make_plot_wo_draw(self): x, y = np.where(self.board == 1) self.sc1.set_offsets(np.array([x, y]).T + 0.5) x, y = np.where(self.board == -1) self.sc2.set_offsets(np.array([x, y]).T + 0.5) self.ax.set_title('Turn {}: {}'.format(self.it, self.player_names[self.curPlayer])) def _init_plot(self): self.fig = plt.figure(figsize=(4, 4), dpi=100) self.ax = self.fig.add_subplot(111) self.ax.set_xlim([0, self.board_size[1]]) self.ax.set_ylim([self.board_size[0], 0]) self.ax.axes.set_xticks(np.arange(0, self.board_size[0]+1)) self.ax.axes.set_yticks(np.arange(0, self.board_size[1]+1)) self.ax.tick_params(length=0) self.ax.set_xticklabels([]) self.ax.set_yticklabels([]) self.pcf = self.ax.pcolorfast(np.ones(self.board_size)*0.34, cmap=plt.cm.Greens, vmin=0, vmax=0.5) self.ax.grid() self.sc1 = self.ax.scatter([], [], c='black', edgecolors='gray', marker='o', s=400) self.sc2 = self.ax.scatter([], [], c='white', edgecolors='gray', marker='o', s=400) 簡単にコードの補足をします。 __init__ で、ヒント用のAlphaZeroを追加しています。しれっとヒント用のMCTSだけシミュレーション数を100に増やしているのは、勝ちたい気持ちの表れです。 _onkey で、ヒントを表示する処理を書いています。このあたりのコードはViewerの時と同じです。 実行 gameui = GameUI(is_human_first=True) さあ、いざ勝負のときです! 下記は倍速で表示しています。 おわりに 今回はおふざけな題材で、Matplotlib でインタラクティブなプロットを作る例について紹介しました。 最後は禁断のヒントモードまでフル活用して、それでも AlphaZero に勝てなかったのですが、 本記事の主目的である、Matplotlib でのインタラクティブなプロットの作り方については、概ね伝えられたのではないかと思います。 私は実際の業務でも、深層強化学習を用いたプロジェクトや画像案件などでこれらの機能を利用したことがあります。特に、深層強化学習をするような場合はデバッグやモデル改善のために、データサイエンティストが自分で可視化できた方が良いでしょう。 知っておいて損はない機能だと思いますので、皆様もぜひ遊んでみて頂ければと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MatplotlibでAlphaZeroとオセロする

この記事は BrainPad Advent Calendar 2021 2日目の記事です。 はじめに データサイエンティストです。普段は記事を書かないROM専ですが、たまにはアドベントカレンダーなるお祭り騒ぎに参加してみようと思います。 この記事では、データサイエンティストになじみの深い Matplotlib を用いて、ゲームを作ります。 データサイエンティストは、Jupyter Notebook や Matplotlib が大好きです。 また、データサイエンティストは、白黒はっきりさせることが大好きです。 つまり、データサイエンティストであれば、Jupyter Notebook 上で Matplotlib を用いてオセロができると聞いたら、胸のドキドキが止まらないことでしょう! ※ 個人の感想です。好みには個人差があります。 ※ 発言は個人の偏見であって、所属組織を代表するものではありません。 完成系 最初に完成系を見せておきましょう。 石を置くために、マスをクリックしています。 では、作っていきます。 Matplotlib によるインタラクティブな可視化 Jupyter Notebook 上で Matplotlib を使う場合には、ほとんどの人は以下を実行すると思います。 通常のコマンド %matplotlib inline Jupyter Notebook 上で Matplotlib によるインタラクティブな可視化を行うには、以下を実行しましょう。 インタラクティブな可視化を行うためのコマンド %matplotlib notebook さて、Matplotlib でクリックやキーボードを用いたインタラクティブな操作を受け付けるには、connectメソッドを用いて、グラフにイベント発生時の処理を結びつけます。公式ドキュメントを見て頂くのが一番だとは思いますが、下記に簡単な例を載せておきます。 マウスクリックに反応するプロットの例 import matplotlib.pyplot as plt import numpy as np xy1 = [] fig = plt.figure() ax1 = fig.add_subplot(111) sc1 = ax1.scatter([], [], color='blue') def on_click(event): xy1.append((event.xdata, event.ydata)) # クリック位置の座標(グラフのx軸とy軸のこと)をxy1に追加 sc1.set_offsets(xy1) # 散布図にxy1を設定 plt.draw() # グラフの再描画 plt.connect('button_press_event', on_click) # eventを引数に取る関数on_clickをbutton_press_eventと紐づける plt.show() button_press_eventはマウスのクリックに対応するイベントの指定で、どのボタン(LEFT, MIDDLE, RIGHT)が押されたかや、座標の情報などの情報を得ることができます。 インタラクティブモードでは、plt.drawでグラフを再描画することが可能です。 キーの押下に反応するプロットの例 import matplotlib.pyplot as plt import numpy as np fig = plt.figure() ax1 = fig.add_subplot(111) ax1.set_xlim([-1, 1]) ax1.set_ylim([-1, 1]) sc1 = ax1.scatter([], [], color='blue') x = np.linspace(start=-1, stop=1, num=100) def on_key(event): ax1.set_title('"{}" was pressed'.format(event.key)) # 押されたキーを表示 try: k = int(event.key) y = np.sin(k * x) sc1.set_offsets(np.array([x, y]).T) plt.draw() except ValueError: pass plt.connect('key_press_event', on_key) # eventを引数に取る関数on_keyをkey_press_eventと紐づける plt.show() このように、キー入力に対応して処理を行うことも可能です。 インタラクティブなプロットが作れることを知っておくと、簡易的なアノテーションツールの作成やシミュレーションの可視化など、データサイエンス業務やAI開発業務でも役立つシチュエーションがありますので、できるということだけでも知っておくと良いでしょう。 ちなみに、%matplotlib notebook として普通にプロットを作成するだけでも、ズームインができるなど多少インタラクティブな要素が得られますが、単純なインタラクティブなデータ可視化であれば、bokehなどの別ライブラリを使った方が良いでしょう。 また、ゲームとして第三者に提供するのであれば Dash と Plotly を使った方が良いのではないかと言う意見はごもっともですが、今回は「Jupyter Notebook 上で Matplotlib を用いてオセロができる」ということ自体がデータサイエンティストのハートをがっちり掴んで離さないと考え、今回はmatplotlibを、オセロをプレイするためのUIとして利用します。 AlphaZeroオセロモデルの準備 オセロをプレイできるようにするために、オセロAIを用意しましょう。 ルールベースで自作しても良いですし、機械学習で自作しても良いです。 今回は手っ取り早く、MIT-Licenceで公開されているAlpha Zero Generalから、学習済みモデルとコードを使わせて頂きます。 !git clone https://github.com/suragnair/alpha-zero-general %cd alpha-zero-general AlphaZero についてここでは説明しませんが、概要に興味がある方は、BrainPad の Platinum Data Blog の下記記事をお読みいただければ、雰囲気は掴めるのではないかと思います。 強化学習入門 Part3 - AlphaGoZeroでも重要な技術要素! モンテカルロ木探索の入門 - オセロをプレイできるようにする 先ほど、matplotlibで人がクリックした座標を取得する方法や、データを更新する方法を説明しました。 あとは、alpha-zero-general のコードと組み合わせれば完成です。 組み合わせる際に、ちょっと変な書き方してしまっている部分がありますが、愛嬌ということで。 GameUIクラス # 元コード(https://github.com/suragnair/alpha-zero-general)のpit.py, Arena.py, OthelloPlayers.pyを参考に作成 from matplotlib import pylab as plt from MCTS import MCTS from othello.OthelloGame import OthelloGame from othello.pytorch.NNet import NNetWrapper as NNet from utils import dotdict import numpy as np class GameUI(object): def __init__(self, is_human_first=True): """ Args: is_human_first (bool) Trueなら人間が先手、Falseなら人間が後手 """ self.game = OthelloGame(8) self.curPlayer = 1 # human self.player1 = lambda x: 'Human' # AlphaZero nnet = NNet(self.game) nnet.load_checkpoint('./pretrained_models/othello/pytorch/', '8x8_100checkpoints_best.pth.tar') args = dotdict({'numMCTSSims': 50, 'cpuct':1.0}) mcts = MCTS(self.game, nnet, args) self.player2 = lambda x: np.argmax(mcts.getActionProb(x, temp=1.0)) # game情報の初期化 if is_human_first: self.players = {-1: self.player2, 1:self.player1} self.player_names = {-1:'AlphaZero', 1:'You'} else: self.players = {-1: self.player1, 1:self.player2} self.player_names = {-1:'You', 1:'AlphaZero'} self.board = self.game.getInitBoard() self.board_size = self.board.shape self.it = 0 self._init_plot() self.fig.show() self._play() def _play(self): while self.game.getGameEnded(self.board, self.curPlayer) == 0: self.it += 1 self._make_plot_wo_draw() self.fig.canvas.draw() action = self.players[self.curPlayer](self.game.getCanonicalForm(self.board, self.curPlayer)) if action == 'Human': # human valids = self.game.getValidMoves(self.game.getCanonicalForm(self.board, self.curPlayer), 1) if valids[self.game.n ** 2] == 1: # パスしかできないとき action = self.game.n ** 2 self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) else: self.cid = self.fig.canvas.mpl_connect('button_press_event', self.onclick) # クリック待ち break else: # AlphaZero self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) if self.game.getGameEnded(self.board, self.curPlayer): win_player = self.game.getGameEnded(self.board, 1) self._make_plot_wo_draw() self.ax.set_title('Game over: Turn {} {} Win!'.format(self.it, self.player_names[win_player])) self.fig.canvas.draw() def onclick(self, event): x = int(event.xdata) y = int(event.ydata) action = self.game.n * x + y if x != -1 else self.game.n ** 2 valids = self.game.getValidMoves(self.game.getCanonicalForm(self.board, self.curPlayer), 1) if valids[action] == 1: self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) self.fig.canvas.mpl_disconnect(self.cid) self._play() else: pass def _make_plot_wo_draw(self): x, y = np.where(self.board == 1) self.sc1.set_offsets(np.array([x, y]).T + 0.5) x, y = np.where(self.board == -1) self.sc2.set_offsets(np.array([x, y]).T + 0.5) self.ax.set_title('Turn {}: {}'.format(self.it, self.player_names[self.curPlayer])) def _init_plot(self): self.fig = plt.figure(figsize=(4, 4), dpi=100) self.ax = self.fig.add_subplot(111) self.ax.set_xlim([0, self.board_size[1]]) self.ax.set_ylim([self.board_size[0], 0]) self.ax.axes.set_xticks(np.arange(0, self.board_size[0]+1)) self.ax.axes.set_yticks(np.arange(0, self.board_size[1]+1)) self.ax.tick_params(length=0) self.ax.set_xticklabels([]) self.ax.set_yticklabels([]) self.ax.grid() self.ax.set_facecolor('#2e8b57') self.sc1 = self.ax.scatter([], [], c='black', marker='o', s=400) self.sc2 = self.ax.scatter([], [], c='white', marker='o', s=400) 簡単にコードの補足をします。 _init_plot でグラフの書式を初期化しています。 背景色を緑色にし、grid を用いて8×8のオセロ盤面を表現しています。 軸の線やラベルを残しておくと Matplotlib らしさが出て、Matplotlib 愛好者はむしろその方が好みかもしれませんが、今回はそれらを消し、見た目をすっきりさせておきました。 player1 は黒色の丸型マーカー、player2 は白色の丸型マーカーの散布図によって表現しています。 _play では、人間の手番かゲーム終了になるまで、ゲームを進めています。 人間の手番になったら、クリックイベントの発生を待ちます。 onclick では、クリックされた位置を取得し、それが着手可能なマスであればゲームを進めます。 以上、オセロゲームの中身は Alpha Zero General のコードを活用させて頂き、可視化と入力は Matplotlib を利用することで、 とっても簡単にオセロゲームが遊べるUIを作ることができました。 実行 GameUI(is_human_first=True) 対局の振り返り さて、オセロが遊べるようになったのは良いのですが、 困ったことに、全然楽しくありません。 Matplotlib で作ったゲーム画面を派手にするとか、 効果音を再生するとか、 キャラクターの顔画像やセリフを表示するとか。 どうすれば楽しくなるのか考えましたが、結局のところ、全然勝てないから楽しくないのだと気づきました。 オセロで勝てるようになるにはどうすれば良いか。元将棋部の私なりに出した結論は、定石や基本の手筋を学ぶことと、対局の振り返りをすることです。 定石や基本の手筋はググることにして、ここでは、対局の振り返り機能を追加してみましょう。 まずは先ほどの GameUI クラスに history というメンバ変数を追加し、各手番で選択された action を保存するように修正します。 GameUIクラス_履歴を保存するようにしたバージョン # 元コード(https://github.com/suragnair/alpha-zero-general)のpit.py, Arena.py, OthelloPlayers.pyを参考に作成 from matplotlib import pylab as plt from MCTS import MCTS from othello.OthelloGame import OthelloGame from othello.pytorch.NNet import NNetWrapper as NNet from utils import dotdict import numpy as np class GameUI(object): def __init__(self, is_human_first=True): """ Args: is_human_first (bool) Trueなら人間が先手、Falseなら人間が後手 """ self.game = OthelloGame(8) self.curPlayer = 1 # human self.player1 = lambda x: 'Human' # AlphaZero nnet = NNet(self.game) nnet.load_checkpoint('./pretrained_models/othello/pytorch/', '8x8_100checkpoints_best.pth.tar') args = dotdict({'numMCTSSims': 50, 'cpuct':1.0}) mcts = MCTS(self.game, nnet, args) self.player2 = lambda x: np.argmax(mcts.getActionProb(x, temp=1.0)) # game情報の初期化 if is_human_first: self.players = {-1: self.player2, 1:self.player1} self.player_names = {-1:'AlphaZero', 1:'You'} else: self.players = {-1: self.player1, 1:self.player2} self.player_names = {-1:'You', 1:'AlphaZero'} self.board = self.game.getInitBoard() self.board_size = self.board.shape self.it = 0 self.history = [] self._init_plot() self.fig.show() self._play() def _play(self): while self.game.getGameEnded(self.board, self.curPlayer) == 0: self.it += 1 self._make_plot_wo_draw() self.fig.canvas.draw() action = self.players[self.curPlayer](self.game.getCanonicalForm(self.board, self.curPlayer)) if action == 'Human': # human valids = self.game.getValidMoves(self.game.getCanonicalForm(self.board, self.curPlayer), 1) if valids[self.game.n ** 2] == 1: # パスしかできないとき action = self.game.n ** 2 self.history.append(action) self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) else: self.cid = self.fig.canvas.mpl_connect('button_press_event', self.onclick) # クリック待ち break else: # AlphaZero self.history.append(action) self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) if self.game.getGameEnded(self.board, self.curPlayer): win_player = self.game.getGameEnded(self.board, 1) self._make_plot_wo_draw() self.ax.set_title('Game over: Turn {} {} Win!'.format(self.it, self.player_names[win_player])) self.fig.canvas.draw() def onclick(self, event): x = int(event.xdata) y = int(event.ydata) action = self.game.n * x + y if x != -1 else self.game.n ** 2 valids = self.game.getValidMoves(self.game.getCanonicalForm(self.board, self.curPlayer), 1) if valids[action] == 1: self.history.append(action) self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) self.fig.canvas.mpl_disconnect(self.cid) self._play() else: pass def _make_plot_wo_draw(self): x, y = np.where(self.board == 1) self.sc1.set_offsets(np.array([x, y]).T + 0.5) x, y = np.where(self.board == -1) self.sc2.set_offsets(np.array([x, y]).T + 0.5) self.ax.set_title('Turn {}: {}'.format(self.it, self.player_names[self.curPlayer])) def _init_plot(self): self.fig = plt.figure(figsize=(4, 4), dpi=100) self.ax = self.fig.add_subplot(111) self.ax.set_xlim([0, self.board_size[1]]) self.ax.set_ylim([self.board_size[0], 0]) self.ax.axes.set_xticks(np.arange(0, self.board_size[0]+1)) self.ax.axes.set_yticks(np.arange(0, self.board_size[1]+1)) self.ax.tick_params(length=0) self.ax.set_xticklabels([]) self.ax.set_yticklabels([]) self.ax.grid() self.ax.set_facecolor('#2e8b57') self.sc1 = self.ax.scatter([], [], c='black', edgecolors='gray', marker='o', s=400) self.sc2 = self.ax.scatter([], [], c='white', edgecolors='gray', marker='o', s=400) 実行 gameui = GameUI(is_human_first=True) では、対局の振り返りができるようにします。 同じクラスに機能を追加しても良いのですが、今回は別クラスとして作成しました。 GameViewerクラス # 元コード(https://github.com/suragnair/alpha-zero-general)のpit.py, Arena.py, OthelloPlayers.pyを参考に作成 from matplotlib import pylab as plt from MCTS import MCTS from othello.OthelloGame import OthelloGame from othello.pytorch.NNet import NNetWrapper as NNet from utils import dotdict import numpy as np class GameViewer(object): def __init__(self, history, numMCTSSims=50): self.game = OthelloGame(8) self.curPlayer = 1 self.history = history # AlphaZero nnet = NNet(self.game) nnet.load_checkpoint('./pretrained_models/othello/pytorch/', '8x8_100checkpoints_best.pth.tar') args = dotdict({'numMCTSSims': numMCTSSims, 'cpuct':1.0}) self.mcts = MCTS(self.game, nnet, args) # game情報の初期化 self.player_names = {-1:'White', 1:'Black'} self.board = self.game.getInitBoard() self.board_size = self.board.shape self.it = 0 self.fig = plt.figure(figsize=(4, 4), dpi=100) self.ax = self.fig.add_subplot(111) self.it += 1 self._init_plot() self.fig.show() self._make_plot_wo_draw() self.fig.canvas.draw() self.cid = self.fig.canvas.mpl_connect('button_press_event', self.onclick) # クリック待ち def _play(self): action = self.history[self.it - 1] self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) self._make_plot_wo_draw() self.fig.canvas.draw() if self.game.getGameEnded(self.board, self.curPlayer): win_player = self.game.getGameEnded(self.board, 1) self.ax.set_title('Game over: Turn {} {} Win!'.format(self.it, self.player_names[win_player])) self.fig.canvas.draw() self.fig.canvas.mpl_disconnect(self.cid) else: self.it+=1 def onclick(self, event): if event.button == 1: self._play() def _make_plot_wo_draw(self): x, y = np.where(self.board == 1) self.sc1.set_offsets(np.array([x, y]).T + 0.5) x, y = np.where(self.board == -1) self.sc2.set_offsets(np.array([x, y]).T + 0.5) obs = self.game.getCanonicalForm(self.board, self.curPlayer) if self.game.getGameEnded(self.board, self.curPlayer) == 0: policy = self.mcts.getActionProb(obs, temp=1.0) self.pcf.set_data(np.array(policy[:-1]).reshape(self.board_size).T) else: self.pcf.set_data(np.zeros(self.board_size)) self.ax.set_title('Turn {}: {}'.format(self.it, self.player_names[self.curPlayer])) def _init_plot(self): self.ax.set_xlim([0, self.board_size[1]]) self.ax.set_ylim([self.board_size[0], 0]) self.ax.axes.set_xticks(np.arange(0, self.board_size[0]+1)) self.ax.axes.set_yticks(np.arange(0, self.board_size[1]+1)) self.ax.tick_params(length=0) self.ax.set_xticklabels([]) self.ax.set_yticklabels([]) self.pcf = self.ax.pcolorfast(np.zeros(self.board_size), cmap=plt.cm.Greens, vmin=0, vmax=0.5) self.ax.grid() self.sc1 = self.ax.scatter([], [], c='black', edgecolors='gray', marker='o', s=400) self.sc2 = self.ax.scatter([], [], c='white', edgecolors='gray', marker='o', s=400) 簡単にコードの補足をします。 基本的には、左クリックで着手を再現して振り返るだけの機能です。 __init__ の中で今回も MCTS のインスタンスを用意しています。これは、各局面でどの手が良かったかを教えてくれる AI として使います。 _init_plot で AI が各局面でどの手が良いと判断しているかを表示する機能を追加しています。具体的には pcolorfast により、マスの色の濃淡で表現しています。そうすると player2 が白色の丸型マーカーだと背景色に埋もれてしまうため、灰色の線で囲むようにしました。 これで、対局を振り返りながら、どうすれば良かったのかを教えてもらえるようになりました。 振り返りの実行 tmp = GameViewer(gameui.history) ヒントモード これで対局の振り返りができるようになり、どうすれば良かったのかをAlphaZeroに教えてもらえるようになりました! しかし、私が強くなるまでには時間がかかります。アドベントカレンダーの記事の公開日も迫っています。 こうなったら! どうすれば良かったのかをAlphaZeroに対局の振り返りで教えてもらうのではなく、対局中に教えてもらいましょう! 常にAlphaZeroが教えてくれている状態だとゲームの体を成さないので、裏ワザ的に、hキーを押すとAlphaZeroが良い手を教えてくれるようにします。ヒントモードの搭載です! GameUIクラス_ヒントモードを搭載したバージョン # 元コード(https://github.com/suragnair/alpha-zero-general)のpit.py, Arena.py, OthelloPlayers.pyを参考に作成 from matplotlib import pylab as plt from MCTS import MCTS from othello.OthelloGame import OthelloGame from othello.pytorch.NNet import NNetWrapper as NNet from utils import dotdict import numpy as np class GameUI(object): def __init__(self, is_human_first=True): """ Args: is_human_first (bool) Trueなら人間が先手、Falseなら人間が後手 """ self.game = OthelloGame(8) self.curPlayer = 1 # human self.player1 = lambda x: 'Human' # AlphaZero nnet = NNet(self.game) nnet.load_checkpoint('./pretrained_models/othello/pytorch/', '8x8_100checkpoints_best.pth.tar') args = dotdict({'numMCTSSims': 50, 'cpuct':1.0}) mcts = MCTS(self.game, nnet, args) self.player2 = lambda x: np.argmax(mcts.getActionProb(x, temp=1.0)) # ForHintMode args = dotdict({'numMCTSSims': 100, 'cpuct':1.0}) self.mcts0 = MCTS(self.game, nnet, args) # game情報の初期化 if is_human_first: self.players = {-1: self.player2, 1:self.player1} self.player_names = {-1:'AlphaZero', 1:'You'} else: self.players = {-1: self.player1, 1:self.player2} self.player_names = {-1:'You', 1:'AlphaZero'} self.board = self.game.getInitBoard() self.board_size = self.board.shape self.it = 0 self.history = [] self._init_plot() self.fig.show() self._play() def _play(self): while self.game.getGameEnded(self.board, self.curPlayer) == 0: self.it += 1 self._make_plot_wo_draw() self.fig.canvas.draw() action = self.players[self.curPlayer](self.game.getCanonicalForm(self.board, self.curPlayer)) if action == 'Human': # human valids = self.game.getValidMoves(self.game.getCanonicalForm(self.board, self.curPlayer), 1) if valids[self.game.n ** 2] == 1: # パスしかできないとき action = self.game.n ** 2 self.history.append(action) self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) else: self.cid = self.fig.canvas.mpl_connect('button_press_event', self.onclick) # クリック待ち self.cid_k = self.fig.canvas.mpl_connect('key_press_event', self.onkey) break else: # AlphaZero self.history.append(action) self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) if self.game.getGameEnded(self.board, self.curPlayer): win_player = self.game.getGameEnded(self.board, 1) self._make_plot_wo_draw() self.ax.set_title('Game over: Turn {} {} Win!'.format(self.it, self.player_names[win_player])) self.fig.canvas.draw() def onclick(self, event): x = int(event.xdata) y = int(event.ydata) action = self.game.n * x + y if x != -1 else self.game.n ** 2 valids = self.game.getValidMoves(self.game.getCanonicalForm(self.board, self.curPlayer), 1) if valids[action] == 1: self.history.append(action) self.board, self.curPlayer = self.game.getNextState(self.board, self.curPlayer, action) self.pcf.set_data(np.ones(self.board_size)*0.34) self.fig.canvas.mpl_disconnect(self.cid) self.fig.canvas.mpl_disconnect(self.cid_k) self._play() else: pass def onkey(self, event): if event.key == 'h': obs = self.game.getCanonicalForm(self.board, self.curPlayer) policy = self.mcts0.getActionProb(obs, temp=1.0) self.pcf.set_data(np.array(policy[:-1]).reshape(self.board_size).T) def _make_plot_wo_draw(self): x, y = np.where(self.board == 1) self.sc1.set_offsets(np.array([x, y]).T + 0.5) x, y = np.where(self.board == -1) self.sc2.set_offsets(np.array([x, y]).T + 0.5) self.ax.set_title('Turn {}: {}'.format(self.it, self.player_names[self.curPlayer])) def _init_plot(self): self.fig = plt.figure(figsize=(4, 4), dpi=100) self.ax = self.fig.add_subplot(111) self.ax.set_xlim([0, self.board_size[1]]) self.ax.set_ylim([self.board_size[0], 0]) self.ax.axes.set_xticks(np.arange(0, self.board_size[0]+1)) self.ax.axes.set_yticks(np.arange(0, self.board_size[1]+1)) self.ax.tick_params(length=0) self.ax.set_xticklabels([]) self.ax.set_yticklabels([]) self.pcf = self.ax.pcolorfast(np.ones(self.board_size)*0.34, cmap=plt.cm.Greens, vmin=0, vmax=0.5) self.ax.grid() self.sc1 = self.ax.scatter([], [], c='black', edgecolors='gray', marker='o', s=400) self.sc2 = self.ax.scatter([], [], c='white', edgecolors='gray', marker='o', s=400) 簡単にコードの補足をします。 __init__ で、ヒント用のAlphaZeroを追加しています。しれっとヒント用のMCTSだけシミュレーション数を100に増やしているのは、勝ちたい気持ちの表れです。 _onkey で、ヒントを表示する処理を書いています。このあたりのコードはViewerの時と同じです。 実行 gameui = GameUI(is_human_first=True) さあ、いざ勝負のときです! 下記は倍速で表示しています。 おわりに 今回はおふざけな題材で、Matplotlib でインタラクティブなプロットを作る例について紹介しました。 最後は禁断のヒントモードまでフル活用して、それでも AlphaZero に勝てなかったのですが、 本記事の主目的である、Matplotlib でのインタラクティブなプロットの作り方については、概ね伝えられたのではないかと思います。 私は実際の業務でも、深層強化学習を用いたプロジェクトや画像案件などでこれらの機能を利用したことがあります。特に、深層強化学習をするような場合はデバッグやモデル改善のために、データサイエンティストが自分で可視化できた方が良いでしょう。 知っておいて損はない機能だと思いますので、皆様もぜひ遊んでみて頂ければと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】超高解像度映像「KNOT ISLAND 2155」をつくりました

はじめに 長崎にある軍艦島デジタルミュージアムの巨大プロジェクタで上映する 映像作品「KNOT ISLAND 2155」をUnityで製作しました。 カラコレ・編集以外は全部一人でやりまして、製作期間は基本日曜に作業して3ヶ月くらい。 編集や音楽のおかげもあって、かなり良い映像になりました。 軍艦島デジタルミュージアムで上映される「KNOT ISLAND 2155」をつくりました。点群データをUnityのVFX Graphで映像にしました。 pic.twitter.com/EFtmeuKgDP— ekunish (@ekunish) November 29, 2021 製作について 手順は下記の通りです。 モデルデータを間引き 建物ごとにモデルを切り出し 切り出したモデルをクレンジング ワイヤーフレーム表示用にモデルデータ書き換え Unityに取り込み VFX Graphで表示 TextMeshProでUI表示 CinemachineとTimelineでカメラワーク調整 Rendermonsterでpng書き出し ffmpegでHapに変換 モデルデータを間引き Meshlabを使い、モデルデータのvertexが1000万程度となるように調節しました。 最終的にはUnity上で25fpsくらいとほぼリアルタイムに処理できました。 建物ごとにモデルを切り出し ここは自動でやる手段が思いつかなかったので、手動です。 軍艦島の建物数十棟を目検で分けていくのは辛い作業でしたが、 Meshmixerだとモデルの形状に合わせてある程度自動で範囲選択してくれるので、 思ったより時間は掛かりませんでした。 切り出したモデルをクレンジング 手動で切り出すとどうしても細かいポリゴンが残ってしまったため、 再びMeshlabのisolation機能を使い、孤立しているパーツを削除しました。 ワイヤーフレーム表示用にモデルデータ書き換え 元々、点群っぽい表現にしたいと思い、UnityのVFX Graphの使用を想定していました。 表現の方向性を検討していく上で、ワイヤーフレーム的な表現を使うことになり、 色々調べたのですが、Particle Stripなどはあるものの、 VFX Graphでワイヤーフレーム表示をする機能は見つかりませんでした。 とりあえず、点群データのフォーマットであるplyをasciiコードで見てみると、 全頂点位置と色情報が並び、その次に各ポリゴンを形成する頂点の組み合わせが並んでいる 割とシンプルな構造であることがわかりました。 Particle Stripは、頂点データを上から順番に読んでいくので、 ポリゴンの順に頂点を並び替えたplyを作ってやれば良さそうです。 本当はunityのプラグインを書けば良いのでしょうが作法がよく分からなかったので pythonでスクリプトを書きました。 二重に被る点が出てきちゃいますが、動けば良いの精神です。 input_dir = 'インプットディレクトリ' output_dir = 'アウトプットディレクトリ' os.makedirs(output_dir, exist_ok=True) for filename in os.listdir(input_dir): base, ext = os.path.splitext(filename) if ext == '.ply': print(filename) f = open(os.path.join(input_dir, filename), 'r') linelist = f.readlines() f.close() is_header = True header = [] vertexes = [] tri_indexes = [] sorted_vertexes = [] for line in linelist: if (is_header): header.append(line) if (line == 'end_header\n'): is_header = False else: elem_num = len(line.split()) if (elem_num == 7): vertexes.append(line) elif(elem_num == 4): tri_indexes.append(line) # sort for tri_index in tri_indexes: indexes = tri_index.split() sorted_vertexes.append(vertexes[int(indexes[1])]) sorted_vertexes.append(vertexes[int(indexes[2])]) sorted_vertexes.append(vertexes[int(indexes[3])]) sorted_vertexes.append(vertexes[int(indexes[1])]) # output w = open(os.path.join(output_dir, filename), 'w') w.write('ply\n') w.write('format ascii 1.0\n') w.write('comment python converted\n') w.write('element vertex ' + str(len(sorted_vertexes)) + '\n') w.write('property float x\n') w.write('property float y\n') w.write('property float z\n') w.write('property uchar red\n') w.write('property uchar green\n') w.write('property uchar blue\n') w.write('property uchar alpha\n') w.write('element face 0\n') w.write('property list uchar int vertex_indices\n') w.write('end_header\n') w.writelines(sorted_vertexes) w.close() Unityに取り込み @keijiro さんのプラグインを使わせていただきました。 読み込んだら、タイプをTextureに変更。 VFX Graphで表示 前述の通り、Particle Stripを使いました。 スクリプトで処理するため、Blackboardでパラメータを外に出しておきます。 注意点はParticleのCapacityです。 建物ごとにVFX Graphデータをつくるにあたり、 最初は、全部のCapacityを1000万とかにしてたのですが、 3棟ほど動かしたあたりでUnityが動かなくなりました。 1日くらい原因が分からず、そもそもVFX Graphの限界なのではと焦ったのですが、 どうやらCapacityが確保するメモリやらの値のようで、 モデルに合わせてやることで解決しました。 TextMeshProでUI表示 文字の色やサイズを変更するため、TextMeshProをつかいました。 htmlっぽいタグで書けるので楽チン。 CinemachineとTimelineでカメラワーク調整 最初はスクリプトでどうにかできるかなと思っていたのですが、 ダイナミックなカメラワークにするため、Cimemachineを使いました。 軌道を設計するにあたってはSmooth pathが使いやすかったです。 各種パラメーターの一部はTimelineで処理してます。 グラフィカルに変数を調整できるのが良いです。 もう少しAfterEffectっぽい感覚に近づけるともっと使いやすくなりそうです。 Render Monsterでpng書き出し 今回のプロジェクタが8K超えの横長超高解像度で、 Unity Recorderでは書き出せませんでした。 また、そもそもmp4も規格として4Kだか8Kまでしか対応していないようで、 Render Monsterを使い、連番のpngで書き出しました。 シーン再生は25FPSくらい出てたんですが、書き出しは1FPSほどでかなり時間掛かりました。 ffmpegでHapに変換 動画は普通ならProResコーデックで良いと思いますが、 TouchdesignerではProResが読み込めませんでした。 調べてみると、Hapには対応しているようだったので、ffmpegで変換しました。 最初再生したらどうやってもカクカクで、どうしようかと思いましたが、 Perfome Modeだと滑らかに動いたので良かったです。 最後に 点群を扱うのも初めてですが、Unityで初めて本格的な映像をつくってみました。 やることも多かったですが、学ぶことが多かったです。 1000万超えの点群をリアルタイムで処理できるVFX Graphはすごいです。 Cinemachineも初めて使ってみましたが、かなり直感的にカメラワークを設計できました。 20万で作ったWindowsを有効活用できてよかったです。 しばらくは常設で上映されると思うので、軍艦島デジタルミュージアムにぜひ行ってみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

量子情報理論の基本:Lattice Surgery

$$ \def\bra#1{\mathinner{\left\langle{#1}\right|}} \def\ket#1{\mathinner{\left|{#1}\right\rangle}} \def\braket#1#2{\mathinner{\left\langle{#1}\middle|#2\right\rangle}} $$ はじめに 前回の記事で量子誤り訂正符号について一区切り付いたと言いましたが、やはりこれはどうしても外せません。というわけで、今回は「Lattice Surgery」を取り上げます。Braidingは格子状に敷き詰められた量子ビット集団に欠陥対を形成しそれを論理量子ビットと見立てて、欠陥を動き回らせることで論理演算を実現するのでした。物理量子ビットに対する演算はすべて局所的に行われるので(つまり、遠距離にある物理量子ビット同士の演算が一切ないので)、ハードウェア的に無理がない実現方式ではあるのですが1、いかんせん用意すべき物理量子ビットが膨大になりがちという欠点がありました。それに対して、平面符号(planar code)はBraidingのように量子ビットを浪費しないのですが、論理的な2量子ビット演算を実現しようとするとどうしても量子ビット同士の遠隔演算が必要になるという欠点がありました。Lattice Surgeryは平面符号をベースにしつつ、局所的な量子演算だけで論理的な2量子ビット演算が実現できる、とっても賢い手法です。本記事では、どういった理屈でそんなことができるのかを勉強します。そして、一通り理解できたところで、量子計算シミュレータqlazyを使ってその演算の一例をシミュレーションしてみます。 参考にさせていただいたのは、以下の文献です。 Clare Horseman, Austin G. Flower, Simon Devitt, Rodney Van Meter, "Surface code quantum computing by lattice surgery", arXiv:1111.4022v3 [quant-ph] 理論説明 平面符号(planar code) まずLattice Surgeryのベースになっている平面符号について説明します。 定義 上図左に示すような格子状に量子ビットを並べ、上図中に示したようにスタビライザーを配備します($X$スタビライザーを黄色、$Z$スタビライザーを緑色で示しました)。それらスタビライザーの同時固有状態を論理符号空間とみなすというのが平面符号です。ここでご注意いただきたいのは、上下の辺に位置するスタビライザーは3端子の$X$スタビライザーになっていて、左右の辺に位置するスタビライザーは3端子の$Z$スタビライザーになっているということです。これが平面符号の特徴です。上下の辺のことを$X$境界(または、smooth boundary)、左右の辺のことを$Z$境界(または、rough boundary)と呼びます。後で、"Lattice"を"Surgery"する際にどっちの境界で考えるかが大事なポイントになりますので覚えておきましょう。さて、上図中には、$6$個の$X$スタビライザーと$6$個の$Z$スタビライザーが存在しますが、各々を$\bar{X_i}, \space \bar{Z_i} \space (i=1,2,\cdots,6)$と表すことにすると、その同時固有状態は、 C = <\bar{X_1}, \bar{X_2}, \cdots, \bar{X_6}, \bar{Z_1}, \bar{Z_2}, \cdots, \bar{Z_6}> \tag{1} のようにスタビライザー形式(スタビライザー群の生成元の集まり)で表すことができます。つまり、これら生成元の任意の積を演算しても不変になる状態(同時固有状態)をこの$C$によって規定して、それを論理符号空間とみなすわけです。ここで、$X$スタビライザーの添字は対応した頂点の番号(その$X$スタビライザーを間接測定するためのアンシラの番号)を表しており、$Z$スタビライザーの添字は対応した面の番号(その$Z$スタビライザーを間接測定するためのアンシラの番号)を表していると思ってください(上図では各々黄色と緑色のひし形で表しました)。$\bar{X_i}, \space \bar{Z_i}$を明示的に列挙すると、 \begin{align} \bar{X_1} &= X_{1} X_{2} X_{4} \\ \bar{X_2} &= X_{2} X_{3} X_{5} \\ \bar{X_3} &= X_{4} X_{6} X_{7} X_{9} \\ \bar{X_4} &= X_{5} X_{7} X_{8} X_{10} \\ \bar{X_5} &= X_{9} X_{11} X_{12} \\ \bar{X_6} &= X_{10} X_{12} X_{13} \tag{2} \end{align} \begin{align} \bar{Z_1} &= Z_{1} Z_{4} Z_{6} \\ \bar{Z_2} &= Z_{2} Z_{4} Z_{5} Z_{7} \\ \bar{Z_3} &= Z_{3} Z_{5} Z_{8} \\ \bar{Z_4} &= Z_{6} Z_{9} Z_{11} \\ \bar{Z_5} &= Z_{7} Z_{9} Z_{10} Z_{12} \\ \bar{Z_6} &= Z_{8} Z_{10} Z_{13} \tag{3} \end{align} です。いま考えている物理量子ビットの数は13個で、式(1)で示した$X$および$Z$スタビライザーの個数は全部で12個です。1ビット分の自由度が残っているので、これで1つの論理量子ビットを表現できることになります2。 この論理符号空間上にある論理状態を$\ket{\Psi}_{L}$とすると、定義によりこれにスタビライザーを演算しても不変になります。すなわち、 \begin{align} \bar{X_i} \ket{\Psi}_{L} &= \ket{\Psi}_{L} \\ \bar{Z_i} \ket{\Psi}_{L} &= \ket{\Psi}_{L} \space (i = 1,2,\cdots,6) \tag{4} \end{align} を満たします。別の言い方をすると、ある論理状態に対してどのスタビライザーを間接測定したとしても必ず$+1$という結果が得られます。表面符号においては、この性質を誤り訂正に利用します。つまり、どこかにビット反転エラーまたは位相反転エラーが発生したら、その付近にあるスタビライザーの測定結果が$-1$になるので、エラーが発生した箇所が推定できて、そのビットに逆演算を施すというのが誤り訂正の基本的な考え方でした(詳細は量子情報理論の基本:トポロジカル表面符号をご参照ください)。 さて、この上で何らかの論理演算を行いたいのですが、それはどのようなものでしょうか。どれか少なくとも一つのスタビライザーと非可換な演算子は式(1)の論理符号空間から状態をはみ出させる効果を持つので、論理演算子にはなり得ません。すべてのスタビライザーと可換な演算子を持ってこないといけないのですが、それをスタビライザーの積で構成してしまうと(つまり式(1)のスタビライザーに従属なものにしてしまうと)、論理状態をまったく変化させませんので、これも論理演算子にはなり得ません。結局、すべてのスタビライザーと可換であり、かつ、それらと独立なものを持ってこないといけません。試しに、そのような演算子のひとつを$Q$とおいてみると、$\bar{X_i} Q \ket{\Psi}_{L} = Q \ket{\Psi}_{L}, \space \bar{Z_i} Q \ket{\Psi}_{L} = Q \ket{\Psi}_{L}$が成り立ち、式(1)のスタビライザーによって状態は不変となり(つまり論理符号空間からはみ出さず)、かつ、$\ket{\Psi}_{L}$とは別の論理状態$Q \ket{\Psi}_{L} \neq \ket{\Psi}_{L}$に変わります。というわけで、確かにこれは論理演算子ということになります。 では、基本的な論理演算子として、論理パウリ$Z$演算子がどのようなものとして定義できるかを考えてみましょう。すべてのスタビライザーと可換で独立な演算子として、何でも良いので適当に$Z_{L}$なるものをもってきて3、それを式(1)で定義された論理符号空間に加えて、 S_{0} = <\bar{X_1}, \bar{X_2}, \cdots, \bar{X_6}, \bar{Z_1}, \bar{Z_2}, \cdots, \bar{Z_6}, Z_{L}> \tag{5} という状態を作ってみます。スタビライザー群の生成元の数と物理量子ビット数が一致するので、これで状態はバッチリ特定されることになります。どんな状態に特定されるかというと、式(1)で定義された論理符号空間内の状態であり、かつ、$Z_{L}$の固有値$+1$に対する固有状態になります。この固有状態を論理的な$\ket{0}$状態とみなして$\ket{0}_{L}$と書くことにすると、 Z_{L} \ket{0}_{L} = \ket{0}_{L} \tag{6} が成り立ちます。また、式(1)に$Z_{L}$を加える代わりに$-Z_{L}$を加えて、 S_{1} = <\bar{X_1}, \bar{X_2}, \cdots, \bar{X_6}, \bar{Z_1}, \bar{Z_2}, \cdots, \bar{Z_6}, -Z_{L}> \tag{7} という状態を作ると、これは$Z_{L}$の固有値$-1$に対する固有状態になっていますので、論理的な$\ket{1}$状態とみなして、$\ket{1}_{L}$と書くことにすると、 Z_{L} \ket{1}_{L} = -\ket{1}_{L} \tag{8} が成り立ちます。どうでしょうか。$Z_{L}$が$Z$演算子のように見えてきますよね。というわけで、この$Z_{L}$を論理パウリ$Z$演算子と定義することにします。あれ?$Z_{L}$って何でもいいものを適当に持ってきたんじゃなかったっけ?こんないい加減な決め方でいいの?という声が聞こえてきそうですが、これでいいんです。つまり、論理的な計算基底をどう決めるかということなのですが、任意性があるという話ですね。ですが、通常は、上図右に示したように左右の$Z$境界(rough boundary)同士を横方向につないだ$Z$演算子の積を論理パウリ演算子$Z_{L} = Z_{1} Z_{2} Z_{3}$として定義したりします4(赤色の線で示しました)。 論理パウリ$Z$演算子が定義できたので、次に論理パウリ$X$演算子を定義します。$Z_{L}$と反可換で式(1)のすべてのスタビライザーと可換なものを選べば良いです。上図右で上下の$X$境界(smooth boundary)を縦方向につないだ$X$演算子の積$X_{L} = X_{1} X_{6} X_{11}$をもってくればその条件を満たします(青色の線で示しました)5。 $X_{L}$の固有値$+1$および$-1$に対する固有状態$\ket{+}_{L}, \ket{-}_{L}$をスタビライザー形式で書くと、 \begin{align} S_{+} &= <\bar{X_1}, \bar{X_2}, \cdots, \bar{X_6}, \bar{Z_1}, \bar{Z_2}, \cdots, \bar{Z_6}, X_{L}> \\ S_{-} &= <\bar{X_1}, \bar{X_2}, \cdots, \bar{X_6}, \bar{Z_1}, \bar{Z_2}, \cdots, \bar{Z_6}, -X_{L}> \tag{9} \end{align} です。ここで、$S_{+}$は$\ket{+}_{L}$、$S_{-}$は$\ket{-}_{L}$を規定するスタビライザー群を表しており、 \begin{align} X_{L} \ket{+}_{L} &= \ket{+}_{L} \\ X_{L} \ket{-}_{L} &= - \ket{-}_{L} \tag{10} \end{align} が成り立っています。また、以上のことから容易に、 \begin{align} X_{L} \ket{0}_{L} &= \ket{1}_{L} \\ X_{L} \ket{1}_{L} &= \ket{0}_{L} \\ Z_{L} \ket{+}_{L} &= \ket{-}_{L} \\ Z_{L} \ket{-}_{L} &= \ket{+}_{L} \tag{11} \end{align} が示せますので、$X_{L}$、$Z_{L}$は各々論理符号空間上でのビット反転演算子、位相反転演算子と思って良いです。さらに式(11)より、$\ket{+}_{L}$は$\ket{0}_{L}$と$\ket{1}_{L}$を足したものに比例していて、$\ket{+}_{L}$は$\ket{0}_{L}$から$\ket{1}_{L}$を引いたものに比例していることがわかり、各々1に正規化されているとすると、 \begin{align} \ket{+}_{L} &= \frac{1}{\sqrt{2}} (\ket{0}_{L} + \ket{1}_{L}) \\ \ket{-}_{L} &= \frac{1}{\sqrt{2}} (\ket{0}_{L} - \ket{1}_{L}) \tag{12} \end{align} が成り立ちます。 初期状態の作成 実際に平面符号で論理演算を行うためには、最初に式(5)で示したような初期論理状態を作成する必要があります。さて、どうすれば良いでしょうか。とにかく式(5)上でズラッと並んだ生成元に対する同時固有状態を作るということなので、何か適当な状態を用意しておいて各々の演算子の固有空間への射影演算を次々に適用していけば良さそうです。具体的には、すべての物理量子ビットが$\ket{0}$になっている状態$\ket{00 \cdots 0}$に対して、 \ket{0}_{L} = \prod_{i=1}^{6} \frac{I+\bar{X_i}}{2} \prod_{j=1}^{6} \frac{I+\bar{Z_i}}{2} \frac{I+Z_{L}}{2} \ket{00 \cdots 0} \tag{13} という射影演算を実行すれば良いです6。ここで各射影演算は互いに可換、かつ、$Z$スタビライザーによる射影と$Z_{L}$演算は$\ket{00 \cdots 0}$を不変に保つので、式(13)は、 \ket{0}_{L} = \prod_{i=1}^{6} \frac{I+\bar{X_i}}{2} \ket{00 \cdots 0} \tag{14} としても同じことです。数学的にはこれで終了なのですが、実際の量子回路で実行する際にはこの射影は測定操作になります。量子回路で書くと、 です。最後のアンシラの測定値が$+1$($\ket{0}$)だった場合$\bar{X_i}$の固有値$+1$の固有状態に射影されますが、測定値が$-1$($\ket{1}$)だった場合固有値$-1$の固有状態に射影されます。したがって、$\bar{X_i}$の測定値が$\epsilon_{i}$だったとすると、出来上がる状態は、 S_{0}^{\prime} = <\epsilon_{1} \bar{X_1}, \epsilon_{2} \bar{X_2}, \cdots, \epsilon_{6} \bar{X_6}, \bar{Z_1}, \bar{Z_2}, \cdots, \bar{Z_6}, Z_{L}> \tag{15} となります。つまり、出来上がる$\ket{0}_{L}$の中身は確率的に変わるということになるのですが、各スタビライザーの測定値を覚えておけば後の論理演算をこのまま実行しても全然問題ないです(と思います)。気持ち悪い人は、測定値が$-1$になったら当該スタビライザーと反可換で他のスタビライザーと可換な演算子を演算することで固有値$-1$の固有状態を$+1$に反転させることができます。具体的には、以下のような演算子を演算すれば良いです。 そうすると、きれいな状態、 S_{0} = <\bar{X_1}, \bar{X_2}, \cdots, \bar{X_6}, \bar{Z_1}, \bar{Z_2}, \cdots, \bar{Z_6}, Z_{L}> \tag{16} が得られます。が、現実的にはコヒーレンス時間は限られているので、このような反転演算を実際にやるのはよろしくないです。いずれにしろ、これで$\ket{0}_{L}$(式(15)または式(16)の形)が出来上がったことになります。$\ket{1}_{L}$はこの$\ket{0}_{L}$に$X_{L}$を演算すれば得られます。 では、$\ket{+}_{L}$を得たい場合はどうすれば良いでしょうか。後で説明する論理アダマール演算を使っても良いですが、初期化しておいてさらにアダマール演算というのはちょっと面倒です。上に類似した初期化プロセスのみで実現できた方がスマートです。どうやるかというと、最初にすべての量子ビットを$\ket{+}$に初期化しておいて、$Z$スタビライザーを順々に間接測定していきます。これで、 S_{+}^{\prime} = <\bar{X_1}, \bar{X_2}, \cdots, \bar{X_6}, \lambda_{1} \bar{Z_1}, \lambda_{2} \bar{Z_2}, \cdots, \lambda_{6} \bar{Z_6}, X_{L}> \tag{17} という状態$\ket{+}_{L}$が得られます。ここで$\lambda_{i}$は$\bar{Z_i}$の測定値です。先ほどのようにきれいな状態を得たい場合は、測定値が$-1$だった場合、そのスタビライザーとのみ反可換な演算子を演算すれば固有値を反転させることができます。結果、 S_{+} = <\bar{X_1}, \bar{X_2}, \cdots, \bar{X_6}, \bar{Z_1}, \bar{Z_2}, \cdots, \bar{Z_6}, X_{L}> \tag{18} が得られます。$\ket{-}_{L}$が欲しい場合は$\ket{+}_{L}$に$Z_{L}$を演算すれば良いです。 マジック状態注入 $\ket{0}_{L}, \ket{1}_{L}, \ket{+}_{L}, \ket{-}_{L}$を作成する方法はわかりましたが、一般の論理状態、 \ket{\Psi}_{L} = \alpha \ket{0}_{L} + \beta \ket{1}_{L} \tag{19} を得たい場合があります。論理$T$演算子のような非クリフォード演算を実現する際にはある特定の状態(マジック状態)をアンシラとして用意しておく必要がありました。そんな場合、式(19)の状態を作りたくなるわけですが、それは、以下のようにすれば作れます。この操作のことをマジック状態注入(magic state injection)と呼びます。 まず、上図左のように格子状に並んだ量子ビットを考えて、その左端中央の量子ビットを$\alpha \ket{0} + \beta \ket{1}$に初期化し、その他の量子ビットをすべて$\ket{0}$に初期化しておきます。次に、左端中央の量子ビット(6番)とその上の量子ビット(1番)に6番を制御ビットにしたCNOTを演算し、さらに左端中央とその下の量子ビット(11番)に6番を制御ビットにしたCNOTを演算します。そうすると、左端縦1列(上図中の紫色の量子ビット)の状態は、 \alpha \ket{000} + \beta \ket{111} \tag{20} になります(上図中)。さらに、$6$個ある$X$スタビライザーを順に間接測定していきます。これで、式(19)の論理状態が得られたことになります(上図右の紫色の量子ビット全体で式(18)の論理状態を構成します)7。 「得られたことになります」とサラッと言いましたが本当にそうなるのか、数式の上で確認してみます。 式(20)から出発することにします。すべての量子ビットに関してきちんと書くと、 \begin{align} &(\alpha \ket{0}_{1} \ket{0}_{6} \ket{0}_{11} + \beta \ket{1}_{1} \ket{1}_{6} \ket{1}_{11}) \ket{0}_{2} \ket{0}_{3} \ket{0}_{4} \ket{0}_{5} \ket{0}_{7} \ket{0}_{8} \ket{0}_{9} \ket{0}_{10} \ket{0}_{12} \ket{0}_{13} \\ &= \alpha \ket{00 \cdots 0} + \beta X_{1} X_{6} X_{11} \ket{00 \cdots 0} \tag{21} \end{align} です(下付きの添字は量子ビット番号を表します)。この状態に対して$X$スタビライザーを順に間接測定していきます。まず第1項について考えると、これは先ほどの初期化でやったように以下のような射影演算に相当します(測定によって確率的に射影後の結果が変わることを$\pm$で表わしています)。 \prod_{i=1}^{6} \frac{I \pm \bar{X_i}}{2} \alpha \ket{00 \cdots 0} = \alpha \ket{0}_{L} \tag{22} というわけで、これは$\ket{0}_{L}$の$\alpha$倍です。同様に第2項は、$X_{1} X_{6} X_{11}$が論理パウリ$X$演算子$X_L$だったことを思い出すと、 \prod_{i=1}^{6} \frac{I \pm \bar{X_i}}{2} \beta X_{1} X_{6} X_{11} \ket{00 \cdots 0} = \beta X_{L} \ket{0}_L = \beta \ket{1}_L \tag{23} となり、これは$\ket{1}_L$の$\beta$倍です。式(22)と式(23)を合わせると、結局、 \alpha \ket{0}_{L} + \beta \ket{1}_{L} \tag{24} が得られたことになります。この方法の肝は最初に論理$X$演算子のチェーンに相当する量子ビットに対して式(20)のような状態を作成するということです。いまたまたま左端の1列の量子ビットに対して式(20)を作りましたが、真ん中もしくは右端のどっちかの縦1列で式(20)を作っておいても良いです。さらに大きな符号距離をもった論理状態を作りたい場合は、最初にサイズの大きな格子を用意しておいて上と同様の手続きを実行すれば良いです。が、一旦小さいサイズの格子で論理状態を作っておいて、後から格子サイズを拡張するやり方もありますので、次に詳しく見ていきます。 格子の拡張 まず、左右にrough boundary、上下にsmooth boundaryがある格子を横方向(つまりrough boundaryを横にずらす方向)に拡張することを考えます。 いま上図左の論理状態が、 \alpha \ket{0}_{L} + \beta \ket{1}_{L} \tag{25} だったとします。この格子に対して右側に隣接する形で量子ビットを追加します(上図右)。ただ追加しただけでは何も起きません。ある操作を加えることで、拡張された格子上でも \alpha \ket{0}_{L}^{new} + \beta \ket{1}_{L}^{new} \tag{26} という状態にできます。横方向に増えたスタビライザーも含めた同時固有状態にしたいので、何となく新しいスタビライザーを間接測定すれば良さそうに思えます。例えば、新たに追加した量子ビットをすべて$\ket{0}$に初期化しておき、$X$スタビライザーを間接測定したらどうでしょうか($Z$スタビライザーを間接測定しても何も起きないので無駄なことはしません)、という当たりをつけてやってみます。 式(25)の第1項の$\ket{0}_{L}$と第2項の$\ket{1}_{L}$に分けて考えます。まず、$\ket{0}_{L}$についてです。量子ビットを追加してすべて$\ket{0}$に初期化した直後の状態は、 <\bar{X_1}, \cdots ,\bar{X_6}, \bar{Z_1}, \cdots ,\bar{Z_6}, Z_L, Z_{14}, Z_{15}, Z_{16}, Z_{17}, Z_{18}> \tag{27} です。もとの$\bar{Z_3}$と$\bar{Z_6}$は3端子なのですが、横方向に増えたので各々$Z_{15}$と$Z_{17}$をかけて$\bar{Z_3} = Z_{3} Z_{5} Z_{15} Z_{8}, \space \bar{Z_6} = Z_{8} Z_{10} Z_{17} Z_{13}$という風に4端子にしておきます。また、$Z_L$は$Z_{14}$をかけて$Z_{L}^{new} = Z_{1} Z_{2} Z_{3} Z_{14}$としておきます。そうすると、式(27)は、 <\bar{X_1}, \cdots ,\bar{X_6}, \bar{Z_1}, \cdots ,\bar{Z_6}, Z_{L}^{new}, Z_{14}, Z_{15}, Z_{16}, Z_{17}, Z_{18}> \tag{28} となります。さらに、5つの孤立した$Z_{14}, Z_{15}, Z_{16}, Z_{17}, Z_{18}$たちを適当に掛け合わせて、3端子の$Z$スタビライザーを2つひねり出します。 \begin{align} &<\bar{X_1}, \cdots ,\bar{X_6}, \bar{Z_1}, \cdots ,\bar{Z_6}, Z_{L}^{new}, Z_{14} Z_{15} Z_{16}, Z_{16} Z_{17} Z_{18}, Z_{14}, Z_{16}, Z_{18}> \\ &= <\bar{X_1}, \cdots ,\bar{X_6}, \bar{Z_1}, \cdots ,\bar{Z_6}, Z_{L}^{new}, \bar{Z_7}, \bar{Z_8}, Z_{14}, Z_{16}, Z_{18}> \\ &= <\bar{X_1}, \cdots ,\bar{X_6}, \bar{Z_1}, \cdots ,\bar{Z_8}, Z_{L}^{new}, Z_{14}, Z_{16}, Z_{18}> \tag{29} \end{align} こう変形しておいてから、おもむろに$X$スタビライザーを測定します。まず、$X_{3} X_{14} X_{15} = \bar{X_7}$を測定します。これに反可換なものは$Z_{14}$しかありません。測定値は$+1$か$-1$になるのですが、それを$\epsilon_{7}$と書くことにすると、 <\bar{X_1}, \cdots ,\bar{X_6}, \bar{Z_1}, \cdots ,\bar{Z_8}, Z_{L}^{new}, \epsilon_{7} \bar{X_7}, Z_{16}, Z_{18}> \tag{30} となります。同じように、$X_{15} X_{8} X_{16} X_{17} = \bar{X_8}$および$X_{17} X_{13} X_{18} = \bar{X_9}$を測定すると、 \begin{align} &<\bar{X_1}, \cdots ,\bar{X_6}, \bar{Z_1}, \cdots ,\bar{Z_8}, Z_{L}^{new}, \epsilon_{7} \bar{X_7}, \epsilon_{8} \bar{X_8}, \epsilon_{9} \bar{X_9}> \\ &= <\bar{X_1}, \cdots ,\bar{X_6}, \epsilon_{7} \bar{X_7}, \epsilon_{8} \bar{X_8}, \epsilon_{9} \bar{X_9}, \bar{Z_1}, \cdots ,\bar{Z_8}, Z_{L}^{new}> \tag{31} \end{align} となります。したがって、新しい論理パウリ$Z$演算子$Z_{L}^{new}$の固有値$+1$に対する固有状態になることがわかります。この状態を$\ket{0}_{L}^{new}$と定義しておきます。 もう一方の$\ket{1}_{L}$については式(27)の$Z_{L}$の符号をマイナスになったものから出発します。量子ビットを追加してすべて$\ket{0}$に初期化した直後の状態は、 <\bar{X_1}, \cdots ,\bar{X_6}, \bar{Z_1}, \cdots ,\bar{Z_6}, -Z_L, Z_{14}, Z_{15}, Z_{16}, Z_{17}, Z_{18}> \tag{32} となっており、上と同じ式展開により、 <\bar{X_1}, \cdots ,\bar{X_6}, \epsilon_{7} \bar{X_7}, \epsilon_{8} \bar{X_8}, \epsilon_{9} \bar{X_9}, \bar{Z_1}, \cdots ,\bar{Z_8}, -Z_{L}^{new}> \tag{33} となるので、これは新しい論理パウリ$Z$演算子$Z_{L}^{new}$の固有値$-1$に対する固有状態です。この状態を$\ket{1_L}^{new}$と定義します8。 結局、このような横方向の拡張によって、 \alpha \ket{0}_{L} + \beta \ket{1}_{L} \Rightarrow \alpha \ket{0}_{L}^{new} + \beta \ket{1}_{L}^{new} \tag{34} ができたことになります。 次に、縦方向(つまりsmooth boundaryを下にずらす方向)に拡張することを考えます。これは、$X$スタビライザーと$Z$スタビライザーの立場をまるごと入れ替えると、まさに横方向の拡張で考えたことと全く同じになります。というわけで、下方向に用意した量子ビットを$\ket{0}$ではなく$\ket{+}$に初期化しておき、$X$スタビライザーではなく$Z$スタビライザーを測定すれば良いということになります。 上図左の論理状態が、 \alpha \ket{0}_{L} + \beta \ket{1}_{L} = \alpha^{\prime} \ket{+}_{L} + \beta^{\prime} \ket{-}_{L} \tag{35} \begin{align} \alpha^{\prime} &= \frac{1}{\sqrt{2}} (\alpha + \beta) \\ \beta^{\prime} &= \frac{1}{\sqrt{2}} (\alpha - \beta) \tag{36} \end{align} だったとします。この格子に対して下側に隣接する形で量子ビットを追加してすべて$\ket{+}$に初期化しておきます(上右図)。 $\ket{+}_{L}$に関して、量子ビットを追加して初期化した直後の状態は、 <\bar{X_1}, \cdots ,\bar{X_6}, \bar{Z_1}, \cdots ,\bar{Z_6}, X_L, X_{14}, X_{15}, X_{16}, X_{17}, X_{18}> \tag{37} です。先ほどと同様に伸ばしたい境界に位置する3端子のスタビライザー($X$スタビライザー)を4端子にして、$X_L = X_{1} X_{6} X_{11}$を新しい$X_{L}^{new} = X_{1} X_{6} X_{11} X_{16}$に変えて、さらに6個の$Z$スタビライザーを順に測定していくと、 <\bar{X_1}, \cdots ,\bar{X_8}, \bar{Z_1}, \cdots ,\bar{Z_6}, \lambda_{7} \bar{Z_7}, \lambda_{8} \bar{Z_8}, \lambda_{9} \bar{Z_9}, X_{L}^{new}> \tag{38} となります。ここで$Z$スタビライザー$Z_{i}$の測定値を$\lambda_{i}$と置きました。これは新しい論理パウリ$X$演算子$X_{L}^{new}$の固有値$+1$に対する固有状態です($\ket{+}_{L}^{new}$と定義します)。 一方、$\ket{-}_{L}$に関して、同様に6個の$Z$スタビライザーを順に測定すると、 <\bar{X_1}, \cdots ,\bar{X_8}, \bar{Z_1}, \cdots ,\bar{Z_6}, \lambda_{7} \bar{Z_7}, \lambda_{8} \bar{Z_8}, \lambda_{9} \bar{Z_9}, -X_{L}^{new}> \tag{39} となります。これは新しい論理パウリ$X$演算子$X_{L}^{new}$の固有値$-1$に対する固有状態です($\ket{-}_{L}^{new}$と定義します)9。 結局、このような縦方向の拡張によって、 \begin{align} &\alpha \ket{0_L} + \beta \ket{1_L} = \alpha^{\prime} \ket{+_{L}} + \beta^{\prime} \ket{-_{L}} \\ &\Rightarrow \alpha^{\prime} \ket{+}_{L}^{new} + \beta^{\prime} \ket{-}_{L}^{new} = \alpha \ket{0_L}^{new} + \beta \ket{1_L}^{new} \tag{40} \end{align} ができたことになります10。 この横方向と縦方向の拡張を繰り返し実行することで、下図に示したように任意の符号距離の論理状態を作成することができます。 格子の縮小 では、逆に格子を縮小するにはどうすれば良いでしょうか。 横方向の縮小は横方向の拡張の逆をイメージすれば良いです。つまり、左の端にある量子ビットを$Z$測定して$\ket{0}$(または$\ket{1}$)に戻します(下図)。 本当にこれで良いのか数式の上で確認してみます。上図左の格子が$\alpha \ket{0}_{L} + \beta \ket{1}_{L}$を表しているとして$\ket{0}_{L}$と$\ket{1}_{L}$が各々縮小操作でどう変化するかを考えます。まず、$\ket{0}_{L}$はスタビライザー形式で書くと、 <\bar{X_1}, \cdots ,\bar{X_9}, \bar{Z_1}, \cdots ,\bar{Z_8}, Z_L> \tag{41} です。ここで、$Z_L = Z_{1} Z_{2} Z_{3} Z_{4}$としておきます。まず1番右の$Z_{4}, Z_{11}, Z_{18}$を測定します。反可換な演算子は各々$\bar{X_3}, \bar{X_6}, \bar{X_9}$です。その測定値を各々$\mu_{4}, \mu_{11}, \mu_{18}$とすると、測定後の状態は、 <\bar{X_1}, \bar{X_2}, \bar{X_4}, \bar{X_5}, \bar{X_7}, \bar{X_8}, \mu_{4} Z_{4}, \mu_{11} Z_{11}, \mu_{18} Z_{18}, \bar{Z_1}, \cdots ,\bar{Z_8}, Z_L> \tag{42} となります。また、3端子の2つの$Z$スタビライザー$\bar{Z_4}, \bar{Z_8}$に$\mu_{4} Z_{4}, \mu_{11} Z_{11}, \mu_{18} Z_{18}$を適当に選んでかけると、 <\bar{X_1}, \bar{X_2}, \bar{X_4}, \bar{X_5}, \bar{X_7}, \bar{X_8}, \mu_{4} Z_{4}, \mu_{11} Z_{11}, \mu_{18} Z_{18}, \bar{Z_1}, \bar{Z_2}, \bar{Z_3}, \bar{Z_5}, \bar{Z_6}, \bar{Z_7}, \mu_{4} \mu_{11} Z_{7}, \mu_{11} \mu_{18} Z_{14}, Z_L> \tag{43} のように単体の$Z$演算子に変えることができます。さらに、4端子の$\bar{Z_3},\bar{Z_7}$に各々$\mu_{4} \mu_{11} Z_{7}, \mu_{11} \mu_{18} Z_{14}$をかけて3端子にして、論理パウリ$Z$演算子に$\mu_{4} Z_{4}$をかけて$Z_{L}^{new} = \mu_{4} Z_{1} Z_{2} Z_{3}$を定義すると、 <\bar{X_1}, \bar{X_2}, \bar{X_4}, \bar{X_5}, \bar{X_7}, \bar{X_8}, \mu_{4} Z_{4}, \mu_{11} Z_{11}, \mu_{18} Z_{18}, \bar{Z_1}, \bar{Z_2}, \mu_{4} \mu_{11} \bar{Z_3}, \bar{Z_5}, \bar{Z_6}, \mu_{11} \mu_{18} \bar{Z_7}, \mu_{4} \mu_{11} Z_{7}, \mu_{11} \mu_{18} Z_{14}, Z_{L}^{new}> \tag{44} となります。整理すると、 <\bar{X_1}, \bar{X_2}, \bar{X_4}, \bar{X_5}, \bar{X_7}, \bar{X_8}, \bar{Z_1}, \bar{Z_2}, \mu_{4} \mu_{11} \bar{Z_3}, \bar{Z_5}, \bar{Z_6}, \mu_{11} \mu_{18} \bar{Z_7}, Z_{L}^{new}> \otimes <\mu_{4} Z_{4}, \mu_{4} \mu_{11} Z_{7}, \mu_{11} Z_{11}, \mu_{11} \mu_{18} Z_{14}, \mu_{18} Z_{18}> \tag{45} となり、後半を無視して、 <\bar{X_1}, \bar{X_2}, \bar{X_4}, \bar{X_5}, \bar{X_7}, \bar{X_8}, \bar{Z_1}, \bar{Z_2}, \mu_{4} \mu_{11} \bar{Z_3}, \bar{Z_5}, \bar{Z_6}, \mu_{11} \mu_{18} \bar{Z_7}, Z_{L}^{new}> \tag{46} とすれば所望の縮小された論理状態を得ることができます。ここで、注意しておきたいことが一つあります。式(46)に含まれる新しい論理パウリ$Z$演算子は、 Z_{L}^{new} = \mu_{4} Z_{1} Z_{2} Z_{3} \tag{47} になっています。つまり、$Z_{4}$の測定値が$-1$だった場合、$Z_{1} Z_{2} Z_{3}$の固有値$-1$の固有状態になりますので、縮小したら論理演算子のチェーンも単純に短くなるのである、と考えていると痛い目を見ます。測定値に応じて論理パウリ$Z$演算子の符号が反転するのでそれを考慮して後の測定結果を解釈する必要があります11。 次に、縦方向の縮小について考えます。横方向の縮小で$X$と$Z$の立場を入れ替えて考えれば良いです。つまり、下端の量子ビットを$X$測定して$\ket{+}$(または$\ket{-}$)に戻します。 一応、数式の上で確認してみます。上図左の格子が、 \alpha \ket{0}_{L} + \beta \ket{1}_{L} = \alpha^{\prime} \ket{+}_{L} + \beta^{\prime} \ket{-}_{L} \tag{48} を表しているとして$\ket{+}_{L}$と$\ket{-}_{L}$が各々縮小操作でどう変化するかを考えます。まず、$\ket{+}_{L}$についてです。最初の状態は、 <\bar{X_1}, \cdots ,\bar{X_9}, \bar{Z_1}, \cdots ,\bar{Z_8}, X_L> \tag{49} です。ここで、$X_L = X_{1} X_{6} X_{11} X_{16}$としておきます。下端の$X_{14}, X_{15}, X_{16}, X_{17}, X_{18}$を測定しますが、そのときの測定値を各々$\nu_{14}, \nu_{15}, \nu_{16}, \nu_{17}, \nu_{18}$とすると、 <\bar{X_1}, \bar{X_2}, \bar{X_3}, \bar{X_4}, \nu_{16} \nu_{17} \bar{X_5}, \nu_{17} \nu_{18} \bar{X_6}, \bar{Z_1}, \bar{Z_2}, \bar{Z_3}, \bar{Z_4}, \bar{Z_5}, \bar{Z_6}, X_{L}> \otimes <\nu_{16} \nu_{17} X_{14}, \nu_{17} \nu_{18} X_{15}, \nu_{16} X_{16}, \nu_{17} X_{17}, \nu_{18} X_{18}> \tag{50} となり、後半を無視して、 <\bar{X_1}, \bar{X_2}, \bar{X_3}, \bar{X_4}, \nu_{16} \nu_{17} \bar{X_5}, \nu_{17} \nu_{18} \bar{X_6}, \bar{Z_1}, \bar{Z_2}, \bar{Z_3}, \bar{Z_4}, \bar{Z_5}, \bar{Z_6}> \tag{51} とすれば所望の縮小された論理状態を得ることができます。先ほどと同様、ここで、注意しておきたいことが一つあります。式(51)に含まれる新しい論理パウリ$X$演算子は、 X_{L}^{new} = \nu_{16} X_{1} X_{6} Z_{11} \tag{52} です。測定値に応じて論理パウリ$X$演算子の符号が反転するのでそれを考慮して後の測定結果を解釈する必要があります12。 論理アダマール演算 次に、論理アダマール演算について説明します。一番簡単なのは各物理量子ビットすべてに対してトランスバーサルにアダマール演算を実行する方法です。以前、フォールトトレラント量子計算を勉強した際には、これでOKとしていました。が、表面符号を前提にすると一つ困ったことが起きます。例えば、式(5)に立ち戻って考えてみてください。これは$\ket{0}_{L}$を表していますが、アダマール演算によって$Z_L$が$X_L$に変わります。つまり、$\ket{+}_{L}$に変わるということなので、確かに論理的なアダマール演算ができているのですが、同時に$X$スタビライザーが$Z$スタビライザーに変わり、$Z$スタビライザーが$X$スタビライザーに変わります。図で表すと以下のようになって、ちょうどもとの格子が90度回転したような格好になります。 これは非常にまずいです。$X$スタビライザーで誤り検出するべきところを$Z$スタビライザーで誤り検出をしてしまうことになるので、これでは誤り訂正になりません。かと言って、格子を物理的に90度回すというのも非現実的です。じゃあ、どうするか。格子サイズの拡張とスワップを使って、90度回転を模した操作を実現すれば良いです。 上図左に示した90度回転してしまった格子を出発点にします。これに対して上図右に示すように量子ビットを右上方向に拡張し、紫色で示した量子ビットだけを残すように縮小します。そうすると紫色で示した部分は元と同じ向きの格子になっています。完全に元の格子の位置に戻したい場合は、さらに適当にスワップ演算をすれば戻ります。以上です。どうでしょう。ちょっと面倒ですか?でも、Braidingで論理アダマール演算をやったときと比べるとはるかに簡単だと思います。 論理2量子ビット演算 論理1量子ビット演算はこれで一通り実現できたということになるので(非クリフォード演算はマジック状態注入とテレポーテーションでできたことにして)、次に、論理2量子ビット演算の代表選手として論理CNOT演算について考えてみます。とにかく、トランスバーサルにCNOT演算を実行するということなので、平面符号では以下のようにやるしかないです(参考文献1のきれいな絵を掲載します)。 上図でバネのようにぐるぐる回っているものがCNOT演算の制御と標的のつながりを表しています。このように3次元的に物理量子ビットを積み上げないとうまくいかないです。というわけで、平面符号で2量子ビット演算を実現するためには、ハードウエアにかなり無理難題を押し付けないと到底実現できそうにありません13。 というわけで、お待たせしました!Lattice Surgeryの出番です! "Lattice"を"Surgery"するように、切ったり(Lattice splitting)くっつけたり(Lattice merging)することで、なんとCNOT演算ができてしまうのです。もう、量子ビットを3次元的に立体構成することに頭を悩ます必要はありません! Lattice Surgery と威勢よく言いましたが、論理CNOTを実現するためには、まずLattice Surgeryにおける基本操作であるmergeとsplitについて理解しておく必要がありますので、その説明から入ります。 Lattice merging 平面符号で表した2つの論理量子ビットを1つの論理量子ビットに合体させる操作のことをlattice mergingと呼びます。特に、rough boundary同士を合わせる操作をrough mergeと呼び、smooth boundary同士を合わせる操作をsmooth mergeと呼びます。 まず、rough mergeについてです。下図上に示すように、左と右の論理状態が各々、 \begin{align} \ket{\psi} &= \alpha \ket{0}_{L} + \beta \ket{1}_{L} \\ \ket{\phi} &= \alpha^{\prime} \ket{0}_{L} + \beta^{\prime} \ket{1}_{L} \tag{53} \end{align} だったとします。rough mergeは左右のrough boundaryの間にある量子ビットを$\ket{0}$に初期化してそれを含む$X$スタビライザーを間接測定することで全体を1個のエンタングルした論理状態にする操作です。 最初は並んだだけの状態なので、 \begin{align} \ket{\psi} \ket{\phi} &= (\alpha \ket{0}_{L} + \beta \ket{1}_{L}) (\alpha^{\prime} \ket{0}_{L} + \beta^{\prime} \ket{1}_{L}) \\ &= \alpha \alpha^{\prime} \ket{0}_{L} \ket{0}_{L} + \alpha \beta^{\prime} \ket{0}_{L} \ket{1}_{L} + \beta \alpha^{\prime} \ket{1}_{L} \ket{0}_{L} + \alpha^{\prime} \beta^{\prime} \ket{1}_{L} \ket{1}_{L} \tag{54} \end{align} です。これがmerge操作でどう変化するか計算してみます。上式の各項を別々に考えます。まず、$\ket{0}_{L} \ket{0}_{L}$です(以下、$\ket{00}_{L}$と簡略化して表します)。間にある量子ビットを$\ket{0}$にした直後の状態をスタビライザー形式で書くと、 \ket{00}_{L} = <\bar{X_1}, \bar{X_2}, \bar{X_6}, \bar{X_7}, \bar{X_{11}}, \bar{X_{12}}, \bar{Z_1}, \bar{Z_2}, \bar{Z_3}, \bar{Z_7}, \bar{Z_8}, \bar{Z_9}, Z_L, \bar{X_4}, \bar{X_5}, \bar{X_9}, \bar{X_{10}}, \bar{X_{14}}, \bar{X_{15}}, \bar{Z_4}, \bar{Z_5}, \bar{Z_6}, \bar{Z_{10}}, \bar{Z_{11}}, \bar{Z_{12}}, Z_{L}^{\prime}, Z_{9}, Z_{20}> \tag{55} ここで、$Z_L = Z_{1} Z_{2} Z_{3}, \space Z_{L}^{\prime} = Z_{4} Z_{5} Z_{6}$とします。また、つなげようとしている境界付近に3端子の$Z$スタビライザーが4つ($= \bar{Z_3}, \bar{Z_4}, \bar{Z_9}, \bar{Z_{10}}$)あるのが気持ち悪いので、各々$Z_9$と$Z_{10}$をかけて4端子にしておきます(式(55)はそうした後の状態だと思ってください)。 この状態に対して、$\bar{X_3}$を間接測定します。$\bar{X_3}$と反可換な演算子は$Z_9, \space Z_L = Z_{1} Z_{2} Z_{3}, \space Z_{L}^{\prime} = Z_{4} Z_{5} Z_{6}$の3つなので、$Z_9$を$Z_L = Z_{1} Z_{2} Z_{3}, \space Z_{L}^{\prime} = Z_{4} Z_{5} Z_{6}$にかけて、反可換なものを$Z_9$だけにして$\bar{X_3}$を測定します。測定値が$\epsilon_{3}$だったとすると、 \rightarrow <\bar{X_1}, \bar{X_2}, \epsilon_{3} \bar{X_3}, \bar{X_4}, \bar{X_5}, \bar{X_6}, \bar{X_7}, \bar{X_9}, \bar{X_{10}}, \bar{X_{11}}, \bar{X_{12}}, \bar{X_{14}}, \bar{X_{15}}, \bar{Z_1}, \bar{Z_2}, \bar{Z_3}, \bar{Z_4}, \bar{Z_5}, \bar{Z_6}, \bar{Z_7}, \bar{Z_8}, \bar{Z_9}, \bar{Z_{10}}, \bar{Z_{11}}, \bar{Z_{12}}, Z_{1} Z_{2} Z_{3} Z_{9}, Z_{4} Z_{5} Z_{6} Z_{9}, Z_{20}> \tag{56} となります。同様に$\bar{X_8}$を測定し測定値が$\epsilon_{8}$だったとすると、 \rightarrow <\bar{X_1}, \bar{X_2}, \epsilon_{3} \bar{X_3}, \bar{X_4}, \bar{X_5}, \bar{X_6}, \bar{X_7}, \epsilon_{8} \bar{X_8},\bar{X_9}, \bar{X_{10}}, \bar{X_{11}}, \bar{X_{12}}, \bar{X_{14}}, \bar{X_{15}}, \bar{Z_1}, \bar{Z_2}, \bar{Z_3}, \bar{Z_4}, \bar{Z_5}, \bar{Z_6}, \bar{Z_7}, \bar{Z_8}, \bar{Z_9}, \bar{Z_{10}}, \bar{Z_{11}}, \bar{Z_{12}}, Z_{1} Z_{2} Z_{3} Z_{9} Z_{20}, Z_{4} Z_{5} Z_{6} Z_{9} Z_{20}> \tag{57} となり、$\bar{X_{13}}$を測定し測定値が$\epsilon_{13}$だったとすると、 \begin{align} &\rightarrow <\bar{X_1}, \bar{X_2}, \epsilon_{3} \bar{X_3}, \bar{X_4}, \bar{X_5}, \bar{X_6}, \bar{X_7}, \epsilon_{8} \bar{X_8}, \bar{X_9}, \bar{X_{10}}, \bar{X_{11}}, \bar{X_{12}}, \epsilon_{13} \bar{X_{13}}, \bar{X_{14}}, \bar{X_{15}}, \bar{Z_1}, \bar{Z_2}, \bar{Z_3}, \bar{Z_4}, \bar{Z_5}, \bar{Z_6}, \bar{Z_7}, \bar{Z_8}, \bar{Z_9}, \bar{Z_{10}}, \bar{Z_{11}}, \bar{Z_{12}}, Z_{1} Z_{2} Z_{3} Z_{4} Z_{5} Z_{6}> \\ &= <\bar{X_1}, \bar{X_2}, \epsilon_{3} \bar{X_3}, \bar{X_4}, \bar{X_5}, \bar{X_6}, \bar{X_7}, \epsilon_{8} \bar{X_8}, \bar{X_9}, \bar{X_{10}}, \bar{X_{11}}, \bar{X_{12}}, \epsilon_{13} \bar{X_{13}}, \bar{X_{14}}, \bar{X_{15}}, \bar{Z_1}, \bar{Z_2}, \bar{Z_3}, \bar{Z_4}, \bar{Z_5}, \bar{Z_6}, \bar{Z_7}, \bar{Z_8}, \bar{Z_9}, \bar{Z_{10}}, \bar{Z_{11}}, \bar{Z_{12}}, Z_{L} Z_{L}^{\prime}> \tag{58} \end{align} となります。これはさらに、 \begin{align} &= <\bar{X_1}, \bar{X_2}, \epsilon_{3} \bar{X_3}, \bar{X_4}, \bar{X_5}, \bar{X_6}, \bar{X_7}, \epsilon_{8} \bar{X_8}, \bar{X_9}, \bar{X_{10}}, \bar{X_{11}}, \bar{X_{12}}, \epsilon_{13} \bar{X_{13}}, \bar{X_{14}}, \bar{X_{15}}, \bar{Z_1}, \bar{Z_2}, \bar{Z_3}, \bar{Z_4}, \bar{Z_5}, \bar{Z_6}, \bar{Z_7}, \bar{Z_8}, \bar{Z_9}, \bar{Z_{10}}, \bar{Z_{11}}, \bar{Z_{12}}, Z_{L} Z_{L}^{\prime}> \\ &= <\bar{X_1}, \bar{X_2}, \epsilon_{3} \epsilon_{8} \epsilon_{13} \bar{X_3} \bar{X_8} \bar{X_{13}}, \bar{X_4}, \bar{X_5}, \bar{X_6}, \bar{X_7}, \epsilon_{8} \bar{X_8}, \bar{X_9}, \bar{X_{10}}, \bar{X_{11}}, \bar{X_{12}}, \epsilon_{13} \bar{X_{13}}, \bar{X_{14}}, \bar{X_{15}}, \bar{Z_1}, \bar{Z_2}, \bar{Z_3}, \bar{Z_4}, \bar{Z_5}, \bar{Z_6}, \bar{Z_7}, \bar{Z_8}, \bar{Z_9}, \bar{Z_{10}}, \bar{Z_{11}}, \bar{Z_{12}}, Z_{L} Z_{L}^{\prime}> \\ &= <\bar{X_1}, \bar{X_2}, \epsilon_{3} \epsilon_{8} \epsilon_{13} X_{3} X_{14} X_{25} \dot X_{4} X_{15} X_{26}, \bar{X_4}, \bar{X_5}, \bar{X_6}, \bar{X_7}, \epsilon_{8} \bar{X_8}, \bar{X_9}, \bar{X_{10}}, \bar{X_{11}}, \bar{X_{12}}, \epsilon_{13} \bar{X_{13}}, \bar{X_{14}}, \bar{X_{15}}, \bar{Z_1}, \bar{Z_2}, \bar{Z_3}, \bar{Z_4}, \bar{Z_5}, \bar{Z_6}, \bar{Z_7}, \bar{Z_8}, \bar{Z_9}, \bar{Z_{10}}, \bar{Z_{11}}, \bar{Z_{12}}, Z_{L} Z_{L}^{\prime}> \\ &= <\bar{X_1}, \bar{X_2}, \epsilon_{3} \epsilon_{8} \epsilon_{13} X_{L} X_{L}^{\prime}, \bar{X_4}, \bar{X_5}, \bar{X_6}, \bar{X_7}, \epsilon_{8} \bar{X_8}, \bar{X_9}, \bar{X_{10}}, \bar{X_{11}}, \bar{X_{12}}, \epsilon_{13} \bar{X_{13}}, \bar{X_{14}}, \bar{X_{15}}, \bar{Z_1}, \bar{Z_2}, \bar{Z_3}, \bar{Z_4}, \bar{Z_5}, \bar{Z_6}, \bar{Z_7}, \bar{Z_8}, \bar{Z_9}, \bar{Z_{10}}, \bar{Z_{11}}, \bar{Z_{12}}, Z_{L} Z_{L}^{\prime}> \tag{59} \end{align} のように変形できます。ここで、$X_{L},\space X_{L}^{\prime}$は各々元の論理ビットに対する2つの論理パウリ$X$演算子であり、$X_{L} = X_{3} X_{14} X_{25}, \space X_{L}^{\prime} = X_{4} X_{15} X_{26}$としました。ごちゃごちゃしてわかりにくいので、4端子と3端子スタビライザーを点々で表し論理パウリ演算子みたいな顔をしているところだけ目立たせると、結局この操作で、 \ket{00}_{L} \rightarrow <\cdots, \epsilon_{3} \epsilon_{8} \epsilon_{13} X_{L} X_{L}^{\prime}, \cdots, Z_{L} Z_{L}^{\prime}> \tag{60} のように変化したことになります。$\epsilon_{i} = (-1)^{m_i}$のように$m_{i}$を定義して$M = m_{3} \oplus m_{8} \oplus m_{13}$とおくと、式(59)は、 \ket{00}_{L} \rightarrow <\cdots, (-1)^{M} X_{L} X_{L}^{\prime}, \cdots, Z_{L} Z_{L}^{\prime}> \tag{61} のように書けます。同じことを$\ket{01}_{L}, \ket{10}_{L}, \ket{11}_{L}$に対しても行うと、各々、 \begin{align} \ket{01}_{L} &\rightarrow <\cdots, (-1)^{M} X_{L} X_{L}^{\prime}, \cdots, -Z_{L} Z_{L}^{\prime}> \\ \ket{10}_{L} &\rightarrow <\cdots, (-1)^{M} X_{L} X_{L}^{\prime}, \cdots, -Z_{L} Z_{L}^{\prime}> \\ \ket{11}_{L} &\rightarrow <\cdots, (-1)^{M} X_{L} X_{L}^{\prime}, \cdots, Z_{L} Z_{L}^{\prime}> \tag{62} \end{align} のように変化します。式(60)と式(61)をよーく眺めてみてください。$\ket{00}_{L}$と$\ket{11}_{L}$(およびその線形結合)は$Z_{L} Z_{L}^{\prime}$の固有値$+1$の固有状態にマッピングされ、$\ket{01}_{L}$と$\ket{10}_{L}$(およびその線形結合)は$Z_{L} Z_{L}^{\prime}$の固有値$-1$の固有状態にマッピングされることがわかります。つまり、もとの論理的2量子ビット状態=4次元ヒルベルト空間(2次元と2次元の直積)が、merge操作によって論理的1量子ビット状態=2次元ヒルベルト空間にちゃんとマッピングされました、ということがわかります。さらに、これが重要なことなのですが、もとの4つの基底がすべて$X_{L} X_{L}^{\prime}$の固有値$(-1)^{M}$の固有空間にマッピングされているということです。つまり、このmerge操作というのは、もとの$\ket{\psi} \otimes \ket{0}_{9} \ket{0}_{20} \otimes \ket{\phi}$に対して$X_{L} X_{L}^{\prime}$という演算子(上図で青いチェーンで示した演算子$X_{3} X_{14} X_{25} X_{26} X_{15} X_{4}$)を間接測定して14測定値が$(-1)^{M}$だった」ということと等価だと解釈しても良いということです。 すなわち、merge操作というのは、 \ket{\psi} \ket{\phi} \rightarrow \ket{\psi} \odot \ket{\phi} = (I + (-1)^{M} X_{L} X_{L}^{\prime}) \ket{\psi} \ket{\phi} \tag{63} という演算に他なりません。ここで、$\odot$をmerge操作を表す演算と定義し15、規格化定数は省略しました(以降同様に規格化定数は省略します)。$\ket{\psi}$と$\ket{\phi}$の間に本当は$\ket{0}_{9} \ket{0}_{20}$が存在しているのですが省略しました16。 式(63)の右辺を式(53)を使って展開すると、$M=0$の場合は、 \ket{\psi} \odot \ket{\phi} = (\alpha \alpha^{\prime} + \beta \beta^{\prime}) (\ket{00}_{L} + \ket{11}_{L}) + (\alpha \beta^{\prime} + \beta \alpha^{\prime}) (\ket{01}_{L} + \ket{10}_{L}) \tag{64} となり、$M=1$の場合は、 \ket{\psi} \odot \ket{\phi} = (\alpha \alpha^{\prime} - \beta \beta^{\prime}) (\ket{00}_{L} - \ket{11}_{L}) + (\alpha \beta^{\prime} - \beta \alpha^{\prime}) (\ket{01}_{L} - \ket{10}_{L}) \tag{65} となります。$M$の値に応じて、merge後の基底を、 \begin{align} \ket{00} + (-1)^{M} \ket{11} &\rightarrow \ket{0}_{L} \\ \ket{01} + (-1)^{M} \ket{10} &\rightarrow \ket{1}_{L} \tag{66} \end{align} のように定めると、式(64)(65)は、 \begin{align} \ket{\psi} \odot \ket{\phi} &= (\alpha \alpha^{\prime} + (-1)^{M} \beta \beta^{\prime}) (\ket{00}_{L} + (-1)^{M} \ket{11}_{L}) + (\alpha \beta^{\prime} + (-1)^{M} \beta \alpha^{\prime}) (\ket{01}_{L} + (-1)^{M} \ket{10}_{L}) \\ &= \alpha (\alpha^{\prime} \ket{0}_{L} + \beta^{\prime} \ket{1}_{L}) + (-1)^{M} \beta (\alpha^{\prime} \ket{1}_{L} + \beta^{\prime} \ket{0}_{L}) \\ &= \alpha \ket{\phi} + (-1)^{M} X_{L}^{\prime} \ket{\phi} \tag{67} \end{align} のようにまとめることができます。これがrough mergeの基本公式です。後で使いますので覚えておきましょう。 次に、smooth mergeについて考えてみます。下図に示すように、左と右の論理状態が各々、 \begin{align} \ket{\psi} &= a \ket{+}_{L} + b \ket{-}_{L} \\ \ket{\phi} &= a^{\prime} \ket{+}_{L} + b^{\prime} \ket{-}_{L} \tag{68} \end{align} だったとします。smooth mergeは左右のsmooth boundaryの間にある量子ビットを$\ket{+}$に初期化してそれを含む$Z$スタビライザーを間接測定することで全体を1個のエンタングルした論理状態にする操作です。 rough mergeの$X$と$Z$の立場を入れ替えれば、先ほどとまったく同じ式展開ができます。smooth merge後の基底を測定値に応じて、 \begin{align} \ket{++}_{L} + (-1)^{M} \ket{--}_{L} &\rightarrow \ket{+}_{L} \\ \ket{+-}_{L} + (-1)^{M} \ket{-+}_{L} &\rightarrow \ket{-}_{L} \tag{69} \end{align} のように定めることにすると、smooth merge後の状態は、 \ket{\psi} \odot \ket{\phi} = a \ket{\phi} + (-1)^{M} b Z_{L}^{\prime} \ket{\phi} \tag{70} となります。これがsmooth mergeの基本公式です。これも後で使いますので覚えておきましょう。 Lattice splitting 平面符号で表した1つの論理量子ビットを2つの論理量子ビットに分離する操作のことをlattice splittingと呼びます。分離した境界がrough boundaryになる操作をrough splitと呼び、smooth boundaryになる操作をsmooth splitと呼びます。 まず、smooth splitについて考えます。下図に示す格子において論理状態が、 \alpha \ket{0}_{L} + \beta \ket{1}_{L} \tag{71} で与えられているとします。smooth splitは真ん中の量子ビット(9番と20番)を$X$測定する操作です。この測定で真ん中に位置する$Z$スタビライザーが消えてなくなり左右に分離した状態になります。本当にそうなるか数式の上で確認してみます。$\ket{0}_{L}$と$\ket{1}_{L}$が各々どう変化するか数式の上で確認してみます。$\ket{0}_{L}$はスタビライザー形式で書くと、 <\bar{X_1}, \cdots , \bar{X_{12}}, \bar{Z_1}, \cdots , \bar{Z_{15}}, Z_{1} Z_{12} Z_{23}> \tag{72} と表わせます。この状態に対して、$X_{9}$測定してみます。$X_{9}$に反可換な演算子は$\bar{Z_3}$と$\bar{Z_8}$なので後者に前者をかけることで反可換な演算子を一つにしておいてから、$X_{9}$測定して測定値が$\mu_{9}$だったとすると、 <\bar{X_1}, \cdots , \bar{X_{12}}, \bar{Z_1}, \bar{Z_2}, \mu_{9} X_{9}, \bar{Z_4}, \bar{Z_5}, \bar{Z_6}, \bar{Z_7}, Z_{3} Z_{4} Z_{14} Z_{15} Z_{20}, \bar{Z_9}, \bar{Z_{10}}, \bar{Z_{11}}, \bar{Z_{12}}, \bar{Z_{13}}, \bar{Z_{14}}, \bar{Z_{15}}, Z_{1} Z_{12} Z_{23}> \tag{73} という状態に変化します。さらに、$X_{20}$を測定します。$X_{20}$に反可換な演算子は$Z_{13}$と$Z_{3} Z_{4} Z_{14} Z_{15} Z_{20}$なので後者に前者をかけることで反可換な演算子を一つにしておいてから、$X_{20}$を測定して測定値が$\mu_{20}$だったとすると、 <\bar{X_1}, \cdots , \bar{X_{12}}, \bar{Z_1}, \bar{Z_2}, \mu_{9} X_{9}, \bar{Z_4}, \bar{Z_5}, \bar{Z_6}, \bar{Z_7}, Z_{3} Z_{4} Z_{14} Z_{15} Z_{25} Z_{26}, \bar{Z_9}, \bar{Z_{10}}, \bar{Z_{11}}, \bar{Z_{12}}, \mu_{20} X_{20}, \bar{Z_{14}}, \bar{Z_{15}}, Z_{1} Z_{12} Z_{23}> \tag{74} となります。ここで、$\mu_{9} X_{9}$と$\mu_{20} X_{20}$をはさむ左右の4端子の$X$スタビライザーに$\mu_{9} X_{9}$と$\mu_{20} X_{20}$をかけて各々3端子にしておきます。さらに、$Z_{1} Z_{12} Z_{23}$と同値である$Z_{3} Z_{14} Z_{25}$を$Z_{3} Z_{4} Z_{14} Z_{15} Z_{25} Z_{26}$にかけると、 \begin{align} &<\bar{X_1}, \bar{X_2}, \mu_{9} \bar{X_3}, \mu_{9} \bar{X_4}, \bar{X_5}, \bar{X_6}, \bar{X_7}, \bar{X_8}, \mu_{20} \bar{X_9}, \mu_{20} \bar{X_10}, \bar{X_11}, \bar{X_{12}}, \bar{Z_1}, \bar{Z_2}, \mu_{9} X_{9}, \bar{Z_4}, \bar{Z_5}, \bar{Z_6}, \bar{Z_7}, Z_{4} Z_{15} Z_{26}, \bar{Z_9}, \bar{Z_{10}}, \bar{Z_{11}}, \bar{Z_{12}}, \mu_{20} X_{20}, \bar{Z_{14}}, \bar{Z_{15}}, Z_{1} Z_{12} Z_{23}> \\ &= <\bar{X_1}, \bar{X_2}, \mu_{9} \bar{X_3}, \bar{X_7}, \bar{X_8}, \mu_{20} \bar{X_9}, \bar{Z_1}, \bar{Z_2}, \bar{Z_6}, \bar{Z_7}, \bar{Z_{11}}, \bar{Z_{12}}, Z_{1} Z_{12} Z_{23}> \otimes <\mu_{9} X_{9}, \mu_{20} X_{20}> \otimes <\mu_{9} \bar{X_4}, \bar{X_5}, \bar{X_6}, \mu_{20} \bar{X_{10}}, \bar{X_{11}}, \bar{X_{12}}, \bar{Z_4}, \bar{Z_5}, \bar{Z_9}, \bar{Z_{10}}, \bar{Z_{14}}, \bar{Z_{15}}, Z_{4} Z_{15} Z_{26}> \tag{75} \end{align} となり、左の論理状態と真ん中の2つの孤立した量子ビットと右の論理状態という3つのパートにきれいに分かれるということになります。真ん中の2つの量子ビットを無視すると、左側は$Z_{1} Z_{12} Z_{23}$の固有値$+1$の固有状態で、右側は$Z_{4} Z_{15} Z_{26}$の固有値$+1$の固有状態であるため、結局、smooth splitによって、$\ket{0}_{L}$は、 \ket{0}_{L} \rightarrow \ket{00}_{L} \tag{76} に変化することになります。同様の式展開によって$\ket{1}_{L}$は、 \ket{1}_{L} \rightarrow \ket{11}_{L} \tag{77} に変化します。式(76)(77)を合わせると、式(71)は、 \alpha \ket{0}_{L} + \beta \ket{1}_{L} \rightarrow \alpha \ket{00}_{L} + \beta \ket{11}_{L} \tag{78} に変化します。これがsmooth splitの基本公式です。後で使いますので覚えておきましょう。 次に、rough splitについて考えてみます。下図に示す格子において論理状態が、 a \ket{+}_{L} + b \ket{-}_{L} \tag{79} で与えられているとします。rough splitは真ん中の量子ビット(9番と20番)を$Z$測定する操作です。この測定で真ん中に位置する$X$スタビライザーが消えてなくなり左右に分離した状態になります。smooth splitにおける$X$と$Z$を入れ替えれば先ほどと全く同じ式展開が成り立ち、結局、 a \ket{+}_{L} + b \ket{+}_{L} \rightarrow a \ket{++}_{L} + b \ket{--}_{L} \tag{80} となります。これがrough splitの基本公式です。これは後で使いません。が、基本公式として覚えておきましょう。 論理CNOT演算 それでは、本当に本当にお待たせしました!本日のメインイベント論理CNOT演算について語るときがやってきました!mergeしたりsplitしたりすることで論理CNOTが実現できるというマジックをこれからご披露します。でも、特に身構えないでも大丈夫です。今までの議論が理解できていればとても簡単な話です17。 まず、下図に示すように3つの論理状態$\ket{C}, \space \ket{INT}, \space \ket{T}$があったとします。 $\ket{C}$はこれからやるCNOT演算の制御ビットで、 \ket{C} = \alpha \ket{0}_{L} + \beta \ket{1}_{L} = \bar{\alpha} \ket{+}_{L} + \bar{\beta} \ket{-}_{L} \tag{81} と表されるものとします。ここで、 \begin{align} \bar{\alpha} = \frac{1}{\sqrt{2}} (\alpha + \beta) \\ \bar{\beta} = \frac{1}{\sqrt{2}} (\alpha - \beta) \tag{82} \end{align} とおきました。$\ket{T}$は標的ビットで、 \ket{T} = \alpha^{\prime} \ket{0}_{L} + \beta^{\prime} \ket{1}_{L} \tag{83} とします。$\ket{INT}$は補助的な役割をする論理状態で、 \ket{INT} = \ket{+}_{L} \tag{84} に初期化されているものとします。 さぁ、準備はできました。ここからCNOT演算を開始します。まず、$\ket{C}$と$\ket{INT}$をsmooth mergeします。式(70)を使うとmerge後の状態は、 \begin{align} \ket{C} \odot \ket{INT} &= \bar{\alpha} \ket{+}_{L} + (-1)^{M} \bar{\beta} \ket{-}_{L} \\ &= \bar{\alpha} (\ket{0}_{L} + \ket{1}_{L}) + (-1)^{M} \bar{\beta} (\ket{0}_{L} + \ket{1}_{L}) \\ &= (\bar{\alpha} + (-1)^{M} \bar{\beta}) \ket{0}_{L} + (\bar{\alpha} - (-1)^{M} \bar{\beta}) \ket{1}_{L} \tag{85} \end{align} となります。ここで、smooth mergeしたときの測定結果$M$が$0$だった場合何もせず、$1$だった場合論理パウリ$X$演算子をかけてビット反転します(あるいは、ビット反転したことを覚えておいて後で辻褄合わせをします)。そうすると、 \ket{C} \odot \ket{INT} = \alpha \ket{0}_{L} + \beta \ket{1}_{L} \tag{86} となります。次に、いまmergeしたばかりの論理状態をsmooth splitします。そうすると、 \ket{C} \odot \ket{INT} \rightarrow \ket{C^{\prime}} \ket{INT^{\prime}} = \alpha \ket{00}_{L} + \beta \ket{11}_{L} \tag{87} になります。さらに、今度は$\ket{INT^{\prime}}$を$\ket{T}$にrough mergeします。測定値を$M^{\prime}$とすると、 \begin{align} \ket{C^{\prime}} (\ket{INT^{\prime}} \odot \ket{T}) &= \alpha \ket{0}_{L} (\ket{0}_{L} \odot \ket{T}) + \beta \ket{1}_{L} (\ket{1}_{L} \odot \ket{T}) \\ &= \alpha \ket{0}_{L} \ket{T} + \beta \ket{1}_{L} (-1)^{M^{\prime}} X_{L} \ket{T} \tag{88} \end{align} となります。制御側が$\ket{0}_{L}$の場合、標的側は$\ket{T}$のままで、制御側が$\ket{1}_{L}$の場合、標的側はビット反転することになりますので、これでCNOT演算が完了したことになります。ただし、測定値$M^{\prime}$が$-1$だった場合、位相反転した結果を返しますので、その場合は制御側を位相反転します(あるいは位相反転したことを覚えておいて後で辻褄合わせをします)。また、式(88)の段階では、$\ket{INT^{\prime}}$と$\ket{T}$がmergeされて一体になった状態(横長の状態)なので、もとの姿に戻したい場合は$\ket{INT^{\prime}}$の各量子ビットを$Z$測定して縮小すれば良いです18。 シミュレーション それでは、今説明したやり方で本当にCNOT演算が実現できるのかを量子計算シミュレータqlazyを使って確認してみます。下図に示したように簡易的に符号距離2の論理状態を3個用意してCNOT演算を実行してみます。 具体的には、マジック状態注入により制御側を$a \ket{0}_{L} + b \ket{1}_{L}$、標的側を$\ket{0}_{L}$にしておきます。すなわち、 \ket{C} \ket{T} = (a \ket{0}_{L} + b \ket{1}_{L}) \ket{0}_{L} \tag{89} にしておき、論理CNOT演算後に、 \ket{C^{\prime}} \ket{T^{\prime}} = a \ket{00}_{L} + b \ket{11}_{L} \tag{90} になることを確認します。最初の$a \ket{0} + b \ket{1}$をランダムに用意するために$U3$ゲートの3つの引数$\alpha, \beta, \gamma$をランダムに設定して、 U3(\alpha, \beta, \gamma) \ket{0} = a \ket{0} + b \ket{1} \tag{91} のように得ることにします19。 実装 全体のPythonコードを以下に示します。 import random from qlazy import QState, PauliProduct from qlazy.tools.Register import CreateRegister,InitRegister class QStateLogical(QState): def set_register(self, dat_cnt, dat_int, dat_tar, bnd_cnt, bnd_tar, anc): self.__dat = {'cnt': dat_cnt, 'int': dat_int, 'tar': dat_tar} self.__bnd = {'cnt': bnd_cnt, 'tar': bnd_tar} self.__anc = anc self.__x_stab = {'cnt': [PauliProduct(pauli_str='XXX', qid=[dat_cnt[0], dat_cnt[1], dat_cnt[2]]), PauliProduct(pauli_str='XXX', qid=[dat_cnt[2], dat_cnt[3], dat_cnt[4]])], 'int': [PauliProduct(pauli_str='XXX', qid=[dat_int[0], dat_int[1], dat_int[2]]), PauliProduct(pauli_str='XXX', qid=[dat_int[2], dat_int[3], dat_int[4]])], 'tar': [PauliProduct(pauli_str='XXX', qid=[dat_tar[0], dat_tar[1], dat_tar[2]]), PauliProduct(pauli_str='XXX', qid=[dat_tar[2], dat_tar[3], dat_tar[4]])], 'bnd_tar': [PauliProduct(pauli_str='XXX', qid=[dat_int[1], dat_tar[0], bnd_tar[0]]), PauliProduct(pauli_str='XXX', qid=[bnd_tar[0], dat_int[4], dat_tar[3]])]} self.__z_stab = {'cnt': [PauliProduct(pauli_str='ZZZ', qid=[dat_cnt[0], dat_cnt[2], dat_cnt[3]]), PauliProduct(pauli_str='ZZZ', qid=[dat_cnt[1], dat_cnt[2], dat_cnt[4]])], 'int': [PauliProduct(pauli_str='ZZZ', qid=[dat_int[0], dat_int[2], dat_int[3]]), PauliProduct(pauli_str='ZZZ', qid=[dat_int[1], dat_int[2], dat_int[4]])], 'tar': [PauliProduct(pauli_str='ZZZ', qid=[dat_tar[0], dat_tar[2], dat_tar[3]]), PauliProduct(pauli_str='ZZZ', qid=[dat_tar[1], dat_tar[2], dat_tar[4]])], 'bnd_cnt': [PauliProduct(pauli_str='ZZZ', qid=[dat_cnt[0], bnd_cnt[0], dat_int[3]]), PauliProduct(pauli_str='ZZZ', qid=[dat_cnt[1], bnd_cnt[0], dat_int[4]])]} self.__lx = PauliProduct(pauli_str='XXXX', qid=[dat_int[0], dat_int[3], dat_cnt[0], dat_cnt[3]]) self.__lz = PauliProduct(pauli_str='ZZZZ', qid=[dat_int[0], dat_int[1], dat_tar[0], dat_tar[1]]) return self def __indirect_measure(self, pp, shots=1): # 間接測定 self.reset(qid=[self.__anc[0]]) self.h(self.__anc[0]) self.operate(ctrl=self.__anc[0], pp=pp) self.h(self.__anc[0]) return self.m(qid=[self.__anc[0]], shots=shots) def initialize(self, alpha=0.0, beta=0.0, gamma=0.0): # マジック状態注入 ''' |cnt> = a |0> + b |1>, |tar> = |0> ''' self.reset() self.u3(self.__dat['cnt'][0], alpha=alpha, beta=beta, gamma=gamma) self.cx(self.__dat['cnt'][0], self.__dat['cnt'][3]) self.h(self.__dat['int'][0]) self.cx(self.__dat['int'][0], self.__dat['int'][3]) for site in ['cnt', 'int', 'tar']: for x_stab in self.__x_stab[site]: mval = self.__indirect_measure(x_stab).last return self def merge_cnt_int(self): # 制御ビットと補助ビットのマージ parity = 0 self.reset(qid=self.__bnd['cnt']) self.h(self.__bnd['cnt'][0]) for z_stab in self.__z_stab['bnd_cnt']: parity += int(self.__indirect_measure(z_stab).last) if parity % 2 == 1: self.operate(pp=self.__lx) return self def merge_int_tar(self): # 補助ビットと標的ビットのマージ self.reset(qid=self.__bnd['tar']) for x_stab in self.__x_stab['bnd_tar']: self.__indirect_measure(x_stab) return self def split_cnt_int(self): # マージされている制御ビット&補助ビットをスプリット return self.mx(qid=[self.__bnd['cnt'][0]]).last def measure(self, shots=1): # 論理パウリZの測定 return self.__indirect_measure(self.__lz, shots=shots).frequency if __name__ == '__main__': shots = 100 alpha, beta, gamma = random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1) print("- alpha, beta, gamma = {:.4f}, {:.4f}, {:.4f}".format(alpha, beta, gamma)) dat_cnt, dat_int, dat_tar = CreateRegister(5), CreateRegister(5), CreateRegister(5) bnd_cnt, bnd_tar, anc = CreateRegister(1), CreateRegister(1), CreateRegister(1) qubit_num = InitRegister(dat_cnt, dat_int, dat_tar, bnd_cnt, bnd_tar, anc) qs_logical = QStateLogical(qubit_num).set_register(dat_cnt, dat_int, dat_tar, bnd_cnt, bnd_tar, anc) qs_logical.initialize(alpha=alpha, beta=beta, gamma=gamma) qs_logical.merge_cnt_int() qs_logical.split_cnt_int() qs_logical.merge_int_tar() result_logical = qs_logical.measure(shots=shots) print("- actual =", result_logical) result = QState(qubit_num=1).u3(0, alpha=alpha, beta=beta, gamma=gamma).m(shots=shots) print("- expect =", result.frequency) 何をやっているか簡単に説明します。main処理部を見てください。 shots = 100 alpha, beta, gamma = random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1) で、測定回数を表す変数に100を入れて、ランダム状態を得るため$U3$ゲートに与える引数alpha, beta, gammaにランダム値を入れています。 dat_cnt, dat_int, dat_tar = CreateRegister(5), CreateRegister(5), CreateRegister(5) bnd_cnt, bnd_tar, anc = CreateRegister(1), CreateRegister(1), CreateRegister(1) qubit_num = InitRegister(dat_cnt, dat_int, dat_tar, bnd_cnt, bnd_tar, anc) で、3つの論理ビットを構成するための物理量子ビット番号のリストを設定しています。dat_cnt,dat_int,dat_tarは各々論理的な制御ビット、補助ビット、標的ビットのビット番号リストです。bnd_cntは制御ビットと補助ビットの境界ビット、bnd_tarは標的ビットと補助ビットの境界ビット、ancはアンシラビット20の番号リストを表しています(リストと言ってもこの場合要素数は1個です)。 qs_logical = QStateLogical(qubit_num).set_register(dat_cnt, dat_int, dat_tar, bnd_cnt, bnd_tar, anc) ここで、QStateLogicalが出てきました。これはQStateを継承したサブクラスです21。main部の上の方でサブクラスを定義して必要なメソッドを追加しています。QStateLogicalのインスタンスを生成した後、すかさずset_registerメソッドで量子ビット番号を内部的に設定します。 次に、 qs_logical.initialize(alpha=alpha, beta=beta, gamma=gamma) で、初期化します。内部的には先ほど得たalpha, beta, gammaを使って、式(89)に示した論理状態をマジック状態注入によって作成しています。後は、論理CNOT演算の手順に従い、 qs_logical.merge_cnt_int() で、制御ビットと補助ビットをmergeし、 qs_logical.split_cnt_int() で、mergeしたものをsplitし、 qs_logical.merge_int_tar() で、補助ビットと標的ビットをmergeします。そして、最後に、 result_logical = qs_logical.measure(shots=shots) print("- actual =", result_logical) で、論理パウリ$Z$演算子を測定して、結果($\ket{0}_{L}$の頻度と$\ket{1}_{L}$の頻度)を表示します。 これが正しいかどうかは、 result = QState(qubit_num=1).u3(0, alpha=alpha, beta=beta, gamma=gamma).m(shots=shots) print("- expect =", result.frequency) で、普通のCNOT演算の結果を表示することで確認します。両者の結果が大体同じになっていれば、論理CNOTが正しく実行されたと思って良いです。 動作確認 上のプログラムを実行した出力例を以下に示します。alpha, beta, gammaをランダムに生成しているので、実行のたびに測定値の頻度は変わります。例えば、 - alpha, beta, gamma = 0.5885, 0.6674, 0.1516 - actual = Counter({'0': 93, '1': 7}) - expect = Counter({'0': 96, '1': 4}) - alpha, beta, gamma = 0.3068, 0.8457, 0.4637 - actual = Counter({'0': 58, '1': 42}) - expect = Counter({'1': 52, '0': 48}) - alpha, beta, gamma = 0.2264, 0.2124, 0.7302 - actual = Counter({'1': 82, '0': 18}) - expect = Counter({'1': 81, '0': 19}) となりました。alpha,beta,gammaの値が様々に変わっても、actualとexpectで大体同じ頻度が得られていることから、正しく論理CNOT演算がシミュレーションできていることが確認できました。 おわりに 今回、スタビライザーの計算をきっちりやったので、かなりまだるっこしい説明になったかもしれませんが、測定結果による符号の反転がどこにどう反映されるのか、自分できちんと確認しておきたかったので、少し手間かけてやってみました。おかげで実装を想定した際の要注意ポイントがわかり理解が進みました。 さて、次回以降どんな話題を取り上げるか、例によって未定ですが、いましばらくは誤り訂正にこだわってみようと思っています。最近FTQC(誤り耐性のある量子コンピュータ)関連で新しい成果が発表されていて22、業界的にになかなか面白い状況になってきた感があります。ありますが、まだまだ自分自身話についていけないところも多く、凄い!という噂の論文を見ても、その凄さや重大さが十分にわからんという状況にあり、何とかそれを味わえるレベルになりたいという思いがあります。というわけで、引き続き気まぐれ&マイペースになりますが、ひとつひとつ潰していきたいと思っています。 以上 イオントラップ方式であれば遠距離間の演算も大丈夫らしいのですが、超電導方式など他の方式の多くでは遠距離演算は難しいと思うので。 ↩ このようなタイプの格子であればどんなサイズの格子を考えても表現できる論理量子ビットは必ず$1$になります。 ↩ 何でも良いと言っても、いま考えているスタビライザー群を含むパウリ群の要素をもってくるのですが...。 ↩ $Z$演算子をトランスバーサルに構成したものが論理$Z$演算子になっていてフォールトトレラントで、かつ、わかりやすいのでこういう定義にしているのだと思います(たぶん)。 ↩ 図で示したのは一例です。$Z$境界(rough boundary)同士を横方向につないだものであれば、何でも論理$Z$演算子とみなして良いですし、$X$境界(smooth boundary)同士を縦方向につないだものであれば、何でも論理$X$演算子とみなして良いです。例えば、$X_{L} = X_{2} X_{7} X_{12}, \space Z_{L} = Z_{6} Z_{7} Z_{8}$等々でもOKです。 ↩ これ大丈夫でしょうか。$U$をパウリ群の要素つまりパウリ積(pauli product)とすると$(I+U)/2$は$U$の固有値$+1$に対する固有空間への射影になります。式(13)はこのような固有空間への射影の射影の射影の、、、という具合に繰り返すことで同時固有状態を得る形になっています。 ↩ $\alpha \ket{0}_{L} + \beta \ket{1}_{L}$は$\alpha \ket{00 \cdots 0} + \beta \ket{11 \cdots 1}$のことではありませんので、お間違いなく。 ↩ $\bar{X_7}, \bar{X_8}, \bar{X_9}$の測定値に依存して対応したスタビライザーの固有値が反転しますのでご注意ください。反転している場所を覚えておけば後の論理演算には支障ありません(と思います)。 ↩ $\bar{Z_7}, \bar{Z_8}, \bar{Z_9}$の測定値によって対応したスタビライザーの固有値が反転しますのでご注意ください。反転している場所を覚えておけば後の論理演算には支障ありません(と思います)。 ↩ 1番最後の等式で新しい格子における論理パウリ$Z$演算子を$Z_L = Z_{1} Z_{2} Z_{3}$としていますが、$Z_{6} Z_{7} Z_{8}$でも$Z_{11} Z_{12} Z_{13}$だと思っても良いです。要するにもとの論理パウリ$Z$演算子と同じものと考えて良いです。 ↩ 気持ち悪い人は論理パウリ$X$を演算すれば、とりあえず論理パウリ$Z$の符号を反転させることができます。が、言うまでもないですが、限られたコヒーレンス時間の中でこういう演算を都度やってしまうのは賢いやり方ではありません。 ↩ 気持ち悪い人は論理パウリ$Z$を演算すれば、とりあえず論理パウリ$X$の符号を反転させることができます。が、言うまでもないですが、限られたコヒーレンス時間の中で、、以下略。 ↩ 量子チップを3次元的に構成するみたいなことは、もしかすると割と最近になって現実感が出てきた話なのかもしれません。が、すみません。このあたりよくわかっていません、汗。 ↩ $X_{L}$を間接測定してから$X_{L}^{\prime}$を間接測定するという意味ではありませんので、念の為。 ↩ 参考文献1では丸付きのMでmergeを表しているのですが、Qiitaでどうやって記述するかわからなかったので(多分できない?)、代わりに$\odot$としました。 ↩ ないけどあると思ってください、心の眼を使って! ↩ 簡単なのですが、これ最初に思いついた人はすごいと思います。 ↩ このとき測定値に応じて標的側の論理パウリ$Z$演算子の符号が逆転する場合があることにご注意ください。つまり、$\ket{0}_{L}$と$\ket{1}_{L}$が入れ替わるかもしれません。その場合論理パウリ$X$演算子を演算すれば元に戻ります(あるいはビット反転したことを覚えておいて後で辻褄合わせをします)。 ↩ qiskitのドキュメントでは、$U3$の引数は$U3(\theta, \phi, \lambda)$のように書いてあると思いますが、qlazyではその引数は$\alpha,\beta,\gamma$と定義してまして、各々$\alpha \rightarrow \lambda, \space \beta \rightarrow \phi, \space \gamma \rightarrow \theta$のように読み替えてください。また、角度の単位は$\pi$ラジアンとしていますので、$\alpha=0.5$は$\pi/2$を表しています。 ↩ 各スタビライザーに対して1つのアンシラが対応するように配備するのが普通ですが、シミュレータの量子ビットをケチるため1個のアンシラを都度リセットして使い回します。 ↩ qlazyのv0.2.2で量子状態を表す基本クラスを継承したサブクラスが作れるようになりました。複雑な量子回路を実行するためにカスタムゲートが作りたくなることがあると思いますが、そんなとき便利に使えると思います。 ↩ 例えば、"Demonstration of fault-tolerant universal quantum gate operations", arXiv:2111.12654 [quant-ph]では、2つの論理量子ビットに対するフォールトトレラントな演算をイオントラップ方式で実現したと報告されています。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mysqlclientインストール時のエラー

EC2とMySQLを接続させるときに出会ったエラー pip install mysqlclient 上記をしているのに,runserverすると django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module. Did you install mysqlclient? と怒られる。 解決策 sudo yum install python3-devel してから pip install mysqlclient するとうまくいった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【続】iOSでAirtestを動かす

はじめに AirtestIDE でスマホアプリ(ゲーム)のテスト自動化に取り組んでいます。そこで実際に作成したスクリプトを実行する環境をつくるために実施したことを綴っていきたいと思います。 こちらは、昨年の記事「iOSでAirtestを動かしてみる」の続編になります。 前回課題となっていた横向きアプリへの対応が解決しましたので、そちらの情報共有と、当該環境を使用して本格的に取り組みを進めるにあたり、CLI環境からの複数端末での動作確認を行いましたので、いくつかの関連トピックを交えつつ共有できればと考えています。 環境 macOS Big Sur 11.6.1 Xcode 13.1 Airtest IDE 1.2.12(Python) iPhone XS(iOS 14.7.1) iPhone 8(iOS 15.1) セットアップ Step1 WebDriverAgent 前回紹介していた iOS-Tagent を Appiumの提供する WebDriverAgent に置き換えることで、横向きのアプリにも対応することができました。 WebDriverAgent をクローンします。 $ git clone https://github.com/appium/WebDriverAgent.git Xcodeで開きます。 ここで、PCに実機をUSB接続しておきます。 スキーム(ここでは「IntegrationApp」となっている箇所)をタップします。 WebDriverAgentRunner を選択し、先程接続したiOS端末をタップします。(ここでは「iPhone」となっている箇所) とりあえず、動かしてみます。 ワーニングを順次解消していきます。 次に、エラーを解消していきます。 WebDriverAgentRunner にて、Teamを選択します。(無い場合は AppleIDから作成) Product Bundle Identiferを設定します。(ユニークな文字列にする) 再度、ビルドします。 問題なさそうであれば、実機のスリープを解除した状態で、Testを実行します。 実機に WebDriverAgentRunnerがインストールされます。ここで、「設定」アイコンをタップします。 設定から「一般」をタップします。 「プロファイルとデバイス管理」をタップします。 WebDriverAgentRunner のデベロッパを信頼します。 PCに戻って、再度 Testを実行します。(手動で停止するまで実行中の状態となります) Step2 Set Ploxy iproxyをインストールします。 インストール $ brew install libimobiledevice iproxyを実行します。 実行 $ iproxy 8100 8100 実行中のメッセージ Creating listening port 8100 for device port 8100 waiting for connection Step3 AirtestIDE 接続情報を入力して「Connect」ボタンをクリックします。 iOS端末に接続されます。これでAirtestのスクリプトを実行することができるようになりました。 関連ノウハウ コマンドラインからの実行 CLIから実行することで、並行して複数端末での個別実行が可能となります。ここでは、2台の端末(iPhone XS、iPhone 8)を接続している状態を想定しています。 WebDriverAgentの複数起動 デバイスidを指定して複数起動する(それぞれ別枠で起動する) $ xcodebuild -project project/appium/WebDriverAgent/WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination 'platform=iOS,id=<iPhone XSのデバイスid>' test $ xcodebuild -project project/appium/WebDriverAgent/WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination 'platform=iOS,id=<iPhone 8のデバイスid>' test iproxyの複数起動 ポート番号とデバイスidを指定する(それぞれ別枠で起動する) $ iproxy 8100 8100 --udid <iPhone XSのデバイスid> $ iproxy 8200 8100 --udid <iPhone 8のデバイスid> AirtestIDE スクリプトの複数実行 ポート番号とデバイスidを指定する(それぞれ別枠で起動する) $ /Applications/AirtestIDE.app/Contents/MacOS/AirtestIDE runner "<スクリプト>.air" --device iOS:///localhost:8100//<iPhone XSのデバイスid> $ /Applications/AirtestIDE.app/Contents/MacOS/AirtestIDE runner "<スクリプト>.air" --device iOS:///localhost:8200//<iPhone 8のデバイスid> デバイスidの確認 デバイスidは idevice_id コマンドで確認できます。 セットアップ $ brew install libimobiledevice 実行 $ idevice_id アプリのインストール AirtestIDEの install() は iOS環境でサポートされていないので、ios-deploy を使うことにしました。 セットアップ $ npm install -g --unsafe-perm=true ios-deploy ipaのインストール $ ios-deploy --bundle <ipaファイル>.ipa インストール先のiOS端末を指定する場合は --id <デバイスid> を使います。 ipaのアンインストール $ ios-deploy --uninstall_only --bundle_id <バンドルid> スリープモードからの復帰 リモート環境から随時利用できるようにiOS端末がスリープ状態から復帰するコードを試してみました。 airtest home() sleep(2) home() text("<iOS端末のパスコード>") タップできないポップアップへの対応 アプリ初回起動時にiOSが出すダイアログが何故かタップできませんでした。しかし、一度アプリを終了すると当該ダイアログが残っており、タップできるようになりました。(テスト対象のアプリによるかもしれません) 参考 appium/WebDriverAgent macにUSB接続されたiPhoneの情報を取得する方法(libimobiledevice) おわりに 参考になった箇所などありましたでしょうか。 これをベースにiOSのテスト環境も構築していきたいと考えています。この記事が何かの助けになりましたら幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pydantic で利用可能な型を追加する

はじめに pydantic という便利な validation ライブラリが python にはありますが、こちらでは validation 可能な型が限られています。それを他の型を拡張しないといけない時があったので、メモがてらまとめました。 どうやるのか https://pydantic-docs.helpmanual.io/usage/types/#custom-data-types このあたりに詳しく書かれていますが、重要なのは __get_validators__ と、そこで yield される関数たちです。pydantic で validation を行う時には、まずは、 __get_validators__ が呼ばれます。これは、ジェネレータになっていて、記された実際の validation 関数を返し、一つ一つ実行していきます。 実際にやってみる。 上記の例を題材に、日本の郵便番号の validator を作ってみます。なお、実際に動かした環境は以下の通りです。 Python 3.9.7 pydantic 1.8.2 import re from pydantic import BaseModel class PostalCodeJP(str): @classmethod def __get_validators__(cls): yield cls.postal_code_should_be_string yield cls.remove_hyphen_if_exists yield cls.postal_code_length_should_be_7 yield cls.postal_code_should_be_numeric_string @classmethod def postal_code_should_be_string(cls, v: str): if not isinstance(v, str): raise TypeError('postal code should be string') return v @classmethod def remove_hyphen_if_exists(cls, v: str): if '-' in v: v = v.replace('-', '') return v @classmethod def posta_code_length_should_be_7(cls, v: str): if len(v) != 7: raise ValueError('postal code should be 7 length string') return v @classmethod def postal_code_should_be_numeric_string(cls, v: str): regex = r'[0-9]+' if not re.fullmatch(regex, v): raise ValueError('postal code should be numeric string') return v class PostalCodeValueObject(BaseModel): postal_code: PostalCodeJP それでは __get_validators__ の一番上から順番に ValidationError を発生させてみます。まずは、郵便番号に文字列以外を用いた場合 p = PostalCodeValueObject(postal_code=1111111) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "pydantic/main.py", line 406, in pydantic.main.BaseModel.__init__ pydantic.error_wrappers.ValidationError: 1 validation error for PostalCodeValueObject postal_code postal code should be string (type=type_error) 次に - を除いた文字列が 7 文字以外になっている場合 p = PostalCodeValueObject(postal_code='111-11111') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "pydantic/main.py", line 406, in pydantic.main.BaseModel.__init__ pydantic.error_wrappers.ValidationError: 1 validation error for PostalCodeValueObject postal_code postal code should be 7 length string (type=value_error) 最後に、全ての文字が数字文字列以外になっている場合 p = PostalCodeValueObject(postal_code='111-111l') # 最後の文字が L の小文字になっている Traceback (most recent call last): File "<stdin>", line 1, in <module> File "pydantic/main.py", line 406, in pydantic.main.BaseModel.__init__ pydantic.error_wrappers.ValidationError: 1 validation error for PostalCodeValueObject postal_code postal code should be numeric string (type=value_error) まとめ pydantic で validation に用いる型を追加したい場合を記しました。 __get_validators__ に validation したい関数を yield する。 公式ドキュメントには重要なことが書かれているので、よく読もう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MAGeCKをMacにインストールしたときのメモ

CRISPR screenの解析パッケージであるMAGeCKをinstallしようと思ったが,以下のwikiの手順通りに進めてもうまくいかなかった 具体的にはzip fileをdownloadしたあとにsetup.pyを実行しようと思ったが,エラーになるというものだった. 解決策 作成者のgithubからcloneを作ったらうまくいった なんで初めうまくいかなかったのかよくわかってない
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Airtestでテンプレート画像の整理を試行錯誤した話

はじめに 今年の3月ごろに突然Airtestを使って自動化テストを作成できないかと言われ、 プログラミング初心者なりにうまく使いこなせるよう試行錯誤してきました。 この記事ではAirtestの画像認識に必要なスクリプト上のテンプレート画像を どのように整理したか、ということを共有させていただこうと思います。 Airtestとは Airtestは中国のNetEase社による画像認識のAI技術を利用した、 デスクトップおよびスマートフォン用アプリケーションの自動操作テストツールです。 アプリケーションからテンプレート画像を取り込み、 それを画像認識に利用して自動操作を行うことができます。 自動テストのスクリプトはPythonで記述します。 独自のIDEが使用でき、 アプリケーションから取り込んだ画像認識用のテンプレート画像が スクリプト上にそのまま表示されるのが特徴です。 Airtestを使い始めたころ Airtestを使い始めたときは、必要なテンプレート画像も少なかったので、 取り込んだテンプレート画像の整理をしようという発想がありませんでした。 そのためスクリプト上にそのまま画像が並んでいる状態でした。 以下の画像は「AndroidのGoogle Chromeで『AIQVE ONE株式会社』と検索する」という例です。 テンプレート画像をそのまま使いまわしていると、 1カ所のUIが変更された場合でも、スクリプト上の複数個所で画像を編集する必要が出てきます。 Airtestに慣れてきて条件分岐が複雑になったり、自動操作を関数にまとめたりすると、 編集すべき画像を見落とす危険性が大きくなってきました。 テンプレート画像を変数に格納してみた そこで画像を扱いやすくするために、変数に代入して使いまわすことにしました。 これなら画像を変数に代入している部分だけ変更すればよいので、UI変更時の手間が少なくなりました。 ※以下から簡略化のため、スクリプト内のテンプレート画像は <画像名> で表記します。 chrome_icon = <Google Chromeのアイコン画像> google_logo = <Googleのロゴ画像> search_word = <検索語句(Android版Chromeの検索バー)の画像> search_result = <検索結果画面での弊社サイトのリンク画像> aiqveone_logo = <AIQVE ONEのロゴ画像> def launch_chrome(): touch(chrome_icon) wait(google_logo) def search(word): touch(search_word) sleep(1) text(word) wait(aiqveone_logo) # ・・・以下略・・・ この方法もスクリプトが短く、使用する画像が少ないなら有効ではありましたが、 画像が多くなってくると変数の命名に困ったり、 スクリプトの画像を変数に格納する部分が縦長になって見づらくなったりと、 さらにもう一工夫が必要に感じました。 また自動操作部分に画像がなくなったことで、 どの画面を操作しているのか スクリプト上から分かりづらくもなってしまいました。 辞書型でまとめてみた というわけで、次に辞書型でテンプレート画像をまとめてみれば、 それらを解決できそうだと考えました。 画面や機能ごとに辞書型の変数を作成し、 キーをテンプレート画像の名前、 値をテンプレート画像として、複数の画像をまとめることにしました。 chrome = { "アイコン": <Google Chromeのアイコン画像>, "ロゴ": <Googleのロゴ画像>, "検索バー": <検索語句(Android版Chromeの検索バー)の画像>, "検索結果": <検索結果画面での弊社サイトのリンク画像>, } aiqveone = { "ロゴ": <AIQVE ONEのロゴ画像>, } def launch_chrome(): touch(chrome['アイコン']) wait(chrome['ロゴ']) def search(word): touch(chrome['検索バー']) sleep(1) text(word) wait(chrome['検索結果']) # ・・・以下略・・・ 辞書名を画面や機能名にして、キーを日本語でつけることで、 どの画面や機能を触っているか多少わかりやすくなりました。 また辞書型ということで、for文でループ処理に使いやすくなりました。 他にもキーと値のペアは横にも並べられるため、 画像が多くなってスクリプトが縦長になることも抑えられました。 ただ、キーを日本語にしたことで、日本語の漢字変換という新たな面倒もでてきました。 例: 戻る <-> もどる、閉じる <-> とじる、など 辞書型でまとめたことで別の部分で面倒臭さが出てきたものの、 しばらくはこの方法をとっていました。 画面や機能ごとにクラスを作成するようにした 自動テストの作成に慣れてくると、 一つのスクリプトで数百もある一連のテストケースを回すようになり、 複数の画面や機能を行き来するようになりました。 それに伴って自動操作の関数がたくさん必要になり、 画像だけでなく関数も雑然としてきため、 スクリプトの全体像の把握が難しく、バグもすぐに直すことが困難になってきました。 そこで今度は画面や機能ごとにクラスを作り、 クラスごとに画像や自動操作のメソッドを管理することにチャレンジしました。 class Chrome: temps = { "アイコン": <Google Chromeのアイコン画像>, "ロゴ": <Googleのロゴ画像>, "検索バー": <検索語句(Android版Chromeの検索バー)の画像>, } def __init__(self, word): self.word = word def launch(self): touch(self.temps['アイコン']) wait(self.temps['ロゴ']) def search(self): touch(self.temps['検索バー']) sleep(1) text(self.word) class SearchResult: temps = { "検索結果": <検索結果画面での弊社サイトのリンク画像>, "ロゴ": <AIQVE ONEのロゴ画像>, } def __init__(self): pass def select_result(self): wait(self.temps['検索結果']) touch(self.temps['検索結果']) wait(self.temps['ロゴ']) # ・・・以下略・・・ 画面や機能ごとにクラスを分けることによって、 それぞれに適した自動操作のメソッドを作ることができ、 スクリプトを組みやすくなりました。 またテンプレート画像がなくても、 クラス名やインスタンス変数名からどの画面の操作をしているか、 辞書型を使用していたとき以上にわかりやすくなりました。 オブジェクト指向の考え方については、 Pythonの入門書等で何となくわかった気になっていましたが、 こうして実際に自分で工夫してみることでその便利さの一端を実感できました。 まとめ このようにプログラミング初心者ながら、 自動化スクリプトを効率的に作成できるよう試行錯誤しています。 とはいえ、どのようなスクリプトでもクラスを作らなければいけないということではなく、 スクリプトの長さによって上記の4パターンを使い分けるのが良いと思います。 まだまだ改善の余地はあると思いますので、 これからもテスト自動化に励んでいこうと思います。 最後まで読んでいただきありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自作Cコンパイラ neo-c2 version 2.0.1リリースです。termuxでvinクローンがコンパイルできなかったので修正してます

タイトル通りです。 あとはオリジナルヒープシステムのコードはコンパイルエラーが出るようにしました。 互換性なくなりましたね。すみません。 あとはAlpine LinuxとiSHですかね。動かしたいの。iSHで動けばAlpine Linuxも動きそうですけど。 ちょっとiSHでコンパイルかけてみます
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ダブルクロスバリデーション(sklearn)

クロスバリデーションによるハイパーパラメータ最適化&評価 from sklearn.datasets import load_diabetes from sklearn.model_selection import GridSearchCV from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection impor KFold X, y = load_diabetes(return_X_y=True) estimator = RandomForestRegressor() param_grid = {'n_estimators':[10,50,100], 'max_depth':[3,5,10]} cv = KFold(5, shuffle=True) gsv = GridSearchCV(estimator, param_grid, cv=cv) gsv.fit(X,y) best_estimator = gsv.best_estimator_ score = cross_val_score(best_estimator, X, y) ダブルクロスバリデーションによるハイパーパラメータ最適化&評価 from sklearn.datasets import load_diabetes from sklearn.model_selection import GridSearchCV from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection impor KFold X, y = load_diabetes(return_X_y=True) estimator = RandomForestRegressor() param_grid = {'n_estimators':[10,50,100], 'max_depth':[3,5,10]} cv = KFold(5, shuffle=True) gsv = GridSearchCV(estimator, param_grid, cv=cv) score = cross_val_score(gsv , X, y) ダブルクロスバリデーションが上記のコードで評価できるのはGridSearchCVとcross_valの仕様によります GridSearchCV.predict 仕様 GridSearchCV.predict ソース GridSearchCV.fit
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Lambda + S3 + Python で PDF を扱う際のTips

初めに PDF を AWS で扱う際に、ローカルの環境と Lambda や S3 では少し扱いに注意が必要だったので、忘れないようにまとめておきます。 1. S3 から PDF を読み込む Python で PDF を扱う場合は PyPDF2 を使うことが多いかと思います。 ローカル環境で読み込む場合は # example code from PyPDF2 import PdfFileReader PDF_PATH = 'pdf/xxx.pdf' # ローカルのディレクトリ pdf = PdfFileReader(PDF_PATH) とパスを指定するだけで簡単に読み込むことができます。 S3 から PDF を読み込む場合は少し工夫が必要です。 # example code import boto3 from PyPDF2 import PdfFileReader from io import BytesIO PDF_PATH = 'data/pdf/xxx.pdf' # S3バケットのディレクトリ BUCKET_NAME = 'your-bucket-name' bucket = boto3.resource('s3').Bucket(BUCKET_NAME) pdf_obj = bucket.Object(path).get()['Body'].read() # S3からの取り出し bytes = BytesIO(pdf_obj) # 変換する pdf = PdfFileReader(bytes) バケットからただ読み込むだけでは機能せず、BytesIO で 変換することで扱うことができます。 2. Lambda上で PDF の一時編集する これは PDF に限らずですが、Lambda で書き込む際は /tmp/ 配下でないとエラーになります。 実際のエラーログ 他のディレクトリを指定すると、このようなエラーになります。 3. Lambda から PDF ファイルを取得する API として PDF を返却する場合があると思います。 # example code with open(output_file, 'rb') as data: pdf = data.read() PDF を開いて Lambda 上でクライアント側に return した場合、自動で utf-8 に変換されるようで、日本語など無効な文字があるとエラーが発生します。 [ERROR] UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 そこで、base64で変換します。そうすることでエラーを回避することができます。 # example code import base64 with open(output_file, 'rb') as data: pdf = data.read() return base64.b64encode(pdf).decode('utf-8') 当然ながらクライアント側でも対応が必要になるので注意してください。 HTMLで表示する場合 デコードは必要ないですが、特定の書き方が必要です。 <html lang="ja"> <head> <meta charset="UTF-8" /> <title>Document</title> </head> <body style="height: 100%; width: 100%; overflow: hidden; margin:0px; background-color: rgb(82, 86, 89);"> <embed style="position:absolute; left: 0; top: 0;" width="100%" height="100%" type="application/pdf" src="data:application/pdf;base64,/* base64 の文字列を入れる */" /> </body> </html> (引用:ブラウザでbase64エンコードしたPDFファイルを表示する) iOS(Swift)で表示する場合 クライアント側では、変換した後に PDFKit の PDFDocument に Data型でセットします。 import PDFKit let pdf = PDFView() pdf.autoScales = true pdf.displayMode = .singlePageContinuous // 変換する if let data = Data(base64Encoded: str) { pdfView.document = .init(data: data) } pdfView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(pdfView) NSLayoutConstraint.activate([ pdfView.leadingAnchor.constraint(equalTo: view.leadingAnchor), pdfView.trailingAnchor.constraint(equalTo: view.trailingAnchor), pdfView.topAnchor.constraint(equalTo: view.topAnchor), pdfView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) 終わりに Lambda 上で実行しないと分からないエラーがそれなりにあるので、1つずつクリアしていくのが苦労しました。また、知見が増えたら追加していこうと思います。 参考文献
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データ画像の水増しコード(colab使用)

はじめに 同じ画像を回転させたりして違う画像へ変更するコード が必要となったため、作成しました。 (注意:Google colab用に作成したので変更箇所は個人で お願いします。) コード 補足
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ubuntuで.ipynbを.pyに変換する方法

はじめに .ipynbを.pyに変換する方法のメモ。 動作環境 Windows 11 Ubuntu 20.04.1 pip 20.0.2 python 3.8 前提条件 python3系 pip (pip3) jupyter インストール 以下のインストールが必要。 sudo apt install jupyter-core sudo apt install jupyter-nbconvert pip3 install jupyter 実行 以下を実行すると.ipynbと同じディレクトリに.pyが作成される。 jupyter-nbconvert --to script terget.ipynb terget.ipynbが変換したい.ipynbファイル。 まとめ Jupyter NotebookでPythonを書くなら、pdftotextを使うのに必要。 タイプミスに気を付ける。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PyQt5のチュートリアル ➁ ツールやモジュールのまとめ

はじめに 前回はPyQt5のHelloWorldをしました。 今回はPyQtのモジュールやツールなどのまとめです 内容は 1.モジュール集 2.ツール集 3.widget集 の3点です。 1.モジュール集 PyQtでよく使われるモジュール集 1.QtCore:他のモジュールから使われる,non-GUIでコアなクラス集 2.QtGui:グラフィックユーザーインターフェース 3.QtMultimedia:lowレベルのマルチメディア様クラス集 4.QtNetwork:ネットワークプログラムのためのクラス集 5.QtOpenGL:OpenGLをつかうためのクラス集 6.QtScript:Qt Scriptsを評価するためのクラス集 7.QtSql:SQLを使ってデータベースとインテグレーションするためのクラス集 8.QtSvg:SVGファイルを表示するためのクラス集 9.QtWebKit:HTMLをレンダリングしたり編集したりするためのクラス集 10.QtXml:XMLを扱うためのクラス集 11.QtWidges:クラシックなデスクトップスタイルのUIを作成するためのクラス集 12.QtDesigner:Qt Designerの拡張のためのクラス集 13.QtAssistant:オンラインヘルプのためのモジュール 2.ツール集 PyQt5開発でよく使われるツール集 1.assistant: Qt Assistant ドキュメントツール 2.pyqt5designer: Qt Designer GUIのレイアウトツール 3.linguist: Qt Linguist 翻訳様ツール 4.lrelease: tsファイルをqmファイルにコンパイルする(ts,qmファイルってなんだろう...) 5.pylupdate5: よくわかりません 6.qmake: Qtソフトビルドツール 7.pyqt5qmlscene: QMLファイルビューアー(QMLファイルとはなんだ...) 8.pyqmlviewer:QMLファイルビューアー 9.pyrcc5:Qtリソースファイルコンパイラー(リソースファイルとは?) 10.pyuic5:uiファイルからコードを生成するQt User Interface Compiler 11.pyqmltestrunner: QMLコードを単体テスト実行する(QMLファイルって何だ...) 12.qdbus: D-Busサービスのためのコマンドラインツール 13.QDoc: ソフトウェアプロジェクトのドキュメントを生成する 14.Qhelpgenerator: Qt Helpファイルの生成とビューイング 15.qmlimportscanner: QMLインポート上での解析と報告 3.widget集 PyQtのAPIの概要 PyQtAPI ・ 400を超えるクラスが含まれる ・QObjectクラスがクラスの元 ・QPaintDeviceクラスはペイントできるオブジェクトクラスの元 QApplicationクラス ・GUIアプリケーションの設定と制御フローを管理 ・メインループ内で発生するウィンドウやその他のソースからのイベントの管理も行う QWidgetクラス ・QObjectクラスとQPaintDeviceクラスから派生したQWidgetクラスはすべてのユーザーインターフェイスオブジェクトとの基本クラス ・QDialogクラスとQFrameクラスもQWidgetクラスから派生している PyQtでよく使用されるWidget集 1.QLabel: テキストや画像を表示するために使われる 2.QLineEdit: ユーザーがテキストの一行目に入るの強制するテキスト? 3.QTextEdit: ユーザーが複数のラインに入るのを強制するテキスト? 4.QPushButton: ボタン 5.QRadioButton: ラジオボタン 6.QCheckBox: チェックボックス 7.QSpinBox: スピンボックス 8.QScrollBar: スクロールバー 9.QSlider: スライダー 10.QComboBox: ドロップボックスに選択肢を出す 11.QMenuBar: QMenuオブジェクトを縦に格納したもの 12.QStatusBar: QMainWindowの下にある.状態を表示 13.QToolBar: QMainWindowの上にある.アクションボタンを集のう 14.QListView: ListModeかIconModeで選択肢を表示 15.QPixmap: QLabel,QPushBUttonオブジェクトのオフスクリーンイメージを表示 16.QDialog: 親情報を持っているモーダルウィンドウ 最後に PyQtでは,GUI開発を楽にするために多くのモジュールやツールを作成してくれているんですね。 今後こういった道具をうまく活用していって便利なGUIを作成できたらいいなと思います。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ゼロから作るDeepLearning3で躓いたところをまとめる

はじめに この記事では、ゼロから作るDeepLearning➂フレームワーク編で躓いたところをまとめていきたいと思います。 本書はかなりわかりやすく丁寧に書かれてはおります。 しかし、ほんの一部省略が発生してしまい、コードを動かしている人であれば、誰もが止まってしまうのではないかと思いまとめることにしました。 私の読書スピードに合わせて順次更新予定となります。 問題 1. ステップ32 高階微分(実装編) この章で書籍初となるあとは読者に任せたが発生します。 具体的には、core.pyと__init__.pyになります。 修正はAddとMulについては書かれていますが、そのほかについては同じように書いてくださいとのことでした。しかし、ここで同じように書いても動かなかったり、同じようにってどういうこと?の人もいるかと思いますのでコードを載せておきます。 core.py import weakref import numpy as np import contextlib # ============================================================================= # Config # ============================================================================= class Config: enable_backprop = True @contextlib.contextmanager def using_config(name, value): old_value = getattr(Config, name) setattr(Config, name, value) try: yield finally: setattr(Config, name, old_value) def no_grad(): return using_config('enable_backprop', False) # ============================================================================= # Variable / Function # ============================================================================= class Variable: __array_priority__ = 200 def __init__(self, data, name=None): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.name = name self.grad = None self.creator = None self.generation = 0 @property def shape(self): return self.data.shape @property def ndim(self): return self.data.ndim @property def size(self): return self.data.size @property def dtype(self): return self.data.dtype def __len__(self): return len(self.data) def __repr__(self): if self.data is None: return 'variable(None)' p = str(self.data).replace('\n', '\n' + ' ' * 9) return 'variable(' + p + ')' def set_creator(self, func): self.creator = func self.generation = func.generation + 1 def cleargrad(self): self.grad = None def backward(self, retain_grad=False, create_graph=False): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() gys = [output().grad for output in f.outputs] # output is weakref with using_config('enable_backprop', create_graph): gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: add_func(x.creator) if not retain_grad: for y in f.outputs: y().grad = None # y is weakref def as_variable(obj): if isinstance(obj, Variable): return obj return Variable(obj) def as_array(x): if np.isscalar(x): return np.array(x) return x class Function: def __call__(self, *inputs): inputs = [as_variable(x) for x in inputs] xs = [x.data for x in inputs] ys = self.forward(*xs) if not isinstance(ys, tuple): ys = (ys,) outputs = [Variable(as_array(y)) for y in ys] if Config.enable_backprop: self.generation = max([x.generation for x in inputs]) for output in outputs: output.set_creator(self) self.inputs = inputs self.outputs = [weakref.ref(output) for output in outputs] return outputs if len(outputs) > 1 else outputs[0] def forward(self, xs): raise NotImplementedError() def backward(self, gys): raise NotImplementedError() # ============================================================================= # 四則演算 / 演算子のオーバーロード # ============================================================================= class Add(Function): def forward(self, x0, x1): y = x0 + x1 return y def backward(self, gy): return gy, gy def add(x0, x1): x1 = as_array(x1) return Add()(x0, x1) class Mul(Function): def forward(self, x0, x1): y = x0 * x1 return y def backward(self, gy): x0, x1 = self.inputs return gy * x1, gy * x0 def mul(x0, x1): x1 = as_array(x1) return Mul()(x0, x1) class Neg(Function): def forward(self, x): return -x def backward(self, gy): return -gy def neg(x): return Neg()(x) class Sub(Function): def forward(self, x0, x1): y = x0 - x1 return y def backward(self, gy): return gy, -gy def sub(x0, x1): x1 = as_array(x1) return Sub()(x0, x1) def rsub(x0, x1): x1 = as_array(x1) return Sub()(x1, x0) class Div(Function): def forward(self, x0, x1): y = x0 / x1 return y def backward(self, gy): x0, x1 = self.inputs gx0 = gy / x1 gx1 = gy * (-x0 / x1 ** 2) return gx0, gx1 def div(x0, x1): x1 = as_array(x1) return Div()(x0, x1) def rdiv(x0, x1): x1 = as_array(x1) return Div()(x1, x0) class Pow(Function): def __init__(self, c): self.c = c def forward(self, x): y = x ** self.c return y def backward(self, gy): x, = self.inputs c = self.c gx = c * x ** (c - 1) * gy return gx def pow(x, c): return Pow(c)(x) def setup_variable(): Variable.__add__ = add Variable.__radd__ = add Variable.__mul__ = mul Variable.__rmul__ = mul Variable.__neg__ = neg Variable.__sub__ = sub Variable.__rsub__ = rsub Variable.__truediv__ = div Variable.__rtruediv__ = rdiv Variable.__pow__ = pow __init__.py # ============================================================================= # step23.pyからstep32.pyまではsimple_coreを利用 is_simple_core = False # is_simple_core = True # ============================================================================= if is_simple_core: from dezero.core_simple import Variable from dezero.core_simple import Function from dezero.core_simple import using_config from dezero.core_simple import no_grad from dezero.core_simple import as_array from dezero.core_simple import as_variable from dezero.core_simple import setup_variable else: from dezero.core import Variable from dezero.core import Function from dezero.core import using_config from dezero.core import no_grad from dezero.core import as_array from dezero.core import as_variable from dezero.core import setup_variable from dezero.core import Config setup_variable() __version__ = '0.0.13' 難しいところは、書籍でコードが省略されていること、そしてGitではこの先のすべてのコードが載ってしまっているので動かないということです。 とくにPOWに関しては、self.inputsに右辺をしただけではエラーになり、x, = self.inputsにする必要があります。 また、このファイルは今後内容が追加されていくため再度勉強しようと思った時にまた同じエラーに遭遇しそうなのでメモしておきました。 ここをコピペで済ませるとstep33でエラーに遭遇して戻ることになります。また、コードを動かすこともstep32ではないので、構成的にも微妙だなとは感じました。 ステップ38 Variableからreshapeを使う 基本的には書籍通りですが、functions.pyの先頭に以下を追加する必要があります。 from dezero.core import Function, Variable, as_variable, as_array reshapeのなかでas_variableを利用しているためです。 続きについて 勉強中のため止まった際に更新します。 それとimport関連でしばしエラーになりますが、上の対応を真似て行えばできますので以降は省略したいと思います。 おわりに この書籍はもうこれ以上分かりやすく説明するのは無理なのではないかというところまでかみ砕かれており今年読んだ中でもトップレベルでよい本だったのですが、やはり一部わかりづらいところはあるようなのでまとめてみることにしました。 誰かの参考になれば幸いです。 参考 ゼロから作るDeepLearning➂フレームワーク編
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CIFAR10で遊んでみた

Alexnet 参考 https://qiita.com/URAN110/items/ea2bfc8f7ba2fc858de3 https://github.com/uran110/AlexNet-Cifar10/blob/master/alexnet_cifar10.py resnet 参考 https://blog.shikoan.com/resnet-multiple-framework/ https://github.com/koshian2/ResNet-MultipleFramework/blob/master/resnet_keras.py 20層 50層
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python]画像を連番付きでラベルシートに印刷するためのPDFを作成する

はじめに QR画像を連番付きで大量印刷する必要があったので、Pythonのreportlabというライブラリを活用して作成しました。 ローカル環境でのその作成方法とスクリプトをまとめておきます。 ※本記事ではスクリプトの全体を載せていますが、解説については長くなってしまうので省略しています。具体的なreportlabの使い方、スクリプトの解説などは別の機会に書きたいと思います。 やりたいこと 印刷する用紙としては、こちらの「エーワン ラベルシール 品番72312」を使いました。 名刺サイズの10枚組のもの。 このラベルに合わせて、QRを連番付きで印刷するためのpdfファイルを作成します。 以下が完成形のpdfです(QRはダミー)。 今回の記事では、このような1枚だけのpdfを作成する流れをまとめています。 いくらか汎用性のあるスクリプトにしているので、数値を変えれば違う様式のラベルシートにも対応している…ハズです。 複数ページにまたがるpdfを作成するためには、もう少し応用する必要があります。 今回使用するPythonライブラリについて Pythonの標準ライブラリとしては、パスを扱うPathlibを使用します。 サードパーティーライブラリとして、ReportLabとPillowの二つを使用します。 1. ReportLab 公式サイト ドキュメント Pythonで様々なpdf生成ができるライブラリです。 ReportLab PLUSという有料プランがあるようですが、今回は無料版を利用して、簡単なテキスト、図形、画像を配置したpdfを生成します。 2. Pillow ドキュメント Pythonで画像を扱うためのライブラリです。こちらもちらっと使用します。 準備 1. プロジェクトディレクトリの構成 まず、今回のプロジェクト用のディレクトリを準備しておきます。 適当なプロジェクト名をつけたディレクトリ(ここではpdfMaker)を作成し、その中にQR画像用のディレクトリ(img)を用意しておきます。 Pythonのスクリプトファイル(.pyや.ipynb)もこのディレクトリの中に作成します。 こんな感じのディレクトリ構造になっていればOKです。 pdfMaker/ ​ ├ img/ * 画像を格納するためのディレクトリ ​ └ code.py * Pythonスクリプトファイル 2. QR画像の準備 先ほど作成したimgディレクトリの中に、使用するQRの画像を用意しておきましょう。 ファイル名は番号.pdfという名前で連番を振っておきます。 このファイル名をそのままpdfにテキスト出力させていくので、余計な文字列は入れないようにします。 番号ではなく出力させたい文字列をファイル名にしても良いですが、Reportlabで日本語フォントを指定する必要があるのでやや注意が必要です。 英数字のみだと表示させやすいですね。 今回はダミーデータを10枚用意しておきました。 2. ライブラリのインストール 環境に応じて、必要なライブラリ(reportlab, Pillow)をインストールしておきます。 pip listで確認して既にインストールされているなら不要です。 ライブラリのインストールは通常、pip install ライブラリ名もしくはpip3 install ライブラリ名で行います。 anaconda使用時の場合はconda install ライブラリ名で行います。 この辺りは環境によって違うので注意が必要です。 例) pip install Pillow pip install reportlab 実装 全体のスクリプト 以下が全体のスクリプトになります。 from pathlib import Path from PIL import Image from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import A4 # 画像ファイルのパスとファイル名(=連番)をタプルで用意する _images = tuple(Path('./img').glob('*.png')) images = tuple(sorted(_images)) images_name = tuple(map(lambda im: im.stem , images)) # (※1)ラベルシートの設定 cards_num= (2, 5) # カードをA4に何枚配置するか(横の枚数, 縦の枚数) A4_mm = (210, 297) # A4用紙のサイズをmmで指定(横、縦) card_mm = (86.4, 50.8) # 1枚のラベルのサイズをmmで指定(横、縦) margin_mm = (18.6, 21.2) # 余白をmmで指定(左右、上下) to_px = A4[0] / A4_mm[0] # mmをpxに変換 card = tuple(( x * to_px for x in card_mm )) # カードのサイズpx margin = tuple(( x * to_px for x in margin_mm )) # 余白のサイズpx # (※2)A4のpdfを作成する file_name = 'sample.pdf' page = canvas.Canvas(file_name, pagesize=A4) page.setLineWidth(3) # 線の太さを指定 page.setFont('Helvetica', 80) # ページのフォントを指定 pos = [margin[0], margin[1]] # 描画の初期地点 i = 0 # A4のpdfに必要なものを描画する while (i < cards_num[0] * cards_num[1]): for y in range(cards_num[1]): for x in range(cards_num[0]): # (※3)図形(長方形、直線)の描画 page.rect(pos[0], pos[1], card[0], card[1]) page.line(pos[0] + card[0]/12, pos[1] + card[1]/4, pos[0] + card[0]/2 - 5, pos[1] + card[1]/4) page.drawString(pos[0] + card[0]/12, pos[1] + card[1]/4 + 5, images_name[i]) # (※4)画像の挿入 image = Image.open(images[i]) page.drawInlineImage(image, pos[0] + card[0]/2 - 5, pos[1] + card[1]/12, width=card[0]/2, height= card[0]/2) i += 1 pos[0] += card[0] pos[0] = margin[0] pos[1] += card[1] # PDFファイルを保存 page.save() スクリプトのカスタマイズ方法 (※1)部分の4つの変数は、ラベルシートの規格に対応しています。 別の規格のラベルシートに印刷したい場合は、この4つの変数を変更することで可能になります。 card_numに何×何でラベルが配置されているのかを指定、 A4_mm、card_mm、margin_mmにそれぞれA4サイズ、1枚のラベルのサイズ、余白をそれぞれmmで入力してあげます。 (※2)以下が、reportlabを使ってpdfを作成していく具体的な処理になっています。 ファイル名file_nameや、線の太さ、フォントの変更など必要に応じて変更することができます。 (※3)(※4)の部分で、図形描画や画像の配置を行なっています。 ここで配置位置をpx単位で変更することができます。 おわりに 詳しい解説は別の機会で。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python]画像を連番付きでラベルシートに印刷するためのPDFを作成するスクリプト

はじめに QR画像を連番付きで大量印刷する必要があったので、Pythonのreportlabというライブラリを活用して作成しました。 ローカル環境でのその作成方法とスクリプトをまとめておきます。 ※本記事ではスクリプトの全体を載せていますが、解説については長くなってしまうので省略しています。別記事で整理したいと思います。 やりたいこと 印刷する用紙としては、こちらの「エーワン ラベルシール 品番72312」を使いました。 名刺サイズの10枚組のもの。 このラベルに合わせて、QRを連番付きで印刷するためのpdfファイルを作成します。 以下が完成形のpdfです(QRはダミー)。 今回の記事では、このような1枚だけのpdfを作成する流れをまとめています。 いくらか汎用性のあるスクリプトにしているので、数値を変えれば違う様式のラベルシートにも対応しているハズです。 また、複数ページにまたがるpdfを作成することもできますが、今回のスクリプトをもう少し応用する必要があります。 今回使用するPythonライブラリについて Pythonの標準ライブラリとしては、パスを扱うPathlibを使用します。 サードパーティーライブラリとして、ReportLabとPillowの二つを使用します。 1. ReportLab 公式サイト ドキュメント Pythonで様々なpdf生成ができるライブラリです。 ReportLab PLUSという有料プランがあるようですが、今回は無料版を利用して、簡単なテキスト、図形、画像を配置したpdfを生成します。 2. Pillow ドキュメント Pythonで画像を扱うためのライブラリです。こちらもちらっと使用します。 準備 1. プロジェクトディレクトリの構成 まず、今回のプロジェクト用のディレクトリを準備しておきます。 適当なプロジェクト名をつけたディレクトリ(ここではpdfMaker)を作成し、その中にQR画像用のディレクトリ(img)を用意しておきます。 Pythonのスクリプトファイル(.pyや.ipynb)もこのディレクトリの中に作成します。 こんな感じのディレクトリ構造になっていればOKです。 pdfMaker/ ​ ├ img/ * 画像を格納するためのディレクトリ ​ └ code.py * Pythonスクリプトファイル 2. QR画像の準備 先ほど作成したimgディレクトリの中に、使用するQRの画像を用意しておきましょう。 ファイル名は番号.pdfという名前で連番を振っておきます。 このファイル名をそのままpdfにテキスト出力させていくので、余計な文字列は入れないようにします。 番号ではなく出力させたい文字列をファイル名にしても良いですが、Reportlabで日本語フォントを指定する必要があるのでやや注意が必要です。 英数字のみだと表示させやすいですね。 今回はダミーデータを10枚用意しておきました。 2. ライブラリのインストール 環境に応じて、必要なライブラリ(reportlab, Pillow)をインストールしておきます。 pip listで確認して既にインストールされているなら不要です。 ライブラリのインストールは通常、pip install ライブラリ名もしくはpip3 install ライブラリ名で行います。 anaconda使用時の場合はconda install ライブラリ名で行います。 この辺りは環境によって違うので注意が必要です。 例) pip install Pillow pip install reportlab 実装 全体のスクリプト 以下が全体のスクリプトになります。 from pathlib import Path from PIL import Image from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import A4 # 画像ファイルのパスとファイル名(=連番)をタプルで用意する _images = tuple(Path('./img').glob('*.png')) images = tuple(sorted(_images)) images_name = tuple(map(lambda im: im.stem , images)) # (※1)ラベルシートの設定 cards_num= (2, 5) # カードをA4に何枚配置するか(横の枚数, 縦の枚数) A4_mm = (210, 297) # A4用紙のサイズをmmで指定(横、縦) card_mm = (86.4, 50.8) # 1枚のラベルのサイズをmmで指定(横、縦) margin_mm = (18.6, 21.2) # 余白をmmで指定(左右、上下) to_px = A4[0] / A4_mm[0] # mmをpxに変換 card = tuple(( x * to_px for x in card_mm )) # カードのサイズpx margin = tuple(( x * to_px for x in margin_mm )) # 余白のサイズpx # (※2)A4のpdfを作成する file_name = 'sample.pdf' page = canvas.Canvas(file_name, pagesize=A4) page.setLineWidth(3) # 線の太さを指定 page.setFont('Helvetica', 80) # ページのフォントを指定 pos = [margin[0], margin[1]] # 描画の初期地点 i = 0 # A4のpdfに必要なものを描画する while (i < cards_num[0] * cards_num[1]): for y in range(cards_num[1]): for x in range(cards_num[0]): # (※3)図形(長方形、直線)とテキストの描画 page.rect(pos[0], pos[1], card[0], card[1]) page.line(pos[0] + card[0]/12, pos[1] + card[1]/4, pos[0] + card[0]/2 - 5, pos[1] + card[1]/4) page.drawString(pos[0] + card[0]/12, pos[1] + card[1]/4 + 5, images_name[i]) # (※4)画像の挿入 image = Image.open(images[i]) page.drawInlineImage(image, pos[0] + card[0]/2 - 5, pos[1] + card[1]/12, width=card[0]/2, height= card[0]/2) i += 1 pos[0] += card[0] pos[0] = margin[0] pos[1] += card[1] # PDFファイルを保存 page.save() スクリプトのカスタマイズ方法 (※1)部分の4つの変数は、ラベルシートの規格に対応しています。 別の規格のラベルシートに印刷したい場合は、この4つの変数を変更することで可能になります。 card_numに何枚×何枚でラベルが配置されているのかをタプル(横の枚数, 縦の枚数)で指定し、 A4_mm、card_mm、margin_mmにそれぞれA4サイズ、1枚のラベルのサイズ、余白をそれぞれmm値のタプル(横, 縦)で入力してあげます。 (※2)以下が、reportlabを使ってpdfを作成していく具体的な処理になっています。 変数file_nameで保存するpdfのファイル名を変更したり、描画する図形の線の太さやフォントの変更など必要に応じて変更することができます。 (※3)(※4)の部分で、図形描画やテキスト、画像の配置を行なっています。 reportlabでは配置位置をpx単位で指定できるので、それらをここで指定して調整することができます。 (本記事ではあまり解説できないので、ドキュメントと照らし合わせてご覧いただけたらと思います。) 変数cardには先ほどmmで指定したラベルサイズがpx値のタプルに変換されて入っているので、配置位置の指定や画像のサイズ指定にはこの値を利用しています。 直接px値で指定もできるので、手持ちの画像に応じてちょうど良い配置を探してみてください。 おわりに 1枚のラベルシートに印刷するためのPDFファイルの作成方法とスクリプト全体を紹介しました。 スクリプトの解説が足りないので、これだけだとカスタマイズのやり方が難しいかと思います。詳細につきましては別で解説記事を書きたいなと思っています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python_ __name__, __main__

実行スクリプト上での__name__は__main__になる my_module.py def print_name(): print(__name__) print_name() $ python my_module.py __main__ moduleとして呼び出した時の__name__はmodule名になる moduleをimportしたときにmoduleを全て一度実行するので、my_moduleが3回printされている。 main.py import my_module my_module.print_name() print(my_module.__name__) print(__name__) $ python main.py my_module my_module my_module __main__ moduleをimportして使用するときに実行したくない処理はif __name__ == "__main__":の中に入れる my_module.py def print_name(): print(__name__) if __name__ == "__main__" print_name() main.py import my_module my_module.print_name() print(my_module.__name__) print(__name__) $python my_module.py __main__ $ python main.py my_module my_module __main__ 公式ドキュメント https://docs.python.org/ja/3/reference/import.html?highlight=__name__#name__ https://docs.python.org/ja/3/library/__main__.html
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AtCoder】初学者,TLEに心を折られる

はじめに PythonでAtCoderにチャレンジしているのですが,TLEで挫折することが多々あります. 今回もそんな感じになったので,初記事を書いてみます.ちなみに,PythonもAtCoderもともにぺーぺーです. 発生している問題・エラー 例えば,タイムリーなこの問題(AtCoder Regular Contest 130 B- Colorful Lines).後工程から見ていく方法でトライしました. 以下の通り回答しましたが.TLEが22/35となりました.平均2206msかかっている. $Q ≤ 3×10^5$ でしたが,おそらくN, Mを毎ループ探してしまうのがダメなのか…しかしそれをしないやり方はあるのか…. import sys input = sys.stdin.readline H, W, C, Q = map(int, input().split()) t = [0] * Q n = [0] * Q c = [0] * Q for i in range(Q): t[i], n[i], c[i] = map(int, input().split()) N = [] M = [] ans = [0] * C for i in range(Q)[::-1]: if t[i] == 1 and n[i] not in N: ans[c[i]-1] += W - len(M) N.append(n[i]) elif t[i] == 2 and n[i] not in M: ans[c[i]-1] += H - len(N) M.append(n[i]) elif len(N) == H and len(M) == W: break print(*ans) 自分で試したこと 此方を参考に,何度かトライをしました. PypyとPythonを行き来. input を sys.stdin.readline にすることで読込時間を1/10程度に短縮(できてる?せいぜい2ms程度しか縮まらなかった.) すべてのマスが埋まったらbreak(悪あがき) whileではなくforを使っているし,あと変えられるところは… という間に,やる気を失い,この記事の下書きを始めてしまいました. 答え合わせ コンテスト終了後に公式解説を確認.全く同じ解き方で一安心.だが何が違うんだ. 一番効いていたのは,読込ではなく,埋まっている行・列(探索対象)をlistではなくset(集合)にすることでした. これで計算時間は1/3~1/4程度に. import sys input = sys.stdin.readline H, W, C, Q = map(int, input().split()) t = [0] * Q n = [0] * Q c = [0] * Q for i in range(Q): t[i], n[i], c[i] = map(int, input().split()) # set(集合)をセット. N = set() M = set() ans = [0] * C for i in range(Q)[::-1]: if t[i] == 1 and n[i] not in N: ans[c[i]-1] += W - len(M) N.add(n[i]) # appendではなくadd. elif t[i] == 2 and n[i] not in M: ans[c[i]-1] += H - len(N) M.add(n[i]) elif len(N) == H and len(M) == W: break print(*ans) 基本的なことですが,setをもっと使おうと思います. 追記 setがtupleになっていたものを修正しました. 参考になる記事を@snhrhdtさんに共有いただきました.「知らないと恥ずかしい」…
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む