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] = a , b までの LCS の長さ
                  i   j

 • 境界値: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 番目の要素が最後にくる増加列
  の長さ
• 候補:以下の内の長い方
 • a のみから成る列
   i

 • a 以前の a 未満の元の列に追加
   i    i
まとめ
•   DP = メモ化 + 部分最適化
    •   部分最適解から全体最適解が得られる時
        に使う
    •   指数空間の問題をほぼ多項式時間で解け
        る
•   添字の処理が面倒(バグの温床)
•   デバッグ・検証が難しい
•   細かな拡張性がわるい
ADP の紹介
動的計画法の問題点
•   行列に対する反復操作

    •   意味がわかりづらい

    •   添字がバグの温床

•   最適化・評価が渾然一体

    •   変更がしづらい

•   効率や正当性の検証がしづらい
ADP とは?
•   発想:解の生成と評価フェーズを分離

    •   最適化関数を再帰的に適用することで計
        算量を減らす

    •   コンパイラによる最適化等も活用

•   対象となる入力は「データ列」

    •   文字列に限らず、行列列、数列でもよい
解生成と評価の分離
•   DP を 文法 と 代数 に分割

    •   うわっ難しそう……;;

    •   名前がゴツいだけ!

    •   列をパーズして解の候補を生成、最適化
        しつつ評価、というのを形式的に書いた
ADP 作業の流れ

1. データ列の要素を決める

2. 扱う演算を決める

3. 評価関数を考える

4. 文法を書く
例:編集距離
•   文字の削除・挿入・置換の回数が最小に
    なるように文字列を書き換えたい

•   データ列の要素:文字

•   扱う演算の決定

    •   削除・挿入・置換を行った場所と、文字
        列の終端がわかればよい
評価代数の決定
•   評価代数とは:

    •   前で決めた演算に対応する評価関数

    •   解の候補から最適解を絞り込む最適化関数 h



                  標準的な「編集距離」
                  のスコア関数
評価代数の例2
   • 解の列挙や数え上げにも使える
    • 文法の定義の正当性を確認出来る!



列挙用代数。Nil などはコンストラクタ   数え上げ用代数
文法の定義(木)
•   入力:(“darling”, “airline”)

    •   一本にしたいので “darling$enilria” のように纏める
文法
•   木構造のパーズに使う文法:木文法

    •   能力的には CFG と同等

        •   導出規則に操作名のラベルを付けただけ
実際のコード:文法
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




    •   ほぼ文法を ASCII に置き換えただけ

    •   評価関数・メモ化の有無を注釈できる
最適化関数 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 Edit
enum = AlignmentAlg (const Nil) Del Ins Match id

-- | 数え上げ用評価代数
count :: AlignmentAlg Char Int
count = 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 Int
  unit = 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 String
   pretty = 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 Int
   billMin = 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?
御清聴
ありがとうございました

Algebraic DP: 動的計画法を書きやすく

  • 1.
  • 2.
    自己紹介 • 石井 大海 (id:mr_konn / @mr_konn) • 早稲田大学数学科三年 • 集合論やモデル理論、圏論、代数学に興味 • 函数型言語、定理証明系など形式手法にも興味 • 2010 Summer Intern から PFI でバイト中 • Haskell で Twitter やWebのクローラを書いている
  • 3.
    今日のおはなし • 朗報:Haskell の話じゃありません! • が、Haskell を使います。 • Algebraic Dynamic Programming (ADP) • 連続データに対する動的計画法の新しい 設計・実装法 • バイオインフォマティクスの分野から
  • 4.
    Table of Contents • 動的計画法の復習 • 動的計画法とは? • 動的計画法の例 • ADP の紹介 • 動的計画法の問題点 • ADP とは?──原理と例 • ADP の改良版 • まとめ
  • 5.
  • 6.
  • 7.
  • 8.
    結局どういうこと? • 小さい範囲の最適解を組み合わせて解ける最適 化問題を • 小さい部分での結果をメモ化しながら解く方法 • 指数時間かかりそうなものがだいたい多項式 時間で解けるようになる • 部分問題と全体の漸化式で表わされ、アルゴリズ ムはメモ化行列への反復作業として記述される
  • 9.
    動的計画法の例 • 編集距離 • 最長一致部分列 •括弧の位置最適化 • 最長増加部分列
  • 10.
    編集距離 • 入力:二つの文字列 • 出力:一方の文字列に文字を挿入・削除す ることで他方の文字列にするのに必要な最 短手数 • 例:(darling, airline) • 4(darling→arling→airling→airlin→airline)
  • 11.
    編集距離の考え方 • 入力:(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を足す • これらの中で最も手数が少ないもの
  • 12.
    編集距離の漸化式 • これをメモ化して、i, jを 1 から埋めて 再帰していく
  • 13.
    実装例(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 • 境界での初期化が必要になる
  • 14.
    問題 • 挿入、削除に加えて置換を 1score とカ ウントするように修正してみよ。 • 実際に書き換える方法を示せ • 最小手が複数ある場合は列挙せよ 一般に評価関数を書き換えたり出力を拡張するのは難しい
  • 15.
    最長一致部分列(LCS) • 入力:二つの文字列 • 出力:一致する部分列の最大長 • 例:LCS(darling, airline) = 5 (“arlin”) • 漸化式:d[i, j] = a , b までの LCS の長さ i j • 境界値:d[0, j] = d[i, 0] = 0
  • 16.
    問題 • 実際に一致部分列を挙げよ •複数ある場合は全て列挙してみよ
  • 17.
    計算順序最適化 • 入力:括弧のついていない数式 A • 出力:適切に括弧を付けて、答えを最大 / 最小化せよ • 例: 1 + 2 × 3 • 最大:(1+2) × 3 = 9 最小:1+(2×3)=7 • 数式のパーズ・ill-formed な部分列は排除し なくてはいけない • 面倒なので、数字は一桁だけにする
  • 18.
    漸化式 • a +b,a, b が最適値のとき。そこで ぞれ a × b が最適値を取るのは、それ • d[i, j] = (i +1)文字∼ j 文字までの最適解 • 後はメモ化して書けばよいが、構文エ ラーなどの NULL の処理が入る • 上の添字で合っているかぶっちゃけ不安
  • 19.
    最長増加部分列(LIS) • 与えられた数列の中で最も長い増加部 分列の長さを求める。 • 入力:{3, 1, 4, 2, 5} • 出力:3({3, 4, 5} と {1, 2, 5} が最長)
  • 20.
    最長増加部分列 • d[i] =i 番目の要素が最後にくる増加列 の長さ • 候補:以下の内の長い方 • a のみから成る列 i • a 以前の a 未満の元の列に追加 i i
  • 21.
    まとめ • DP = メモ化 + 部分最適化 • 部分最適解から全体最適解が得られる時 に使う • 指数空間の問題をほぼ多項式時間で解け る • 添字の処理が面倒(バグの温床) • デバッグ・検証が難しい • 細かな拡張性がわるい
  • 22.
  • 23.
    動的計画法の問題点 • 行列に対する反復操作 • 意味がわかりづらい • 添字がバグの温床 • 最適化・評価が渾然一体 • 変更がしづらい • 効率や正当性の検証がしづらい
  • 24.
    ADP とは? • 発想:解の生成と評価フェーズを分離 • 最適化関数を再帰的に適用することで計 算量を減らす • コンパイラによる最適化等も活用 • 対象となる入力は「データ列」 • 文字列に限らず、行列列、数列でもよい
  • 25.
    解生成と評価の分離 • DP を 文法 と 代数 に分割 • うわっ難しそう……;; • 名前がゴツいだけ! • 列をパーズして解の候補を生成、最適化 しつつ評価、というのを形式的に書いた
  • 26.
    ADP 作業の流れ 1. データ列の要素を決める 2.扱う演算を決める 3. 評価関数を考える 4. 文法を書く
  • 27.
    例:編集距離 • 文字の削除・挿入・置換の回数が最小に なるように文字列を書き換えたい • データ列の要素:文字 • 扱う演算の決定 • 削除・挿入・置換を行った場所と、文字 列の終端がわかればよい
  • 28.
    評価代数の決定 • 評価代数とは: • 前で決めた演算に対応する評価関数 • 解の候補から最適解を絞り込む最適化関数 h 標準的な「編集距離」 のスコア関数
  • 29.
    評価代数の例2 • 解の列挙や数え上げにも使える • 文法の定義の正当性を確認出来る! 列挙用代数。Nil などはコンストラクタ 数え上げ用代数
  • 30.
    文法の定義(木) • 入力:(“darling”, “airline”) • 一本にしたいので “darling$enilria” のように纏める
  • 31.
    文法 • 木構造のパーズに使う文法:木文法 • 能力的には CFG と同等 • 導出規則に操作名のラベルを付けただけ
  • 32.
    実際のコード:文法 alignmentGrammar :: AlignmentAlgChar 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 • ほぼ文法を ASCII に置き換えただけ • 評価関数・メモ化の有無を注釈できる
  • 33.
    最適化関数 h と文法 •解全体に h を適用すると指数時間 • パーズしながら h が適用出来れば枝が 刈れて、多項式時間で解ける! • 「h を適用可能」を文法に注釈
  • 34.
    コード:評価代数 -- | 編集距離の評価代数の型 dataAlignmentAlg 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 Edit enum = AlignmentAlg (const Nil) Del Ins Match id -- | 数え上げ用評価代数 count :: AlignmentAlg Char Int count = AlignmentAlg nil' del' ins' match' h' where nil' _ = 1; del' _ s = s; ins' s _ = s; match' _ s _ = s h' [] = [] h' x = [sum x]
  • 35.
    編集距離の代数 --| 編集距離計算用の評価代数 unit :: AlignmentAlg Char Int unit = 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] -- 編集距離最小のものを選ぶ • 列挙も評価も統一的に記述出来る!
  • 36.
    試してみる • 二つの代数の積 ⨂を使うと、複数の条 件を組み合わせることが出来る 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"))]
  • 37.
    例:最長一致部分列 • 実は、先程の文法を使って書ける • 文字が一致した時だけスコ アを +1 • 最大値を持つものが欲しい • 重複ケースが出るので、枝 刈りするには文法を改善
  • 38.
    例:計算順序最適化 • 今度は複数桁に対応してみる • 必要な演算は加算、乗算、数値、数値の拡張 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] }
  • 39.
    代数 -- | 数式を pretty print する代数 pretty :: BillAlg Char String pretty = 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 Int billMin = 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] • 列挙・数え上げ・最大化も同様
  • 40.
    文法 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 をつけてもしょうがない -- メモ化する意味も余りない • メモ化・最適化関数の適用を陽に指定できる強み • 文法を定義するのでパーズと解生成を一気にできる
  • 41.
    例:最長増加部分列 • 解の生成で増加列を直に生成出来ればいいが… • 木文法は CFG とほぼ同等。「増加列」の概念 は木のラベル込みだと文脈依存になる(ハズ) • 全ての部分文字列を生成して、評価関数でフィ ルターするより他にない • ADP には向かない例(省略)
  • 42.
    まとめ:ADP • データ列に対する DPの抽象化 • 解の生成と評価・最適化を分離 • モジュラリティが高い • 似た問題をほぼ同じ枠組で解ける • 添字が出て来ない(バグ温床の解消) • 文法のデザインが一番難しい • CFG で表現出来ない物は余り向かない
  • 43.
    時間計算量? • 書き易さや検証可能性はよい。計算量は? • 仮定:最適化関数h の返す値の数は有界(1 個とか n 個以下とか) • count 代数や最適化関数は一つなので良い • n-best 解も n で抑えられるのでよい • 列挙代数は爆発するので駄目 • 文法から計算量の見積りが出来る
  • 44.
    計算量の見積り • weight(G) : 文法に現れる木の中でサイズが抑えられない非 終端記号(非有界ノードと呼ぶ)の数の最大値 - 1 • このとき、時間計算量 O(n2+width(G)) となる • 計算量の見積りも簡単 • 計算量を減らすには文法の非終端記号を分割して工夫する
  • 45.
    見積りの例 • 編集距離:非有界ノードは editのみ。 どの木にも一つずつしか出現しないの で width(G) = 0。計算量はO(n2) • 計算順序:非有界ノードは bill と number。bill は add や mul に二回登場。 width(G) = 1。計算量はO(n3)
  • 46.
    発展 • ADP は生のチューンされた DP よりは速くな かったりする • Stream Fusion の技術と組み合わせて C のと 定数倍くらいの差まで迫れる • 複数入力を一つに纏める必要がある • MCFG(Multiple CFG)を使って複数入力に対 応したバージョンもある
  • 47.
    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 • 複数入力をどう変形するかルールを書く • 細かい解説は省略。
  • 48.
    参考文献 • “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
  • 49.
  • 50.