Upgrade to Pro — share decks privately, control downloads, hide ads and more …

俺の kd-tree

俺の kd-tree

Rust で kd-tree を自作し、crates.io に公開しました。kd tree とは?パフォーマンスは? 設計は? といったあたりをまとめた発表資料です。

4c41dd5087889e60e220abd377b52c4a?s=128

Terada Yuichiro

November 24, 2020
Tweet

More Decks by Terada Yuichiro

Other Decks in Programming

Transcript

  1. 俺の kd-tree 2020-11-24 Y.Terada @ CADDi

  2. 自己紹介 • てらだ( @u_1roh ) • CADDi で図面の画像解析 に取り組んでいます。 •

    Rust でアルゴリズム、い いですよね!
  3. こんなことを話します • kd-tree を自作して crates.io に publish してみた • そもそも

    kd-tree とは? • なぜ自作したの? • どんな実装? • パフォーマンスは?
  4. kd-tree とは • k-dimensional tree • k 次元空間の点群を高速に探索可能にするデータ構造 • こんな探索が出来ます

    – 【最近傍探索】点群から、指定座標に最も近い点を探索 – 【 k 近傍探索】近いものから k 個探索( k が被ってる…) – 【領域探索】指定領域に含まれる点を全て探索
  5. kd-tree の構築( 2D の場合) • 2 分木のノードに点をひとつ格納 • X で分割

    → Y で分割 → X で分割 → Y で分割 → … https://www.datasciencecentral.com/profiles/blogs/implementing-kd-tree-for-fast-range-search-nearest-neighbor
  6. 最近傍探索 問合せ点 Q この枝における 最近傍点 探索不要な枝 探索済みの枝

  7. crates.io を探すと

  8. 使ってみた • `kdtree` – 遅い – 点の逐次追加でツリーを構築するスタイル – 任意次元の kd

    tree が作れる – 点座標の数値型は `num_traits::Float` 型(つまり `f64` と `f32`) – 型が `KdTree<f64, T, [f64; 3]>` みたいになる(`f64` を 2回書く) • `fux_kdtree` – 速い( `kdtree` の 2.5〜 3倍) – 点の配列から一気に構築するスタイル – 3次元までしか作れない – 点座標の数値型は `f64` のみ – `fux_kdtree::kdtree::KdtreePointTrait` を実装しないといけない
  9. 作りたいもの • `fux_kdtree` 並の速度で、 • 任意次元で使えて、 • 整数値の座標値が扱えて、 • 設計が洗練されているもの

  10. できた!

  11. ベンチマーク(構築処理) fux_kdtree kdtree 俺の (f64) 俺の (i32) [0, 1]^3 の空間に

    一様な乱数で 10^n 個の点を生成 10^2 点 10^3 点 10^4 点
  12. ベンチマーク(最近傍探索) fux_kdtree kdtree 俺の (f64) 俺の (i32) 10^2 点 10^4

    点 10^3 点
  13. 機能はまだ足りない • 最近傍探索しかできない – ◦ 【最近傍探索】指定座標に最も近い点を探索 – ☓ 【 k

    近傍探索】近いものから k 個探索 – ☓ 【領域探索】指定領域に含まれる点を全て探索 • 今後、機能追加していきたい
  14. 俺の kd-tree 使い方 編

  15. 基本のき use kd_tree::KdTree; let items: Vec<[i32; 3]> = vec![[1, 2,

    3], [3, 1, 2], [2, 3, 1]]; let kdtree: KdTree<[i32; 3]> = KdTree::build(items); assert_eq!(kdtree.nearest(&[3, 1, 2]).unwrap().item, &[3, 1, 2]); • KdTree<[i32; 3]> ← 型がシンプル! • 整数座標が使える!(※ unsigned はダメ)
  16. f64 の座標を使う • 浮動小数点座標の場合は build_by_ordered_float() を使う。 – f64 は Ord

    を実装していないので。 – ordered_float::OrderedFloat を利用 use kd_tree::KdTree; let kdtree: KdTree<[f64; 3]> = KdTree::build_by_ordered_float(vec![ [1.0, 2.0, 3.0], [3.0, 1.0, 2.0], [2.0, 3.0, 1.0] ]); assert_eq!( kdtree.nearest(&[3.1, 0.9, 2.1]).unwrap().item, &[3.0, 1.0, 2.0] );
  17. マップのようにも使えます • KdMap<P,T> … Map のように使える kd tree – P

    に点座標 – T には任意の型が入る use kd_tree::KdMap; let kdmap: KdMap<[isize; 3], &'static str> = KdMap::build(vec![ ([1, 2, 3], "foo"), ([2, 3, 1], "bar"), ([3, 1, 2], "buzz"), ]); assert_eq!(kdmap.nearest(&[3, 1, 2]).unwrap().item.1, "buzz");
  18. KdPoint カスタム実装 use kd_tree::{KdPoint, KdTree}; struct Item { point: [f64;

    2], id: usize } impl KdPoint for Item { type Scalar = f64; type Dim = typenum::U2; // 2 dimensional tree. fn at(&self, k: usize) -> f64 { self.point[k] } } let kdtree: KdTree<Item> = KdTree::build_by_ordered_float(vec![ Item { point: [1.0, 2.0], id: 111 }, Item { point: [2.0, 3.0], id: 222 }, Item { point: [3.0, 4.0], id: 333 }, ]); assert_eq!(kdtree.nearest(&[1.9, 3.1]).unwrap().item.id, 222);
  19. KdSlice • KdTree のスライス型( String に対する str ) – Sized

    ではない → 常に参照型で扱う – KdTree は Deref<Target=KdSlice<...>> を実装 • KdSlice::sort() は items を move しない(ソートするだ け) use kd_tree::KdSlice; let mut items: Vec<[i32; 3]> = vec![[1, 2, 3], [3, 1, 2], [2, 3, 1]]; let kdtree: &KdSlice<[i32; 3]> = KdSlice::sort(&mut items); assert_eq!(kdtree.nearest(&[3, 1, 2]).unwrap().item, &[3, 1, 2]);
  20. 俺の kd-tree データ構造 編

  21. バランス良く構築 8 個 8 個 4 個 3 個 4

    個 3 個
  22. 構築フロー items: &mut [T] x 座標でソート ↑items.len()/2 y 座標でソート ↑items.len()/2

    x 座標でソート ↑items.len()/2 このまま実装すると ソートが多いので遅い
  23. Quick select • 必要なのはソートではない – 中央値の左右で要素が分かれていれば良い – 左右それぞれの枝はソートされている必要がない • Quick

    select • `pdqselect` クレート • k 番目を境に要素を分ける
  24. `kd_sort_by()`

  25. 並び替えるだけで kd tree になる • binary-tree と似ている – [T] を

    sort() しておけば binary_search() できる – 同様に、 [T] を並び替えておけば最近傍探索できる • 構築に必要なのは次元と kd_compare だけ – (注:最近傍探索は kd_compare だけでは足りません。距離が測 れないといけないので。) • このシンプルさ&柔軟さを最大限に活かした設計にしたい。
  26. KdSliceN<T, N> pub struct KdSliceN<T, N: Unsigned>(PhantomData<N>, [T]); impl<T, N:

    Unsigned> KdSliceN<T, N> { pub fn sort_by<F>(items: &mut [T], compare: F) -> &Self where F: Fn(&T, &T, usize) -> Ordering + Copy, { kd_sort_by(items, N::to_usize(), compare); unsafe { &*(items as *const _ as *const Self) } } } 次元 unsized 参照を返す unsafe を使ってキャスト
  27. KdTreeN<T, N> pub struct KdTreeN<T, N: Unsigned>(PhantomData<N>, Vec<T>); impl<T, N:

    Unsigned> KdTreeN<T, N> { pub fn build_by<F>(mut items: Vec<T>, compare: F) -> Self where F: Fn(&T, &T, usize) -> Ordering + Copy, { kd_sort_by(&mut items, N::to_usize(), compare); Self(PhantomData, items) } }
  28. KdPoint

  29. KdPoint の実装

  30. Type aliases pub type KdSlice<T> = KdSliceN<T, <T as KdPoint>::Dim>;

    pub type KdTree<T> = KdTreeN<T, <T as KdPoint>::Dim>; pub type KdMap<P, T> = KdTree<(P, T)>; pub type KdMapSlice<P, T> = KdSlice<(P, T)>;
  31. まとめ • 自作 kd-tree を crates.io に公開した • 性能 –

    `kdtree` より速い(構築も探索も) – 探索は `fux_kdtree` より速い • 機能 – 整数座標が扱える – 高次元が扱える ※ CADDi では Rust でアルゴリズムを書きたいエンジニアを募集しています
  32. None