MLで使うプリミティブな色々
Google Colab
Pythonのサンドボックスな実行環境。
MLでPythonを扱う上でデファクト。
- MLはWebと比較して実行にマシンリソース(GPU)が必要になるので、オンラインの実行環境は非常に需要がある。無料でも制限付きでGPUが使えるのがありがたい。
- Jupyter NotebookというOSSのコード実行環境を採用している。MLのコードはWebと比較して重い計算が出てきやすい。コードを少し変えたからと言ってファイルという単位で実行し直していると非常に余分な時間がかかるので、コードの行単位で分割することで高効率な作業を実現できる。
- サンドボックスなので(無料の場合)メモリやファイルは時間制限でガシガシ消えていく。永続化する場合はGoogleドライブに入れてColab上にマウントする。
from google.colab import drive
drive.mount('/content/drive')
%cd ./drive/MyDrive
呪文のように全ノートブックの先頭でとりあえずこれをやっておくと楽。
- 変数を保存したい場合はPython標準ライブラリのpickleを使うと良い。プレーンなオブジェクトじゃなくてもバイナリとしてファイルに保存できる。
NumPy
多次元配列(multidimensional array)を扱うプリミティブなライブラリ。
ndarray
MLでは多次元配列を高頻度で使用するので、各種ライブラリで様々な関数の返り値が numpy の ndarray を使用している。
NumPyはndarrayの様々なCRUDを扱うライブラリ。
Chainerの入門記事が分かりやすい。
このページの内容に沿って見ていく。
ベクトル、行列、テンソルなど、様々なものをndarrayで使用するため、そのndarrayの形をサクッと確認したいことは多い。
ndim
に次元数が、shape
プロパティに配列の形が、size
に要素数の合計が入っている。
result = [
# 次元数1
np.array([]),
np.array([1,2,3]),
# 次元数2
np.array([[1,2,3], [4,5,6], [7,8,9]]),
# np.array([[1,2,3], [1,2], [1,2,3,4,5]])) 個数が違うものは deprecated
# 次元数3
np.array([[[255,255,255],[100,100,100],[0,0,0]], [[10,10,10],[30,30,30],[60,60,60]], [[1,1,1],[50,50,50],[200,200,200]]])
]
for index, a in enumerate(result):
print(f"----{index}----")
print(a.ndim)
print(a.shape)
print(a.size)
出力:
----0----
1
(0,)
0
----1----
1
(3,)
3
----2----
2
(3, 3)
9
----3----
3
(3, 3, 3)
27
ndarrayは個数の違う多次元配列には対応していない。個数が違うと以下のようなWarningが出る。
VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated.
画像の shape の例
例えば 1200x630の画像をndarrayとして読み込んで shape を取得すると
(1200, 630, 3)
のようになる。
これは1200 x 630のピクセル数で、ピクセル一つ一つの情報として RGB 値が入っていることを示している。
仮に透過情報も扱う画像の場合はRGBAになるので (1200, 630, 4)
となる。
ndarrayの様々な作り方
result = [
np.zeros((3, 3)), # shape を指定して0で埋める
np.ones((3, 3)), # shape を指定して1で埋める
np.empty((6,3)), # 形だけ決まっている空の配列を作る。zerosやonesよりも数倍速い処理になる cf. https://deepage.net/features/numpy-empty.html
np.full((3, 3), 999), # shape を指定して999(第二引数)で埋める
np.eye(5), # 指定されたサイズの単位行列を返す
np.random.random((4, 5)), # shape を指定して0から1の間の乱数で埋める
np.arange(3, 10, 2), # 3から2ずつ増加する配列を作り、10未満の最大数で止める
]
for index, a in enumerate(result):
print(f"----{index}----")
print(a)
print(a.ndim)
print(a.shape)
print(a.size)
出力:
----0----
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
2
(3, 3)
9
----1----
[[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]]
2
(3, 3)
9
----2----
[[6.9e-323 7.9e-323 4.9e-323]
[4.4e-323 5.4e-323 2.5e-323]
[3.5e-323 4.4e-323 1.5e-323]
[7.9e-323 4.0e-323 6.4e-323]
[5.4e-323 1.5e-323 4.0e-323]
[4.4e-323 4.9e-324 3.0e-323]]
2
(6, 3)
18
----3----
[[999 999 999]
[999 999 999]
[999 999 999]]
2
(3, 3)
9
----4----
[[1. 0. 0. 0. 0.]
[0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0.]
[0. 0. 0. 1. 0.]
[0. 0. 0. 0. 1.]]
2
(5, 5)
25
----5----
[[0.13317067 0.97725122 0.26874046 0.770574 0.2408572 ]
[0.88666322 0.64015112 0.52966009 0.00427423 0.14917473]
[0.60448598 0.75563329 0.65254733 0.26456363 0.87173293]
[0.41818714 0.73037552 0.41211239 0.52775178 0.80706315]]
2
(4, 5)
20
----6----
[3 5 7 9]
1
(4,)
4
要素の取得の仕方
result = [
# 次元数1
np.array([1,2,3,4,5,6,7,8]),
# 次元数2
np.array([[1,2,3], [4,5,6], [7,8,9]]),
# np.array([[1,2,3], [1,2], [1,2,3,4,5]])) 個数が違うものは deprecated
# 10x10 の単位行列
np.eye(10)
]
array1 = result[0]
array2 = result[1]
array3 = result[2]
print(array1[3]) # 添字。4番目の値を返す
print(array1[3:6]) # スライス。4番目から6番目の値を返す
print(array2[1,2]) # 複数次元に跨る添字。2行3列目を返す
print(array3[2:7,3:9]) # 複数次元に跨るスライス。3行〜7行の中の、4列から9列の値でできたndarrayを返す
print(array3[[6, 2, 8], [3, 1, 9]]) # 整数配列による指定。1つ目の要素は行の指定。2つ目の要素は列の指定。二つの配列は同じ要素数を持つ必要がある。値をピックした配列を返す
出力:
4
[4 5 6]
6
[[0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0.]
[0. 0. 0. 1. 0. 0.]]
[0. 0. 0.]
上記のどの方法でも、[]
の中の要素は順番に次元に対応する。
行列(2次元配列)の場合はarray[行の指定, 列の指定]
になるし、3次元配列の場合はarray[1次元目の指定, 2次元目の指定, 3次元目の指定]
となる。
次元数より少ない要素数での指定は可能だが、次元数を超える要素数での指定はエラーとなる。
整数配列による指定では、各配列は同じ要素数を持つ必要がある。
そうでない場合以下のようなエラーが出る。
print(array4[[6, 2, 8], [3, 1, 9, 2]])
IndexError: shape mismatch: indexing arrays could not be broadcast together with shapes (3,) (4,)
変更
# 10x10 の単位行列を用意
result = [np.eye(10) for _ in range(6)]
a1, a2, a3, a4, a5, a6 = result
# 添字で範囲を指定して代入
a1[0] = 2 # 1行目を2で埋める
a2[0, 5] = 2 # 0-5に2を代入
# a2[0, 5, 3] = 2 # IndexError: too many indices for array: array is 2-dimensional, but 3 were indexed
# スライス形式で範囲を指定して一括代入
a3[2:8, 3:7] = 2 # 範囲全てを2で埋める
# a3[2:8, 3:7] = 2, 4 # ValueError: could not broadcast input array from shape (2,) into shape (6,4)
a4[2:8, 3:7] = 2, 4, 7, 5 # 範囲全ての行に2,4,7,5を代入
# 整数配列で要素を指定して一括代入
a5[[6, 2, 8], [3, 1, 9]] = 3
# a5[[6, 2, 8], [3, 1, 9]] = 3, 7 # ValueError: shape mismatch: value array of shape (2,) could not be broadcast to indexing result of shape (3,)
a6[[6, 2, 8], [3, 1, 9]] = 3, 6, 8 # 6-3に3、2-1に6、8-9に8を入れる
for index, a in enumerate(result):
print(f"----a{index+1}----")
print(a)
出力:
----a1----
[[2. 2. 2. 2. 2. 2. 2. 2. 2. 2.]
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]
----a2----
[[1. 0. 0. 0. 0. 2. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]
----a3----
[[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 2. 2. 2. 2. 0. 0. 0.]
[0. 0. 0. 2. 2. 2. 2. 0. 0. 0.]
[0. 0. 0. 2. 2. 2. 2. 0. 0. 0.]
[0. 0. 0. 2. 2. 2. 2. 0. 0. 0.]
[0. 0. 0. 2. 2. 2. 2. 0. 0. 0.]
[0. 0. 0. 2. 2. 2. 2. 1. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]
----a4----
[[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 2. 4. 7. 5. 0. 0. 0.]
[0. 0. 0. 2. 4. 7. 5. 0. 0. 0.]
[0. 0. 0. 2. 4. 7. 5. 0. 0. 0.]
[0. 0. 0. 2. 4. 7. 5. 0. 0. 0.]
[0. 0. 0. 2. 4. 7. 5. 0. 0. 0.]
[0. 0. 0. 2. 4. 7. 5. 1. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]
----a5----
[[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 3. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
[0. 0. 0. 3. 0. 0. 1. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1. 3.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]
----a6----
[[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 6. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
[0. 0. 0. 3. 0. 0. 1. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1. 8.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]
ndarrayの計算
ndarray同士は標準の四則演算の演算子で計算することができるが、これによって行うことのできる計算は 行列の計算ではなく、 返り値は単純に同じ要素同士を計算した結果を新しいndarrayに格納したものになる。
(この計算方法をアダマール積というらしい。)
eye = np.eye(3)
ten = np.full((3, 3), 10)
for index, a in enumerate([
eye + ten,
eye - ten,
eye * ten,
eye / ten,
eye + 5, # ブロードキャストにより全要素で計算される
eye - 5,
eye * 5,
eye / 3,
eye // 3,
eye ** 2,
(eye + 3) % 4, # % の結果を見るために3を足したものを使用
]):
print(f"----{index+1}----")
print(a)
出力:
----1----
[[11. 10. 10.]
[10. 11. 10.]
[10. 10. 11.]]
----2----
[[ -9. -10. -10.]
[-10. -9. -10.]
[-10. -10. -9.]]
----3----
[[10. 0. 0.]
[ 0. 10. 0.]
[ 0. 0. 10.]]
----4----
[[0.1 0. 0. ]
[0. 0.1 0. ]
[0. 0. 0.1]]
----5----
[[6. 5. 5.]
[5. 6. 5.]
[5. 5. 6.]]
----6----
[[-4. -5. -5.]
[-5. -4. -5.]
[-5. -5. -4.]]
----7----
[[5. 0. 0.]
[0. 5. 0.]
[0. 0. 5.]]
----8----
[[0.33333333 0. 0. ]
[0. 0.33333333 0. ]
[0. 0. 0.33333333]]
----9----
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
----10----
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
----11----
[[0. 3. 3.]
[3. 0. 3.]
[3. 3. 0.]]
行列積
通常の行列積は、np.dot()
もしくはndarrayのdot()
を使う。
a = np.eye(3)
b = np.full((3, 3), 4)
c = np.random.randint(0, 10, (3, 3))
print("----a, b----")
print(np.dot(a, b))
print(a.dot(b))
print("----b, c----")
print(np.dot(b, c))
print(b.dot(c))
print("----c, b----")
print(np.dot(c, b))
print(c.dot(b))
出力:
----a, b----
[[4. 4. 4.]
[4. 4. 4.]
[4. 4. 4.]]
[[4. 4. 4.]
[4. 4. 4.]
[4. 4. 4.]]
----b, c----
[[44 48 28]
[44 48 28]
[44 48 28]]
[[44 48 28]
[44 48 28]
[44 48 28]]
----c, b----
[[32 32 32]
[32 32 32]
[56 56 56]]
[[32 32 32]
[32 32 32]
[56 56 56]]
その他の関数については以下を参照。
ブロードキャスト
本来行列の計算は形が同じもの同士でしか行えないが、NumPyでは計算するndarrayのうち形が小さい方を大きい方に合わせるように拡大する機能があり、これをブロードキャストと呼ぶ。
# https://tutorials.chainer.org/ja/08_Introduction_to_NumPy.html のブロードキャストのコードを参照
a = np.random.randint(0, 10, (2, 1, 3))
b = np.random.randint(0, 10, (3, 1))
print("----a----")
print(a)
print("----b----")
print(b)
c = a + b
print("----c----")
print(c)
print(c.shape)
出力:
----a----
[[[7 9 3]]
[[9 1 6]]]
----b----
[[7]
[2]
[0]]
----c----
[[[14 16 10]
[ 9 11 5]
[ 7 9 3]]
[[16 8 13]
[11 3 8]
[ 9 1 6]]]
(2, 3, 3)
詳細
ブロードキャストできるかどうかを判定するために、まずは比較する配列の次元数を合わせる。
今回はaが3次元配列でbが2次元配列なので、bを3次元配列にする。
その際、小さい方の配列を新しい配列でラップしていく。つまり、新しく追加される次元の要素数は常に1となる。
[[7], [2], [0]] # 次元数: 2
↓ 新しい配列でラップする
[[[7], [2], [0]]] # 次元数: 3
↓ 大きい方と同じ次元数になるまで繰り返す
[[[[7], [2], [0]]]] # 次元数: 4
↓
...
shape:
a: (2, 1, 3)
b: (3, 1)
^ 次元数が足りないので配列でラップする
↓
a: (2, 1, 3)
b: (1, 3, 1)
^ 要素数1の新しい次元が追加される
次に、次元ごとに要素数を比較し、以下のOR条件を満たす場合に、ブロードキャストが実行される。
- 要素数が同じである
- どちらかの要素数が1である
今回の例の場合、全ての次元でどちらかの要素数が1となっているので条件を満たす。
条件を満たす場合、ブロードキャストにより以下の処理を行い要素数を揃えることで計算を正常に完了させる。
- 要素数が同じ場合:特別な処理は不要。
- どちらかの要素数が1の場合:要素数が1である方の値を、他方の要素数分コピーして数を揃える。
従って、ブロードキャストを経た計算結果は、各次元の要素数が大きい方に揃う。
a: (2, 1, 3)
b: (1, 3, 1)
↓
c: (2, 3, 3)
別の例
# 次元数が2つ以上異なっても、上記の条件を満たせばブロードキャストにより計算が完了する
a = np.random.randint(0, 10, (2, 4, 2, 1, 3))
b = np.random.randint(0, 10, (3, 3))
c = a + b
print(c.shape) # (2, 4, 2, 3, 3)
# 後ろから2つ目の次元が 2 と 3 で条件を満たさないのでブロードキャストができず、エラーになる。
a = np.random.randint(0, 10, (4, 2, 2, 3))
b = np.random.randint(0, 10, (3, 3))
c = a + b # ValueError: operands could not be broadcast together with shapes (4,2,2,3) (3,3)
print(c.shape)
NumPy のブロードキャストは慣れるまで直感に反するように感じる場合があるかもしれません。 しかし、使いこなすと同じ計算が Python のループを使って行うよりも高速に行えるため、ブロードキャストを理解することは非常に重要です。
by https://tutorials.chainer.org/ja/08_Introduction_to_NumPy.html
基本的な統計量
x = np.random.randint(0, 10, (8, 10))
for index, a in enumerate([
x.mean(), # 平均値
x.mean(axis=1), # ゼロベースインデックス。2次元目の値で計算する
x.var(), # 分散
x.std(), # 標準偏差
x.max(), # 最大値
x.max(axis=1), # 2次元目の値で計算する
x.min(), # 最小値
]):
print(f"----{index+1}----")
print(a)
出力:
----1----
4.8625
----2----
[5.8 5.2 4.7 5.6 4.1 4.4 3.6 5.5]
----3----
8.31859375
----4----
2.884197245335346
----5----
9
----6----
[9 9 9 8 9 9 7 9]
----7----
0
ndarray をファイルとして永続化する
-
np.save()
とnp.load()
- pickle、npz、npyのいずれかの形式で保存できる。
- 上記でpickle使うと良いと書いたけど、保存対象がndarrayならこの関数を使えば良い。
-
np.savetxt()
とnp.loadtxt()
- .dat、.csv、.txtのいずれかの形式で保存できる。
- ただし対象は二次元配列まで。三次元以上は保存できない。
reshape
多次元配列を内部的に一次元配列に戻し、そこから指定されたshapeの配列に変更する。
array.reshape(2,6)
[[1,2,3,4], [5,6,7,8], [9,10,11,12]]
↓
[1,2,3,4,5,6,7,8,9,10,11,12]
↓
[[1,2,3,4,5,6], [7,8,9,10,11,12]]
比較
mask1 = np.array([0,0,0,0,1,1,1,1,1,2,2,2,1,1,1,1,0,0,0,1,1,1,2,2,2]).reshape(5,5)
mask2 = np.array(mask1)
mask3 = np.array([0,0,0,0,1,1,2,1,1,2,2,2,1,1,0,1,0,0,0,1,1,1,1,2,2]).reshape(5,5) # 一部異なる配列
# True / Falseになるところを(見づらいので) int に変換して表示
for index, a in enumerate([
mask1,
np.array(mask1 == mask2, np.int8),
np.array(mask1 == mask3, np.int8),
np.array(np.isclose(mask1, mask2), np.int8), # デフォルトでは rtol=1e-05, atol=1e-08
np.array(np.isclose(mask1, mask3, rtol=0, atol=1), np.int8), # 1までの差分は誤差とみなす
np.allclose(mask1, mask3, rtol=0, atol=1),
np.array(mask1 == 0, np.int8), # ブロードキャストされて全要素と比較される
np.array(mask1 <= 1, np.int8),
np.array(mask1 > 1, np.int8),
]):
print(f"----{index+1}----")
print(a)
出力:
----1----
[[0 0 0 0 1]
[1 1 1 1 2]
[2 2 1 1 1]
[1 0 0 0 1]
[1 1 2 2 2]]
----2----
[[1 1 1 1 1]
[1 1 1 1 1]
[1 1 1 1 1]
[1 1 1 1 1]
[1 1 1 1 1]]
----3----
[[1 1 1 1 1]
[1 0 1 1 1]
[1 1 1 1 0]
[1 1 1 1 1]
[1 1 0 1 1]]
----4----
[[1 1 1 1 1]
[1 1 1 1 1]
[1 1 1 1 1]
[1 1 1 1 1]
[1 1 1 1 1]]
----5----
[[1 1 1 1 1]
[1 1 1 1 1]
[1 1 1 1 1]
[1 1 1 1 1]
[1 1 1 1 1]]
----6----
True
----7----
[[1 1 1 1 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 1 1 1 0]
[0 0 0 0 0]]
----8----
[[1 1 1 1 1]
[1 1 1 1 0]
[0 0 1 1 1]
[1 1 1 1 1]
[1 1 0 0 0]]
----9----
[[0 0 0 0 0]
[0 0 0 0 1]
[1 1 0 0 0]
[0 0 0 0 0]
[0 0 1 1 1]]
np.array_equal
配列同士の比較でブロードキャストをしたくない場合はnp.array_equal
を使う。
a1 = np.array([1,2,3] * 3).reshape(3,3)
a2 = np.array(a1) # 同じ配列
a3 = np.array([1,2,3])
a4 = np.array([1,2,float('nan')] * 3).reshape(3,3) # NaN を含む
a5 = np.array(a4) # 上全く同じ
for index, a in enumerate([
a1,
np.array_equal(a1, a2), # 全く同じなので True
np.array_equal(a1, a3), # shape が異なるので False
np.array_equiv(a1, a3), # array_equivはshapeが違ってもブロードキャストして比較する。ブロードキャストすれば同じなので True
np.equal(a1, a2), # == での比較と同じ
a1 == a2,
a1 == a3, # ブロードキャストされるので全て True
a4 == a5, # NaN 同士の比較は常に False
np.isclose(a4, a5, equal_nan=True) # equal_nan=True にしておくと NaN同士の比較が True になる
]):
print(f"----{index+1}----")
print(a)
出力:
----1----
[[1 2 3]
[1 2 3]
[1 2 3]]
----2----
True
----3----
False
----4----
True
----5----
[[ True True True]
[ True True True]
[ True True True]]
----6----
[[ True True True]
[ True True True]
[ True True True]]
----7----
[[ True True True]
[ True True True]
[ True True True]]
----8----
[[ True True False]
[ True True False]
[ True True False]]
----9----
[[ True True True]
[ True True True]
[ True True True]]
コロンだけのスライス
スライスにおいて前後の数字を指定せずコロンだけを使う方法がある。
コロンだけを置くと、その次元における全ての要素をそのまま指定していることになる。
a1 = np.arange(20).reshape(2,2,5)
for index, a in enumerate([
a1,
a1[:], # a1 の第一階層の要素を全て指定。つまり全く同じ配列を返す
a1[:, 1], # 第一階層の要素を全て指定するが、第二階層では1を指定する。つまり第一階層の要素数は変わらないが、第二階層の次元が分解され、2番目の要素が選択される
a1[:, 1].shape,
a1[:, 1, :], # [:, 1] と同義。
a1[:, 1, :].shape,
a1[:, :, 2], # 第一階層と第二階層の全ての要素を指定するが、第三階層では2を指定する。第三階層の次元が分解され3番目の要素が選択される
a1[:, :, 2].shape
]):
print(f"----{index+1}----")
print(a)
出力:
----1----
[[[ 0 1 2 3 4]
[ 5 6 7 8 9]]
[[10 11 12 13 14]
[15 16 17 18 19]]]
----2----
[[[ 0 1 2 3 4]
[ 5 6 7 8 9]]
[[10 11 12 13 14]
[15 16 17 18 19]]]
----3----
[[ 5 6 7 8 9]
[15 16 17 18 19]]
----4----
(2, 5)
----5----
[[ 5 6 7 8 9]
[15 16 17 18 19]]
----6----
(2, 5)
----7----
[[ 2 7]
[12 17]]
----8----
(2, 2)
...
)
Ellipsis(...
はEllipsisという組み込みのシングルトンオブジェクトを参照するリテラル。
名前の通り省略を表す。
説明はこちら非常に分かりやすい:
ndarrayのスライスで使用すると、前後の指定を省略(=全てコロンだけの指定と同義=全て選択)することができる。
Ellipsisは一度のインデックスの指定において1個しか使えない。
a1 = np.arange(240).reshape(2,2,3,4,5)
for index, a in enumerate([
a1.shape,
a1[..., 3].shape, # 最初〜最後の階層1個手前までの指定が全て省略され、3は最後の階層の指定になる
a1[:, :, :, :, 3].shape, # 上と同義
a1[..., 2, 3].shape, # 最初〜最後の階層2個手前までの指定が全て省略され、2と3は最後の二つの階層の指定になる
a1[:, :, :, 2, 3].shape, # 上と同義
a1[1, ...].shape, # 第二階層以降の指定が全て省略される
a1[1].shape, # 上と同義
a1[1, 1, ...].shape, # 第三階層以降の指定が全て省略される
a1[1, 1].shape, # 上と同義
# a1[1, ..., 1, ..., 0].shape, # IndexError: an index can only have a single ellipsis ('...')
]):
print(f"----{index+1}----")
print(a)
出力:
----1----
(2, 2, 3, 4, 5)
----2----
(2, 2, 3, 4)
----3----
(2, 2, 3, 4)
----4----
(2, 2, 3)
----5----
(2, 2, 3)
----6----
(2, 3, 4, 5)
----7----
(2, 3, 4, 5)
----8----
(3, 4, 5)
----9----
(3, 4, 5)
np.newaxis
とnp.expand_dims()
とreshape
np.newaxisはNone
へのエイリアス。
ndarrayのインデックスの中でNoneを使用すると、そこに新しい要素数1の次元が追加される。
つまりそこに元々あった要素を空の配列でラップする。
また np.expand_dims()
やreshape
でも同じことができる。
参考:
a1 = np.arange(20).reshape(2,2,5)
for index, a in enumerate([
a1.shape,
a1[np.newaxis, ...].shape, # 第一階層に新しい次元を追加する
a1[np.newaxis].shape, # 上と同義(None以降の指定は省略できる)
np.array([a1]).shape, # 上と同義
a1[:, np.newaxis, ...].shape, # 第二階層に新しい次元を追加する(元々第二階層にあった要素を新しい配列でラップ)
np.expand_dims(a1, 1).shape, # 上と同義
a1[0, np.newaxis, ...].shape, # 先頭の次元を分解しつつ、新しい次元を追加する(元々第二階層にあった要素を新しい配列でラップ)
a1[..., np.newaxis].shape, # 最後の階層に新しい次元を追加する(元々最後の階層にあった値を新しい配列でラップ)
np.expand_dims(a1, -1).shape, # 上と同義
a1.reshape(2,2,5,1).shape, # 上と同義
a1[np.newaxis, ..., np.newaxis].shape, # 複数使用可能
# np.expand_dims(a1, (0, 4)).shape # expand_dims で二つの次元を同時に追加することはできない
]):
print(f"----{index+1}----")
print(a)
出力:
----1----
(2, 2, 5)
----2----
(1, 2, 2, 5)
----3----
(1, 2, 2, 5)
----4----
(1, 2, 2, 5)
----5----
(2, 1, 2, 5)
----6----
(2, 1, 2, 5)
----7----
(1, 2, 5)
----8----
(2, 2, 5, 1)
----9----
(2, 2, 5, 1)
----10----
(2, 2, 5, 1)
----11----
(1, 2, 2, 5, 1)
OpenCV
import cv2
画像の読み込み
image = cv2.imread("path/to/image", [読み込みモード])
返り値はndarray。
第二引数のフラグによってデータが変わる。
デフォルトが cv2.IMREAD_COLOR
でこれはRGBの3種類による読み込み。仮に画像がRGBAだったとしても、第二引数に何も指定しなければRGBで読み込まれる。画像自体が持つ情報をそのまま読み込む場合は cv2.IMREAD_UNCHANGED
を指定する。
画像のリサイズ
cv2.resize(image, (400,300), ...)
TensorFlow, Keras, PyTorch
割と勝手に解釈すると
- デファクトはTensorFlow。
- あまり複雑なことをしなかったり、小規模だったりする場合にKerasを使うと良い。
- アプリケーションへの実際の実装においてはPyTorchを使うと良さそう。
ざっくりの比較まとめ:
- 抽象度
- TensorFlow は低抽象度。複雑なことが書けるがコードは複雑になる。
- Keras は高抽象度。非常にシンプルに書けるが複雑なことはできない。
- PyTorch は中間くらい。独自構造が少なく(?)デバッグがやりやすい。
- 実行速度
- TensorFlowとPyTorchは同じくらい。
- Kerasは相対的に遅い。他二つが僅差なの対して、Kerasは1.5倍くらい遅いことも。
- 扱うデータのサイズ
-
Kerasは比較的遅いので、小規模データセットに使用されます。一方、TensorflowやPyTorchは高速な実行ができるので、大規模モデルや大規模データセットに使用されます。
-
- 人気
- 長らくTensorFlowが主流だった。後にKerasが登場し徐々に広まっている。
- PyTorchは新しめで、にわかに人気が高まっている。