私はどういうわけか私の投影のためにテンサー(マトリックス拡張)を必要としていました。グーグルは、茂みの周りに、そしてあなたが必要とするもののすべての種類のライブラリの数を見つけました-いいえ。私は5日間の計画を実行し、必要なものを実行する必要がありました。テンサーと最適化のトリックの操作に関する短いメモ。
では、何が必要ですか?
- N次元マトリックス(テンサー)
- データ構造としてテンソルを操作するための基本的なメソッドセットの実装
- 数学関数の基本セットの実装(行列およびベクトル用)
- 一般的なタイプ、つまり任意。そしてカスタムオペレーター
そして、私たちの前にすでに何が書かれていますか?
, Towel , :
System.Numerics.Tensor . , , , . , , .
MathSharp, NumSharp, Torch.Net, TensorFlow, /ML-, .
- ,
- Transpose — «» , O(V), V — «».
, . , , , ., ( , )
System.Numerics.Tensor . , , , . , , .
MathSharp, NumSharp, Torch.Net, TensorFlow, /ML-, .
要素の保存、転置、サブテンサー
アイテムは1次元配列に格納されます。インデックスのセットから要素を取得するには、インデックスに特定の係数を掛けて加算します。つまり、テンソル[3 x 4 x5]があるとします。次に、3つの要素(ブロック)の配列を形成する必要があります(彼自身が名前を思いついた)。次に、最後の要素は1、最後から2番目の要素は5、最初の要素は5 * 4 = 20です。つまり、ブロック= [20、5、1]です。たとえば、インデックス[1、0、4]でアクセスする場合、配列内のインデックスは20 * 1 + 5 * 0 + 4 * 1 = 24のようになります。これまでのところ、すべてが明確です。
転置
...つまり、軸の順序を変更します。ばかげて簡単な方法は、新しい配列を作成し、そこに要素を新しい順序で配置することです。ただし、多くの場合、転置して目的の軸の順序で作業してから、軸の順序を元に戻すと便利です。もちろん、この場合、線形配列(LM)自体を変更することはできません。特定のインデックスを参照する場合は、単に順序を変更します。
関数について考えてみましょう。
private int GetFlattenedIndexSilent(int[] indices)
{
var res = 0;
for (int i = 0; i < indices.Length; i++)
res += indices[i] * blocks[i];
return res + LinOffset;
}
ご覧のとおり、ブロックを交換すると、交換軸の可視性が作成されます。したがって、これを記述します。
public void Transpose(int axis1, int axis2)
{
(blocks[axis1], blocks[axis2]) = (blocks[axis2], blocks[axis1]);
(Shape[axis1], Shape[axis2]) = (Shape[axis2], Shape[axis1]);
}
軸の数と長さを場所によって変更するだけです。
サブテンサー
N次元テンソルのサブテンサーはM次元テンソル(M <N)であり、オリジナルの一部です。たとえば、形状テンソル[2 x 3 x4]のゼロ要素は形状テンソル[3x4]です。シフトするだけで得られます。
インデックスnでサブテンサーを取得するとします。次に、最初の要素はn *ブロック[0] + 0 *ブロック[1] + 0 *ブロック[2] + ...です。つまり、オフセットはn *ブロック[0]です。したがって、元のテンソルをコピーせずに、シフトを記憶し、データへのリンクを使用して、シフトを使用して新しいテンソルを作成します。また、ブロックから要素、つまり要素ブロック[0]を破棄する必要があります。これは最初の軸であるため、呼び出しはありません。
その他の作曲操作
他のすべてはすでにこれらからフォローしています。
- SetSubtensorは、要素を目的のサブテンサーに転送します
- Concatは新しいテンソルを作成し、そこで2つの要素を転送します(最初の軸の長さは2つのテンサーの軸の長さの合計です)
- スタックは、いくつかのテンサーを1つにグループ化し、軸を追加します(たとえば、スタック([3 x 4]、[3 x 4])-> [2 x 3 x 4])
- スライスは特定のサブテンザーからスタックを返します
私が定義したすべての合成操作はここにあります。
数学的操作
ここではすべてがすでに単純です。
1)ポイントワイズ操作(つまり、2つのテンサーの場合、操作は対応する要素のペアに対して(つまり、同じ座標で)実行されます)。実装はここにあります(そのような醜いコードがなぜ以下にあるのかについての説明)。
2)マトリックスの操作。製品、反転、およびその他の簡単な操作は、私には説明を必要としないようです。決定要因について話す話がありますが。
決定者の物語
3)ベテランの操作(ドットおよびクロス製品)。
最適化
テンプレート?
C#にはテンプレートはありません。したがって、クラッチを使用する必要があります。一部の人々は、デリゲートへの動的コンパイルを考え出しました。たとえば、2つのタイプの合計を実装します。
しかし、カスタムが欲しかったので、ユーザーが構造を継承する必要があるインターフェースを開始しました。この場合、プリミティブは線形配列自体に格納され、加算、差分、およびその他の関数は次のように呼び出されます。
var c = default(TWrapper).Addition(a, b);
これは、メソッドの前にインライン化されています。そのような構造の実装の例。
インデックス作成
さらに、インデクサーでparamsを使用することは論理的であるように見えますが、つまり、次のようになります。
public T this[params int[] indices]
実際、呼び出しごとに配列が作成されるため、多くのオーバーロードを作成する必要があります。インデックスを操作する他の関数でも同じことが起こります。
例外
また、すべての例外を実行し、#if ALLOW_EXCEPTIONSブロックをチェックインしました。これは、間違いなくすぐに必要であり、インデックスに問題がない場合に備えてです。パフォーマンスがわずかに向上します。
実際、これは単なるマイクロ最適化ではなく、セキュリティの面で多くのコストがかかります。たとえば、クエリがテンソルに送信されます。テンソルでは、自分の理由で、データの正確性をすでにチェックしています。では、なぜ別のチェックが必要なのですか?そして、特に整数を使用して不要な算術演算を保存する場合は、それらは無料ではありません。
マルチスレッド
Billyに感謝します。非常にシンプルで、Parallel.Forを介して実装されていることがわかりました。
マルチスレッドは万能薬ではありませんが、慎重に有効にする必要があります。 i7-7700HQでテンサーをポイントごとに追加するためのベンチマークを実行しました。
ここで、Y軸は、特定のサイズ(X軸サイズ)の2つの整数テンサーで単一の操作を実行する時間(マイクロ秒単位)を示します。
つまり、マルチスレッドが意味をなす特定のしきい値があります。考える必要がないように、Threading.Autoフラグを作成し、マルチスレッドを有効にできるボリュームから始めて、定数を愚かにハードコーディングしました(よりスマートな自動方法はありますか?)。
同時に、ライブラリはまだゲームマトリックスよりも高速ではなく、CUDAで計算されるマトリックスよりも高速です。何のために?それらはすでに書かれていますが、私たちの主なものはカスタムタイプです。
このような
ここに短いメモがあります、読んでくれてありがとう。プロジェクトのgithubへのリンクはこちらです。そして、その主なユーザーは、AngouriMathシンボリック代数ライブラリです。
AngouriMathのテンサーについて少し
, AM- Entity, . ,
var t = MathS.Matrices.Matrix(3, 3,
"A", "B", "C", // , "A * 3", "sqrt(sin(x) + 5)"
"D", "E", "F",
"G", "H", "J");
Console.WriteLine(t.Determinant().Simplify());
A * (E * J - F * H) + C * (D * H - E * G) - B * (D * J - F * G)