Saya entah bagaimana membutuhkan tensor (ekspansi matriks) untuk proyeksi saya. Mencari di Google, menemukan berbagai macam perpustakaan, di sekitar semak-semak, dan apa yang Anda butuhkan - tidak. Saya harus menerapkan rencana lima hari dan menerapkan apa yang dibutuhkan. Catatan singkat tentang bekerja dengan tensor dan trik pengoptimalan.
Jadi apa yang kita butuhkan?
- Matriks berdimensi-N (tensor)
- Implementasi sekumpulan metode dasar untuk bekerja dengan tensor sebagai struktur data
- Implementasi satu set dasar fungsi matematika (untuk matriks dan vektor)
- Jenis generik, yaitu, apa saja. Dan operator khusus
Dan apa yang sudah tertulis di hadapan kita?
, Towel , :
System.Numerics.Tensor . , , , . , , .
MathSharp, NumSharp, Torch.Net, TensorFlow, /ML-, .
- ,
- Transpose โ ยซยป , O(V), V โ ยซยป.
, . , , , ., ( , )
System.Numerics.Tensor . , , , . , , .
MathSharp, NumSharp, Torch.Net, TensorFlow, /ML-, .
Penyimpanan elemen, transposisi, subtensor
Item akan disimpan dalam array satu dimensi. Untuk mendapatkan elemen dari sekumpulan indeks, kita akan mengalikan indeks dengan koefisien tertentu dan menjumlahkannya. Misalkan kita memiliki tensor [3 x 4 x 5]. Kemudian kita perlu membentuk array dari tiga elemen - blok (dia sendiri yang datang dengan namanya). Maka elemen terakhir adalah 1, kedua dari belakang adalah 5, dan elemen pertama adalah 5 * 4 = 20. Artinya, blok = [20, 5, 1]. Misalnya, saat mengakses dengan indeks [1, 0, 4], indeks dalam array akan terlihat seperti 20 * 1 + 5 * 0 + 4 * 1 = 24. Sejauh ini semuanya jelas
Mengubah urutan
... yaitu, mengubah urutan sumbu. Cara yang bodoh dan mudah adalah membuat array baru dan meletakkan elemen di sana dalam urutan baru. Tetapi sering kali lebih mudah untuk mengubah urutan, bekerja dengan urutan sumbu yang diinginkan, dan kemudian mengubah urutan sumbu kembali. Tentu saja, dalam kasus ini, Anda tidak dapat mengubah array linier (LM) itu sendiri , dan saat mengakses indeks tertentu, kami hanya akan mengubah urutannya.
Pertimbangkan fungsinya:
private int GetFlattenedIndexSilent(int[] indices)
{
var res = 0;
for (int i = 0; i < indices.Length; i++)
res += indices[i] * blocks[i];
return res + LinOffset;
}
Seperti yang Anda lihat, jika Anda menukar blok , maka visibilitas sumbu swap akan dibuat. Oleh karena itu , kami akan menulis ini:
public void Transpose(int axis1, int axis2)
{
(blocks[axis1], blocks[axis2]) = (blocks[axis2], blocks[axis1]);
(Shape[axis1], Shape[axis2]) = (Shape[axis2], Shape[axis1]);
}
Kami hanya mengubah jumlah dan panjang sumbu di beberapa tempat.
Subtensor
Subtensor dari tensor dimensi-N adalah tensor dimensi-M (M <N), yang merupakan bagian dari aslinya. Misalnya, elemen nol dari tensor bentuk [2 x 3 x 4] adalah tensor bentuk [3 x 4]. Kami mendapatkannya hanya dengan menggeser.
Mari kita bayangkan bahwa kita mendapatkan subtensor pada indeks n . Kemudian, elemen pertama adalah n * blok [0] + 0 * blok [1] + 0 * blok [2] + ... . Artinya, pergeserannya adalah n * blok [0] . Karenanya, tanpa menyalin tensor asli, kita mengingat pergeseran , membuat tensor baru dengan tautan ke data kita, tetapi dengan pergeseran. Dan Anda juga perlu membuang elemen dari blok, yaitu blok elemen [0], karena ini adalah sumbu pertama, tidak akan ada panggilan ke sana.
Operasi Komposisi Lainnya
Semua orang lain sudah mengikuti dari ini.
- SetSubtensor akan meneruskan elemen ke subtensor yang diinginkan
- Concat membuat tensor baru, dan di sana ia akan meneruskan elemen dua (sedangkan panjang sumbu pertama adalah jumlah panjang sumbu dari dua tensor)
- Tumpukan mengelompokkan sejumlah tensor menjadi satu dengan sumbu tambahan (misalnya, tumpukan ([3 x 4], [3 x 4]) -> [2 x 3 x 4])
- Slice mengembalikan Stack dari subtenzer tertentu
Semua operasi komposisi yang telah saya tentukan dapat ditemukan di sini .
Operasi matematika
Semuanya sudah sederhana di sini.
1) Operasi pointwise (yaitu, untuk dua tensor, operasi dilakukan pada sepasang elemen yang sesuai (yaitu, dengan koordinat yang sama)). Implementasinya ada di sini (penjelasan mengapa kode jelek ada di bawah).
2) Operasi pada matriks. Produk, inversi, dan operasi sederhana lainnya, menurut saya, tidak memerlukan penjelasan. Meski banyak yang bisa diceritakan tentang determinannya.
Kisah Penentu
3) Operasi pada veteran (produk titik dan silang).
Optimasi
Template?
Tidak ada templat di C # . Karena itu, Anda harus menggunakan kruk. Beberapa orang telah membuat kompilasi dinamis menjadi delegasi, misalnya , mengimplementasikan penjumlahan dua jenis.
Namun, saya menginginkan kustom, jadi saya memulai antarmuka dari mana pengguna perlu mewarisi strukturnya. Dalam hal ini, primitif disimpan dalam array linier itu sendiri, dan fungsi penambahan, perbedaan, dan lainnya disebut sebagai
var c = default(TWrapper).Addition(a, b);
Yang diuraikan sebelum metode Anda. Contoh implementasi struktur seperti itu .
Pengindeksan
Lebih lanjut, meskipun tampaknya logis untuk menggunakan params di pengindeks, yaitu, seperti ini:
public T this[params int[] indices]
Faktanya, setiap panggilan akan membuat array, jadi Anda harus membuat banyak kelebihan beban . Hal yang sama terjadi dengan fungsi lain yang bekerja dengan indeks.
Pengecualian
Saya juga mengarahkan semua pengecualian dan pemeriksaan ke dalam blok #if ALLOW_EXCEPTIONS jika Anda benar-benar membutuhkannya dengan cepat, dan pasti tidak ada masalah dengan indeks. Ada sedikit peningkatan performa.
Faktanya, ini bukan hanya pengoptimalan mikro, yang akan menghabiskan banyak biaya dalam hal keamanan. Misalnya, kueri dikirim ke tensor Anda, di mana Anda sudah, karena alasan Anda sendiri, melakukan pemeriksaan untuk kebenaran data. Lalu mengapa Anda perlu pemeriksaan lagi? Dan mereka tidak gratis, terutama jika kita bahkan menyimpan operasi aritmatika yang tidak perlu dengan bilangan bulat.
Multithreading
Terima kasih Billy, ternyata sangat sederhana dan diterapkan melalui Parallel.For.
Meskipun multithreading bukanlah obat mujarab, dan harus diaktifkan dengan hati-hati. Saya menjalankan tolok ukur untuk penambahan tensor pointwise pada i7-7700HQ:
Di mana sumbu Y menunjukkan waktu (dalam mikrodetik) untuk melakukan operasi tunggal dengan dua tensor integer dengan ukuran tertentu (ukuran sumbu X).
Artinya, ada ambang tertentu yang membuat multithreading masuk akal. Agar tidak perlu berpikir, saya membuat bendera Threading.Auto dan dengan bodohnya melakukan hardcode pada konstanta, mulai dari volume yang sama dengan yang Anda dapat mengaktifkan multithreading (apakah ada metode otomatis yang lebih cerdas?).
Pada saat yang sama, perpustakaan masih tidak lebih cepat dari matriks game, atau bahkan lebih dari yang dihitung pada CUDA. Untuk apa? Itu sudah ditulis, tetapi yang utama kami adalah tipe khusus.
Seperti ini
Ini catatan singkatnya, terima kasih sudah membaca. Tautan ke github proyek ada di sini . Dan pengguna utamanya adalah pustaka aljabar simbolik AngouriMath .
Sedikit tentang tensor kami di 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)