Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

自己紹介 ● てらだ( @u_1roh ) ● CADDi で図面の画像解析 に取り組んでいます。 ● Rust でアルゴリズム、い いですよね!

Slide 3

Slide 3 text

こんなことを話します ● kd-tree を自作して crates.io に publish してみた ● そもそも kd-tree とは? ● なぜ自作したの? ● どんな実装? ● パフォーマンスは?

Slide 4

Slide 4 text

kd-tree とは ● k-dimensional tree ● k 次元空間の点群を高速に探索可能にするデータ構造 ● こんな探索が出来ます – 【最近傍探索】点群から、指定座標に最も近い点を探索 – 【 k 近傍探索】近いものから k 個探索( k が被ってる…) – 【領域探索】指定領域に含まれる点を全て探索

Slide 5

Slide 5 text

kd-tree の構築( 2D の場合) ● 2 分木のノードに点をひとつ格納 ● X で分割 → Y で分割 → X で分割 → Y で分割 → … https://www.datasciencecentral.com/profiles/blogs/implementing-kd-tree-for-fast-range-search-nearest-neighbor

Slide 6

Slide 6 text

最近傍探索 問合せ点 Q この枝における 最近傍点 探索不要な枝 探索済みの枝

Slide 7

Slide 7 text

crates.io を探すと

Slide 8

Slide 8 text

使ってみた ● `kdtree` – 遅い – 点の逐次追加でツリーを構築するスタイル – 任意次元の kd tree が作れる – 点座標の数値型は `num_traits::Float` 型(つまり `f64` と `f32`) – 型が `KdTree` みたいになる(`f64` を 2回書く) ● `fux_kdtree` – 速い( `kdtree` の 2.5〜 3倍) – 点の配列から一気に構築するスタイル – 3次元までしか作れない – 点座標の数値型は `f64` のみ – `fux_kdtree::kdtree::KdtreePointTrait` を実装しないといけない

Slide 9

Slide 9 text

作りたいもの ● `fux_kdtree` 並の速度で、 ● 任意次元で使えて、 ● 整数値の座標値が扱えて、 ● 設計が洗練されているもの

Slide 10

Slide 10 text

できた!

Slide 11

Slide 11 text

ベンチマーク(構築処理) fux_kdtree kdtree 俺の (f64) 俺の (i32) [0, 1]^3 の空間に 一様な乱数で 10^n 個の点を生成 10^2 点 10^3 点 10^4 点

Slide 12

Slide 12 text

ベンチマーク(最近傍探索) fux_kdtree kdtree 俺の (f64) 俺の (i32) 10^2 点 10^4 点 10^3 点

Slide 13

Slide 13 text

機能はまだ足りない ● 最近傍探索しかできない – ○ 【最近傍探索】指定座標に最も近い点を探索 – ☓ 【 k 近傍探索】近いものから k 個探索 – ☓ 【領域探索】指定領域に含まれる点を全て探索 ● 今後、機能追加していきたい

Slide 14

Slide 14 text

俺の kd-tree 使い方 編

Slide 15

Slide 15 text

基本のき 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 はダメ)

Slide 16

Slide 16 text

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] );

Slide 17

Slide 17 text

マップのようにも使えます ● KdMap

… 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");

Slide 18

Slide 18 text

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 = 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);

Slide 19

Slide 19 text

KdSlice ● KdTree のスライス型( String に対する str ) – Sized ではない → 常に参照型で扱う – KdTree は Deref> を実装 ● 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]);

Slide 20

Slide 20 text

俺の kd-tree データ構造 編

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

構築フロー items: &mut [T] x 座標でソート ↑items.len()/2 y 座標でソート ↑items.len()/2 x 座標でソート ↑items.len()/2 このまま実装すると ソートが多いので遅い

Slide 23

Slide 23 text

Quick select ● 必要なのはソートではない – 中央値の左右で要素が分かれていれば良い – 左右それぞれの枝はソートされている必要がない ● Quick select ● `pdqselect` クレート ● k 番目を境に要素を分ける

Slide 24

Slide 24 text

`kd_sort_by()`

Slide 25

Slide 25 text

並び替えるだけで kd tree になる ● binary-tree と似ている – [T] を sort() しておけば binary_search() できる – 同様に、 [T] を並び替えておけば最近傍探索できる ● 構築に必要なのは次元と kd_compare だけ – (注:最近傍探索は kd_compare だけでは足りません。距離が測 れないといけないので。) ● このシンプルさ&柔軟さを最大限に活かした設計にしたい。

Slide 26

Slide 26 text

KdSliceN pub struct KdSliceN(PhantomData, [T]); impl KdSliceN { pub fn sort_by(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 を使ってキャスト

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

KdPoint

Slide 29

Slide 29 text

KdPoint の実装

Slide 30

Slide 30 text

Type aliases pub type KdSlice = KdSliceN::Dim>; pub type KdTree = KdTreeN::Dim>; pub type KdMap

= KdTree<(P, T)>; pub type KdMapSlice

= KdSlice<(P, T)>;

Slide 31

Slide 31 text

まとめ ● 自作 kd-tree を crates.io に公開した ● 性能 – `kdtree` より速い(構築も探索も) – 探索は `fux_kdtree` より速い ● 機能 – 整数座標が扱える – 高次元が扱える ※ CADDi では Rust でアルゴリズムを書きたいエンジニアを募集しています

Slide 32

Slide 32 text

No content