情報システム基礎論2:第 7 回 (担当: 古賀) kd-tree 2014 年 6 月 3 日 レジュメ URL: http://sd.is.uec.ac.jp/koga/lecture/IF2/ はじめに 1 これまで検索対象は 1 次元データ、つまりスカラ量であった。今回は 2 次元データ、3 次元データといっ たベクトルデータ検索を考える。ベクトルデータを検索するためのデータ構造である kd-tree を紹介する。 • d: 次元数 2 kd-tree kd-tree(k dimensional tree) は 2 分探索木を多次元用に拡張したもの。 • S = {v1 , v2 , · · · , vn }: d 次元ベクトルの集合。S を kd-tree に登録する。 2.1 基本アイデア 20 9 5 30 16 35 図 1: 1 次元の 2 分探索木 1 次元の場合、2 分探索木では • 根ノードが数直線を 2 分割する。 • 根以外のノードは部分数直線を 2 分割する。 9 20 30 図 2: 2 分探索木による数直線の分割 d 次元の場合は、 • 根ノードは d 次元空間を 2 分割すればよい。 1 • 根以外のノードは d 次元部分空間を 2 分割すればよい。 但し、分割方向に自由度がある → 分割方向を指定して分割する 2.2 アルゴリズム S から kd-tree を作るアルゴリズム kd-tree(S) を示す。 Definition 1 d 次元ベクトル p = (p1 , p2 , . . . , pd ) とする。つまり、pi :ベクトル p の第 i 次元の値。 Step 0: if S = φ, return 空の kd-tree else if |S| = 1, return 1 ノードのみからなる kd-tree /* 以下は S が 2 つ以上のデータを含む時 */ Step 1: S から根ノード root を選ぶ。また、分割次元 split を定める。 Step 2: S 0 = S − root とする。 Step 3: S 0 を 2 つに分割する。 0 Sleft = {p ∈ S 0 |psplit ≤ rootsplit } 0 Sright = {p ∈ S 0 |psplit > rootsplit } 0 Step 4: 左部分木 Tl を生成する。Tl = kd-tree(Sleft ) 0 Step 5: 右部分木 Tr を生成する。Tr = kd-tree(Sright ) Step 6: root が根で Tl と Tr をそれぞれ左子ノード、右子ノードとした木 T を構築。T を戻り値として 終了。 分割次元の選択: いくつかの方法が考えられる。 1. 次元を順番に選択。実装簡単。 2. 分散が大きい次元を選択 root の選択: 左右の部分木をバランスさせたい → 分割次元 split に関する S のメジアンを root とする。 例:2 次元の 4 点 (2,5)(6,3)(3,8)(8,9) に大して kd-tree を作った例。分割次元は y,x の順番で選択。 (8,9) (3,8) (2,5) y (6,3) (3,8) x (2,5) (6,3) (8,9) 図 3: 2 次元 kd-tree 2 アルゴリズム kd-tree の計算量: root 選択時のメジアン計算はソーティングが必要なので O(n log n)。 0 ソーティング時に S 0 right と Sleft を決定できる。また、kd-tree の高さは O(log n)。よって、計算量は O(n log n × log n) = O(n log2 n). 2.3 検索操作と挿入操作 検索操作 access(q) は通常の 2 分探索木とほぼ同じ。d 次元ベクトル q と kd-tree の内部ノードの分割次 元の値を比較し、左子ノードに進むか、右子ノードに進むかを決定。 挿入操作 insert(p) も 2 分探索木とほぼ同じ。但し、p の挿入により葉ノードが内部ノードに変化する場 合は分割次元の指定が必要となる。 バランスがとれた kd-tree では 1 回の検索、挿入の計算量は O(log n)。挿入操作を繰り返すと 2 分木の バランスが崩れる可能性があるが、1 次元の場合とは異なり木の再構成は簡単でない。 3 類似検索 ここまで検索操作 access(q) とは q そのものをデータベース S から探すことであった。 一方、S から q に近いデータを探す操作を類似検索と呼ぶ。類似検索の結果は q と一致する必要はない。 q をクエリ (query) と呼ぶ。 1. NN-探索 (nearest neighbor search): q に最も近い S のデータを探す。D(p, q) は p,q 間の距離を表す。 return p ∈ S such that D(p, q) ≤ D(p0 , q) for any other p0 ∈ S 2. kNN-探索 (k nearest neighbor search): q に最も近いデータを S から k 個探す。 3. レンジ探索 (range search): q から距離 r 以内の S のデータを探す。 return p ∈ S such that D(p, q) ≤ r 以下では NN-探索について論じる。NN-探索は、q と S の全データとの距離を計算すれば自明にできる。 いかに距離計算の回数を減らせるかが NN-探索では重要 1 次元データならバイナリサーチで O(log n) 回の距離計算で可能。 3 a2 (8,9) R2 a2(8,9) R2 q q (2,5) (2,5) (6,3) (6,3) R1 R1 図 4: 類似検索で必要となる距離計算 kd-tree による NN-検索 4 4.1 アイデア 図 4 は 3 点からなる単純な 2 次元 kd-tree である。点 (2,5) によって、空間が 2 つの長方形 R1 と R2 に 分割されている。R1 の split 次元の値 < R2 の split 次元の値。q はクエリ。 q は上長方形 R2 に含まれるので R2 に含まれる点が q に近そう。そこで R2 に含まれる点の中で q に一 番近い点 a2 を求めてみる (図 4 では (8,9))。この時、 • q を中心とし半径 r = D(q, a2 ) の超球が下長方形 R1 と重ならない時、親ノード及び R1 内の点につ いて距離計算は省略できる (図 4 左)。距離計算回数=1。 • R1 と重なる時は、親ノード及び R1 内の点に対して距離を調べる必要がある (図 4 右)。距離計算回 数=3。 4 4.2 アルゴリズム アルゴリズム NNfind(q, S, R, root) (簡易版) • 入力:(1) クエリ q, (2) データ集合 S, (3)d 次元超直方体 R, (4)kd-tree の根ノード root • 出力:(1)S の中で一番 q に近い点 a, (2) その距離 dst。 Step 1: q が R1 と R2 のどちらに近いかを判定。 • Rn : q に近い超直方体 • Rf : q に遠い超直方体 if qsplit < rootsplit , Rn = R1 ; Rf = R2 ; else Rn = R2 ; Rf = R1 ; /* qsplit ≥ rootsplit */ Step 2: Rn の中で一番 q に近い点 an 及びその距離 dstn を求める。これは再起呼出で記述できる。 • Sn : Rn に内包される S の部分集合 • Sf : Rf に内包される S の部分集合 (an , dstn )=NNfind(q, Sn , Rn ,root の Rn 側の子ノード); Step 3: q を中心とする超球が Rf と重なるかで処理を切り替える。 if (q を中心とする半径 dstn の超球と Rf が重ならない){ return(an , dstn ); } else{ 親ノードとの距離 D(q, root) 計算。 /* Rf の中で一番 q に近い点 af 及びその距離 dstf を再起呼出で計算*/ (af , dstf )=NNfind(q, Sf , Rf ,root の Rf 側の子ノード); dst = min{dstn , D(q, root), dstf }; a は dst に対応する点; return(a, dst); } ——————————————————— 再起できないケース: Step 1 の前に記述する。 Step 0: if(root=null) return(null,∞); if(root が葉ノード) return(root, D(q, root)) • Rn ,Rf を「q から近い超直方体」、「q から遠い超直方体」としたのは再起呼出時に q が R に含まれ ないケースが発生するため。図 5 参照。 5 R Rf (8,9) Rn (3,8) (2,5) Rn R (8,9) Rf (3,8) (2,5) q (6,3) q (6,3) 図 5: 再起呼出時の処理 4.2.1 簡易版の改良 Step 3 で q を中心とする超球の半径を dstn としているのは無駄。過去に見付かった q へ最も近い点ま での距離を max dist とし、半径 max dist の超球を使えばよい。図 6 参照。dstn を半径とする超球は Rf と重なるが、max dist を半径とする超球は Rf と重ならない。 R Rf (8,9) Rn (3,8) dstn (2,5) q max_dist (6,3) R1 図 6: 簡易版の無駄な処理 NNfind の最終的なアルゴリズムを 7 ページに示す。下線が付いた部分が簡易版との差分。 4.3 実行時間 NNfind の実行時間 = log n + 調べる超直方体の数。 右辺第 1 項は kd-tree 上で q を検索して葉に到達するまでの時間。右辺第 2 項は S に含まれる点の分布に 依存。S の中で q に最も近い点を a とすると、q を中心とする半径 D(q, a) の超球と重なる超直方体は少な くとも調べなければならない。 • D(q, a) 小 → 調べる超直方体数小。例えば、R の中にデータが高密度で万遍なく分布する場合。 • D(q, a) 大 → 調べる超直方体数大 実験的に実行時間は • 低次元空間であれば n にはあまり依存しない。 6 • 空間次元数 d には指数的に依存。 アルゴリズム NNfind(q, root) • 入力:(1) クエリ q, (2)kd-tree の根ノード root • 出力:(1)S の中で一番 q に近い点 a, (2) その距離 dst。 Step 0: /* 再起できないケースの処理 */ if(root=null) return(null,∞); if(root が葉ノード) { if(D(q, root) < max dist) max dist = D(q, root); return(root, D(q, root)); } Step 1: q が R1 と R2 のどちらに近いかを判定。 • Rn : q に近い超直方体 • Rf : q に遠い超直方体 if qsplit < rootsplit , Rn = R1 ; Rf = R2 ; else Rn = R2 ; Rf = R1 ; /* qsplit ≥ rootsplit */ Step 2: Rn の中で一番 q に近い点 an 及びその距離 dstn を求める。これは再起呼出で記述できる。 • Sn : Rn に内包される S の部分集合 • Sf : Rf に内包される S の部分集合 (an , dstn )=NNfind(q,root の Rn 側の子ノード); Step 3: q を中心とする超球が Rf と重なるかで処理を切り替える。 if (q を中心とする半径 max dist の超球と Rf が重ならない){ return(an , dstn ); } else{ 親ノードとの距離 D(q, root) 計算。 if(D(q, root) < max dist) max dist = D(q, root); /* Rf の中で一番 q に近い点 af 及びその距離 dstf を再起呼出で計算*/ (af , dstf )=NNfind(q,root の Rf 側の子ノード); dst = min{dstn , D(q, root), dstf }; a は dst に対応する点; return(a, dst); } 7 kd-tree での NN 探索がうまくいく例といかない例 Andrew Moore, ”An introductory tutorial on kd-trees”, Technical Report No.209, Computer Laboratory, University of Cambridge, 1991 より引用。 良い例 悪い例 8
© Copyright 2024 ExpyDoc