スライスについて
b = a[:,2,:]
この記事では上のようなスライスでどのようなリストが得られるのか、またmap関数のように多次元のnp.arrayのそれぞれの要素に関数を適用する方法について記述します。
3次元リストの例
a = np.array([
[[0,1],[2,3],[4,5]],
[[6,7],[8,9],[10,11]],
[[12,13],[14,15],[16,17]]
])
print(a.shape)
出力
(3, 3, 2)
3次元リストを定義します。この次元を0次元、1次元、2次元でスライスします。
次元0でスライス

0次元は一番外側の括弧で作られたリストです。その括弧とその要素を区切るカンマを大きく表示しました。要素は3つあり、それぞれが青い丸文字で示されています。
次元0でスライスすると3つの要素が抜き出されます。
次元1でスライス

1次元は1つネストした括弧で作られるリストです。先ほどと同様に括弧とその要素を区切るカンマを大きく表示しています。
リストをスライスするとは、この大きくした括弧の中でどの要素を代表とするか、とみることができます。例えば ① の要素を代表とすると、[[0,1],[2,3],[4,5]]は[0,1]となります。大きく表示した括弧とカンマは消えていることに注意してください。
このようにしてすべての次元1のリストを代表に置き換えたものがスライスされたリストです。
次元2でスライス

先ほどと同じように2つネストした次元2の括弧とその要素を区切るカンマを大きく表示しています。
スライスは、この大きく表示した括弧のリストが個々の要素で入れ替わることです。次元2の要素は2つなので、最初の方を抽出したリストと、2つ目のを抽出したリストと2つを作成することができます。
mapからapply_along_axisへ
本記事で取り扱うapply_along_axis関数の前に、map関数について説明します。多次元リストになるとmap関数を複数回使用しなければいけないところ、apply_along_axisなら1回で処理をすることができます。
まずは1次元の例からです。
b = list(map(lambda x:x+1,np.array([0,1,2,3])))
print(b)
出力
[1, 2, 3, 4]
1次元のnp.arrayの場合は、python標準のリストと同じようにmap関数を使用できます。この例の場合はラムダ式でそれぞれの要素の値を1つ増やしています。
次に多次元リストとして画像データを例として使用します。
[r,g,b] = [1,129,30]
a = np.array([
[[r,g,b],[r,g,b],[r,g,b]],
[[r,g,b],[r,g,b],[r,g,b]]
])
print(a)
出力
[[[ 1 129 30]
[ 1 129 30]
[ 1 129 30]][[ 1 129 30]
[ 1 129 30]
[ 1 129 30]]]
例えば画像データをrgbのそれぞれの値で構成される横3,縦2ピクセルの画像をリストで定義するとします。このそれぞれのピクセルに関数を適用するにはmap関数を2回使用する必要があります。
# [r,g,b]のリストを受け取り何か変換する関数
def func(rgb):
# 例えば blue 要素だけを残す
rgb[0]=0
rgb[1]=0
return rgb
# mapを2回実行する。
b = np.array(list(map(lambda row:list(map(func,row)),a)))
print(b)
出力
[[[ 0 0 30]
[ 0 0 30]
[ 0 0 30]][[ 0 0 30]
[ 0 0 30]
[ 0 0 30]]]
1つ目のmapで画像の1行を取り出し、次のmapでその行から列を取り出しています。この例ではピクセルの3番目要素である blue の値だけを残しています。
同じことをapply_along_axisを使って実現します。
c = np.apply_along_axis(func1d=func,axis=2,arr=a)
print(c)
出力
[[[ 0 0 30]
[ 0 0 30]
[ 0 0 30]][[ 0 0 30]
[ 0 0 30]
[ 0 0 30]]]
numpy.apply_along_axisを1度だけ呼び出します。次からはこのapply_along_axis関数の詳細について述べていきます。
apply_along_axisについて
numpy.apply_along_axisは、多次元のうち1つの次元に着目してmap関数を適用するようなものです。この着目する次元をここではその次元でスライスすると呼びます。
numpy.apply_along_axis(func1d, axis, arr, *args, **kwargs)
# func1d: 適用する関数
# axis: スライスする次元
# arr: 対象とするリスト
次元1でスライスするapply_along_axis

上の3つのリストは、次元1でスライスした3つのリストです。この3つのリストが関数の引数になるというイメージです。ただ具体的には要素ごとの計算なので、3つのリストの同じ場所にある要素が引数となって関数が呼び出されます。
例えば関数を要素の合計を求める sum にすると、次元1のリストのすべての要素が足し合わされたリストが作成されます。スライスしたときに次元1の括弧とその要素を区切るカンマは消えていますので、元のリストの次元は(3,3,2)の3次元だったわけですが、apply_along_axisの結果は、右の方の3が消えて(3,2)の2次元となります。
次元2でスライスするapply_along_axis
次にスライスする次元を2とすると、次のような計算が行われます。

先ほどと同様に、次元2でスライスした2つのリストが、その要素ごとに関数を呼び出されます。
tensorflow.math.reduce_xxx
tensorflow.math.reduce_から始まる関数には、reduce_sum, reduce_maxなどがあります。これらはapply_along_axisの関数を特定して実行するのと同じです。
numpy.apply_along_axis(func1d=sum,axis,arr) ⇔ tensorflow.math.reduce_sum(arr,axis)
numpy.apply_along_axis(func1d=max,axis,arr) ⇔ tensorflow.math.reduce_max(arr,axis)
pythonだけでなく様々な言語で実装されているreduceという名前の関数は、名前の通り”減らします”。何を減らすかというと次元です。1次元のリストならその要素すべてを使って1つの値を作ります。合計、最大値、平均などです。多次元になるとある次元にしぼってその次元の要素が合計や最大値の計算に使われて無くなります。その次元というのを取り出すのがスライスです。
参考
numpy slicing (公式ページ)
numpy.apply_along_axis (公式ページ)
tensorflow.math.reduce_sum
tensorflow.math.reduce_max