0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonで〇×ゲームのAIを一から作成する その205 ndarray の axis と tuple による要素の指定(indexing)

Posted at

目次と前回の記事

Python のバージョンとこれまでに作成したモジュール

本記事のプログラムは Python のバージョン 3.13 で実行しています。また、numpy のバージョンは 2.3.5 です。

以下のリンクから、これまでに作成したモジュールを見ることができます。本文で説明しますが、下記の ai.py は前回のファイルから修正を行っています

リンク 説明
marubatsu.py Marubatsu、Marubatsu_GUI クラスの定義
ai.py AI に関する関数
mbtest.py テストに関する関数
util.py ユーティリティ関数の定義
tree.py ゲーム木に関する Node、Mbtree クラスなどの定義
gui.py GUI に関する処理を行う基底クラスとなる GUI クラスの定義

AI の一覧とこれまでに作成したデータファイルについては、下記の記事を参照して下さい。

ndarray の axis

前回の記事では 3 次元の np.bool の ndarray でゲーム盤を表現する NpBoolBoard クラスの作成を開始しました。今回の記事ではその続きを行うために必要な知識として ndarray の axis などについて最初に説明します。

数学の 2 次元の座標空間を図示 する際に、下図のように x 座標y 座標基準を表す x 軸と y 軸 を矢印と目盛りのついた直線で表記するのが一般的です。

n 次元ndarray でも同様に、n 個のインデックス(添字)の 基準となる直線を表記 することがあり、その直線のことを axis(軸) と呼びます。

グラフを描画する matplotlib には Axes という x 軸と y 軸による複数の軸で座標を管理するオブジェクトがありますが、この axes は axis の複数形です。

1 ~ 3 次元の ndarray の axis

ndarray ではそれぞれの インデックスに対応する軸先頭のインデックスから順axis 0axis 1axis 2、・・・のように 0 から始まる整数 で区別して表記します。

今回の記事では 1 ~ 3 次元ndarray と axis の関係 について説明します。なお、4 次元以上 の ndarray は図で表現するのが難しい点と、NpBoolBoard では利用しないので説明を省略しますが、考え方は 1 ~ 3 次元の ndarray と変わりません

1 次元の ndarray の Axis

下記は 5 つの要素 を持つ (5, ) の形状(shape)の 1 次元の ndarray を作成して print で表示 するプログラムです。なお、以前の記事で説明したように、n 次元の ndarray の形状(Axis 0 のインデックスの数, Axis 1 のインデックスの数, ・・・, Axis n-1 のインデックスの数) という n 個の要素を持つ tuple で表現します。

import numpy as np

nd1d = np.array([1, 2, 3, 4, 5])
print(nd1d)

実行結果

[1 2 3 4 5]

下図はこの ndarray とその axis を表す図です。正方形が ndarray の要素 を、矢印が axis 0 を、矢印の上の数値 が axis 0 の インデックスの番号 を表します。

1 次元の ndarray を図示する際に、上図のように axis 0横方向 で、インデックスの番号を 左から右に増えていく ように表記するのが 一般的 だと思いますが、ndarray を下図のように 縦方向 や、右から左に増えていく ように図示しても図が表す ndarray の内容は変わりません。状況に応じて最もわかりやすいと思う方法で図示すると良いでしょう1。本記事では 1 次元の ndarray を上図のように表記することにします。

なお、printndarray を表示 した際に 左から右表示する理由 は、print文字列左から右に表示するから です。

2 次元の ndarray の Axis

下記は 〇× ゲームのゲーム盤と同じ (3, 3) の形状2 次元の ndarray を作成して print で表示するプログラムです。

nd2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(nd2d)

実行結果

[[1 2 3]
 [4 5 6]
 [7 8 9]]

下図は print での表示と 同じ方向 でこの ndarray とその axis を表す図です。

print2 次元の ndarray を表示 すると上図のように axis 0 を上から下にaxis 1 を左から右 に増えるように表示するので、2 次元の ndarray を図示 する際は 上図のように図示するのが一般的 だと思いますが、1 次元の ndarray の場合と同様に、下図のように axis 0 を横 に、axis 1 を縦 にしても 同じ ndarray を図示 したことになります。図示はしませんが axis 0 を下から上のように向きを変えても同様です。

上図の場合は 数学の 2 次元の座標(x, y) のように表記 するのと同様に axis 0 と axis 1x 座標、y 座標の順2 になっているので、数学の座標系に慣れている 方にとっては わかりやすい のではないかと思います。実際に ListBoard や NpBoolBoard クラスなどでは 数学の座標にならって マスの 座標を (x, y) という tuple で表現し、axis 0 を x 軸 に、axis 1 を y 軸 に割り当て、print でゲーム盤を表示する際に呼び出される __str__ メソッドでは 上図のようにゲーム盤を表示 しました。

ただし、今回の記事では以後は 2 次元の ndarray を一般的な図示の方法にあわせて axis 0 を左から右にaxis 1 を上から下に表記 することにします。

3 次元の ndarray の Axis

下記は NpBoolBoard のゲーム盤と同じ (2, 3, 3) の形状3 次元の ndarray を作成して print で表示するプログラムです。

nd3d = np.array([
    [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
    [[10, 11, 12], [13, 14, 15], [16, 17, 18]]
])
print(nd3d)

実行結果

[[[ 1  2  3]
  [ 4  5  6]
  [ 7  8  9]]

 [[10 11 12]
  [13 14 15]
  [16 17 18]]]

下図は print で表示する文字列 とその axis を表す図 です。print で表示される文字列は 左から右に文字列を表示する行縦に並べて表示 するという 2 次元で表示 するため、3 次元の ndarray立体的に表示することはできません。そのため、下図のように axis 0 に関しては nd3d[0] が表す 2 次元の ndarraynd3d[1] が表す 2 次元の ndarrayの順 でそれぞれの内容を 縦に並べて表示 します。

下図は この 3 次元の ndarray とその axis を立体的に表す図 です。手前 に表示されているのが nd3d[0] で、その後ろに表示 されているのが nd3d[1] です。

それぞれの axis の方向は以下のようになります。

axis 方向
0 手前から奥
1 上から下の垂直方向
2 左から右の水平方向

図は省略しますが、axis の方向や向きを変えても同じ ndarray を図示することができます。

tuple による ndarray の要素の指定

n 次元の ndarray の要素は 2 種類の方法で指定 することができますが、それぞれ 行われる処理が異なる 点に注意が必要です。実際に筆者は今回の記事を記述するまで、この後で説明する 2 種類の方法が同じ処理を行うと勘違いしていました。

余談ですが、要素を指定することを英語では indexing と呼びます。参考までに indexing についての numpy の公式のリンクを下記に示します。

n 個の [] で指定する方法

n 次元の ndarray に対して n 個の [] の中に axis 0 から順番に対応する インデックスを表す整数を記述 することで、n 次元の list と同様の方法 で ndarray の 特定の 1 つの要素を指定 することができます。下記は 2 次元の ndarray と list に対して 同じ方法で特定の要素を表示 するプログラムです。なお、l2dl は list の頭文字です。

l2d = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(nd2d[1][2])
print(l2d[1][2])

実行結果

6
6

1 つの [] の中に tuple を記述して指定する方法

ndarray の場合は、下記のプログラムのように 1 つの [] の中n 個の axis に対応する インデックスの値 を要素とする tuple を記述 することでも 特定の要素を指定 することができます。実行結果が同じであることから上記と同じ要素が表示されることが確認できます。

print(nd2d[1, 2])

実行結果

6

以前の記事で説明したように、上記のように tuple で記述したほうが処理速度が速くなる という性質の違いがありますが、それ以外にも 以前の記事で説明した スライス表記 でインデックスを表記 した場合に 大きな違いが生じます

例えば、下記のプログラムのように すべてのインデックスを表す : を記述 した場合は 実行結果に大きな違いが生じます

print(nd2d[:][1])
print(l2d[:][1])
print(nd2d[:, 1])

実行結果

[4 5 6]
[4, 5, 6]
[2 5 8]

nd2d[:]l2d[:] は、以前の記事で説明したように、nd2dl2d と同じ要素を持つデータが計算 されるので、下記のプログラムのように nd2d[:]nd2d[:][1]l2d[:]l2d[:][1]同じ値が計算 されます。nd2d[:, 1] の場合に行われる処理についてはこの後で詳しく説明します。

print(nd2d[1])
print(nd2d[:][1])
print(l2d[1])
print(l2d[:][1])

実行結果

[4 5 6]
[4 5 6]
[4, 5, 6]
[4, 5, 6]

n 次元の ndarray や list に対して [] を複数記述 して特定の要素を指定する場合は、最後の [] 以外にスライス表記を記述 すると 見た目と異なる計算が行われる 点に注意して下さい。

上記のような処理が行われることがあるため、2 次元以上の ndarray の要素を指定 する場合は、この後で説明する tuple を基本的に利用したほうが良い でしょう。なお、1 次元の ndarray の場合も (1, ) のように 1 つの要素を持つ tuple を記述してもかまいませんが、わざわざそのように記述するメリットはないので 整数を記述するのが一般的 だと思います。

tuple によって指定される要素

[] の中に記述した tuple によって指定される ndarray の要素 は下記の手順で決まります。

  • tuple の 各要素の値対応する axis のインデックスの 値または範囲 を表す
  • それぞれの axis に対して設定された 条件をすべて満たす要素 が対象となる

例えば、先程の下記のプログラムのように先程の 2 次元の ndarray に対して [1, 2] を記述して print で表示すると、実行結果のように 6 が表示 されます。

print(nd2d[1, 2])

実行結果

6

上記のプログラムでは、下図の赤字の axis 0 が 1axis 1 が 2 という 条件を満たす水色の要素が参照 されて 6 が表示されるという処理が行われます。今回の記事では以後の図でも 同様の色分けで図示 します。

下記のプログラムのように先程の 2 次元の ndarray に対して [1, :] を記述して print で表示すると、実行結果のように [4 5 6] という 1 次元の ndarray が表示 されます。

print(nd2d[1, :])

実行結果

[4 5 6]

上記のプログラムでは、axis 1すべてのインデックスの範囲 を表す : というスライス表記 を記述しているので、下図の赤字の axis 0 が 1axis 1 のすべてのインデックス という 条件を満たす 下図の 水色の要素 を表す [4, 5, 6] という 1 次元の ndarray が表示 されるという処理が行われます。

[] 内に記述する tuple の要素の省略

n 次元の ndarray[] 内に n 未満の要素 を持つ tuple を記述した場合は n 個の要素を持つ tuple末尾の : の要素が省略 されたものとして計算が行われます。

例えば、3 次元の ndarray[] 内に下記の記述を行った場合は同じ計算が行われます。

  • (0, ) という 1 つの要素を持つ tuple
  • (0, :) という 2 つの要素を持つ tuple
  • (0, :, :) という 3 つの要素を持つ tuple

このことは下記のプログラムの実行結果に、すべて同じ内容が表示される ことから確認できます。なお、1 つの要素を持つ tuple は () を省略できないので (0, ) を記述しました。

[] 内に (1, :, :) を記述したの処理についてはこの後で図を用いて詳しく説明します。

print(nd3d[(0, )])
print(nd3d[0, :])
print(nd3d[0, :, :])

実行結果

[[1 2 3]
 [4 5 6]
 [7 8 9]]
[[1 2 3]
 [4 5 6]
 [7 8 9]]
[[1 2 3]
 [4 5 6]
 [7 8 9]]

下記は上記の 3 つの 処理速度を計測 するプログラムです。実行結果から 同じ処理を行う場合[] の中の tuple の要素の数が少ないほうが処理速度が速い ことが確認できます。

print(nd3d[(0, )])
print(nd3d[0, :])
print(nd3d[0, :, :])

実行結果

119 ns ± 1.37 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
180 ns ± 4.22 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
260 ns ± 13.9 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

tuple の要素に 1 つだけ整数を記述した場合の処理

[] 内に記述する tuple の要素の 1 つだけに整数を記述 し、残りの要素に : を記述 した場合は、n 個ある axis の 1 つだけ特定の値で固定される ため、新しく計算される ndarray残りの n - 1 個の axis が存在する n - 1 次元の ndarray になります。

具体例として 1 ~ 3 次元のそれぞれの ndarray の場合について説明します。

1 次元の ndarray の場合

1 次元の ndarray に対して下記のプログラムのように 特定の整数インデックスに記述 すると、実行結果のように 1 次元の ndarray の 対応する要素の値が計算 されます。

print(nd1d[1])

実行結果

2

計算された値 には axis が存在しなくなる ため 0 次元の ndarray であるとみなす ことができますが、ndarray0 次元の ndarray を表現することはできない ので、その代わりに 元の ndarray と同じデータ型 のデータが計算されます。下記はそのことを確認するプログラムで、データのデータ型を計算する する組み込み関数 type の値 と、ndarray の dtype 属性 から どちらも np.int64 のデータ であることが確認できます。

print(type(nd1d[1]))
print(nd1d.dtype)

実行結果

<class 'numpy.int64'>
int64

この性質は 2 次元以上の ndarray の場合も同様 で、以下のような計算が行われます。

ndarray の [] にインデックスを記述した結果、特定の 1 つの要素が計算 された場合は 元の ndarray と同じデータ型のデータが計算 される。そうでない場合は、インデックスに対応する複数の要素を持つ 新しい ndarray が計算 される。

list や ndarray のような 複数の要素から構成されるデータ の事を ベクトル型、数値型や np.int64 のような 1 つのデータ の事を スカラー型 と呼びます。

参考までに Wikipedia のスカラーとベクトルの記事のリンクを下記に示します。

2 次元の ndarray の場合

2 次元の ndarray の場合は、下記のプログラムのように 1 つの tuple の要素のみに整数を記述し、残りの要素に : を記述すると 次元が 1 つ減った 1 次元の ndarray が計算されます。

なお、下記の nd2d[1, :] は先ほど説明したように nd2d[(1, )] のように 1 つの要素を持つ tuple で記述でき、その方が処理速度が高速になりますが、残りの要素に : を指定 していることが 明確になるよう に下記では nd2d[1, :] のように記述しました。この後の 3 次元の ndarray の場合も同様です。

print(nd2d[1, :])
print(nd2d[:, 1])

実行結果

[4 5 6]
[2 5 8]

下記は、nd2d[1, :] を記述した場合に 参照される要素 と、計算された 1 次元の ndarray を表す図です。

 

下記は 計算前と計算後 の ndarray の axis の対応表 です。2 次元の ndarrayaxis 0 の値が決まった ので、作成された 1 次元の ndarray には 対応する axis はありません。また、残りの 2 次元の ndarray の axis 1 は、作成された 1 次元の ndarrayaxis 0 に対応 します。

計算前 計算後
axis 0 なし
axis 1 axis 0

下記は、nd2d[:, 1] を記述した場合に 参照される要素 と、計算された 1 次元の ndarray を表す図です。なお、1 次元の ndarray を縦に図示しても構いませんが、一般的な 1 次元の ndarray の図示の方法に合わせて横に図示しました。

 !

下記は 計算前と計算後 の ndarray の axis の対応表 です。2 次元の ndarrayaxis 1 の値が決まった ので、作成された 1 次元の ndarray には 対応する axis はありません。また、残りの 2 次元の ndarray の axis 0 は、作成された 1 次元の ndarrayaxis 0 に対応 します。

計算前 計算後
axis 0 axis 0
axis 1 なし

3 次元の ndarray の場合

3 次元の ndarray の場合も 考え方は同様 で、1 つの axis に対して値を指定し、残りの axis に対して : を指定した場合は、3 - 1 = 2 次元の ndarray が計算 されます。下記のプログラムは axis 0 に 0 を指定した場合です。

print(nd3d[0, :, :])

実行結果

[[1 2 3]
 [4 5 6]
 [7 8 9]]

下記は、参照される要素計算された 2 次元の ndarray を表す図です。

 

下記は 計算前と計算後 の ndarray の axis の対応表 です。3 次元の ndarrayaxis 0 の値が決まった ので、作成された 2 次元の ndarray には 対応する axis はありません。また、残りの 3 次元の ndarray の axis 1 と axis 2 は、作成された 2 次元の ndarrayaxis 0 と axis 1 に対応 します。

計算前 計算後
axis 0 なし
axis 1 axis 0
axis 2 axis 1

下記のプログラムは axis 1 に 0 を指定した場合です。

print(nd3d[:, 0, :])

実行結果

[[ 1  2  3]
 [10 11 12]]

下記は、参照される要素計算された 2 次元の ndarray を表す図です。2 次元の ndarray の axes の向きは一般的な表記に合わせて axis 0 を縦に、axis 1 を横にしましたので、左の水色の部分と右で 要素の並びの向きが変わっています が、内容は同じ です。

 

下記は 計算前と計算後 の ndarray の axis の対応表 です。3 次元の ndarrayaxis 1 の値が決まった ので、作成された 2 次元の ndarray には 対応する axis はありません。また、残りの 3 次元の ndarray の axis 0 と axis 2 は、作成された 2 次元の ndarrayaxis 0 と axis 1 に対応 します。

計算前 計算後
axis 0 axis 0
axis 1 なし
axis 2 axis 1

まとめ

上記から以下の事がわかります。

  • n 次元の ndarray に対して tuple の要素の 1 つだけに整数を記述 し、残りの要素に : を記述した場合は n - 1 次元の ndarray が作成 される
  • ただし、n = 1 の場合は 元の ndarray と同じデータ型 のデータが作成される
  • 元の ndarray新しく作成される ndarrayaxis は、整数を記述した tuple の要素に 対応する axis の番号を $x$ とすると以下のように対応する
    • $k < x$ の場合は元の axis $k$ と作成された axis $k$ が対応する
    • $k ≧ x$ の場合は元の axis $k - 1$ と作成された axis $k$ が対応する

tuple の要素に 2 つ以上の整数を記述した場合の処理

[] 内に記述する tuple の 2 つ以上の k 個の要素整数を記述 し、残りの要素に : を記述 した場合は、n 個ある axis の k 個が特定の値で固定される ため、新しく計算される ndarray残りの n - k 個の axis が存在する n - k 次元の ndarray になります。

下記のプログラムは 3 次元の ndarray の axis 0 と axis 1 の 2 つの axis に 0 を指定 した場合で、実行結果から 3 - 2 = 1 次元の ndarray が計算 されたことが確認できます。

print(nd3d[0, 0, :])

実行結果

[1 2 3]

下記は、参照される要素計算された 1 次元の ndarray を表す図です。

 

下記は 計算前と計算後 の ndarray の axis の対応表 です。3 次元の ndarrayaxis 0 と axis 1 の値が決まった ので、作成された 1 次元の ndarray には 対応する axis はありません。残りの 3 次元の ndarray の axis 2 は、作成された 1 次元の ndarrayaxis 0 に対応 します。

計算前 計算後
axis 0 なし
axis 1 なし
axis 2 axis 0

上記から、tuple の複数の要素に整数を記述 した場合は、計算される ndarray の次元 と計算前と計算後の ndarray の axis の対応 は以下のようになることがわかります。

  • 整数を記述した要素に 対応する axis が削除 され、整数を記述した要素の数だけ ndarray の次元が減る
  • 削除された分だけ axis の 番号がずれる

axis の対応について頭の中だけで考えると混乱してしまう場合は、図を書いて確認することをお勧めします。

tuple の要素に : 以外のスライス表記を記述した場合

tuple の要素に : 以外のスライス表記3 を記述すると、対応する axis のスライス表記で 指定したインデックスのみ が対象となります。その結果 ndarray の 次元は減りません が、対応する axis の インデックスの範囲 がスライス表記で指定した 数に減ります

下記のプログラムは 2 次元の ndarrayaxis 00 と 12 つ のインデックスを表す 0:2 を指定 した場合で、実行結果から元の (3, 3) の形状に対して axis 0 のインデックスの数が 2 に減った (2, 3) の形状の 2 次元の ndarray が計算 されたことが確認できます。

print(nd2d.shape)
print(nd2d[0:2, :])
print(nd2d[0:2, :].shape)

実行結果

(3, 3)
[[1 2 3]
 [4 5 6]]
(2, 3)

下記は、参照される要素計算された 2 次元の ndarray を表す図です。

 

下記は 計算前と計算後 の ndarray の axis 0 のインデックスの対応表 です。:2 には 2 は含まれない ので計算前の 2 に対応するインデックス は計算後には ありません

計算前 計算後
0 0
1 1
2 なし

下記のプログラムは 3 次元の ndarrayaxis 10 と 22 つ のインデックスを表す ::2 を指定 した場合で、実行結果から元の (2, 3, 3) の形状に対して axis 1 のインデックスの数が 2 に減った (2, 2, 3) の形状の 3 次元の ndarray が計算されたことが確認できます。

print(nd3d.shape)
print(nd3d[:, ::2, :])
print(nd3d[:, ::2, :].shape)

実行結果

(2, 3, 3)
[[[ 1  2  3]
  [ 7  8  9]]

 [[10 11 12]
  [16 17 18]]]
(2, 2, 3)

下記は、参照される要素計算された 2 次元の ndarray を表す図です。

 

下記は 計算前と計算後 の ndarray の axis 1 のインデックスの対応表 です。::2 には 1 は含まれない ので計算前の 1 に対応するインデックス は計算後には ありません。また、axis 1 の元の 2 に対応するインデックスが 1 になります。

計算前 計算後
0 0
1 なし
2 1

インデックスの一部が削除 され、その分だけ インデックスが ずれる という性質は、先程の tuple の要素に整数を記述した場合の axis が削除されてずれる 点と 似ています

indexing に関する公式のドキュメントのリンク

今回の記事で紹介した indexing に関する numpy のドキュメントのリンクを示します。

一つの要素の indexing の詳細については下記を参照して下さい。

スライス表記による indexing の詳細については下記を参照して下さい。

今回の記事で紹介しなかった ...np.newaxis を利用する方法については下記を参照して下さい。

tuple 以外を利用したより高度な indexing の方法については下記を参照して下さい。

今回の記事のまとめ

今回の記事では ndarray の axis と tuple による要素の指定(indexing)について説明しました。次回の記事では今回の記事の内容を元に NpBoolBoard クラスの定義の続きを行います。

本記事で入力したプログラム

リンク 説明
marubatsu.ipynb 本記事で入力して実行した JupyterLab のファイル

次回の記事

近日公開予定です

  1. 一般的にはそのように表記することはほとんどないと思いますが、axis を斜め方向にしたり、円のような曲線の形状にしてもかまいません。この後で図示しますが、本記事では 3 次元の ndarray の axis 0 を斜めに表記します

  2. ただし、y 座標は数学の座標と異なり上下が逆になります

  3. : と同じ意味を持つ 0:::1:: などを除きます

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?