libtorchにおけるテンソルのスライスやリダクションが分かりづらい時

 libtorchにおいて3次元以上のスライスで-1を指定し、テンソルをリダクションする時の記法で迷ったので練習。

using torch::indexing::Slice;
using torch::indexing::None;
using torch::indexing::Ellipsis;
//3階テンソル
torch::Tensor t = torch::arange(18).view({ 3,2,3 });
cout << "t:\n" << t << endl;;
//0次元と2次元の配列番号は網羅、1次元は最終配列番号のみ参照
//3x3の2階テンソルになり、1,2次元で構成される行列の行列方向はリダクション時に維持される
cout << "t[:,-1]:\n" << t.index({ Slice(), -1 }) << endl;
//0,1次元の配列番号は網羅、2次元は最終配列番号のみ参照
//3x2の2階テンソルになり、1,2次元で構成される行列の行列方向はリダクション時に維持されない
cout << "t[:,:,-1]:\n" << t.index({ Slice(), Slice(), -1 }) << endl;
//0次元の配列番号のみ網羅、1,2次元は最終配列番号のみ参照
//1,2次元で構成される行列の最終要素のみを抽出し3次元ベクトルに
cout << "t[:,-1,-1]:\n" << t.index({ Slice(), -1, -1 }) << endl;
//1,2次元の配列番号を網羅し、0次元の最終配列要素を参照
//0次元の最終要素の1,2次元で構成される行列を抽出し2x3の2階テンソルに
//リダクション時に1,2次元で構成される行列の行列方向はリダクション時に維持される
cout << "t[-1,:]:\n" << t.index({ -1,Slice() }) << endl;
cout << "t[-1,:,:]:\n" << t.index({ -1,Slice(),Slice() }) << endl;//上と同じ結果になる
//2次元の配列番号を網羅し、0,1次元の最終配列要素を参照
//0次元最終要素の1,2次元で構成される行列の最終行のみを抽出し3次元ベクトルに
cout << "t[-1,-1,:]:\n" << t.index({ -1,-1,Slice() }) << endl;
t:
(1,.,.) =
	0  1  2
	3  4  5

(2,.,.) =
	 6   7   8
	 9  10  11

(3,.,.) =
	12  13  14
	15  16  17
[ CPULongType{3,2,3} ]
t[:,-1]:
	3   4   5
	9  10  11
	15  16  17
[ CPULongType{3,3} ]
t[:,:,-1]:
	2   5
	8  11
	14  17
[ CPULongType{3,2} ]
t[:,-1,-1]:
	5
	11
	17
[ CPULongType{3} ]
t[-1,:]:
 	12  13  14
 	15  16  17
[ CPULongType{2,3} ]
t[-1,-1,:]:
 	15
 	16
 	17
[ CPULongType{3} ]

 特に忘れやすいのは、ixjxkの3階テンソルで網羅する配列番号の範囲を指定するとき、t[:,-1](t.index({ Slice(), -1 }))、などと記述すると、jについてのみ最終配列番号を参照しiとkについては網羅するという意味になったりする点だろうか。
 また、view関数とは違い、要素を保持したままの自動形状変更ではなく切り抜きである。
 スライスは何度やっても忘れる非常に問題のある記法だと思う。