LaTeX 備忘録
dvipdfmxでTrueTypeフォントを追加する方法
TEXMFLOCAL
のパスを取得する
$ kpsewhich -var-value=TEXMFLOCAL
TEXMFLOCAL
/fonts/truetype/ 以下に.ttf
ファイルをいれる- フォントの追加を反映させる
$ sudo mktexlsr
- texソースで以下のようにmapする(ゴシック体の場合)(mapファイルをいじるやり方もある)
\AtBeginDvi{\special{pdf:mapline gbm H GenShinGothic-Regular.ttf}} \AtBeginDvi{\special{pdf:mapline gbmv V GenShinGothic-Regular.ttf}}
dvipdfmxでlatexパッケージ(.sty)を追加する方法
TEXMFLOCAL
のパスを取得する
$ kpsewhich -var-value=TEXMFLOCAL
TEXMFLOCAL
/tex/latex/ 以下に.sty
ファイルをいれる- パッケージの追加の追加を反映させる
$ sudo mktexlsr
- texソースで以下のようにmapする(ゴシック体の場合)(mapファイルをいじるやり方もある)
\usepackage{PACKAGENAME}
GuakeTerminalに機能を追加してみた
前書き
この記事はEEIC3年のAセメスター実験「大規模ソフトウェアを手探る」にてレポート課題としてdoradorasukiとKotaro7750とshugo256によって書かれているものです。この実験はオープンソースソフトウェアのソースコードを手探った上で何らかの機能を追加しようという実験です。今回私達のグループでは筆者の一人が日頃から愛用しているLinuxのターミナルエミュレータであるGuakeについて手探ってみました。3人の共同執筆記事ということを念頭に置いて読んでください。
環境
Ubuntu 18.04.3 LTS
課題設定
まずはじめにGitHub上のissueを各自で読み、欲しい機能を考えました。 その結果、このissueでリクエストされている機能を追加してみようということになりました。 この機能を簡潔に説明すると、タブをピンどめしてGuake自体を終了してもタブの状態を保存できるというものです。 少し調べてみたところ、標準でカレントディレクトリの情報くらいは保存して復元できるようになっており、その機能を拡張するようにすれば良さそうだと思ったのでこの課題を実装してみることにしました。
使用した内部的な機能
GuakeにはTerminalとやりとりするための様々な関数が実装されていてそれに乗っかることで様々な挙動をさせることが可能です。ここでは今回使った関数などを中心に紹介したいと思います。
ターミナルでコマンドを実行する
# guake/terminal.py class GuakeTerminal(Vte.Terminal): ... def execute_command(self, command): if command[-1] != '\n': command += "\n" self.feed_child(command)
terminal.py
のGuakeTerminal
クラスのインスタンスメソッドです。Guake内ではタブごとにこのクラスが作られているのですが、execute_command
メソッドを実行することでターミナルで実行可能なコマンドを実行させることが可能です。
ちなみにfeed_child
は親クラスであるVte.Terminal
のメソッドであり、これは引数に与えたコマンドをターミナル上であたかもユーザーが実行したかのように(表示にも反映)して実行するという動きをします。Vte.Terminal
はGtkのターミナルエミュレート用のクラスとして用意されているもののようです。
VteTerminalのシグナルをキャッチして関数を実行する
# guake/terminal.py class GuakeTerminal(Vte.Terminal): def __init__(self, guake): ... self.connect(<シグナル名>, <動作させたい関数>) ...
Vte.Terminal
のインスタンスメソッドconnect
を用いると、自前のメソッドとVteTerminalのシグナルを結びつけることができます。今回は最終的には使わずに実装できましたが、キーボード入力やディレクトリ移動などを取得したいときなどに便利だ思います。ちなみに使っていた頃は上のようにGuakeTerminal
クラスのコンストラクタで呼び出していました。
追加機能その1:開いているタブのコマンド実行履歴をバックアップする
現状と目標
ターミナルに与えたコマンドを保存する機能自体は多くのシェルの機能として実装されています。例えばBashであればコマンドは実行するたびにキャッシュされ、Bashが終了するタイミングで.bash_history
というファイルに書き込まれます。
しかしこの機能では、下図のように別々のターミナルで実行したコマンドも区別されずに同一のファイルに保存されてしまうので、複数のターミナルのhistoryがごちゃ混ぜになってしまうことがよくあります。
ターミナルごとにhistoryが混ざらずに残ってくれると嬉しいなあということで、今回はGuakeの機能としてタブごとのコマンドのバックアップ機能を作ることとしました。具体的にはGuakeの各タブに対してユニークなhistoryファイルを割り当て(下図)、そこにそのタブでコマンドが叩かれるたびにその内容を書き込んでいくというもので、Guakeが終了したとしても次回起動時に復帰できるということを目標としました。
実装
上記の設計から気付くとは思いますが、今回の実装は基本的にBash依存となっています。 というのも、historyを保存するhistoryコマンドがBashのビルトイン関数であり、他のシェルを使っている場合にはそもそも保存されないからです。 一応シェル依存にならないように他の機構を考えはした(後述)のですが、どれもあまり美しくなかったのでこの機構を採用しました。
実装は大きく分けて以下の2つに大別されるのでそれぞれ説明していきます。
コマンド履歴保存
Guakeにはもともと備わっている機能として、タブが何個開いていて、どのディレクトリにいるのかという情報をファイルに保存するというものがあり、Guakeを開いた際にファイルからタブ情報を読み込むことで復元しています。このファイルを拡張してコマンド履歴の保存ファイルの場所も保存する設計にしました。
タブとテキストファイルを紐付ける
上述のタブ情報はXDG_CONFIG_HOME/session.json
(環境変数で指定されています)に保存されています。ここに以下のように新たにhistory
というkeyを追加し、そのvalueをコマンド履歴保存用のテキストファイルへのパスとすることで各タブとそのテキストファイルを紐付けるようにしました。
{ "schema_version": 2, "timestamp": 1572598653, "workspace": { "0": [ [ { "panes": [ { "type": "term", "directory": "/home/denjo/.config/guake", "history": "/home/denjo/.config/guake/tabs/HoNYAJD7Y5.txt" } ], "label": "denjo@DJ00059: ~/.config/guake", "custom_label_set": false } ] ] } }
具体的には、下のようにGuakeTerminal
クラスのインスタンス変数としてhistory_path
を用意し、ここにhistoryファイルへのパスを持っておくようにしました。ちなみにcreate_random_file_name()はXDG_CONFIG_HOME/guake/tabs/
以下のランダムなテキストファイル名を生成する関数です。
# guake/terminal.py class GuakeTerminal(Vte.Terminal): def __init__(self, guake): ... self.history_path = self.create_random_file_name() ...
ここで定義したファイル名history_path
は、タブごとの情報をsession.json
に書き込める形式に変換して返す関数であるsave_box_layout()
を拡張することで実際にファイルに書き込むようにしています。
# guake/boxes.py def save_box_layout(self, box, panes: list): ... directory = box.terminal.get_current_directory() panes.append({'type': btype, 'directory': directory,'history': str(box.terminal.history_path)})
boxはメンバにそれぞれのターミナルの情報を持っており、そこから上述のhistory_path
を取得して、'history'のvalueとして追記することでsession.json
に書き込みました。
タブのコマンド履歴をテキストファイルに書き込む
どうやらnotebook.py
のTerminalNotebook
クラスのインスタンスメソッドnew_page_with_focus
が、タブ生成時に呼ばれる関数であるということがpdbなどを用いることで判明したので、実装としてはこの関数のreturnの手前に下のような2行を書き加えるだけです。
# guake/notebook.py class TerminalNotebook(Gtk.Notebook): ... def new_page_with_focus(...): ... command = 'PROMPT_COMMAND=$PROMPT_COMMAND"\n history -w {}"'.format(terminal.history_path) terminal.execute_command_and_reset_output(command) return box, page_num, terminal
まず、terminal.execute_command_and_reset_output(command)
についてterminal
はGuakeTerminal
クラスのインスタンスであり、execute_command_and_reset_output
メソッドについては後述しますが、前述のexecute_command
メソッドを用いてコマンドを実行した上でエスケープシーケンスを用いて出力を削除する関数です。これがないとユーザーの画面に堂々とhistory -w
コマンドが表示されてしまい、流石にカッコ悪いです。
この関数に渡すcommand
は、PROMPT_COMMAND
という環境変数に対してhistoryを書き込むコマンドhistory -w [filename]
を書き加えるという処理です。PROMPT_COMMAND
はBash特有の機能であり、この変数に書かれたコマンドが、プロンプトが表示される直前(=コマンドの実行終了直後)に実行されるというものです。
history
コマンドおよび環境変数PROMPT_COMMAND
はともにBash依存の機能ではありますが、これらと似たような機能はzshなど他のシェルにおいても備わっている(と信じている)ので、少しの変更で対応できるのではないかと踏んでいます。
エスケープシーケンスについて
上述のexecute_command_and_reset_output
メソッドはGuakeTerminal
クラスのメソッドとして以下のように実装しました。
# guake/terminal.py class GuakeTerminal(Vte.Terminal): ... def execute_command(self, command): ... def execute_command_and_reset_output(self, command): command = ' ' + command command += '\n echo -en "\e[0;0H\e[0J"' self.execute_command(command)
ここでは実行したいコマンドの後にecho
コマンドを加えるということをしています。-e
はエスケープシーケンスを有効にし、-n
は出力後に改行を入れないというオプションです。\e[0;0H
でターミナルの先頭に移動し、\e[0J
で画面の出力のクリアを行うので、この二つの呪文によりターミナル画面のリセットが実現できます。
なお、上述のPROMPT_COMMAND
のセットはターミナル起動時にしか行わないので、ターミナル使用中に画面がリセットされてしまうということはありません。またシェルの画面リセットのコマンドとしてreset
やclear
がありますが、これらはともに不採用となりました。reset
は画面以外の色々なこともリセットしている(要出典)ようで実行に数秒かかってしまい、clear
はぱっと見画面がリセットされたように見えるだけで上にスクロールすると元の画面が消えずに残ってしまっていたからです。
ちなみにcommand
の先頭にスペースを加えているのは、このコマンドをターミナルのhistory
に反映させないための裏技です。
コマンド履歴復帰
Guakeにもともと備わっているタブ情報復元処理が私達がやりたい処理のタイミングと内容が似ているため、タブ情報の復帰のタイミングでhistory -r
コマンドを実行する設計にしました。
タブ情報の復帰はguake_app.py
のGuake
クラスのインスタンスメソッドrestore_tabs
でsession.json
から読みだすという形で行われているようです。大きなメソッドなので実装は割愛しますが、この関数の中でsession.json
のhistory
に書かれたfilepathを読み、history -r <filepath>
を各タブで実行することでコマンド履歴の復元を実現している。
ボツ案
上で書いた機構以外にもいくつか考えた機構があるので紹介していこうと思います。
入力を検知してコマンドを抽出し保存する
"使用した内部的な機能"の節で説明したように、Vte.Terminal
クラスを用いればターミナルの出す様々なシグナルを利用できます。その一つにテキストが入力されたときに発火されるというもの(“commit”
)があります。
このシグナルを受け取り、入力された文字を取得することでコマンドを取得できると考えました。 しかし、このイベントの発火条件がテキストの状態が変わったときとなっているため、Delete周りの処理が煩雑になると考え、この機構はボツとなりました。
Enterキーを押されたらhistoryコマンドを実行する
前述のVteTerminalのシグナルの中に画面に描画されている文字が変更されたときに発火されるもの(“text-inserted”
)があります。
このシグナルを使うと、VteTerminalに対して送ろうとしている文字列を取得することが可能であるため、Enterキーが押されたときにhistoryコマンドを実行した上でそのhistoryコマンドの描画をエスケープシーケンスを用いて消そうと考えました。しかしながら、エスケープシーケンスがうまく動作させることができずこの機構はボツとなりました。
画面に表示されている文字列からコマンドを抽出する
Vte.Terminal
クラスではget_text
というメソッドが提供されていて、これを用いるとプロンプトを含めたターミナルに表示されているテキスト全体をstringとして取得することができます。
この関数を上述の手法と同様にして、Enterキーが押されるたびに呼び出して最後の行だけを取り出せば、[プロンプト]+[直近の実行コマンド]という文字列が得られます。したがってプロンプトの文字列を保持しておけば、実行コマンドのみを取り出せるので、これをhistoryファイルに一行ずつ書き込めばコマンド実行ごとにhistoryが更新されることになります。
シグナルを用いればcdでプロンプトの長さが変わるタイミングも検知できたので、このアプローチは一応成功しました。しかし、プロンプトの長さを保持するやり方は無理矢理感満載で美しくないこと、そして前述のPROMPT_COMMAND
といううってつけの機能の存在に気づいたことによりボツとなりました。
シェルの標準入力からコマンドを取ってくる
標準入力からコマンドを取得すること自体は可能でしたが、コマンドを投げつけること(改行コードの送信)がなぜかできませんでした。
追加機能その2:複数のタブで同時にコマンドを実行できるようにする
現状と目標
少し講義の時間が余ったので教授に複数タブで同じコマンドを同時実行できたらsshとかするときに便利だよねっていうことで同時実行するコマンドを作ることにしました。 現状同じことをやろうとするとコピペする、スクリプトを組んで実行するなどの方法が考えられますが、もっと手軽に実行できるようにするために専用のUIを作ってそこから実行させるように設計しました。
実装
挙動としては、 1. Ctrl+Shift+sでコマンド実行のためのウィンドウを開き、 2. 実行するコマンドを入力し、 3. 実行するタブを選択し、 4. Enter又はクリックで実行される
としました。
ウィンドウクラスの作成
ウィンドウはGtkを用いて下のように作成しました。
チェックボックスを外したものに関してはコマンドが実行されません。
実装方法としてはGtkのサンプルコードをもとにclassの生成を行いました。
class MultiExecWindow(Gtk.Window): def __init__(self,guake): super(MultiExecWindow, self).__init__() self.guake=guake self.init_ui() self.text="" def init_ui(self): grid = Gtk.Grid() execBtn = Gtk.Button(label="execute") execBtn.connect("clicked", self.on_button_clicked) grid.attach(execBtn, 3, 0, 1, 1) grid.set_column_spacing(5) self.add(grid) entry = Gtk.Entry() entry.connect("key-release-event", self.on_key_release) grid.attach(entry, 0, 0, 3, 1) self.label = Gtk.Label("") self.label.set_width_chars(10) grid.attach(self.label, 1, 0.5, 1, 1) self.terminal_checkbutton = [] self.terminal_name = {} for t in self.guake.notebook_manager.iter_terminals(): cur_directory = t.get_current_directory() if cur_directory in self.terminal_name: self.terminal_name[cur_directory] = self.terminal_name[cur_directory] + 1 checkBtn = Gtk.CheckButton(label= (cur_directory + ":[" + str(self.terminal_name[t.get_current_directory()])) + "]" ) else: self.terminal_name[cur_directory] = 0 checkBtn = Gtk.CheckButton(label=cur_directory) grid.attach_next_to(checkBtn,None,3,1,1) checkBtn.set_active(True) self.terminal_checkbutton.append({"button":checkBtn,"uuid":t.get_uuid()}) self.set_border_width(5) self.set_title("Exexute in all terminals") self.set_default_size(300, 50) self.connect("destroy", self.on_finished) def on_key_release(self, widget,ev,data=None): self.text=widget.get_text() #エンターキーを回収 if ev.keyval==Gdk.KEY_Return: self.on_button_clicked(widget) def on_button_clicked(self, widget): selected_terminal_uuid = self.select_terminal() self.guake.exec_multi(self.text,selected_terminal_uuid) self.on_finished(None) def on_finished(self,widget): self.hide() self.guake.show() def select_terminal(self): selected_terminal_uuid = [] for checkBtn in self.terminal_checkbutton: if checkBtn["button"].get_active(): selected_terminal_uuid.append(checkBtn["uuid"]) return selected_terminal_uuid
このクラスを用いてウィンドウを作るのですが、その処理をguake_app.py内のGuake class内で呼び出しました。
def exec_multi_window(self): win = MultiExecWindow(self) self.hide() win.show_all()
Guake classには設定が呼び出されたりする時用にGuakeを閉じたり開いたりするメソッドが実装されていたため、そのメソッドを用いて設定と同じようなウィンドウ遷移をするようにしました。また、コマンドの複数タブにおける実行に関してはiter_terminalsというタブの情報を取得するものが存在していたため、これを流用し、execute_commandを用いて実行しました。
def exec_multi(self,command,selected_terminal_uuid): for t in self.notebook_manager.iter_terminals(): if t.get_uuid() in selected_terminal_uuid: t.execute_command(command)
ショートカットの作成
guake/data/org.guake.gschema.xml
にショートカットの一覧が書いてあったのでそこに追加したいショートカットを追記すればうまく行くと思い追記します。
<key name="exec-multi-window" type="s"> <default>'<Control><Shift>s'</default> <summary>execute in multi window</summary> <description>Accelerator to active function that exectute in multi window.</description> </key>
しかし、このままだとうまくショートカットが読み込まれません。どうやら、xmlファイルをコンパイルしたものを読んでいるらしいのでコンパイルしているコードを探します。
#guake/guake_app.py class Guake(SimpleGladeApp): def __init__(self): ... try: #try_to_compile_glib_schemas() schema_source = load_schema() except GLib.Error: # pylint: disable=catching-non-exception log.exception("Unable to load the GLib schema, try to compile it") try_to_compile_glib_schemas() schema_source = load_schema() self.settings = Settings(schema_source)
このように、Guakeクラスでスキーマをコンパイルしようとしていることがわかるので、一度コンパイル済みスキーマファイルを削除することで例外を出し、再コンパイルさせます。
ショートカットのスキーマへの登録はできたので、次はショートカット時のイベントハンドラを登録していきます。
関連しているであろうコードはどこにあるのか探してみると、guake/keybindgins.py
というお誂え向けのファイルがあったのでそこを探してみると、
#guake/keybindings.py class Keybindings(): def __init__(self, guake): ... # Setup local keys keys = [ 'toggle-fullscreen', 'new-tab', 'new-tab-home', 'close-tab','save-tabs','exec-multi-window' 'rename-current-tab', ... ] for key in keys: guake.settings.keybindingsLocal.onChangedValue(key, self.reload_accelerators) self.reload_accelerators() ... def reload_accelerators(self, *args): ... self.load_accelerators() def load_accelerators(self): ... key, mask = Gtk.accelerator_parse(getk('new-tab')) if key > 0: self.accel_group.connect(key, mask, Gtk.AccelFlags.VISIBLE, self.guake.accel_add)
Keybindingsクラスのコンストラクタで、リストの要素をハンドラと結びつけていることがわかります。例えば上記のnew-tab
という要素に対しては、Guakeクラスのaccel_add
関数をハンドラとしていることがわかります。
このことから、xmlファイルに書いた名前を要素に追加して、それをハンドラに登録すればうまく行きそうなことが読み取れるので、追記します。(すでに上のリストにはexec_multi_window
という要素を追記してあります)
そして、load_accelerators
関数にaccel_exec_multi
関数というハンドラを結びつけるように追記します。
accel_exec_multi
関数は、Guakeクラスのメンバ関数として実装します。
def accel_exec_multi(self, *args): self.exec_multi_window() return True ... def exec_multi_window(self): win = MultiExecWindow(self) self.hide() win.show_all()
結果として、Ctrl+Shift+sを押すことで作成したウィンドウが出るようになりました。
感想
- 日頃から常用しているGuake Terminalのコードを実際に読み、コードを改変、追加することで機能を継いできたという経験ができとても刺激的でした。愛着もわいたのでばりばりGuakeを使っていきたいと思います。
- 規模はそこまででもなかったが、このくらいの規模のソフトウェアをデバッグして、機能を追加するという経験はしたことがなかったので楽しかったです。
- 全体を理解するのが困難である大きなプログラムに対して、必要な情報を見つけ出して所望の機能を実現するようにいじる、ということを通して、自分の中での既存のソフトウェアの中を除いて見ることへのハードルがかなり下がりました。
参考
Neural Collage 論文 ざっくりまとめ
学習済みのドメイン内において自然なコラージュ画像を作ることができる
この前読んだStyleGANにもそのまま適用できるようで激アツ
Conditional Normalization Layer (先行研究)
- 普通のBatch Normalizationとは異なり、正規化後の特徴マップ$x$に対して施すAffine変換$y = \gamma x + \beta$において、$\gamma$と$\beta$がパラメータではなくて、クラスラベルのAffine変換から求まる
- つまり入力のクラスごとに正規化のパラメータが変わる
- これにより各層に入力するクラスラベルの情報を入れられるので、全体的には形は犬だけど細かなテクスチャは猫みたいなことができる?
- ラベル化されたデータセットが必要
- StyleGANで用いられているAdaINもこれのfamilyということになっている
- ラベルはないが、各層ごとにスタイルを注入できるから?
今回の論文特有の手法
Spatial Conditional Batch Normalization(sCBN)
- 上述CBNの進化形で、ラベル情報を注入する層のみならず位置もいじれる
- CBN(図左)では各層にクラスラベル(ここでは"ポメラニアン"と"ブルドッグ")を注入することで好きな犬種を生成できていた
- sCBN(右)ではユーザーがクラスごとのヒートマップを指定することで、顔はブルドッグだけど毛並みはポメラニアンみたいな画像が生成できる
- やっていることは基本的に上で出てきたクラスごとの$\gamma$と$\beta$の指定したヒートマップに対する重み付き和を取っているだけ(上の例では顔の部分だけブルドッグの重みがでかかった)
Spatial Feature Blending
- sCBNでは画像の一部を別のラベルに変換する、ということを実現したが、ここでは画像の一部を別の画像に差し替えることができる
- こちらもやってることはとても簡単で、元画像と混ぜたい画像の潜在変数をそれぞれ用意し、両方ともGeneratorに入れながら各層で指定したヒートマップによる重み付き和を取りながら進めていく
- $l$番目の層における特徴マップの計算が上式
- $i$は混ぜる画像のインデックスに対応し、$F$が特徴マップ、$M$が画像ごとのヒートマップ(どの部位を使うか)
- $U$がスライドやリサイズするための行列(例えば画像Bの目の部分を画像Aに移植したい場合、画像A, Bの目の位置が違う時にこれで調整ができる)
- つまり$U, M$はユーザーが指定する
Manifold Projection
- 上述のSpatial Feature Blendingは画像の潜在変数があることが前提となっている
- モデルが生成した画像ではない実際の画像に適用するには、各画像を潜在空間に写像する必要がある
- この論文ではgenerator, discriminatorの他に新たにEncoderを学習させることでこれを実現している
- Encoderに一回入れるだけでは精度がイマイチということで元画像との類似度を損失としてbackpropagationにより潜在変数を調整していくようだ
- 推論時に何度もbackpropagationするのは大変、ということでEncoder一つでは飽き足らず、上図A, BのようにさらにAuxiliary Networkを加えている
- Aux Networkでは潜在変数$z$ではなく、その次元を拡張した$\zeta$にbackpropすることにより、より早く元画像に近づけることができるとのこと
- ただしこのためには$G, D \to E \to A, B$という三段階の訓練が必要
モデルの応用法を考える
- 特にSpatial Feature Blendingに関してはおそらく任意の訓練ずみ画像生成GANに適用できそうだ
- こんなんで本当に上手くいくんだろうか
- 論文ではStyleGANへ応用した例も載っており、アニメ画像のコラージュでも使えることが示されている
- 上図左下が、StyleGAN+今回の手法を使って上3枚を合体させた結果
- 顔パーツのカスタマイズ
- 服装、ポーズの変更?
- 自然な口パクに関しては、もともと口の閉じている画像に空いている口を移植して連続変化させればいいのではないか
- ちょうど上の例でも口の形を変えている
- 連続変化はヒートマップの値を滑らかに変えればいいのか?
- githubのREADMEに連続変化させるgifがあった
- Manifold ProjectionについてはStyleGANではDiscriminatorの全結合層を除いたモデルがそのまま使える説がある
- 学習法的に$D = G^{-1}$になっていることが期待される(要検証)
- 仮にManifold Projectionが不要だった場合、追加の学習は一切不要
- 実装に関しては、githubにあるのは今回の手法をSNGANに適用したもので、犬をいじることしかできない
- StyleGANの公式実装を読み解いて改造するしかなさそう
- やること自体は各ブロックに特徴マップの重み付き和をとる部分をたすだけなので難しくはない
- StyleGANで用いているAdaINはCBNのfamilyだからsCBNも適用できるぜ!みたいなことが論文にあったが、CBNでいうクラスラベルはAdaINでいうところのstyle画像のlatentなので、sCBNを頑張ったところでSpatial Feature Blending以上の効果は得られなそう
- 口あり、なしのラベルつきのBigGANとかを訓練して、それのsCBNによって口を自然に動かす、というのも考えられる
- これだとManifold Projectionが本質的に不要
- 口あり、なしのラベルつきのBigGANとかを訓練して、それのsCBNによって口を自然に動かす、というのも考えられる
参考
強化学習 Sutton本8章 一般化と関数近似
Sutton本の第8章の内容のメモです
関数近似の背景
- これまでの章ではテーブル形式の推定価値関数を用いてきた
- それぞれの状態(行動対)について1つの推定値を持っておかねばならない
- 表を保持するためのメモリや、表を埋め尽くすための計算量が膨れ上がる
- 限定された部分状態集合での経験をうまく一般化することで、より広い状態空間に対する良い近似を作りたい
関数近似による価値予測
価値関数$V_t$をパラメータベクトル$\vec{\theta_t}$に完全に依存するものとして定義し直す
- $\vec{\theta_t}$だけを計算して保持すれば良いので、計算量、メモリの節約
- ただし当然ながら$len(\vec{\theta_t}) \ll (状態数)$
$\vec{\theta_t}$の一つの要素パラメータをいじることが多くの推定値の変更を意味する
MSE
$$ MSE(\vec{\theta_t}) = \sum_{s\in S}P(s)[V^{\pi}(s) - V_t(s)]^2 $$
- 教師ありの回帰モデルと同様に平均二乗誤差(MSE)の最適化問題をといて$\vec{\theta_t}$を計算する
- 上述のように$V_t(s)$の自由度は状態数と比べて極めて少ない(希少資源)
- 全ての状態において誤差を0とすることは不可能
- 分布$P(s)$によりどの状態を犠牲にするかを決める(バックアップの分布)
- この$P$を方策$\pi$による$s$の訪問回数を用いて定めたのが方策オン型分布
- $\pi$によって生じない状態は気にしない
最急降下法
$$ \vec{\theta_{t+1}} = \vec{\theta_{t}} + \alpha(v_t - V_t(s_t))\nabla_{\vec{\theta_{t}}}V_t(s_t) $$
時刻$t$における状態$s_t$に対して、目標出力$v_t$を得た場合を考える
おなじみの更新式
- $E[v_t] = V^{\pi}(s_t)$(真の価値)である場合$v_t$はunbiasedな推定値と呼び、この時$\vec{\theta_{t}}$を局所最適解へ収束させることができる
- 逆にTD($\lambda$)における$\lambda$収益$R^\lambda_t$やDP目標$r_{t+1}+\gamma V_t$のようなブートストラップ手法ではunbiasedな推定値とはならず、収束する保証はない
適格度トレース
- TD($\lambda$)の後方観測的な更新式
$$ \vec{\theta_{t+1}} = \vec{\theta_{t}} + \alpha\delta_t\vec{e_{t}} $$
- $\delta_t$は目標値との差分に対応(p215(8.6))し、適格度ベクトル$\vec{e_{t}}$は(8.7)式のように更新される
- 前章では
- $e$は各状態$s$について定まる
- 状態$s$への最近の訪問が多いほど$e(s)$が大きくなる
- $e(s_t)$は$V(s_t)$をどれだけ更新するか
ここでは
- $e$は$\vec{\theta_{t}}$のそれぞれの要素$\theta_t(i)$について定まる
- 最近の$V(s)$の$\theta_t(i)$に対する勾配が大きいほど$e_t(i)$は大きくなる
- $e_t(i)$は$V_t(s)$のうちの$\theta_t(i)$をどれだけ更新するか
とりあえずこれで最適化手法は手に入った。では具体的な関数形はどうするか?
- 誤差逆伝播法を用いた多層NN
- 線形手法
線形手法
- 各状態において特徴量ベクトル$\vec{\phi_{s}}$とパラメータの内積を取る。線形回帰と一緒(p217(8.8式))
- 当然線形モデルでは表せない状況はいっぱいある → 色々と工夫があるらしい
- 円による荒いコード化
- タイルコーディング
- 動径基底関数(粗いコード化を連続値に一般化)
- ただし以上の三つは目的関数の複雑さではなく問題の次元に左右される
- Kanervaコーディング
- 特徴量をバイナリ空間で表現(バイナリ特徴)し、その距離を比較
- k近傍法的な?
- 特徴量をバイナリ空間で表現(バイナリ特徴)し、その距離を比較
関数近似を用いた制御
- ここまで状態価値関数についてやったことを行動価値予測に拡張する
- 今まで通り$P \to Q$, $s \to (s,a)$とするだけ!
- 図8.8, 8.9について
- 特徴集合$F_a$は今までの文脈からいくと全ての$(s,a)$について同じものであるはず
- 状況によって特徴量が変わってくるタスクを考慮している?
- 前回の復習
- Sarsaは方策オンで、εグリーディに従って行動していく
- Q学習は方策オフで、実際に取る行動はSarsaと同じだが、更新に用いるのはgreedyな行動
- 特徴集合$F_a$は今までの文脈からいくと全ての$(s,a)$について同じものであるはず
方策オフ型ブートストラップ
- ブートストラップ手法は方策オン型分布に対してしか収束性を示せない
- 方策オフ型(Q学習,DPなど)では推定方策に従って訪問される状態の分布と、実際にバックアップされる状態の分布が異なる
- ただし、例えばQ学習などで挙動方策と推定方策が十分近い場合はきちんと収束してくれるという経験的事実はあるようだ
ブートストラップを行うべきか
- 上述のように非ブートストラップ手法は、関数近似と組み合わせた場合に信頼性が高い
- ただ、ブートストラップの方がこれまた経験的にはるかに性能がいいらしい(p240図8.15)
- より高速に学習するとのこと
まとめ
- これまでのテーブル型の価値関数を軽くする方法としての関数近似
- 関数形としてはナウでヤングなNNの他に線形手法なるものがある
- 粗いコード化や基底関数といった工夫がある
- ブートストラップは収束の保証はないが、経験的に強い
参考
線形手法についての記事 https://qiita.com/triwave33/items/78780ec37babf154137d
PEPSI (画像補完) アーキテクチャまとめ
GANを用いて下のようにマスク下部分を補完しようという研究。背景の復元やアニメーションなどに使えそう。
Coarse-to-fine Network(先行研究)
- Free-Form Image Inpainting with Gated Convolution
- coarse networkとrefinement networkからなる
- coarse networkで最初に粗く穴を埋める
- coarse networkの結果から特徴を抽出し、refinement networkでより細かい補完を行う
- このように粗いのを一旦生成してからそれを磨くという方法は、ぼんやりした画像になりがちなGANにおいては有効らしい
- refinement networkではCAM(Contextual Attention Module)を用いて前景(穴になっている部分)と背景(穴でない部分)の関係を学習する
- 具体的には、coarse networkで生成された粗い補完の施された画像の特徴マップに対し、前景とその周辺の背景それぞれから3x3のパッチを抽出し、下のようにコサイン類似度をとる
- そのコサイン類似度そソフトマックスにぶち込むことで、重み を得る。これを用いた背景パッチの重み付き和により、より細やかな画像にrefineすることができるらしい
- fが前景、bが背景のパッチで、 はハイパーパラメータ
要するにAttention的なノリ
これをベースとして改善していこうというけんきう
この論文では
上記手法の問題点
- refinement network単体だと微妙(下のd 老けてもうた)
- となると入力に対してcoarseとrefinementという2つのencoder-decoder networkを施さねばならず、特に高画質な画像だと計算コストが高くつく
PEPSIのアーキテクチャ
- 上図のように、PEPSIでは2つのステージを一つの共通なnetworkにまとめた
- decoderは大部分共通とはいえ、inpainting pathとcoarse pathの二つに分岐している
- coarse pathでは上述のcoarse network同様にencoder出力の特徴マップから粗めの補完を行う
- inpainting pathではencoder出力の特徴マップをCAMに通すことで ”reconstruct”し、新たな特徴マップをcoarse pathと同様のネットワークに通す
- 推論の時はInpainting pathしか使わないので計算コストが抑えられる
- coarse pathではL1ノルムのみが、inpainting pathではL1ノルムとGANのロスが適用される
- 二つのパス両方に対応するようにencoderによる潜在変数空間が学習されることとなる
- これは憶測だが、真っ黒マスク→ぼんやり補完という写像とCAMによるぼんやり補完→くっきり補完という写像が一致するという仮定のもとでネットワークを共有していて、これによってcoarseとrefineをなにも直列にしなくても、並列化した学習によって同等のクオリティが得られるということだろうか
Discriminatorは特徴マップのピクセル単位でReal/Fakeの判定を行うことで、マスクがどんな形であっても対応できる
Modified CAM
- 前節で述べたようにPEPSIでもCAMを用いているが、これについても変更が加えられている
先行研究ではコサイン類似度を用いていたが、これは特徴空間での位置関係を歪めることとなる。これの代わりにユークリッド距離を用いたことで、複雑な画像の補完にも対応できるようになったらしい(下図)
- ただし距離はコサイン類似度と異なって の値を取りうるので、下のようなtruncated distance similarity score を導入したらしい(要は正規化してtanhに突っ込む)
- これも空間歪めまくりな気がするが...
結果として先行研究と同等orそれ以上の結果を、より軽いモデルで実現したようです
強化学習 Sutton本5章 モンテカルロ法について
Sutton本の第5章の内容のメモです
前章で扱ったDPとの関係性
- 価値関数の計算や方策評価,改善といった基本的な考え方は一緒
- DPと違って環境の完全な知識(期待報酬, 遷移確率など)は必要とせず、経験のみから価値を推定
- エピソード的(=終わりがある)タスク限定
- エピソードごとに価値と方策を更新
MC法による方策評価
訪問MC法
- 状態の価値=その状態から開始した場合の期待収益
- 状態を通過した全てのエピソードにおける収益の平均を取れば良さそう ➡︎ 訪問MC法
- 逐一訪問MC法 : 各エピソードにおいて複数回sを訪問している場合その全てを加味する
- 初回訪問MC法 : 各エピソードにおける初回のみ考慮する
- 状態を通過した全てのエピソードにおける収益の平均を取れば良さそう ➡︎ 訪問MC法
- 初回訪問、逐一訪問共に大数の法則より(sへの訪問回数)→∞でに収束するらしい
- ちなみに一つのエピソード(=対局)中に同じ状態が再び訪れることはないタスク(オセロとか)では初回訪問も逐一訪問も一緒
DPに対する強み
- DPでは今見ている状態の価値を、その先にある状態の推定量から推定していた(ブートストラップ)
- つまり1つの状態の推定が他の状態に依存している
- 一部の状態についてだけ知りたいというときも他の不要なたくさんの状態も見る必要がある
- MC法では各状態に対する推定は完全に独立である
- 1つの状態の価値推定にかかる計算量は状態数から独立
- 部分集合の価値だけを知りたい時に強力
MC法による行動価値推定
- 環境についてのモデルがない場合、状態の価値だけでは方策は定まらない
- モデル = ある行動を行なった結果どの状態に遷移するかみたいなこと(?)
- 逆にモデルがある場合(行動1->状態A,行動2->状態B)状態の価値(状態A>状態B)のみによって方策(行動1を選ぼう)が得られる
じゃあどうすりゃいいの
- 基本は前節でやったことを(状態,行動)の対に対して行えば良い
- 前節では状態を訪問したエピソード群の収益からを推定
- ここでは状態行動対を訪問したエピソード群の収益からを推定
- ただし、方策が決定論的である場合は全く訪問されないが出てきてしまう
- ここで2章で扱った探査の考え方が出てくる
開始点探査
- n本腕バンディットと似たようなはなし
- 任意の状態行動対についてそれが開始点として選ばれる確率がゼロにならないように定める
- このもとでエピソード数無限大の極限をとれば全ての状態行動対が無限回行動されるという仮定(開始点探査の仮定)
- 一見当たり前に思えるが、環境との間での相互作用から学習を行う場合などは通用しないらしい(?)
- シミュレーションでは開始点操作は可能だが、実際の経験から学習したいと思った時は通用しないという欠点がある
- 開始点探査の代替案として、単に方策$\pi$を非決定論的にすれば良いという説もある
MC法による一般化方策反復
- 基本的に4.4節,4.6節あたりのことがMC法でも成立しますよ〜という感じ
- 方策評価→方策改善(グリーディ)→方策評価→...の繰り返し
モンテカルロES
- 今までは方策評価が無限個のエピソードについて行われるという仮定でやってきた
- DPの時の1スイープしなきゃいけない仮定と似ている
- これを取り払うには方策改善の前に方策評価を完全に完了させることを諦めれば良い
- 非同期DPと一緒
- 1エピソード見るたびにを更新しても更新しちゃう
方策オン型MC法
- やっぱり開始点探査の仮定も取り払いたいらしい
- その方法として方策オン型とオフ型があり、ここでは前者について説明
- 前述の通り方策を非決定論的とすれば良い
εソフト方策
- 全ての状態,行動について となる方策のこと
- 今回はこの一例としてεグリーディ方策を用いている
- 方策改善定理がこのεソフト方策についても成立することがこの節で示されている
- つまり方策反復はεソフトについても機能する
他の方策に追従する方策評価
- これまである一つの方策で無限のエピソードを生成し、それによってを方策評価していた
- 一般化方策反復を行うには現在の方策とは別の方策によって生成された過去のエピソードも用いて$\pi$を方策評価できないか?(そうしないとサンプルが集まらない)
- 結論としては がなりたてばよい
- つまり で取りうる全ての行動が の時も出現すれば良い
- 前節のεソフト方策はこれを満たす
- この時方策それぞれにおける状態への番目の初回訪問の生起確率をとし、で生成されたへの番目の初回訪問を含むエピソードでの収益をとすれば、の方策評価はをで重み付けした平均となる(p135(5.3)式)
- この時はp135の直後の式より環境のダイナミクスに依存しない形で計算が可能らしい
方策オフ型MC法
- オン型では制御(=探査?グリーディ?)が方策の中に組み込まれ、それを評価する形になっていた
- オフ型では挙動方策と推定方策の二つに分離
- 挙動方策は挙動(=制御)を生成
- 推定方策は評価され改善される方策(今まで通りのやつ)
- 分離することで推定方策では決定論的な方策(例えばグリーディ)を探ることができる
- ここでは挙動方策に従って生成されたエピソードを用いて推定方策を評価する必要があるので、前節の話が生きる
- p136のアルゴリズムは正直よくわからない
- 今までの話とは違って各状態行動ついに対して収益を定める必要がある
- エピソードごと、ではなく非グリーディ行動ごとに方策評価をしている
- 非グリーディ行動と非グリーディ行動の間のグリーディ行動を見て、前節のような重み付けを実行している...?
漸進的実装
- 2.5節でも出てきた、報酬が得られるたびに保存して毎回全部の平均を取るのは計算量やメモリの無駄だから漸化式にしてしまえという話
- 今回は(5.4)式のような重み付け和だからちょっと違うね〜というだけ
- p136のアルゴリズムのように分母分子に分ける方法でもいいのでは?と思った
まとめ
- モンテカルロ法はDPと比べて,
- サンプリングされた経験から直接最適挙動を学習でき、
- 遷移確率などのモデルを必要とせず、
- 状態群の部分集合に対して効率的に推定ができるすごいやつ
- 一部の状態について十分な訪問を確保できないという欠点に対しては、
- エピソード群が全てをカバーするようにする開始点探査と、
- エージェントが探査を維持し続ける方策オン/オフ型MC法とがある
マスタリングTCP/IP 入門編 読書メモ 【第2章】
今回からいよいよTCP/IPの話に入っていきます。
元書籍→マスタリングTCP/IP
前回の記事→
TCP/IPの概要
- 元々軍事の文脈でネットワークの一部が破壊されても迂回路によってデータを配送できる分散型パケットネットワークとして考案された
- 中央集権的なネットワークでは通信回線の交換局が破壊されると死ぬ
- UNIXの中に実装され、UNIXとともに発達、普及してきた
- 標準化の精神として、ある程度プロトコルの実装されたプロトタイプがある状態で仕様を煮詰めたため、実用性が高いプロトコルとなったらしい
- これが前章でやったOSIを尻目にどんどん普及していった要因らしい
インターネット
TCP/IPプロトコルの階層モデル
基本的には1章でやったOSI参照モデル に当てはめられるようだ(図2.8)
ハードウェア(物理層)
- TCP/IPでは通信媒体や、通信する上での信頼性、セキュリティ、帯域、遅延時間などの制約を設けず、とにかくネットワークで接続された装置間で通信できることを前提に作られているらしい(??)
ネットワークインターフェース層(データリンク層)
- イーサネットなどのデータリンクを利用して通信するためのインターフェースとなる層
- NIC(Network Interface Card)を動かすためのデバイスドライバ
- デバイスドライバはOSとハードウェアの橋渡しとなるソフトウェア
- ハードウェア層とまとめられることも多い
インターネット層(ネットワーク層)
- IPプロトコルを用いて最終目的のホストまでパケットを配送
- ICMP(Internet Control Message Protocol)
- IPパケット転送中の異常をそう信玄に伝えるためのプロトコル
- ネットワークの診断などにも利用
- ARP(Address Resolution Protocol)
トランスポート層(トランスポート層)
アプリケーション層(セッション層以上の各層)
- セッション層以上の処理は全てアプリケーションプログラムで実現されるという考え
- ただ、結局プログラム内でOSIモデルと同様の役割をもつ各モジュールに分けそう
- TCP/IPのアプリケーションの多くはサービスを提供するサーバーと受けるクライアントからなるクライアント/サーバーモデルで作られる
- WWW(World Wide Web)
- 電子メール
- ファイル転送(FTP)
- 遠隔ログイン(TELNETとSSH(Secure Shell))
- ネットワーク管理(SNMP)
通信の処理の流れ
パケットヘッダ
- 各階層でデータに付加される情報をヘッダという
- 送信元、宛先などプロトコルのための情報
- 階層から見れば、上位層から受け取る全ての情報(上位層のヘッダも含む)は単なるデータとして認識
- 各層におけるヘッダには少なくとも、「宛先と送信元の情報」および「上位層のプロトコルが何かを示す情報」が含まれる(図2.19)
パケットの送信処理
電子メールの場合(図2.18)
①アプリケーションの処理
- キーボードの入力をUTF-8などの規則に基づいて符号化
- メールソフトによっては複数のメールをまとめて送信するなどの機能がある場合も
- OSIでいうセッション層のような機能
- TCPにコネクションの確立を指示し、確立され次第メールを送信する
②TCPモジュールの処理
- アプリケーションからもらったデータにTCPヘッダを付加
- 送受信ポート番号
- パケットの順番を示すシーケンス番号
- データが壊れていないことを保証するチェックサム
- ここでデータを相手に確実に届けることが保証される
③IPモジュールの処理
- TCPからもらったデータ(TCPヘッダ込み)にIPヘッダを付加
- IPパケットが完成したら、経路制御表(ルーティングテーブル)を参照し、次のルーターやホストを決定
- 次の通信先のMACアドレスを調べ、IPパケットとともにイーサネットドライバへ渡す
④ネットワークインタフェース(イーサネットドライバ)の処理
- IPパケットにイーサネットのヘッダを付加
- ここでできたイーサネットパケットが物理層により運ばれる
- 送信処理中にパケット破壊検出のためのFCS(Frame Check Sequence)がハードウェアで計算され、パケットの最後につけられる
パケットの受信処理
送信処理とは逆順にパケットが流れていく
⑤ネットワークインタフェースイーサネットドライバ)の処理
- イーサネットヘッダのMACアドレスが自分宛てかを確認し、違う場合はスルー
- イーサネットタイプフィールドを調べ、イーサネットプロトコルが運ぶデータの種類を確認
- この場合はIPなのでIPを処理するルーチンにデータ(IPヘッダ以降)を渡す
⑥IPモジュールの処理
- ヘッダから読み取られる宛先のIPアドレスが自分のホストのものであればそのまま受信
- 上位層のプロトコル(TCP/UDP)を調べる
- ルーターの場合は宛先は自分宛てではないことが多く、この場合は経路制御表から次の行き先を調べて転送処理
⑦TCPモジュールの処理
TCPの場合は
- チェックサムによる壊れてないかの確認
- シーケンス番号による順番の確認
- ポート番号による通信しているアプリケーションの確認 が行われる
データが正しく届いた場合は送信元に確認応答を送る
- 送信側はこの確認応答を受け取るまで繰り返しデータを送り続ける
⑧アプリケーションの処理
- 受信したデータを解析し、ハードディスクなどにメッセージを格納しつつディスプレイに内容を表示する
- 処理が正常に終了した場合はそれを送信元のアプリケーションに伝え、ダメな場合は異常終了のメッセージを送る
前期実験で訳も分からずいじったソケットプログラミングの理解が若干進んだような気がする
次回→(coming soon)
最初の記事→