All Posts

全ての記事をそのまま表示している。

とても重くなってしまっているかもしれない。

hugoに移行した

前からhugoというスタティックサイトジェネレータが気になっていて試してみたりもしたのが、 org-modeで記事が書けると公式で言われている割にほとんど使い物にならず、移行はしていなかった。

今回もう一度トライした結果ox-hugoという拡張機能がほぼ自分の思っていた通りのことをしてくれることが分かったので、 思い切って移行した。ox-hugoがとても気が利いているので、移行はほぼコピペだけですんだ。

記事を最近書いていないのに移項だけするのは気が引けたが、見栄えもよくなったし記事を書くのも かなり楽になったのでよしとしよう。

一つ不満点としてHugoがMarkdownファイル内の改行をまともに取り扱わないというのがある。 上の文章では「記事を書くのも」と「かなり楽に」の間に半角スペースがあるが、これは 私が意図したものではない。HugoがMarkdownをHTMLに変換するときに改行を単語の切れ目だと判断したために 誤って挿入されたものだ。(この文章にもいくつか変な場所にスペースがある。)

org-modeで改行を使わずに書けば問題は回避できるが、文章が書きづらくなるのでそのままにしている。 極端に見栄えが悪くなるわけでもないし、そのうちHugoにも修正が入るだろう。



hello bucklescript!

bucklescriptを使うとOCamlを使ってjavascriptが書けると聞いたので試した。

やってみたら結構面白かったのでページの作成までやった。

以下のスライドバーとボタンとキャンバスはOCamlからbucklescriptで作ったjavascriptのソースコードで配置されている。

つまり、

<script src="/js/hello_buckle.js"></script>

とだけ書いて生成している。

hello bucklescript!



オートマトン in OCaml

前にpythonでやったことをOCamlでやったのでまとめた。

このページの実行例はこのリポジトリのルートで jbuilder utop src で起動するutopで実行した。

作成・実行

作って…

# let dfa =
  DFA.cons
    ["0"; "1"]    (* アルファベット *)
    [(0, "0", 0); (* 遷移関数 *)
     (0, "1", 1); (* (q, c, q') は「状態qでcを読むとq'に遷移する」を意味する *)
     (1, "0", 0);
     (1, "1", 1)]
    0             (* 初期状態 *)
    [1];;         (* 受理状態 *)
val dfa : int DFA.t = <abstr>

走らせる。

# DFA.run dfa "0001";;
- : bool = true

# DFA.run dfa "0110";;
- : bool = false

# DFA.run dfa "";;
- : bool = false

NFAでは空文字列Emptyと「その他全て」を意味するAnyOtherが使える

# let nfa =
    let a, b, c, d = 1, 2, 3, 4 in
    NFA.cons ["0"; "1"; "2"]
      [(a, Char "0", [a]);         (* 遷移先は状態ではなく状態の集合になる *)
       (a, AnyOther, [b]);
       (b, Char "1", [b]);
       (b, AnyOther, [c]);
       (c, Char "2", [c]);
       (c, Empty,    [a;b])]
      [a]                          (* 初期状態も状態の集合*)
      [c];;
val nfa : int NFA.t = <abstr>

# NFA.run nfa "";;
- : bool = false

# NFA.run nfa "00";;
- : bool = false

 # NFA.run nfa "10";;
- : bool = true

# NFA.run maton "21012";;
- : bool = true

# NFA.run nfa "011010001";;
- : bool = false

遷移図の出力

Dot言語のソースコードを出力する関数がある。

# DFA.print_dfa dfa string_of_int;;
digraph finite_state_machine {
rankdir=LR
node [shape = point] init
node [shape = ellipse, peripheries=2]
"1"
node [shape = ellipse, peripheries=1]
init -> "0"
node [shape = ellipse, peripheries=1]
"0" -> "0" [label = "0"]
"0" -> "1" [label = "1"]
"1" -> "0" [label = "0"]
"1" -> "1" [label = "1"]
}

これをgraphvizでコンパイルすると、以下の画像が得られる。

NFAでも同様。

# print_nfa nfa string_of_int;;
digraph finite_state_machine {
rankdir=LR
node [shape = point] init
node [shape = ellipse, peripheries=2]
"3"
node [shape = ellipse, peripheries=1];
init -> "1"
node [shape = ellipse, peripheries=1]
"1" -> "1" [label = "0"]
"1" -> "2" [label = "other"]
"2" -> "2" [label = "1"]
"2" -> "3" [label = "other"]
"3" -> "3" [label = "2"]
"3" -> "1" [label = "ε"]
"3" -> "2" [label = "ε"]
}

NFAのDFAへの変換

与えられたNFAを、認識する言語が同じDFAに変換するアルゴリズムを実装した。

これだけ読んで分かるかどうかは微妙だが、ソースコードを張る。

let to_dfa = fun maton ->
  let init = (saturate maton maton.initial) in
  let finals =
    if anything_in_common init maton.finals
    then ref [init]
    else ref [] in
  let trans = ref [] in
  let rec loop searched to_search =
    match to_search with
    | [] -> ()
    | state::tl ->
        let nexts_triplet =
          image (fun c -> state, c, transit maton state c) maton.alphabet in
        let nexts = map (fun (_, _, x) -> x) nexts_triplet in
        trans := nexts_triplet @ !trans;
        finals := nexts
                   |> filter (anything_in_common maton.finals)
                   |> union !finals;
        loop (set_add searched state) (union tl (diff nexts (state::searched)))
  in
  let _ = loop [] [init] in
  DFA.cons maton.alphabet !trans init !finals

次のように使う。

# let nfa =
    let a, b, c = 0, 1, 2 in
    NFA.cons ["0"; "1"; "2"]
      [(a, Char "0", [a]);
       (a, Empty,    [b]);
       (b, Char "1", [b]);
       (b, Empty,    [c]);
       (c, Char "2", [c])]
      [a] [c] ;;
val nfa : int NFA.t = <abstr>

# let dfa = (NFA.to_dfa nfa);;
val dfa : int list DFA.t = <abstr>

変換されたDFAの状態は元のNFAの状態の集合なので、 int NFA.t を変換した dfa の型が int list DFA.t になっている。

この nfa , dfa を画像にすると、変換がどう行われたのかが分かる。

# NFA.print_nfa nfa string_of_int;;
(* 出力される文字列をgraphvizで画像にしたものを張った。以下同じ。 *)
# DFA.print_dfa dfa (ListExt.string_of_list string_of_int)

DFAの最小化

Myhill–Nerodeの関係を使ったDFAの最小化アルゴリズムも実装した。

コードはかなり長く、以下のようになる。

let minimize maton =
  (* helper functions *)
  let transitable_into marked (p, q) =
    exists
      (fun c ->
         let p', q' = transit maton p c, transit maton q c in
         mem (p', q') marked || mem (q', p') marked)
      maton.alphabet in
  let mark_initial_cond (p, q) =
    (mem p maton.finals && not (mem q maton.finals)) ||
    (not (mem p maton.finals) && mem q maton.finals) in
  let transitions_from s new_states =
    let origin = find (mem s) new_states
    and next c = find (mem (transit maton s c)) new_states in
    (map (fun c -> origin, c, next c) maton.alphabet) in
  let states = collect_states maton in
  let rec loop (marked, unmarked) =
    let to_add = filter (transitable_into marked) unmarked in
    if subset to_add marked then marked, unmarked
    else loop ((union marked to_add), (diff unmarked to_add)) in
  let connected_component udgraph v =
    let rec loop acc = function
      | [] -> acc
      | (x, y) :: tl when x = v || y = v -> loop (union acc [x; y]) tl
      | _ :: tl -> loop acc tl in
    loop [v] udgraph in
  (* main procedure *)
  (* (p, q) ∈ marked <-> p and q are distinguishable *)
  let init_marked, init_unmarked =
    partition mark_initial_cond (original_pairs states) in
  (* saturate marked and unmarked *)
  (* "marked ∩ unmarked = ∅" stays true after loop *)
  let marked, unmarked = loop (init_marked, init_unmarked) in
  (* calculate new states from marked and unmarked *)
  let new_states =
    fold_left_ignore
      (fun acc s -> exists (fun set -> mem s set) acc)
      (* largest undistinguishable set which contains s *)
      (fun acc s -> (connected_component unmarked s) :: acc)
      [] states in
  (* also calculate new transition from new_states *)
  let new_trans =
    fold_left_ignore
      (fun acc s -> exists (fun (set, _, _) -> mem s set) acc)
      (fun acc s -> (transitions_from s new_states) @ acc)
      [] states in
  cons
    maton.alphabet
    new_trans
    (* note : exactly one element of new_states contains maton.inits *)
    (find (mem maton.inits) new_states)
    (filter (anything_in_common maton.finals) new_states)

使用例:

# let maton = DFA.cons ["0"; "1"]
      [(0, "0", 1);
       (0, "1", 2);
       (1, "0", 0);
       (1, "1", 3);
       (2, "0", 4);
       (2, "1", 5);
       (3, "0", 4);
       (3, "1", 5);
       (4, "0", 4);
       (4, "1", 5);
       (5, "0", 5);
       (5, "1", 5)]
      0 [2;3;4] ;;
val maton : int DFA.t = <abstr>

# DFA.print_dfa maton string_of_int;;

これを最小化すると、

# let minimized = DFA.minimize maton;;
val minimized : int list DFA.t = <abstr>

# DFA.print_dfa minimized (MyExt.ListExt.string_of_list string_of_int);;

となる。

最小化されたDFAを見ると元のDFAのどの状態が同一視されているのかが分かる。



教科書

講義で使った教科書のリスト。

読みきった本は読書メーターで管理しているが、講義で使う教科書は途中で終わることが多いので読書メーターで管理しきれない。そこでここにまとめた。

「あの講義で使ったあの本の名前、何だっけ?」というときのためにリストしてある。講義で教科書・参考書として指定されていなくても講義が無かったら読まなかっただろう本も載せた。

ハードウェア実験

計算機構成論

言語処理系論 (演習を含む)

情報論理

離散数学

オペレーティングシステム (演習を含む)

生物情報学論

  • なし

情報科学実験

集合と位相

ハードウェア構成法

計算機システム

形式言語理論

アルゴリズムとデータ構造

情報数学

統計物理学

生命科学

物性化学

現代生命科学

記号論理学

アルゴリズム入門

電磁気学

構造化学

図形科学

基礎統計

情報

  • 山口 和紀 (2006), 情報, 東京大学出版会 (リンクは2版)

力学

  • 今井 功, 高見 穎郎, 高木 隆司, 吉澤 徴, 下村 裕 (2006), 演習力学 新訂版, サイエンス社

熱力学

微積分学

  • 斎藤 毅 (2013), 微積分, 東京大学出版会

線形代数学



ランダムピック

整数の組 \((i, j)\) に対してランダムな整数を割り振りたい。 それも、全ての組について乱数を保存することなく割り振りたい。

# モジュールのインポート
%matplotlib inline
from math import floor
from itertools import product
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams["figure.figsize"] = (10, 10)

失敗例

_random = np.random.permutation(256)
def not_random_pick(i, j):
    return _random[(i + j) % 256]

こうすると \(random\_pick(i, j) = random\_pick(i - 1, j + 1)\) が成り立ってしまうので値がランダムにならない。

canvas = np.array([[not_random_pick(i, j) for i in range(256)] for j in range(256)])
plt.imshow(canvas / 256, cmap=plt.cm.binary)

プロットすると全くランダムでないのがよく分かる。

成功例

\(random\_pick(i, j) = random\_pick(i - 1, j + 1)\) が成り立つのがまずいので、以下のように _random から値を二回取り出すようにすれば良い。

_random[x]_random[x+1] には関連がないので、 random_pick(i, j) の返り値もランダムになる。

_random = np.random.permutation(256)
def random_pick(i, j):
    return _random[(_random[i] + j) % 256]
canvas = np.array([[random_pick(i, j) for i in range(256)] for j in range(256)])
plt.imshow(canvas / 256, cmap=plt.cm.binary)

工夫

mod演算無し

_random から値を取り出すときに (_random[i] + j) % 256 が配列の大きさを超えないようにモジュロ演算をしているが、 _random に入っている値 と j には制限があるので、 _random の長さを大きくすることでモジュロ演算を省くことができる。

_random = np.zeros(512, dtype=np.int) # 512 = (_random[x]の最大値) + (jの最大値)
for i, val in enumerate(np.random.permutation(256)):
    _random[i] = val
    _random[i + 256] = val

def random_pick(i, j):
    return _random[_random[i] + j]

canvas = np.array([[random_pick(i, j) for i in range(256)] for j in range(256)])
plt.imshow(canvas / 256, cmap=plt.cm.binary)

高次元化

高次元でも「 _random から引いてきた値に引数を足してまた _random から引く」を繰り返せば同じことができる。

_random = np.random.permutation(256)
def random_pick_higher_order(args):
    ans = 0
    for x in args:
        ans = _random[(ans + x) % 256]
    return ans


# 3次元でやる
canvas = np.array([[[random_pick_higher_order([i, j, k]) for i in range(256)]
                    for j in range(256)] for k in range(256)])
fig, (ax0, ax1, ax2) = plt.subplots(ncols=3, figsize=(10, 30))
# 適当に切ってプロットする
ax0.imshow(canvas[:, :, 32] / 256, cmap=plt.cm.binary)
ax1.imshow(canvas[:, 63, :] / 256, cmap=plt.cm.binary)
ax2.imshow(canvas[157, :, :] / 256, cmap=plt.cm.binary)

返り値を整数以外にする

random_picker の返り値を使って別の配列から引くようにすれば任意のオブジェクトを返すようにできる。

_alphabets = "abcdefghijklmnopqrstuvwxyz"
_random = np.random.permutation(26)
def random_pick_alphabet(i, j):
    return _alphabets[_random[(_random[i % 26] + j) % 26]]

canvas = [[random_pick_alphabet(i, j) for i in range(10)] for j in range(10)]
for xs in canvas:
    for x in xs:
        print(x, end=" ")
    print("")
g j w s e f b p c t
m b t l u y e v a n
o e n w x q u c g h
j u h t k s x a m r
b x r n i l k g o d
e k d h z w i m j p
u i p r f t z o b v
x z v d y n f j e c
k f c p q h y b u a
i y a v s r q e x g

まとめ

クラスにまとめる。

このクラスはコンストラクタの引数として配列を受け取り、 インスタンスに関数呼び出しをするとその配列の要素がランダムに返る。

class RandomPicker():
    # とりあえず256としておいて、必要ならば後で更新する
    # 256のままだと picker(10, 0) = picker(266, 256) となる
    random_size = 256
    random = np.random.permutation(random_size)

    def __init__(self, base, arg_max=0):
        # arg_maxには予想される引数の最大値を渡す
        # 予想できない/ランダム性がそれほど必要ない 場合はデフォルトのままでいい
        self.base = base
        arg_max = max(len(base), arg_max)
        if arg_max > RandomPicker.random_size:
            RandomPicker.random_size = arg_max
            RandomPicker.random = np.random.permutation(arg_max)

    def __call__(self, *args):
        ans = RandomPicker.random[args[0] % RandomPicker.random_size]
        for i in range(1, len(args)):
            ans = RandomPicker.random[(ans + args[i]) % RandomPicker.random_size]
        return self.base[ans % len(self.base)]

次のように使う。

picker = RandomPicker(np.linspace(0.0, 1.0, 10))
canvas = np.array([[(picker(i - 1, j - 1))
                    for i in range(256)]
                   for j in range(256)])
plt.imshow(canvas, cmap=plt.cm.binary)

要素数\(10+256=266\) の配列を確保するだけで\(256\times256\)のホワイトノイズを作れて嬉しい。

各ピクセルに対する処理(関数を適用する、隣接ピクセルの平均を取る…)が増えればそれだけこの方法による利点は大きくなる。