algebraic dp: 動的計画法を書きやすく
TRANSCRIPT
Algebraic DP:動的計画法を書きやすく
石井 大海
自己紹介• 石井 大海 (id:mr_konn / @mr_konn)
• 早稲田大学数学科三年
• 集合論やモデル理論、圏論、代数学に興味
• 函数型言語、定理証明系など形式手法にも興味
• 2010 Summer Intern から PFI でバイト中
• Haskell で Twitter やWebのクローラを書いている
今日のおはなし• 朗報:Haskell の話じゃありません!
• が、Haskell を使います。
• Algebraic Dynamic Programming (ADP)
• 連続データに対する動的計画法の新しい設計・実装法
• バイオインフォマティクスの分野から
Table of Contents• 動的計画法の復習
• 動的計画法とは?
• 動的計画法の例
• ADP の紹介
• 動的計画法の問題点
• ADP とは?──原理と例
• ADP の改良版
• まとめ
動的計画法の復習
動的計画法とは?動的計画法(どうてきけいかくほう、英: Dynamic
Programming, DP)は、コンピュータ科学の分野において、ある最適化問題を複数の部分問題に分割して解く際に、そこまでに求められている以上の最適解が求められないような部分問題を切り捨てながら解いていく手法である。分割統治法がトップダウン的な手法であるのに対し、動的計画法はボトムアップ的な手法といえる。
── 動的計画法 - Wikipedia
動的計画法とは?動的計画法(どうてきけいかくほう、英: Dynamic
Programming, DP)は、コンピュータ科学の分野において、ある最適化問題を複数の部分問題に分割して解く際に、そこまでに求められている以上の最適解が求められないような部分問題を切り捨てながら解いていく手法である。分割統治法がトップダウン的な手法であるのに対し、動的計画法はボトムアップ的な手法といえる。
── 動的計画法 - Wikipedia
結局どういうこと?• 小さい範囲の最適解を組み合わせて解ける最適化問題を
• 小さい部分での結果をメモ化しながら解く方法• 指数時間かかりそうなものがだいたい多項式時間で解けるようになる
• 部分問題と全体の漸化式で表わされ、アルゴリズムはメモ化行列への反復作業として記述される
動的計画法の例
• 編集距離
• 最長一致部分列
• 括弧の位置最適化
• 最長増加部分列
編集距離• 入力:二つの文字列• 出力:一方の文字列に文字を挿入・削除することで他方の文字列にするのに必要な最短手数
• 例:(darling, airline)
• 4(darling→arling→airling→airlin→airline)
編集距離の考え方• 入力:(a1a2...an, b1b2...bm)
• d[i, j] = a1...ai と b1...bj の編集距離
• d[i+1, j+1] の候補:
• ai+1 までを bj に編集して、bj+1を足す
• ai までを bj+1 に編集して、ai+1 を削る
• ai までを bj に編集して、ai+1を削ってbj+1を足す
• これらの中で最も手数が少ないもの
編集距離の漸化式
• これをメモ化して、i, j を 1 から埋めて再帰していく
実装例(Ruby)def edit_distance(as, bs) n = as.length m = bs.length d = Hash.new {|h, k| h[k] = Hash.new(0)} for i in 0..n d[i][0] = i end for j in 0..m d[0][j] = j end for i in 1..n for j in 1..m delta = as[i] == bs[j] ? 0 : 2 d[i][j] = [d[i-1][j] + 1, d[i][j-1] + 1, d[i-1][j-1] + delta].min end end return d[n][m]end
• 境界での初期化が必要になる
問題
• 挿入、削除に加えて置換を 1 score とカウントするように修正してみよ。
• 実際に書き換える方法を示せ
• 最小手が複数ある場合は列挙せよ
一般に評価関数を書き換えたり出力を拡張するのは難しい
最長一致部分列(LCS)• 入力:二つの文字列
• 出力:一致する部分列の最大長• 例:LCS(darling, airline) = 5 (“arlin”)
• 漸化式:d[i, j] = ai, bj までの LCS の長さ• 境界値:d[0, j] = d[i, 0] = 0
問題
• 実際に一致部分列を挙げよ
• 複数ある場合は全て列挙してみよ
計算順序最適化• 入力:括弧のついていない数式 A
• 出力:適切に括弧を付けて、答えを最大 / 最小化せよ• 例: 1 + 2 × 3
• 最大:(1+2) × 3 = 9 最小:1+(2×3)=7
• 数式のパーズ・ill-formed な部分列は排除しなくてはいけない
• 面倒なので、数字は一桁だけにする
漸化式• a + b, a × b が最適値を取るのは、それぞれ a, b が最適値のとき。そこで
• d[i, j] = (i +1)文字~ j 文字までの最適解
• 後はメモ化して書けばよいが、構文エラーなどの NULL の処理が入る
• 上の添字で合っているかぶっちゃけ不安
最長増加部分列(LIS)
• 与えられた数列の中で最も長い増加部分列の長さを求める。• 入力:{3, 1, 4, 2, 5}
• 出力:3({3, 4, 5} と {1, 2, 5} が最長)
最長増加部分列• d[i] = i 番目の要素が最後にくる増加列の長さ
• 候補:以下の内の長い方• ai のみから成る列• ai 以前の ai 未満の元の列に追加
まとめ• DP = メモ化 + 部分最適化• 部分最適解から全体最適解が得られる時に使う
• 指数空間の問題をほぼ多項式時間で解ける
• 添字の処理が面倒(バグの温床)• デバッグ・検証が難しい• 細かな拡張性がわるい
ADP の紹介
動的計画法の問題点• 行列に対する反復操作
• 意味がわかりづらい• 添字がバグの温床
• 最適化・評価が渾然一体
• 変更がしづらい• 効率や正当性の検証がしづらい
ADP とは?• 発想:解の生成と評価フェーズを分離• 最適化関数を再帰的に適用することで計算量を減らす
• コンパイラによる最適化等も活用
• 対象となる入力は「データ列」• 文字列に限らず、行列列、数列でもよい
解生成と評価の分離• DP を 文法 と 代数 に分割
• うわっ難しそう……;;
• 名前がゴツいだけ!
• 列をパーズして解の候補を生成、最適化しつつ評価、というのを形式的に書いた
ADP 作業の流れ
1. データ列の要素を決める
2. 扱う演算を決める
3. 評価関数を考える
4. 文法を書く
例:編集距離• 文字の削除・挿入・置換の回数が最小になるように文字列を書き換えたい
• データ列の要素:文字
• 扱う演算の決定
• 削除・挿入・置換を行った場所と、文字列の終端がわかればよい
評価代数の決定• 評価代数とは:
• 前で決めた演算に対応する評価関数
• 解の候補から最適解を絞り込む最適化関数 h
標準的な「編集距離」のスコア関数
評価代数の例2
• 解の列挙や数え上げにも使える
• 文法の定義の正当性を確認出来る!
列挙用代数。Nil などはコンストラクタ 数え上げ用代数
文法の定義(木)• 入力:(“darling”, “airline”)
• 一本にしたいので “darling$enilria” のように纏める
文法• 木構造のパーズに使う文法:木文法
• 能力的には CFG と同等
• 導出規則に操作名のラベルを付けただけ
実際のコード:文法
• ほぼ文法を ASCII に置き換えただけ
• 評価関数・メモ化の有無を注釈できる
alignmentGrammar :: AlignmentAlg Char ans -> String -> String -> [ans]alignmentGrammar AlignmentAlg{..} inp1 inp2 = let inp = inp1 ++ '$': reverse inp2 edit = tabulated $ -- 計算結果がメモ化可能であるということ nil <<< char '$' -- 終端;区切りは '$' ||| del <<< anychar -~~ edit -- 削除 ||| ins <<< edit ~~- anychar -- 挿入 ||| match <<< anychar -~~ edit ~~- anychar -- 置換 ... h -- 最適化函数がこれらの枝全てに適用可能 z = mk inp (_, n) = bounds z char = char' z anychar = acharSep' z '$' tabulated = table n in axiom' n a
最適化関数 h と文法• 解全体に h を適用すると指数時間
• パーズしながら h が適用出来れば枝が刈れて、多項式時間で解ける!
• 「h を適用可能」を文法に注釈
コード:評価代数-- | 編集距離の評価代数の型data AlignmentAlg alph ans = AlignmentAlg { nil :: alph -> ans -- ^ 終端 , del :: alph -> ans -> ans -- ^ 削除 , ins :: ans -> alph -> ans -- ^ 挿入 , match :: alph -> ans -> alph -> ans -- ^ 置換 , h :: [ans] -> [ans] -- ^ 最適化函数 }-- | 列挙用のデータ型data Edit = Nil | Del Char Edit | Ins Edit Char | Match Char Edit Char deriving (Eq, Ord, Show)
-- | 列挙用評価代数enum :: AlignmentAlg Char Editenum = AlignmentAlg (const Nil) Del Ins Match id
-- | 数え上げ用評価代数count :: AlignmentAlg Char Intcount = AlignmentAlg nil' del' ins' match' h' where nil' _ = 1; del' _ s = s; ins' s _ = s; match' _ s _ = s h' [] = [] h' x = [sum x]
編集距離の代数
• 列挙も評価も統一的に記述出来る!
-- | 編集距離計算用の評価代数unit :: AlignmentAlg Char Intunit = AlignmentAlg nil' del' ins' match' h' where nil' _ = 0 -- 終端のスコアはゼロ del' _ s = s + 1 -- 削除したら距離 + 1 ins' s _ = s + 1 -- 挿入したら距離 + 1 match' a s b | a == b = s -- 文字が一致したらそのまま | otherwise = s + 2 -- 一致しなければ + 2 h' [] = [] h' xs = [minimum xs] -- 編集距離最小のものを選ぶ
試してみる• 二つの代数の積 ⨂ を使うと、複数の条件を組み合わせることが出来る
ghci> alignmentGrammar count "darling" "airline"[48639]
ghci> alignmentGrammar unit "darling" "airline"[4]
ghci> alignmentGrammar (unit ⨂ pretty) "darling" "airline"[(4,("da-rling-","-airlin-e")),(4,("da-rlin-g","-airline-")), (4,("da-rling","-airline"))]
例:最長一致部分列• 実は、先程の文法を使って書ける
• 文字が一致した時だけスコアを +1
• 最大値を持つものが欲しい
• 重複ケースが出るので、枝刈りするには文法を改善
例:計算順序最適化• 今度は複数桁に対応してみる
• 必要な演算は加算、乗算、数値、数値の拡張
data Bill = Add Bill Char Bill -- ^ 加算 | Mul Bill Char Bill -- ^ 乗算 | Ext Bill Char -- ^ 数字の桁の続き "123" の 3 の部分 | Val Char -- ^ 数字 deriving (Show, Eq, Ord)
data BillAlg alph ans = BillAlg { add :: ans -> alph -> ans -> ans , mul :: ans -> alph -> ans -> ans , ext :: ans -> alph -> ans , val :: alph -> ans , h :: [ans] -> [ans] }
代数
• 列挙・数え上げ・最大化も同様
-- | 数式を pretty print する代数pretty :: BillAlg Char Stringpretty = BillAlg add' mul' ext' val' h' where add' x _ y = "(" ++ x ++ " + " ++ y ++ ")" mul' x _ y = "(" ++ x ++ " * " ++ y ++ ")" val' c = [c] ext' i c = i ++ [c] h' = id
-- | 最小化代数billMin :: BillAlg Char IntbillMin = BillAlg add' mul' ext' val' h' where add' x _ y = x + y mul' x _ y = x * y val' c = digitToInt c ext' i c = i * 10 + digitToInt c h' [] = [] h' xs = [minimum xs]
文法
• メモ化・最適化関数の適用を陽に指定できる強み
• 文法を定義するのでパーズと解生成を一気にできる
billGrammar :: BillAlg Char ans -> String -> [ans]billGrammar BillAlg{..} inp = axiom' n bill where bill = tabulated $ -- 結果をメモ化する number ||| (add <<< bill ~~- char '+') ~~~ bill -- 右側のパーズ結果が一定の文字数を越えないとき ~~- を使う -- 結合的でないので括弧がついている ||| (mul <<< bill ~~- char '*') ~~~ bill ... h -- 数式それ自体には最適化函数を逐次適用 number = val <<< digit ||| ext <<< number ~~- digit -- 数値のパーズに h をつけてもしょうがない -- メモ化する意味も余りない
例:最長増加部分列
• 解の生成で増加列を直に生成出来ればいいが…
• 木文法は CFG とほぼ同等。「増加列」の概念は木のラベル込みだと文脈依存になる(ハズ)
• 全ての部分文字列を生成して、評価関数でフィルターするより他にない
• ADP には向かない例(省略)
まとめ:ADP
• データ列に対する DP の抽象化• 解の生成と評価・最適化を分離• モジュラリティが高い• 似た問題をほぼ同じ枠組で解ける
• 添字が出て来ない(バグ温床の解消)• 文法のデザインが一番難しい• CFG で表現出来ない物は余り向かない
時間計算量?• 書き易さや検証可能性はよい。計算量は?• 仮定:最適化関数 h の返す値の数は有界(1個とか n 個以下とか)• count 代数や最適化関数は一つなので良い• n-best 解も n で抑えられるのでよい• 列挙代数は爆発するので駄目
• 文法から計算量の見積りが出来る
計算量の見積り
• weight(G) : 文法に現れる木の中でサイズが抑えられない非終端記号(非有界ノードと呼ぶ)の数の最大値 - 1
• このとき、時間計算量 O(n2+width(G)) となる
• 計算量の見積りも簡単
• 計算量を減らすには文法の非終端記号を分割して工夫する
見積りの例• 編集距離:非有界ノードは edit のみ。どの木にも一つずつしか出現しないので width(G) = 0。計算量はO(n2)
• 計算順序:非有界ノードは bill と
number。bill は add や mul に二回登場。width(G) = 1。計算量はO(n3)
発展• ADP は生のチューンされた DP よりは速くなかったりする
• Stream Fusion の技術と組み合わせて C のと定数倍くらいの差まで迫れる
• 複数入力を一つに纏める必要がある• MCFG(Multiple CFG)を使って複数入力に対応したバージョンもある
MCFGの例:編集距離
• 複数入力をどう変形するかルールを書く• 細かい解説は省略。
rewriteDel [c, a1, a2] = ([c, a1], [a2])rewriteIns [c, a1, a2] = ([a1], [c, a2])rewriteMatch [c1,c2,a1,a2] = ([c1, a1], [c2, a2])edit = tabulated2 $ nil <<< (EPS, EPS) >>>|| id2 ||| del <<< anychar ~~~|| edit >>>|| rewriteDel ||| ins <<< anychar ~~~|| edit >>>|| rewriteIns ||| match <<< anychar ~~~ anychar ~~~|| edit >>>|| rewriteMatch ... h
参考文献• “The Algebraic Dynamic Programming Homepage”, ADP project
team, 2009
• “adp-multi”, Maik Riechert, 2012
• “A Discipline of Dynamic Programming over Sequence Data”, Robert Giegerich, Carsten Meyer and Peter Steffen, 2004
• “Sneaking Around concatMap - Efficient Combinators for Dynamic Programming”, Christian Honer zu Siederdissen, 2012
• “Algebraic Dynamic Programming”, R. Giegerich and C. Meyer, 2002
• “プログラミングコンテストチャレンジブック”, 秋葉拓哉, 岩田陽一 and 北側宜稔, 毎日コミュニケーションズ, 2010
• “動的計画法 - Wikipedia”, Wikipedia, 2012
Any Questions?
御清聴ありがとうございました