Pythonなんか大っ嫌い(# ゚Д゚)
pythonが嫌いな理由の一つに[]の使い方がある。[]の中に命令文が入っていたり、呪文のような構文が入っていたり。[]の中に書くのは配列関連の操作のようでfor文wihile文を簡略化できるようだが、何でも1行で書けばいいってものでもないだろう。プログラムというのはアルゴリズムの設計書なのだから、分かりやすさが大事だと昭和のおじさんは思う。
まあ、この配列の扱いやすさがPythonの特徴で大量のデータ処理を簡潔に処理できるというのは分かる。負荷高いくせに遅いけどね。 カーボンニュートラルが叫ばれるこの時代、コンピューターのエネルギー消費効率にも気を使ってほしいものだ。
スライス
pythonの[]の謎の構文にスライスがある。こういうのだ。
def sample(cxcy):
return torch.cat([cxcy[:, :2] - (cxcy[:, 2:] / 2), # x_min, y_min
cxcy[:, :2] + (cxcy[:, 2:] / 2)], 1) # x_max, y_max
なんじゃこりゃ(# ゚Д゚)、腹立つ。
こんなの昭和のおじさんにはタイプミスにしか見えないがPythonのインタープリタはこれを通すらしい。
一つ一つ見てみる。
cxcy
は2次元配列で合っているのか。型宣言が無いので分かりにくい(# ゚Д゚)
要素が整数なのか浮動小数点なのかも分からん(# ゚Д゚)
まあintの配列ということにしておこう。
cxcy[:, :2]
これは何だろう(# ゚Д゚) そもそも[]
の中に:
があるのだ?
どうもこれはpythonのスライスという機能らしい。
上のサイトによると[]
の中の':'は範囲を表すものらしい。こういう使い方だそうだ。
x = [0, 1, 2, 3, 4, 5, 6]
print(x[2:5])
# [2, 3, 4]
これは分かった。なるほどこれは直感的で便利そうだ。科学技術計算の式としては分かりやすい。にわかプログラマが飛び付くのも分かるような気がする。遅いけどね。 C++だと配列はfor文でコピーするか、トリッキーなポインタ操作になる。(ちなみに今のC++だと配列操作はvectorテンプレートを使う手もある)
では[:]
とか[:2]
とかは何なのだッ、これはタイプミスでこれが通ってしまうはpythonのバグでは無いのかッ(# ゚Д゚)/バンバン
x = [0, 1, 2, 3, 4, 5, 6]
print(x[:3])
# [0, 1, 2]
print(x[3:])
# [3, 4, 5, 6]
print(x[:])
# [0, 1, 2, 3, 4, 5, 6]
↑こういうことらしい。:
の周りの数字を省略すると配列の最初、もしくは終わりまでという意味になるらしい。いちいち配列のサイズを調べなくてもいいわけだ。まあ便利と言えば便利かな。遅いけどね。
priors[:, :2]
はどんな意味になるのかというと、マトリクスの2次要素の0から2個目まで抽出ということか。サンプルを作ってJupyterで動かしてみる。
priors=[[ 0, 1, 2, 3],
[10,11,12,13],
[20,21,22,23],
[30,31,32,33]]
print(priors[:,:2])
TypeError: list indices must be integers or slices, not tuple
あれ? エラー(# ゚Д゚) タプルはダメってか。じゃあnumpyで。
priors=np.array(
[[0, 1, 2, 3],
[10,11,12,13],
[20,21,22,23],
[30,31,32,33]])
print(priors[:,:2])
[[ 0 1]
[10 11]
[20 21]
[30 31]]
仕組みはわかったよーん。スライスには他にステップとかマイナス指定とかあるみたいだが省略する。
torch::Tensorのスライス機能
さて、スライスをどうやってC++でコーディングするかである。for文でゴリゴリ書いてもいいけど、元コードと表現の乖離が激しいなあ。と思ってたらtorch::Tensor型にスライスの機能があるらしい。↓これだ
Slice型?があって、そこのコンストラクタにスライスと同じような順番で引数をぶち込んでindexメソッドに渡せばいいわけだ。たぶん。
で作ってみたサンプルがこれ↓。
#include <torch/torch.h>
#include <iostream>
int main() {
int x[4][4] =
{ { 0, 1, 2,3 },
{10,11,12,13},
{20,21,22,23},
{30,31,32,33} };
torch::Tensor ts_x0 =
torch::from_blob(
x,
{4,4},
torch::TensorOptions().dtype(torch::kInt)
);
std::cout << ts_x0 << std::endl;
torch::Tensor ts_x1 = ts_x0.index({
torch::indexing::Slice(torch::indexing::None,torch::indexing::None),
torch::indexing::Slice(torch::indexing::None,2)
});
std::cout << ts_x0 << std::endl;
std::cout << ts_x1 << std::endl;
}
実行結果
0 1 2 3
10 11 12 13
20 21 22 23
30 31 32 33
[ CPUIntType{4,4} ]
0 1 2 3
10 11 12 13
20 21 22 23
30 31 32 33
[ CPUIntType{4,4} ]
0 1
10 11
20 21
30 31
[ CPUIntType{4,2} ]
おお出た出た。念のためメソッドをコールしたインスタンス側もチェックしたが、見たところメンバ変数には影響しないようだ。さくっと作ったと言いたいところだけど、実はintの配列をTensor型にするのにしばし悩んだ。それはまたあとで。
で冒頭のコードは、
def sample(cxcy):
return torch.cat([cxcy[:, :2] - (cxcy[:, 2:] / 2), # x_min, y_min
cxcy[:, :2] + (cxcy[:, 2:] / 2)], 1) # x_max, y_max
こうなった↓。for文で書いた方が字数は少ないかも。usingを使っていればすっきりすると思う。
torch::Tensor sample(torch::Tensor cxcy)
{
//cxcy[:, : 2]
torch::Tensor cxcy0002 = cxcy.index({
torch::indexing::Slice(torch::indexing::None,torch::indexing::None),
torch::indexing::Slice(torch::indexing::None,2) });
//cxcy[:, 2: ]
torch::Tensor cxcy0020 = cxcy.index({
torch::indexing::Slice(torch::indexing::None,torch::indexing::None),
torch::indexing::Slice(2, torch::indexing::None) }) / 2;
torch::Tensor _ret = torch::cat({cxcy0002-cxcy0020, cxcy0002+cxcy0020}, 1);
return _ret;
}
分かりやすさからは程遠いがコンパイルは通った。これで合っているのかなあ。
参考リンク