2018/08/2

【06.基礎3】NumPyを学ぶ

どうも、ディープなクラゲです。
ゼロから学ぶディープラーニング推論」シリーズの6回目記事です。
このシリーズでは、Neural Compute StickとRaspberryPiの使い方をゼロから徹底的に学び、成果としてディープラーニングの推論アプリケーションが作れるようになることを目指しています。

第6回目は、NumPyを学びます。
今回も実践的に「ゼロから学ぶディープラーニング推論」で使うNumPyに絞ってじっくりと説明してゆきます!
ここで完全に理解しなくても、ディープラーニング推論を進める上で構文が出てきたときに「そういえばあったな」と思い出し、ここを見返せるようになれば良いかと思います。

【 目次 】


NumPyとは

NumPyとは数値計算を効率的に行うための拡張モジュールです。
簡単に言うと、ベクトルや行列などの多次元配列を高速に計算するためのライブラリです。
Python基礎でコード例を書きましたが、NumPyを使用するためにはnumpyモジュールをimportする必要があります。

import numpy

上記のimportでも全く問題ないのですが、慣例的に np という別名を付けることが一般的です。

import numpy as np

ここからは、実際にコードを書いて実行しながら学習することを推奨します。
"nano test.py"でファイルを新規作成し、コードを貼り付けて保存し、"python3 test.py"で実行してください。

ndarray

NumPyで扱う多次元配列は ndarray という名称です。(N-dimensional array の略)
ndarrayはPython基礎で習ったリストと関連性があります。リストと表記が似ていて混同しやすいので注意しましょう。
ちなみに、プログラムのソースコードには ndarray という関数は出てきません。

import numpy as np
 
= [10, 20, 30]
= np.array(a)
print(b)
 
# 実行結果 
# [10 20 30] 

上記のソースコードの解説です。
変数 a はPython基礎で習ったリストです。
変数 b がndarrayです。 np.arrayという関数を使ってリストをndarrayに変換しています。
最後にprintで表示していますが、ndarrayはリストと異なりカンマがありません。

初期化

ndarrayの初期化方法です。既に先ほどのコードで出ていますが、np.arrayを使って行います。
ここでは、具体的にリストとndarrayの違いを比較しながら確認してゆきましょう。
実行結果を見ると分かりますが、リストにはカンマがついていますが、ndarrayにはカンマが無いのが確認できます。

import numpy as np
 
#リスト 
= [10, 20, 30]
print(a)
 
#ndarray 
= np.array([10, 20, 30])
print(b)
 
# 実行結果 
# [10, 20, 30] 
# [10 20 30] 

2次元の場合の比較です
ndarrayの結果は途中で改行が入っています。

import numpy as np
 
#リスト 
= [[10, 20, 30], [40, 50, 60]]
print(a)
 
#ndarray 
= np.array([[10, 20, 30], [40, 50, 60]])
print(b)
 
# 実行結果 
# [[10, 20, 30], [40, 50, 60]] 
# [[10 20 30] 
# [40 50 60]] 

要素参照

要素参照についても、具体的にリストとndarrayの違いを比較しながら確認してゆきましょう。
1次元の場合の要素参照はリストもndarrayも同じです。

import numpy as np
 
#リスト 
= [10, 20, 30]
print(a[1])
 
#ndarray 
= np.array([10, 20, 30])
print(b[1])
 
# 表示結果 
# 20 
# 20 

2次元の場合、表示結果は同じですが、参照方法が異なることに注目してください。
実はndarrayも b[1][2] という書き方でもOKなのですが、ndarrayの場合は慣例的に b[1, 2]という書き方をします。

import numpy as np
 
#リスト 
= [[10, 20, 30], [40, 50, 60]]
print(a[1][2])
 
#ndarray 
= np.array([[10, 20, 30], [40, 50, 60]])
print(b[12])
 
# 表示結果 
# 60 
# 60 

データ型

ndarrayの要素は、整数型、小数型、ブール型(True or False)など様々な型を指定できます。
同じ小数型であっても、np.float16, np.float32, np.float64 など精度の異なる型の種類がたくさんあります。
ここではdtypeとastypeについて見てゆきます。今回は省略しますが多次元でも同じです。

dtype

データ型を指定するときや参照するときに dtype を使います
以下の例ではndarray初期化時にデータ型をnp.float32に指定しています。また最後にprintにてデータ側を出力しています。
ndarrayの要素にドットが付いているのは、小数型になった証です。

import numpy as np
 
= np.array([10, 20, 30], dtype = np.float32)
print(a)
print(a.dtype)
 
# 表示結果 
# [10. 20. 30.] 
# float32 

astype

画像入力データなどは、関数に処理をさせる前に、予め決まったデータ型に合わせておく必要があります。
astypeを使うと強制的にデータ型を変更することができます。

import numpy as np
 
= np.array([10, 20, 30], dtype = np.int32)
print(a.dtype)
 
= a.astype(np.float32)
print(b.dtype)
 
# 表示結果 
# int32 
# float32 

形状

ndarrayの次元数やデータ型以外にもう一つ重要な要素があります。
それが形状です。形状は各次元における要素の数のことです。
ここではshapeとreshapeについて学びましょう。

shape

shapeを使うと、形状を表示させることができます。
まずは1次元の例です。
なんか中途半端なカンマで終わっていますが、要素数が 4 ということで合っていると思います。
1次元の場合はこのような (x,) という中途半端なカンマで表示されます。

import numpy as np
 
= np.array([1, 2, 3, 4])
print(a.shape)
 
# 表示結果 
# (4,) 

今度は2次元の例です。(3, 4) ということで一致しています。

import numpy as np
 
= np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
print(a.shape)
 
# 表示結果 
# (3, 4) 

最後に3次元の例。長いので途中で改行しました。結果は (2, 3, 4 ) で一致。

import numpy as np
 
= np.array([[[ 1,  2,  3,  4], [ 5,  6,  7,  8], [ 9, 10, 11, 12]], 
              [[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]]])
print(a.shape)
 
# 表示結果 
# (2, 3, 4) 

reshape

reshapeを使うと、次元も含め形状を強制的に変更させることができます。
ただし、要素数の合計が変更前と変更後で一致している必要があります。
変更後の形状は (x, y, ...) というタプル(Python基礎で学習済)で記述します。
形状(3, 4) を 形状(2, 3, 2) に変更する例です。どちらも要素数の合計は12です。

import numpy as np
 
= np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
= np.reshape(a, (232))
print(b.shape)
print(b)
 
# 表示結果 
# (2, 3, 2) 
# [[[ 1  2] 
#   [ 3  4] 
#   [ 5  6]] 
# 
#  [[ 7  8] 
#   [ 9 10] 
#   [11 12]]] 

配列生成

ndarrayの初期化は、これまでのコードで書いてきたように np.array(リスト) で行いますが、要素に規則性がある場合は、リスト指定ではない別の方法で初期化も可能ですので、主に3つ紹介します

np.zeros

np.zerosを使うと指定した形状で要素が全て0であるndarrayを生成できます。
形状はタプルで指定します。1次元のときは値だけでもOKです。
データ型は np.float64 がデフォルトですが、引数で指定もできます。
以下は 形状(2, 3) のndarrayを np.zerosを使って生成する例です。要素のデータ型が小数であるため、表示結果はドットが付いています。

import numpy as np
 
= np.zeros((23))
print(a)
 
# 表示結果 
# [[0. 0. 0.] 
#  [0. 0. 0.]] 

np.ones

np.onesを使うと指定した形状で要素が全て1であるndarrayを生成できます。
要素が1であること以外はnp.zerosと同じです。

import numpy as np
 
= np.ones((23))
print(a)
 
# 表示結果 
# [[1. 1. 1.] 
#  [1. 1. 1.]] 
 

np.arange

np.arangeを使うと、連番や等差数列でndarrayを生成することができます。
ただし1次元のみなのでタプル指定はできません。多次元にしたい場合はreshapeなどを使います。
以下のコードは 0 から 10-1 までの連番生成の例です。等差数列の例は割愛します。

import numpy as np
 
= np.arange(10)
print(a)
 
# 実行結果 
# [0 1 2 3 4 5 6 7 8 9] 

スライス

Python基礎でリストのスライスを学びましたが、同様にndarrayでもスライス可能です。
ここでは3次元のndarrayに対するスライスを行ってみたいと思います。
ハイフンの複数出力は、見やすくするための区切り線です。

import numpy as np
 
= np.array([[[ 1,  2,  3], [ 4,  5,  6], [ 7,  8,  9]], 
              [[10, 11, 12], [13, 14, 15], [16, 17, 18]]])
print(a)
 
print('--------------------')
 
= a[:, :, 0:1]
print(b)
 
# 表示結果 
# [[[ 1  2  3] 
#   [ 4  5  6] 
#   [ 7  8  9]] 
# 
#  [[10 11 12] 
#   [13 14 15] 
#   [16 17 18]]] 
# -------------------- 
# [[[ 1] 
#   [ 4] 
#   [ 7]] 
# 
#  [[10] 
#   [13] 
#   [16]]] 

a[:, :, 0:1] の最後をスライスから参照に変えて、a[:, :, 0] にしてみます。
要素数の合計は先ほどと同じですが、2次元に変わりました。
ちょっと複雑ですが、このような使い方も出来るということだけ、頭の片隅に置けば良いと思います。

import numpy as np
 
= np.array([[[ 1,  2,  3], [ 4,  5,  6], [ 7,  8,  9]], 
              [[10, 11, 12], [13, 14, 15], [16, 17, 18]]])
print(a)
 
print('--------------------')
 
= a[:, :, 0]
print(b)
 
# 表示結果 
# [[[ 1  2  3] 
#   [ 4  5  6] 
#   [ 7  8  9]] 
# 
#  [[10 11 12] 
#   [13 14 15] 
#   [16 17 18]]] 
# -------------------- 
# [[ 1  4  7] 
#  [10 13 16]] 

ソート

np.argsortを使うと、配列(リストやndarray)を昇順に並べたときのインデックスをndarrayで返します。
この説明だけだと分からないと思いますので、具体的にサンプルソースで確かめてみましょう

import numpy as np
 
= np.array([10, 50, 40, 30, 20])
= np.argsort(a)
print(b)
 
# 表示結果 
# [0 4 3 2 1] 

[10, 50, 40, 30, 20] を昇順に(つまり小さい方から大きい方へと)並べると [10, 20, 30, 40, 50] になるのは分かると思います。
このとき元々のリストに [0, 1, 2, 3, 4] というindexラベルを付けて、並べた後の結果を見てみると [0, 4, 3, 2, 1] になるということです。

ndarrayの場合は以下のような書き方もできます。結果は同じです。

import numpy as np
 
= np.array([10, 50, 40, 30, 20])
= a.argsort()
print(b)
 
# 表示結果 
# [0 4 3 2 1] 

さらに、スライスと組合せて使うことも可能です。一見複雑そうに見えますが、1つ1つ追ってゆけば分かると思います。
a は最初の ndarray
b は a の argsort
c はさらに要素の並びを逆にした形
d はさらに上位3つまでを選抜

import numpy as np
 
= np.array([10, 50, 40, 30, 20])
= a.argsort()
= a.argsort()[::-1]
= a.argsort()[::-1][:3]
 
print(a)
print(b)
print(c)
print(d)
 
# 実行結果 
# [10 50 40 30 20] 
# [0 4 3 2 1] 
# [1 2 3 4 0] 
# [1 2 3] 

その他

np.mean

meanを使うと配列要素の平均を求めることができます。
まずは分かりやすい1次元配列で見てみましょう
変数cのように、ndarray.mean()という形式でも同じ結果が得られます

import numpy as np
 
= np.array([1, 2, 3, 4, 5])
 
= np.mean(a)
print(b)
 
= a.mean()
print(c)
 
# 実行結果 
# 3.0 
# 3.0 

3次元配列の場合を見てみましょう
多次元の場合は、引数が無い場合は全ての要素の平均になります。
引数には軸を指定できます。例えば 1軸を指定した場合は1軸のみの平均値になっていることに注目してください

import numpy as np
 
= np.array([[[1, 2], [3, 4]],[[5, 6], [7, 8]],[[9, 10], [11, 12]]])
print(a)
print(a.shape)
print('--------------------')
 
= a.mean()
print(b)
print(b.shape)
print('--------------------')
 
= a.mean(1)
print(c)
print(c.shape)
print('--------------------')
 
# 実行結果 
# [[[ 1  2] 
#   [ 3  4]] 
# 
#  [[ 5  6] 
#   [ 7  8]] 
# 
#  [[ 9 10] 
#   [11 12]]] 
# (3, 2, 2) 
# -------------------- 
# 6.5 
# () 
# -------------------- 
# [[ 2.  3.] 
#  [ 6.  7.] 
#  [10. 11.]] 
# (3, 2) 
# -------------------- 

ややこしいですが、以下のような書き方もできます。
d は先ほどの cを使って書くと、c.mean(1) と同等です。
結果的には 1,2,3,4の平均、5,6,7,8の平均、9,10,11,12の平均になっていると思います。
覚える必要はないです。この ndarray.mean(1).mean(1) という形式はサンプルソースで出てきますので、出てきたらここを見返してください。

import numpy as np
 
= np.array([[[1, 2], [3, 4]],[[5, 6], [7, 8]],[[9, 10], [11, 12]]])
print(a)
print(a.shape)
print('--------------------')
 
= a.mean(1).mean(1)
print(d)
print(d.shape)
print('--------------------')
 
# 実行結果 
# [[[ 1  2] 
#   [ 3  4]] 
# 
#  [[ 5  6] 
#   [ 7  8]] 
# 
#  [[ 9 10] 
#   [11 12]]] 
# (3, 2, 2) 
# -------------------- 
# [ 2.5  6.5 10.5] 
# (3,) 
# -------------------- 

np.argmax

np.argmaxを使うと、配列(リストやndarray)の中で最大値の要素のインデックスを値で返します。

import numpy as np
 
= np.array([10, 50, 40, 30, 20])
= np.argmax(a)
print(b)
 
# 実行結果 
# 1 

配列の中の最大値は50で、そのインデックスは 1 です。

np.nonzero

引数の配列に対して、0以外の要素のインデックスを1次元配列のタプル形式で返します。

import numpy as np
 
= np.array([10, 0, 20, 30, 0])
= np.nonzero(a)
print(b)
 
# 実行結果 
# (array([0, 2, 3], dtype=int32),) 

配列の要素がブール値の場合はTrueの要素が対象になります。

import numpy as np
 
= np.array([True, True, False, True, False])
= np.nonzero(a)
print(b)
 
# 実行結果 
# (array([0, 1, 3], dtype=int32),) 

np.multiply

配列の要素同士の掛け算を行います。基本的に配列は同じ形状である必要があります。

import numpy as np
 
= np.array([[1, 2, 3],[4, 5, 6]])
= np.array([[7, 8, 9],[0, 1, 2]])
 
= np.multiply(a, b)
print(c)
 
# 実行結果 
# [[ 7 16 27] 
#  [ 0  5 12]] 

ただし、下記のようにスカラーとの掛け算などは形状が異なりますが、計算可能です。

import numpy as np
 
= np.array([[1, 2, 3],[4, 5, 6]])
= 2
 
= np.multiply(a, b)
print(c)
 
# 実行結果 
# [[ 2  4  6] 
# [ 8 10 12]] 

np.divide

配列の要素同士の割り算を行います。
同様にスカラーとの割り算は計算可能です。

import numpy as np
 
= np.array([[10, 20, 30], [40, 50, 60]])
= np.array([[ 5,  4,  3], [ 2,  1,  8]])
 
= np.divide(a, b)
print(c)
 
# 実行結果 
# [[ 2.   5.  10. ] 
#  [20.  50.   7.5]] 

np.transpose

np.transposeは配列の軸を入れ替えます。
これも具体的な例を見た方が分かりやすいです。

(3, 2)の形状を持つ配列 a に対してtransposeを行います。
a は 2つの要素を持つ配列が3つある配列です。
このとき、3の箇所が0軸、2の箇所が1軸です。
transpose(a, (1, 0)) とは、配列 a の0軸と1軸を入れ替えるという意味です。
実行結果を見れば何となくイメージがつかめると思います。今回はその程度の認識でOKです。

mport numpy as np
 
= np.array([[0, 1], [2, 3], [4, 5]])
print(a)
print(a.shape)
 
print('--------------------')
 
= np.transpose(a, (10))
print(b)
print(b.shape)
 
# 実行結果 
# [[0 1] 
#  [2 3] 
#  [4 5]] 
# (3, 2) 
# -------------------- 
# [[0 2 4] 
#  [1 3 5]] 
# (2, 3) 

np.load

np.loadは、np.saveで保存したファイルを読み込むことができます。
np.saveで保存するデータはndarrayです。
実際にやってみましょう。まず適当なndarrayをtest.npyとして保存します。

import numpy as np
 
= np.array([[1, 2, 3],[4, 5, 6]])
np.save('test.npy', a)

実行結果は何も出ませんが、lsを行うと test.npy が出来ていると思います。
これを np.load を使って読み込みます。実行すると読み込めていることが確認できます。

import numpy as np
 
= np.load('test.npy')
print(a)
 
# 実行結果 
# [[1 2 3] 
#  [4 5 6]] 

np.loadtxt

np.loadtxtはテキストファイルを読み込んでリスト化することが出来ます。
要素の区切りには区切り文字を使います。例えば Tab などです。
実際にテキストファイルを作ってみましょう。

まずtest.txtを新作成します。

nano test.txt

中身はこんな感じで、各文字列の間は Tab を使ってください。

hello   Python  Numpy   jellyfish       kurage

test.txtを読み込んで表示させます。
np.loadtxtの引数2番目はdtypeです。ここを省略するとデフォルトのfloatになりエラーになります。
引数3番目は区切り文字の指定で、今回はTabを意味する'\t'を指定してます。

import numpy as np
 
= np.loadtxt('test.txt', dtype='str', delimiter='\t')
print(a[3])
 
# 実行結果 
# jellyfish 

これで今回のサンプルソースで使用するNumPyを一通り学びました。次回はいよいよディープラーニング推論の実行に挑戦します!
以上、「NumPyを学ぶ」でした。