Terrarium

自分だけの花を育てよう この小さなテラリウムで

シェルスクリプトを使ってGitHubの特定のOrganizationの持つリポジトリを全てCloneしたい

表題の通りです.あるOrganizationのリポジトリを全部バックアップする必要があったため,それをシェルスクリプトでやってみようという話です.

TL;DR

curl -u [USERNAME] -s https://api.github.com/orgs/[ORGANIZATION]/repos | grep "ssh_url" | sed -e 's/\".*\".*\"\(.*\)\".*/\1/' | xargs -L1 git clone

調査

シェルスクリプトはあまり触れたことがなかったので,やり方を調べる必要がありました.以下は自分向けのメモとして冗長に上記コマンドにたどり着くまでの経緯を書きます.

1. Organizationの全リポジトリの取得方法

やりたいことを英単語でつらつら書いて検索すると似たような事例が出てきました.

Clone all repos from a GitHub organization · GitHub

どうやらcurlを使って特定のAPIにアクセスすれば取得できそうです.このページではRubyを使って書いていますが今回はシェルスクリプトのみでやりたいので,前半のみ参考にします.

2. リポジトリのURLの取得

curlAPIにアクセスするとOrganizationの持つリポジトリの情報が色々取得できますが,ここからCloneのためのURLを抽出する必要があります.ざっと中身を見ていくとssh_urlを使えばCloneできそうです.というわけでgrep "ssh_url"を使って特定の行のみ取り出せました.

次はこの行からURLの部分のみを抽出したいのですが,そのためにはsedを使えば良さげです.マッチさせたい部分を括弧でくくるとできるらしいので,2つ目のダブルクォーテーションに囲まれた文字列を取り出します.すなわち"[文字列]"[文字列]"([取り出したい文字列])"[文字列]ということです.すごく適当ですが…マッチした部分はその順番を用いて\1で取り出せるので,sed -e s/[正規表現]/\1/と書くことによってURLのみを取り出すことができました.

(ちなみに括弧もエスケープする必要があること,\1の後にもバックスラッシュが必要であるというところで一瞬つまずきました…)

stackoverflow.com

stackoverflow.com

3. clone

これまでで取得したURLについて,それぞれgit cloneを走らせます.そのためにはxargsを使えそうですので,使い方を調べるとxargs git cloneでできると思ったのですが,too many argumentsと怒られました.入力に使う行数を指定する必要があったようです(当たり前だ…)xargs -L1 git cloneで実行できました.

まとめ

記事執筆も含めこれまでおおよそ40分くらい.書き終わった後でほぼ同じStackOverflowのページを見つけました…

stackoverflow.com

PythonでSwift-likeなstructを使いたいので検討(してみたがダメだった)

Pythonはいろいろなことを簡単に書けるのでよく使っています. ところが『実践Swift』を読んでみたところ,その型のある世界に憧れるようになってしまいました… (大学で型についていろいろ勉強したのもある)

www.amazon.co.jp

今回はSwift-like(と言い切ってしまっていいのか不安が残りますが)な構造体を使いたい場合にどんな実装をすれば良いのかについての検討です.

Swiftでは変数(や関数)をまとめた型としてenumstructclassprotocolなどがあります. 『実践Swift』では,structは変数のまとまりを値型として扱いたい場合に用いると書かれています.

一番簡単な例が座標情報などですね. Pythonでクラスを用いて実装すると次のようになります.

class Point():
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(10, 20)
print(p.x) # 10
print(p.y) # 20

一見これで良さそうに見えるのですが,インスタンス同士の代入を行なった場合クラスでは参照がコピーされます. すなわち片方の座標の値を変更するともう片方も変更されてしまいます. これは座標というただの値を表現する手法として適切ではありません.

以下がその例です.

# (上の続き)
q = p

q.x = 30

print(q.x) # 30
print(p.x) # 30

Swiftのstructを用いると,この座標を参照ではなく値そのものがコピーされるため,以上のような問題は発生しません. (そもそもC言語でも同じです) ちなみに上のコードでq = pの部分をq = copy.deepcopy(p)とするとうまくいきます(import copyが必要)が,できればq = pでコピーできてほしいものです.

すなわちPythonで値型で構造的に記述したいというのが今回の目的です.

ということをTwitterに呟いたら,enum.Enumを使うという啓示をいただきました.

早速調べると…おお…これは使えそうだ(早とちり)ということで書いてみました. 実装には次のサイトを参考にさせていただきました.

py3.hateblo.jp

import enum

class EnumTest(int, enum.Enum):
    # 継承元最初のintはvalueメソッドにおける加算にて型を決める必要があるため書く
    _x: int = 10
    _y: int = 15

    # getter を試した
    @property
    def x(self):
        return self._x
        # return self._x.value()
        # とすればうまくいくが全てのpropertyでこれをやるのは非効率
        # いい方法がないものか…?

    @classmethod
    def value(self) -> int:
        return self._x + self._y

# getterを利用(うまくいかない)
print(EnumTest.x)
# 要素を参照(名前が出てくる)
print(EnumTest._x)
# 要素の値を知る方法(ちょっとめんどい)
print(EnumTest._x.value())
# メソッドを利用(これはうまくいく)
print(EnumTest.value())

# 上から
# <property object at 0x1050a6b38>
# EnumTest._x
# 25
# 25

やりたかったようにEnumTest.xで簡単に値が取得できればよかったのですがちょっと追加の実装がいるのであまり綺麗じゃないですね.

上の実装では致命的な欠陥がいくつかあります.

  • 値の変更ができない
  • Constructorがない(座標データごとにclassを宣言する必要がある)

これではSwift-likeなstructとは離れ過ぎていますね. さらなる検討を重ねるか諦めるか…

ちなみにnamedtupleを使うとpoint.xという形で値が取得できるようですが,こちらも値を変更できないという問題があるそうです.

うーん…そもそも値型の構造体を使うよりもっとPython-likeな手法があるのかもしれないなぁ…

というわけで検討してみたがダメだったという記録です.

(追記) namedtupleが値の変更を行えないと書きましたが関数型っぽく書くなら値の変更は必要ないですね… さらにnamedtupleにメソッドを追加した例もあるのでこちらを使った方が便利そうです. まあいずれにせよスッキリとは書けないみたいだ…

Python で namedtuple を使ってバリューオブジェクトを作る | CUBE SUGAR STORAGE

超基本的なコンパイラを作ってみる(1. 字句解析編)

コンパイラ

大学で言語処理系論というコンパイラを扱う講義を受講したので,その実践を兼ねて自分でコンパイラを作ろうと決意しました. いきなり"なんとか言語"のコンパイラを作るのは難しいので,本当に単純なものから始めたいと思います.

作る題材は何かと言うと,『よくある例 (The all-time favorite) 、卓上計算機です。』(引用元:http://ocaml.jp/archive/ocaml-manual-3.06-ja/manual026.html

新規性のカケラもない企画ですが,自分で作るところに意味があると思っています. もちろんコピペではなく「参考にして」オリジナルの機能も導入しながら作っていきます.

コンパイラの構成

コンパイラを作るためにはまず構成を知らなくてはいけないので,調べました. 参考文献はこちらです. いわゆるタイガーブックというやつですね.

https://www.amazon.co.jp/%E6%9C%80%E6%96%B0%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9%E6%A7%8B%E6%88%90%E6%8A%80%E6%B3%95-Andrew-W-Appel/dp/4798114685

さて,コンパイラのフェーズには(上の本よりも)大雑把に分けて次のものがあります.

  1. 字句解析
    • ソースファイルを個々のトークンへと分解する
  2. 構文解析
  3. 意味解析
    • 変数や式の型を検査して各句の意味を決定する
  4. 中間コード生成
  5. 最適化
    • 高速化を図る
  6. 機械語コード生成
    • 最適化した中間コードを元に機械語コードを生成する

字句解析

まずは字句解析器から作ってみたいと思います. 字句解析の入出力は次のイメージです.

  • 入力:x = 3.1 + y * 20
  • 出力:ID("x") EQ Float(3.1) PLUS ID("y") TIMES INT(20)

入力された文字列の各単語について,対応するトークンを割り当てます.

字句解析器の仕組み

字句解析器をどのように作るかというと, “正規表現” を使います. 正規表現についての説明は割愛します.

正規表現のプログラムでの実装は,正規表現オートマトンに変換することで行います.

しかしここでは簡単のため"字句解析器生成ツール"というものを使いたいと思います.

Lex

Lexは字句解析器生成ツールです. 正規表現トークンの情報を書いたファイルを入力すると,字句解析器を生成してくれます.

今回はLexのOcaml版,Ocamllexを使います.(Ocamlをインストールすれば一緒についてきた気がします…)

Ocamllex

Ocamllexの使い方については,下のリンクに書いてあります(がかなり分かりづらかったです) Lexer and parser generators (ocamllex, ocamlyacc)

以下,上のサイトのまとめになっています.

Ocamllexの設定ファイル(拡張子は.mll)のフォーマットは次のようになっています.

%{
  header:あらかじめ設定しておきたい事項や読み込んでおきたいモジュールを書いておく
%}
  declarations:変数を宣言したりする.正規表現を変数にしておくときとか
%%
  rules:正規表現を用いてトークンの定義を記述する
%%
  trailer: 最後に実行したい処理や後に使いたいOCamlのコードを書いておける

以上を書いたファイルをlexer.mllとして保存します. ここで,今回記述する文法の仕様を決めたいと思います.拡張する可能性もあるのでかなりざっくりですが…

  • 四則演算はそのまま記述できるように
  • 後に変数が使えるように,1文字目が英字,2文字目以降が英数字もしくはアンダースコアなものをIDとして認識

例えば今回は次のように記述しました. なおこのファイルは上のサイトだけでなく以下の講義サイトも参考にしています(講義資料は受講者しか見れませんが…)

http://www-kb.is.s.u-tokyo.ac.jp/~koba/class/compiler/

{
(* トークンの型を宣言 *)
type token = PLUS | MINUS | TIMES | DIV | LPAREN | RPAREN | EOL | EOF | ID of string | INT of int
}

(* よく使う正規表現に名前をつける *)
let space = [' ''\t''\r']
let digit = ['0'-'9']
let lower = ['a'-'z']
let upper = ['A'-'Z']

(* トークンのルール *)
rule tokenize = parse
| space+ { tokenize lexbuf } (* そのままバッファを返す *)
| (lower|upper)(lower|digit|upper|"_")+ { ID(Lexing.lexeme lexbuf) } (* 上で述べたIDの定義に従って正規表現を書いた *)
| digit+ { INT(int_of_string(Lexing.lexeme lexbuf)) } (* 数字 今はint型のみ *)
| "+" { PLUS }
| "-" { MINUS }
| "*" { TIMES }
| "/" { DIV }
| "(" { LPAREN }
| ")" { RPAREN }
| "\n" { EOL }
| eof { EOF }
| _
    { Format.eprintf "unknown token %s" (* エラー *)
    (Lexing.lexeme lexbuf);
    failwith "lex error" }

{
(* trailer *)
(* 字句解析を実行するコード *)
let rec readloop lexbuf =
    let t = tokenize lexbuf in
        if t=EOF then []
            else t::(readloop lexbuf)

(* main ファイル名 で実行 *)
let main filename =
    let lexbuf = Lexing.from_channel(open_in filename) in
        readloop lexbuf
}

以上を準備したら,次の手順で字句解析を実行できます.

  1. 入力ファイルを用意
    • 例えばx1 - (2 + 3) / 5 *6と書いたファイルをin.mlとして用意
  2. ocamllex lexer.mllを実行する.エラーが出たら直す
  3. lexer.mlが生成されているので,シェル上でocamlと叩いてインタラクティブ環境を起動し,#use "lexer.ml"と入力してエンター.ここでエラーになること重ある(特にtrailer内でのエラーが多い)
  4. 同じインタラクティブ環境で,trailerに書いたコードを読み出す.この場合ではmain "in.ml"と入力する
  5. エラーがなければ,次のように出力されるはず
token list =
[ID "x1"; MINUS; LPAREN; INT 2; PLUS; INT 3; RPAREN; DIV; INT 5; TIMES;
 INT 6; EOL]

きちんとスペースなども考慮して字句解析が行われていることがわかります.

まとめ

Ocamllexを使うことで,オートマトンなどの記述の必要なく字句解析を実行することができました. 機会があればオートマトンからフルスクラッチで書いてみたいと思います.そこまで手間でないかもしれないので…

字句解析はただの文字列が解釈されるだけであまり面白くないので,次はこれを構文木の形に直す構文解析を行ってみたいと思います.

OpenAI Gymを動かしてみた(keras-rlを用いたDQN編)

前回OpenAI Gymのチュートリアルを使ってQ学習を試してみました. 今回はAlphaGoなどで話題のDQN(Deep Q Network)を試してみたいと思います.

1. Deep Q Network

Deep Q Networkとは,Q学習で用いられるQ関数(状態を入力にとり,各行動の価値を返す.行動価値関数)をDeep Neural Networkで表現したものです. Q学習においては,行動価値関数はテーブルを用いて表現されるのが一般的です. しかし,ゲームなどの多くの状態をもつ環境や,実世界などの連続値環境では,テーブルのサイズが大きくなりうまく学習ができません. このテーブルをニューラルネットワークで近似しようというのが基本的なアイデアです. (以上は私の定性的理解です)

DQNは学習が収束しにくいなどの欠点があり,それを回避するためにExperience Replayなどの様々な手法が取り入れられていますが,今回はkeras-rlという強化学習ライブラリを使って簡単に試してみたいと思います.

2. keras-rl

kerasは機械学習のライブラリの一つです.

https://keras.io/ja/

TheanoやTensorflowのラッパーであり,これらに比べて簡単にニューラルネットワークを記述することができます.

keras-rlはkeras向けの強化学習ライブラリで,

  • Deep Q Learning (DQN)
  • Double DQN
  • Deep Deterministic Policy Gradient (DDPG)

など様々な強化学習手法を試すことができます.

github.com

ドキュメントはこちらです.

keras-rl.readthedocs.io

3. 試してみる

まずはkerasとkeras-rlのインストールを行います. なおこの部分は以下のリンクを参考にしました. qiita.com

  • keras
pip install keras
  • keras-rl
git clone https://github.com/matthiasplappert/keras-rl.git
pip install ./keras-rl

keras-rlを展開したディレクトリに,example/dqn_cartpole.pyというファイルがあると思うので,それを実行します.

するとLayer情報が出力された後,Cartpoleの描画が始まります. しばらくすると学習され,持続時間が伸びていくことが確認できます.

私の環境では200エピソード程度でほぼ最後まで続くようになりました. (Q学習では数千エピソード程度の学習が必要だったので,それより優れているかもしれません.パラメータチューニングを行なっていないのでなんとも言えませんが…)

4. その他

自分でアルゴリズムを作りたい,などの場合は,CartPole-v0環境についてのドキュメントを確認すると良いと思います.

github.com

また,env.render()で描画される画面は,ディスプレイに見えていない時(ウィンドウの後ろにあるなど)は描画がスキップされるらしく,必要な時だけ見えるようにしておくことで学習の高速化が図れます.

今回DQNの細かい理論,アルゴリズムには触れませんでしたが,下のリンクはPythonとKeras,Tensorflowを用いてDQNの実装例があるので,このような記事を参考にすると良いと思います.

DQNをKerasとTensorFlowとOpenAI Gymで実装する