私はN次元の点の巨大なセットを持っています(数千万; Nは100に近いです)。
空間的な局所性を維持しながら、これらのポイントを1つの次元にマッピングする必要があります。 ヒルベルト空間充填曲線 を使用して実行します。
各ポイントについて、曲線上で最も近いポイントを選択します。ポイントのヒルベルト値(カーブの開始から選択したポイントまでのカーブの長さ)は、私が求める単一の次元の値です。
計算は瞬時である必要はありませんが、まともな現代の家庭用PCハードウェアでは数時間以内であると私は期待しています。
実装に関する提案はありますか?私を助けるライブラリはありますか? (言語はそれほど重要ではありません。)
私はついに故障し、いくらかのお金を払い出しました。 AIP(American Institute of Physics)には、Cのソースコードを含む素敵な短い記事があります。JohnSkillingによる「Programmingthe Hilbertcurve」(AIP Conf。Proc。707、381(2004)から)には、次のコードを含む付録があります。両方向のマッピング。これは、1を超える任意の数の次元で機能し、再帰的ではなく、大量のメモリを消費する状態遷移ルックアップテーブルを使用せず、ほとんどの場合ビット演算を使用します。したがって、それは適度に高速で、優れたメモリフットプリントを備えています。
記事を購入することを選択した場合、ソースコードにエラーが見つかりました。
次のコード行(関数TransposetoAxesにあります)にエラーがあります。
for(i = n-1; i> = 0; i-)X [i] ^ = X [i-1];
修正は、大なり記号(> =)を大なり記号(>)に変更することです。この修正を行わないと、変数「i」がゼロになったときに負のインデックスを使用してX配列にアクセスし、プログラムが失敗します。
アルゴリズムがどのように機能するかを説明している記事(コードを含めて7ページの長さ)を読むことをお勧めしますが、これは明らかではありません。
私は彼のコードを自分で使用するためにC#に変換しました。コードは次のとおりです。 Skillingは、渡されたベクトルを上書きして、その場で変換を実行します。入力ベクトルのクローンを作成し、新しいコピーを返すことを選択しました。また、拡張メソッドとしてメソッドを実装しました。
Skillingのコードは、Hilbertインデックスを転置として表し、配列として格納されます。ビットをインターリーブして単一のBigIntegerを形成する方が便利だと思います(辞書でより便利で、ループで反復するのが簡単など)が、マジックナンバーやビット演算などを使用してその演算とその逆を最適化しました。コードが長いので省略しました。
namespace HilbertExtensions
{
/// <summary>
/// Convert between Hilbert index and N-dimensional points.
///
/// The Hilbert index is expressed as an array of transposed bits.
///
/// Example: 5 bits for each of n=3 coordinates.
/// 15-bit Hilbert integer = A B C D E F G H I J K L M N O is stored
/// as its Transpose ^
/// X[0] = A D G J M X[2]| 7
/// X[1] = B E H K N <-------> | /X[1]
/// X[2] = C F I L O axes |/
/// high low 0------> X[0]
///
/// NOTE: This algorithm is derived from work done by John Skilling and published in "Programming the Hilbert curve".
/// (c) 2004 American Institute of Physics.
///
/// </summary>
public static class HilbertCurveTransform
{
/// <summary>
/// Convert the Hilbert index into an N-dimensional point expressed as a vector of uints.
///
/// Note: In Skilling's paper, this function is named TransposetoAxes.
/// </summary>
/// <param name="transposedIndex">The Hilbert index stored in transposed form.</param>
/// <param name="bits">Number of bits per coordinate.</param>
/// <returns>Coordinate vector.</returns>
public static uint[] HilbertAxes(this uint[] transposedIndex, int bits)
{
var X = (uint[])transposedIndex.Clone();
int n = X.Length; // n: Number of dimensions
uint N = 2U << (bits - 1), P, Q, t;
int i;
// Gray decode by H ^ (H/2)
t = X[n - 1] >> 1;
// Corrected error in Skilling's paper on the following line. The appendix had i >= 0 leading to negative array index.
for (i = n - 1; i > 0; i--)
X[i] ^= X[i - 1];
X[0] ^= t;
// Undo excess work
for (Q = 2; Q != N; Q <<= 1)
{
P = Q - 1;
for (i = n - 1; i >= 0; i--)
if ((X[i] & Q) != 0U)
X[0] ^= P; // invert
else
{
t = (X[0] ^ X[i]) & P;
X[0] ^= t;
X[i] ^= t;
}
} // exchange
return X;
}
/// <summary>
/// Given the axes (coordinates) of a point in N-Dimensional space, find the distance to that point along the Hilbert curve.
/// That distance will be transposed; broken into pieces and distributed into an array.
///
/// The number of dimensions is the length of the hilbertAxes array.
///
/// Note: In Skilling's paper, this function is called AxestoTranspose.
/// </summary>
/// <param name="hilbertAxes">Point in N-space.</param>
/// <param name="bits">Depth of the Hilbert curve. If bits is one, this is the top-level Hilbert curve.</param>
/// <returns>The Hilbert distance (or index) as a transposed Hilbert index.</returns>
public static uint[] HilbertIndexTransposed(this uint[] hilbertAxes, int bits)
{
var X = (uint[])hilbertAxes.Clone();
var n = hilbertAxes.Length; // n: Number of dimensions
uint M = 1U << (bits - 1), P, Q, t;
int i;
// Inverse undo
for (Q = M; Q > 1; Q >>= 1)
{
P = Q - 1;
for (i = 0; i < n; i++)
if ((X[i] & Q) != 0)
X[0] ^= P; // invert
else
{
t = (X[0] ^ X[i]) & P;
X[0] ^= t;
X[i] ^= t;
}
} // exchange
// Gray encode
for (i = 1; i < n; i++)
X[i] ^= X[i - 1];
t = 0;
for (Q = M; Q > 1; Q >>= 1)
if ((X[n - 1] & Q)!=0)
t ^= Q - 1;
for (i = 0; i < n; i++)
X[i] ^= t;
return X;
}
}
}
動作するコードをC#でgithubに投稿しました。
https://github.com/paulchernoch/HilbertTransformation を参照してください
更新:私はちょうど公開しました(2019年秋)a Rust "hilbert"と呼ばれるcrates.ioのクレート。これもSkillingのアルゴリズムを使用しています。 https://crates.io/crates)を参照してください。/hilbert
ここに示すn-> 1および1-> nからのマッピングのアルゴリズム "ヒルベルト空間充填曲線を使用した1次元値とn次元値の間のマッピングの計算" J K Lawder
「SFCモジュールとKademliaオーバーレイ」をグーグルで検索すると、システムの一部として使用していると主張するグループが見つかります。ソースを表示すると、おそらく関連する関数を抽出できます。
これがあなたが望むことをどのように行うのか私にはわかりません。この些細な3Dの場合を考えてみましょう。
001 ------ 101
|\ |\
| \ | \
| 011 ------ 111
| | | |
| | | |
000 -|---- 100 |
\ | \ |
\ | \ |
010 ------ 110
これは、次のパスで「Hilbertized」できます。
001 -----> 101
\ \
\ \
011 111
^ |
| |
000 | 100 |
\ | \ |
\ | \ V
010 110
1Dオーダーに:
000 -> 010 -> 011 -> 001 -> 101 -> 111 -> 110 -> 100
これが厄介な部分です。以下のペアと1D距離のリストを検討してください。
000 : 100 -> 7
010 : 110 -> 5
011 : 111 -> 3
001 : 101 -> 1
すべての場合において、左側と右側の値は互いに同じ3D距離(最初の位置で+/- 1)であり、これは同様の「空間的局所性」を意味しているように見えます。ただし、次元の順序(上記の例では、y、z、zの順に)を選択して線形化すると、その局所性が失われます。
別の言い方をすれば、開始点を取得し、残りの点をその開始点からの距離で並べ替えると、結果が大幅に異なります。 000
開始として、例:
1D ordering : distance 3D ordering : distance
---------------------- ----------------------
010 : 1 001,010,100 : 1
011,101,110 : sqrt(2)
111 : sqrt(3)
011 : 2
001 : 3
101 : 4
111 : 5
110 : 6
100 : 7
この効果は、次元の数とともに指数関数的に増大します(各次元の「サイズ」が同じであると想定)。
PaulChernochのコードをJavaに変換し、クリーンアップするのに少し時間を費やしました。特に、元の紙にアクセスできないため、コードにバグがある可能性があります。から。しかし、それは私が書くことができたユニットテストに合格します。それは以下です。
大きなデータセットの空間インデックスについて、 Z-Order 曲線とヒルベルト曲線の両方を評価したことに注意してください。 Z-Orderははるかに優れた品質を提供すると言わざるを得ません。ただし、自由に試してみてください。
/**
* Convert the Hilbert index into an N-dimensional point expressed as a vector of uints.
*
* Note: In Skilling's paper, this function is named TransposetoAxes.
* @param transposedIndex The Hilbert index stored in transposed form.
* @param bits Number of bits per coordinate.
* @return Point in N-space.
*/
static long[] HilbertAxes(final long[] transposedIndex, final int bits) {
final long[] result = transposedIndex.clone();
final int dims = result.length;
grayDecode(result, dims);
undoExcessWork(result, dims, bits);
return result;
}
static void grayDecode(final long[] result, final int dims) {
final long swap = result[dims - 1] >>> 1;
// Corrected error in Skilling's paper on the following line. The appendix had i >= 0 leading to negative array index.
for (int i = dims - 1; i > 0; i--)
result[i] ^= result[i - 1];
result[0] ^= swap;
}
static void undoExcessWork(final long[] result, final int dims, final int bits) {
for (long bit = 2, n = 1; n != bits; bit <<= 1, ++n) {
final long mask = bit - 1;
for (int i = dims - 1; i >= 0; i--)
if ((result[i] & bit) != 0)
result[0] ^= mask; // invert
else
swapBits(result, mask, i);
}
}
/**
* Given the axes (coordinates) of a point in N-Dimensional space, find the distance to that point along the Hilbert curve.
* That distance will be transposed; broken into pieces and distributed into an array.
*
* The number of dimensions is the length of the hilbertAxes array.
*
* Note: In Skilling's paper, this function is called AxestoTranspose.
* @param hilbertAxes Point in N-space.
* @param bits Depth of the Hilbert curve. If bits is one, this is the top-level Hilbert curve.
* @return The Hilbert distance (or index) as a transposed Hilbert index.
*/
static long[] HilbertIndexTransposed(final long[] hilbertAxes, final int bits) {
final long[] result = hilbertAxes.clone();
final int dims = hilbertAxes.length;
final long maxBit = 1L << (bits - 1);
inverseUndo(result, dims, maxBit);
grayEncode(result, dims, maxBit);
return result;
}
static void inverseUndo(final long[] result, final int dims, final long maxBit) {
for (long bit = maxBit; bit != 0; bit >>>= 1) {
final long mask = bit - 1;
for (int i = 0; i < dims; i++)
if ((result[i] & bit) != 0)
result[0] ^= mask; // invert
else
swapBits(result, mask, i);
} // exchange
}
static void grayEncode(final long[] result, final int dims, final long maxBit) {
for (int i = 1; i < dims; i++)
result[i] ^= result[i - 1];
long mask = 0;
for (long bit = maxBit; bit != 0; bit >>>= 1)
if ((result[dims - 1] & bit) != 0)
mask ^= bit - 1;
for (int i = 0; i < dims; i++)
result[i] ^= mask;
}
static void swapBits(final long[] array, final long mask, final int index) {
final long swap = (array[0] ^ array[index]) & mask;
array[0] ^= swap;
array[index] ^= swap;
}
もう1つの可能性は、データに kd-tree を構築してから、ツリーを順番にトラバースして順序を取得することです。 kdツリーを構築するには、優れた中央値検出アルゴリズムが必要です。そのアルゴリズムには多くのものがあります。
1次元でヒルベルト曲線を使用する方法がわかりません。
距離を維持しながら(最小のエラーで)ポイントをより低い次元にマッピングすることに関心がある場合は、「多次元尺度構成法」アルゴリズムを調べることができます。
シミュレーテッドアニーリングは1つのアプローチです。
編集:コメントをありがとう。ヒルベルト曲線アプローチの意味がわかりました。ただし、これは難しい問題であり、N = 1億と1,000万のデータポイントを考えると、どのアプローチも局所性を適切に維持し、妥当な時間で実行できるとは思いません。 kd-treesはここでは機能しないと思います。
全順序を見つけることが重要でない場合は、ローカリティベースのハッシュやその他の近似最近傍スキームを調べることができます。入力サイズを減らすためにポイントのバケットを使用した階層的多次元尺度構成法は、適切な順序付けを提供する可能性がありますが、このような高次元では疑わしいです。