CS228 Python Tutorial

Adapted by Volodymyr Kuleshov and Isaac Caswell from the CS231n Python tutorial by Justin Johnson (http://cs231n.github.io/python-numpy-tutorial/).

はじめに

このコースのすべての課題にPythonプログラミング言語を使用します。Pythonは非常に汎用的なプログラミング言語ですが、いくつかの一般的なライブラリ(numpy、scipy、matplotlib)の助けを借りて、科学計算のための強力な環境にもなりえます。

Pythonやnumpyの経験がない皆さんは、このチュートリアルによって、Pythonプログラミング言語と科学計算のためのPythonの両方を学んでください。

なお、Matlabの知識がある場合にはMatlabのユーザーのためのnumpyのページを参照することをおすすめします。

このチュートリアルで扱うのは以下の項目です:

  • Pythonの基礎: 基本的なデータ型(コンテナ、リスト、辞書、集合、タプル)
  • Numpy: 配列、配列のインデックス、データ型、配列の算術、ブロードキャスト
  • Matplotlib: 図の作成、複数からなる図の構成、画像
  • IPython: ノートブックの作り方、典型的な作業過程

Pythonの基礎

Pythonは、高度に動的に型指定されたmultiparadigmプログラミング言語です。Pythonコードは、非常に強力なアイデアをコードのごくわずかな行で表現することができ、非常に読みやすいので、しばしば擬似コードに似ていると言われています。例として、古典的なクイックソートアルゴリズムをPythonで実装します:

In [3]:
def quicksort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr[len(arr) // 2]
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    return quicksort(left) + middle + quicksort(right)

print( quicksort([3,6,8,10,1,2,1]) )
[1, 1, 2, 3, 6, 8, 10]

Pythonのバージョン

現在サポートされているPythonのバージョンは2.7と3.4の2種類です。やや混乱することに、Python 3.0は後方互換性のない多くの変更を言語に導入したため、2.7で書かれたコードは3.4で動作しない可能性があります。このクラスでは、すべてのコードがPython 3.4を使用します。

コマンドラインであなたのPythonのバージョンを確認することができます python --version

基本データ型

ほとんどの言語と同様に、Pythonには、整数、浮動小数点数、ブール値、文字列などの多くの基本型があります。これらのデータ型は、他のプログラミング言語と同様に動きます。

他の言語と同様に、整数および浮動小数点数があります。

In [4]:
x = 3
print( x, type(x) )     # xの値とデータ型の表示
3 <class 'int'>
In [5]:
print( x + 1 )   # Addition; 足し算
print( x - 1 )   # Subtraction; 引き算
print( x * 2 )   # Multiplication; 掛け算
print( x ** 2 )  # Exponentiation; 乗数
4
2
6
9
In [6]:
x += 1
print( x ) #  "4" を表示
x *= 2
print( x )  #  "8" を表示
4
8
In [7]:
y = 2.5
print( type(y) ) # "<type 'float'>" と出力
print( y, y + 1, y * 2, y ** 2 ) #  "2.5 3.5 5.0 6.25"と出力
<class 'float'>
2.5 3.5 5.0 6.25

C言語とは異なり、Pythonは単項インクリメント(x++)やデクリメント(x--)演算子を持っていないことに注意してください。

Pythonには、長い整数と複素数を組み込みで扱えます。これについての詳細は ドキュメントをご覧ください。

ブール

Pythonはブール論理の通常の演算子のすべてを実装していますが、&&||のような記号ではなく、andorというように英語の単語を使用しています

In [9]:
t, f = True, False
print( type(t) ) # Prints "<type 'bool'>"
<class 'bool'>

論理演算をすべて使ってみましょう

In [10]:
print( t and f )  #  AND : 論理積
print( t or f )  #  OR : 論理和
print( not t )   #  NOT : 否定
print( t != f )  #  XOR : 排他的論理和
False
True
False
True

文字列

Pythonには文字列に対する素晴らしいサポートがあります

In [12]:
hello = 'hello'   # 文字列を示すには、一重引用符でも
world = "world"   # 二重引用符でも、どちらも使える
print( hello, len(hello) )
hello 5
In [13]:
hw = hello + ' ' + world  # 文字列の結合
print( hw ) # "hello world"と出力
hello world
In [14]:
hw12 = '%s %s %d' % (hello, world, 12)  # sprintf 流の文字列生成
print( hw12 ) # "hello world 12"と出力
hello world 12

Stringオブジェクトには便利なメソッドがたくさんあります。次はその例:

In [15]:
s = "hello"
print (s.capitalize() )  # 文字列をキャピタライズ(先頭のみ大文字); "Hello"と出力
print (s.upper() )      # 文字列を大文字に変換;  "HELLO"と出力
print (s.rjust(7) )     # 文字列を右揃え(左にスペースを入れる); "  hello"と出力
print (s.center(7) )    # 文字列を中央寄せ(左右にスペースを入れる); " hello "と出力
print (s.replace('l', '(ell)') )  # すべてのlを(ell)で置き換える; "he(ell)(ell)o"と出力
print ('  world '.strip() )  # 先頭と末尾の空白文字を切り落とす; "world"と出力
Hello
HELLO
  hello
 hello 
he(ell)(ell)o
world

すべての文字列メソッドのリストについて知りたければドキュメントをご覧ください。

コンテナ

Pythonには、リスト、辞書、セット、タプルなどの組み込みコンテナタイプが用意されています。

リスト

Pythonのリストは配列に相当するものですが、サイズ変更が可能で、さまざまな型の要素を含むことができます:

In [16]:
xs = [3, 1, 2]   # リストを作る
print (xs, xs[2])
print (xs[-1] )    # 負のインデックスは後ろから数えることを意味する。だから "2" を表示する
[3, 1, 2] 2
2
In [17]:
xs[2] = 'foo'    # rリストはいろいろな方の要素をもつことができる
print (xs)
[3, 1, 'foo']
In [18]:
xs.append('bar') # リストの最後に新しい要素を付け加える
print (xs)  
[3, 1, 'foo', 'bar']
In [19]:
x = xs.pop()     # リストの最後から要素を取り除き、その要素を返す
print (x, xs) 
bar [3, 1, 'foo']

今までと同様に、リストについての詳細は ドキュメント でみることができます。

スライス

リストの要素一つ一つにアクセスするのに加えて、Pythonはサブリストにアクセスする手段を提供しています。これはスライスとして知られてます。

In [21]:
nums = list(range(5))    # range で指定された範囲の整数からなるリストをlistで作る。どちらも組み込みの関数
print (nums )        # "[0, 1, 2, 3, 4]"と出力
print (nums[2:4])    #  2 番目から 4 番目までの要素(4番目の要素は含まない)からなるスライス:  "[2, 3]"と出力
print (nums[2:])     #  2番目 から最後の要素からなるスライス;  "[2, 3, 4]"と出力
print (nums[:2])     # 先頭から2番目までの要素(2番目の要素は含まない) からなるスライス: "[0, 1]"と出力
print (nums[:])      #  全部の要素からなるスライス: ["0, 1, 2, 3, 4]"と出力
print (nums[:-1] )   # スライスのインデックス指定にも負の数が使える:  ["0, 1, 2, 3]"と出力
nums[2:4] = [8, 9]   # 新しい要素で num[2:4]のスライスの部分を置換
print (nums)         #  "[0, 1, 8, 9, 4]"と出力
[0, 1, 2, 3, 4]
[2, 3]
[2, 3, 4]
[0, 1]
[0, 1, 2, 3, 4]
[0, 1, 2, 3]
[0, 1, 8, 9, 4]

ループ

このようなリストの要素を繰り返し(ループ)で使用することができます:

In [23]:
animals = ['cat', 'dog', 'monkey']
for animal in animals:
    print (animal)
cat
dog
monkey

ループの本体で要素のインデックスにアクセスしたい場合は、組み込みのenumerate機能を使用します:

In [24]:
animals = ['cat', 'dog', 'monkey']
for idx, animal in enumerate(animals):
    print( '#%d: %s' % (idx + 1, animal) )
#1: cat
#2: dog
#3: monkey

リスト内包

プログラミングではよく、データ変換したくなることがあります。その簡単な例として、次のような二乗の数値を計算するコードを考えてみましょう。

In [25]:
nums = [0, 1, 2, 3, 4]
squares = []
for x in nums:
    squares.append(x ** 2)
print(squares)
[0, 1, 4, 9, 16]

このようなコードは、リストの内包記法を使って簡単に書くことができます:

In [26]:
nums = [0, 1, 2, 3, 4]
squares = [x ** 2 for x in nums]
print (squares)
[0, 1, 4, 9, 16]

リスト内包表記には条件も含めることができます。

In [27]:
nums = [0, 1, 2, 3, 4]
even_squares = [x ** 2 for x in nums if x % 2 == 0]
print (even_squares)
[0, 4, 16]

辞書

辞書とは JavaのMapやJavaScriptのオブジェクトと同様に、 (キー、値)というペアを記録します。これは次のように使用できます:

In [29]:
d = {'cat': 'cute', 'dog': 'furry'}  # いくつかのデータを持つ辞書を作る
print (d['cat'])       # 辞書から項目を取り出す; "cute"と出力する
print ('cat' in d)     # 辞書に指定されたキーがあるかどうか調べる; "True"と出力する
cute
True
In [34]:
d['fish'] = 'wet'      # 辞書に新たな項目を記憶させる
print (d['fish'] )     # "wet"と出力する
wet
In [31]:
print (d['monkey'])  # KeyError: 'monkey'は辞書dの項目にはないためエラーになる
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-31-39608aeda0ef> in <module>()
----> 1 print (d['monkey'])  # KeyError: 'monkey' not a key of d

KeyError: 'monkey'
In [35]:
print (d.get('monkey', 'N/A'))  #  default値をつけて項目にアクセス; "N/A"と出力する
print (d.get('fish', 'N/A'))    # default値をつけて項目にアクセス;  "wet"と出力する
N/A
wet
In [37]:
del d['fish']        # 辞書dから項目を削除
print (d.get('fish', 'N/A')) # "fish" はもはやや辞書dには登録されていない: "N/A"と出力する
N/A

このドキュメントから辞書について必要なことがらすべてを学ぶことができます。

辞書内のキーを反復処理するのは簡単です:

In [32]:
d = {'person': 2, 'cat': 4, 'spider': 8}
for animal in d:
    legs = d[animal]
    print ('A %s has %d legs' % (animal, legs))
A spider has 8 legs
A person has 2 legs
A cat has 4 legs

キーとそれに対応する値にいっぺんにアクセスしたい場合は、itemsメソッドを使います:

In [45]:
d = {'person': 2, 'cat': 4, 'spider': 8}
for (animal, legs) in d.items():
    print ('A %s has %d legs' % (animal, legs))
A spider has 8 legs
A person has 2 legs
A cat has 4 legs

辞書の内包記法

リスト内包に似た方法で、簡単に辞書を構築することができます。その例:

In [46]:
nums = [0, 1, 2, 3, 4]
even_num_to_square = {x: x ** 2 for x in nums if x % 2 == 0}
print (even_num_to_square)
{0: 0, 2: 4, 4: 16}

辞書について、詳細は このドキュメント でみることができます。

集合(セット)

集合とは、異なる要素の順序付けられていない集まりのことです。例として、次を考えてみましょう。

In [47]:
animals = {'cat', 'dog'}
print ('cat' in animals )  # 集合にある要素が含まれているかどうかチェックする;  "True"と出力する
print ('fish' in animals)  # "False"と出力する
True
False
In [48]:
animals.add('fish')      # 集合に要素を付け加える
print ('fish' in animals)
print (len(animals))       # 集合の要素数を返す
True
3
In [49]:
animals.add('cat')       # 集合にすでに要素がある場合にはなにもしない
print (len(animals) )      
animals.remove('cat')    # 集合から要素を取り除く
print (len(animals) )     
3
2

繰り返し

集合の繰り返し処理は、リストの反復処理と同じ構文で行います。ただし、集合の順序は順序付けされていないため、集合の要素を参照する順番を前提にすることはできません。

In [50]:
animals = {'cat', 'dog', 'fish'}
for idx, animal in enumerate(animals):
    print ('#%d: %s' % (idx + 1, animal))
# "#1: fish", "#2: dog", "#3: cat"と出力する
#1: cat
#2: fish
#3: dog

集合の内包記法

リストや辞書のように、集合の内包表記を使用して集合を作ることができます。

In [52]:
from math import sqrt
print ({int(sqrt(x)) for x in range(30)})
{0, 1, 2, 3, 4, 5}

いつものように、集合についての詳細は このドキュメントをみてください。

タプル

タプルは値の(不変の)順序付けられたリストです。タプルは多くの点でリストに似ています。リストとの最も重要な違いの1つは、タプルを辞書のキーとしても集合の要素としても使用できますが、リストはできないということです。簡単な例を示します:

In [53]:
d = {(x, x + 1): x for x in range(10)}  # タプルをキーとする辞書を作る
t = (5, 6)       # タプルを作る
print (type(t))
print (d[t] )  
print (d[(1, 2)])
<class 'tuple'>
5
1
In [54]:
t[0] = 1    # タプルに新たな要素を付け加えたり削除したりはできません
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-54-0a69537257d5> in <module>()
----> 1 t[0] = 1

TypeError: 'tuple' object does not support item assignment

このドキュメントに、タプルに関する詳細な情報があります。

関数

Pythonの関数はdefキーワードを使用して定義されます。その例が次:

In [56]:
def sign(x):
    if x > 0:
        return 'positive'
    elif x < 0:
        return 'negative'
    else:
        return 'zero'

for x in [-1, 0, 1]:
    print (sign(x))
negative
zero
positive

次のように、オプションのキーワード引数を取る関数を定義することがよくあります:

In [57]:
def hello(name, loud=False):
    if loud:
        print ('HELLO, %s' % name.upper())
    else:
        print ('Hello, %s!' % name)

hello('Bob')
hello('Fred', loud=True)
Hello, Bob!
HELLO, FRED

Pythonの関数について詳しくは このドキュメントを参照してください。

クラス

Pythonでクラスを定義するための構文は簡単です:

In [58]:
class Greeter:

    # Constructor
    def __init__(self, name):
        self.name = name  # インスタンス変数を作成する

    # Instance method
    def greet(self, loud=False):
        if loud:
            print ('HELLO, %s!' % self.name.upper())
        else:
            print ('Hello, %s' % self.name)

g = Greeter('Fred')  #  Greeter クラスのインスタンスの作成
g.greet()            # インスタンスメソッドを呼び出す; "Hello, Fred"と出力
g.greet(loud=True)   # インスタンスメソッドを呼び出す;  "HELLO, FRED!"と出力
Hello, Fred
HELLO, FRED!

Pythonのクラスについて このドキュメントにより、 詳しく知ることができます。

Numpy

numpy(「ナンパイ」と読む)とは、Pythonで科学技術計算のための中核となるライブラリのことです。これは、高性能多次元配列オブジェクト、およびこれらの配列を処理するためのツールを提供しています。既にMATLABに精通している人なら、 Matlabユーザーのための NumPyyという文書から有用な情報が得られるでしょう。

Numpyを使うには、まず次のようにしてnumpyパッケージをimport(取り込む)必要があります:

In [59]:
import numpy as np

配列

numpy配列の要素は、すべて同じ型の値でなければならず、非負整数のタプルによってインデックスされます。 次元数とは配列の階数(ランク)のことで、 配列の形状(shape)とは、配列のそれぞれの次元ごとのサイズを与えるタプル(要素は整数)のことです。

ネストしたPythonリストからnumpy配列を初期化できます。また、角括弧により要素にアクセスすることができます:

In [60]:
a = np.array([1, 2, 3])  # 階数1(1次元)の配列を作る
print (type(a), a.shape, a[0], a[1], a[2])
a[0] = 5                 # 配列の要素を更新する
print (a)                  
<class 'numpy.ndarray'> (3,) 1 2 3
[5 2 3]
In [62]:
b = np.array([[1,2,3],[4,5,6]])   # 階数2 (2次元)の配列を作る
print(b)
[[1 2 3]
 [4 5 6]]
In [63]:
print (b.shape )                       # 配列bの形状を出力 
print (b[0, 0], b[0, 1], b[1, 0])
(2, 3)
1 2 4

Numpyには、配列を作成するための関数がたくさんあります:

In [64]:
a = np.zeros((2,2))  # すべての要素が0である配列を作る
print( a )
[[ 0.  0.]
 [ 0.  0.]]
In [65]:
b = np.ones((1,2))   # すべての要素が1である配列を作る
print (b)
[[ 1.  1.]]
In [67]:
c = np.full((2,2), 7) # 定数配列を作る
print (c)
[[ 7.  7.]
 [ 7.  7.]]
/opt/anaconda3/lib/python3.5/site-packages/numpy/core/numeric.py:301: FutureWarning: in the future, full((2, 2), 7) will return an array of dtype('int64')
  format(shape, fill_value, array(fill_value).dtype), FutureWarning)
In [69]:
d = np.eye(2)        #  2x2 の単位行列を作る
print (d)
[[ 1.  0.]
 [ 0.  1.]]
In [70]:
e = np.random.random((2,2)) # 配列を作り、乱数で要素を埋める
print (e)
[[ 0.23053414  0.62012597]
 [ 0.7365531   0.5301187 ]]

配列の別な作成方法については このドキュメントを参照してください。

配列のインデックス作成

Numpyは、配列にインデックスを付けるいくつかの方法を提供しています。

スライス

Pythonのリストと同様に、numpyの配列をスライスすることができます。配列は多次元であるため、配列の各次元にスライスを指定する必要があります。

In [72]:
import numpy as np

# 次のような階数2で形状(3,4)の配列を作る
# [[ 1  2  3  4]
#  [ 5  6  7  8]
#  [ 9 10 11 12]]
a = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])

# スライスを用いて最初の2行、1列目と2列目の要素からなる部分配列を抜き出す
#  b はそのような形状(2,2)の配列
# [[2 3]
#  [6 7]]
b = a[:2, 1:3]
print (b)
[[2 3]
 [6 7]]

配列のスライスは同じデータに対して違った見方を与えるものなので、スライスの要素を書き換えると、元の配列の要素も書き換わってしまいます。

In [ ]:
 
In [73]:
print (a[0, 1] ) 
b[0, 0] = 77    # b[0, 0] は a[0, 1]と同じデータ
print ( a[0, 1] )   # だからbの要素を書き換えるとaも変わる
2
77

整数インデックスとスライスインデックスを混在させることもできます。しかし、これを行うと元の配列よりも低い階数の配列が得られます。これは、MATLABが配列スライシングを処理する方法とはまったく異なります。

In [74]:
# 次のような階数 2 の形状 (3, 4)の配列を作る
a = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
print (a)
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]

配列の真ん中の行にあるデータにアクセスする2通リの方法を見ます。整数インデックスとスライスを混在させることで低い階数の配列ができます。一方、スライスだけを使うと、元の配列と同じ階数の配列ができます。

In [75]:
row_r1 = a[1, :]    # 配列aの2行目に対する階数 1 のスライス
row_r2 = a[1:2, :]  # 配列aの2行目に対する階数 2 のスライス
row_r3 = a[[1], :]  # 配列aの2行目に対する階数 2のスライス
print (row_r1, row_r1.shape )
print (row_r2, row_r2.shape)
print (row_r3, row_r3.shape)
[5 6 7 8] (4,)
[[5 6 7 8]] (1, 4)
[[5 6 7 8]] (1, 4)
In [76]:
# 配列の列に対しても同じことが起こります
col_r1 = a[:, 1]
col_r2 = a[:, 1:2]
print (col_r1, col_r1.shape)
print()
print (col_r2, col_r2.shape)
[ 2  6 10] (3,)

[[ 2]
 [ 6]
 [10]] (3, 1)

整数配列のインデックス:

スライスを使用してnumpyの配列にインデックスした結果は、常に元の配列の部分配列になります。これと対照的に、整数配列インデックスは、別の配列のデータを使用して任意の配列を構築することを可能にします。次に例を示します。

In [77]:
a = np.array([[1,2], [3, 4], [5, 6]])

# 整数配列インデックスの例
# 返される配列は形状(3, )
print (a[[0, 1, 2], [0, 1, 0]] )

# 上の例は次のものと等価
print ( np.array([a[0, 0], a[1, 1], a[2, 0]]) )
[1 4 5]
[1 4 5]
In [78]:
# When using integer array indexing, you can reuse the same
# element from the source array:
print (a[[0, 0], [1, 1]])

# Equivalent to the previous integer array indexing example
print (np.array([a[0, 1], a[0, 1]]))
[2 2]
[2 2]

整数配列のインデックスが便利なのは、行列の各行から1つの要素を選択したり変更したりする場合です。

In [79]:
# 要素を選んで配列を作る
a = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
print (a)
[[ 1  2  3]
 [ 4  5  6]
 [ 7  8  9]
 [10 11 12]]
In [80]:
# インデックスの配列を作る
b = np.array([0, 2, 0, 1])

# bのインデックスを用いてそれぞれの行からひとつずつ要素を選ぶ
print (a[np.arange(4), b] ) # "[ 1  6  7 11]"と出力
[ 1  6  7 11]
In [81]:
# bのインデックスを用いてそえぞれの行からひとつずつ要素の値を変化させる
a[np.arange(4), b] += 10
print (a)
[[11  2  3]
 [ 4  5 16]
 [17  8  9]
 [10 21 12]]

ブール配列のインデックス

ブール配列インデックスを使用すると、配列の任意の要素を取り出すことができます。このタイプの索引付けは、ある条件を満たす配列の要素を選択するために頻繁に使用されます。次に例を示します。

In [2]:
import numpy as np

a = np.array([[1,2], [3, 4], [5, 6]])

bool_idx = (a > 2)  # 2よりも大きな要素を見つける;
                    # これによりaと同じ形状のブール値のNumpy配列が返る
                    #  bool_idx のそれぞれの要素は
                    # 対応するaの要素が2より大きいかどうかの情報を与える

print (bool_idx)
[[False False]
 [ True  True]
 [ True  True]]
In [83]:
# ブール配列インデックスbool_idxを用いて階数1の配列を作る
# bool_idxの要素がTrueである要素に対応するaの要素からなる
print (a[bool_idx])

# 以上の事を一行で完結に書くことができる:
print (a[a > 2])
[3 4 5 6]
[3 4 5 6]

話を簡潔にするため、numpy配列のインデキシングに関する詳細は省略しました。もっと知りたい場合は このドキュメントを読みましょう。

データ型

numpy配列はすべて、同じ型の要素から構成されます。Numpyは、配列を構築するために使用できる多数の数値データ型を提供しています。Numpyは配列を作成するときにデータ型を推測しようとしますが、配列を構築する関数は通常、明示的にデータ型を指定するオプションの引数も含みます。次に例を示します。

In [84]:
x = np.array([1, 2])  # Let numpy choose the datatype
y = np.array([1.0, 2.0])  # Let numpy choose the datatype
z = np.array([1, 2], dtype=np.int64)  # Force a particular datatype

print (x.dtype, y.dtype, z.dtype)
int64 float64 int64

numpyのデータ型について詳しく知りたい場合は、 このドキュメントのを読むことをお勧めします。

配列の数学

基本的な数学関数は配列の要素ごとに作用し、演算子オーバーロードとnumpyモジュールの関数としても利用できます。

In [86]:
x = np.array([[1,2],[3,4]], dtype=np.float64)
y = np.array([[5,6],[7,8]], dtype=np.float64)

# 要素ごとの和;両方とも配列を返す
print (x + y)
print (np.add(x, y))
[[  6.   8.]
 [ 10.  12.]]
[[  6.   8.]
 [ 10.  12.]]
In [88]:
# 要素ごとの差; どちらも配列を返す
print (x - y)
print (np.subtract(x, y))
[[-4. -4.]
 [-4. -4.]]
[[-4. -4.]
 [-4. -4.]]
In [89]:
# 要素ごとの積; どちらも配列を返す
print (x * y)
print (np.multiply(x, y))
[[  5.  12.]
 [ 21.  32.]]
[[  5.  12.]
 [ 21.  32.]]
In [90]:
# 要素ごとの商; どちらも配列を返す
# [[ 0.2         0.33333333]
#  [ 0.42857143  0.5       ]]
print (x / y)
print (np.divide(x, y))
[[ 0.2         0.33333333]
 [ 0.42857143  0.5       ]]
[[ 0.2         0.33333333]
 [ 0.42857143  0.5       ]]
In [92]:
# 要素ごとの平方根; 配列を返す
# [[ 1.          1.41421356]
#  [ 1.73205081  2.        ]]
print (np.sqrt(x))
[[ 1.          1.41421356]
 [ 1.73205081  2.        ]]

*はMATLABとは異なり、行列乗算ではなく、要素単位の乗算です。我々は、dot関数を使って、行列の積を計算したり、行列とベクトルの積を求めたり、ベクトルの内積を計算します。dotは、numpyモジュールの関数としても、配列オブジェクトのインスタンス・メソッドとしても利用できます。

In [93]:
x = np.array([[1,2],[3,4]])
y = np.array([[5,6],[7,8]])

v = np.array([9,10])
w = np.array([11, 12])

# Inner product of vectors; both produce 219
print (v.dot(w))
print (np.dot(v, w))
219
219
In [94]:
# Matrix / vector product; both produce the rank 1 array [29 67]
print (x.dot(v))
print (np.dot(x, v))
[29 67]
[29 67]
In [95]:
# Matrix / matrix product; both produce the rank 2 array
# [[19 22]
#  [43 50]]
print (x.dot(y))
print (np.dot(x, y))
[[19 22]
 [43 50]]
[[19 22]
 [43 50]]

Numpyは、配列の計算を実行するための多くの便利な関数を提供しています。sumは最も有用な関数の一つです。

In [3]:
x = np.array([[1,2],[3,4]])

print (np.sum(x))   # Compute sum of all elements; prints "10"
print (np.sum(x, axis=0)) # Compute sum of each column; prints "[4 6]"
print (np.sum(x, axis=1))  # Compute sum of each row; prints "[3 7]"
10
[4 6]
[3 7]

numpyが提供する数学関数の完全なリストは このドキュメントにあります。

配列を使用して数学関数を計算することとは別に、配列内のデータを再形成または操作する必要があることがよくあります。このタイプの操作の最も単純な例は行列を転置することですが、 これには単に配列オブジェクトのT属性を使えば良いのです:

In [4]:
print (x)
print (x.T)
[[1 2]
 [3 4]]
[[1 3]
 [2 4]]
In [5]:
v = np.array([[1,2,3]])
print ( v ) 
print (v.T)
[[1 2 3]]
[[1]
 [2]
 [3]]

ブロードキャスト

ブロードキャストはNumpyがいろいろな形状の配列に対し算術演算を許すような強力なメカニズムです。よくあるケースは、小さな配列と大きな配列があり、小さな配列を何回か大きな配列に対して演算する、という場合です。

例えば、行列のそれぞれの行にある定数ベクトルを加算したいとしましょう。それにはこのようにすればできます:

In [6]:
# ベクトルvを行列xの各行に加算する
# その結果を行列yとして記憶する
x = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
v = np.array([1, 0, 1])
y = np.empty_like(x)   # xと同じサイズの空の行列を作る

# ループを用いてvを行列xの各行に加える
for i in range(4):
    y[i, :] = x[i, :] + v

print( y )
[[ 2  2  4]
 [ 5  5  7]
 [ 8  8 10]
 [11 11 13]]

これはこれで動く。しかし、行列 x がかなり大きい場合、 Python でこのような繰り返しをやるととても遅くなる。ここで、ベクトル v を行列 x の各行に足すというのは、 v のコピーを垂直にたくさん積み上げた行列 vv を作り、それから行列 xと行列vv を要素ごとに足すことに等しい。そこで、この操作を次のように実現することができる:

In [8]:
vv = np.tile(v, (4, 1))  # vを4つ積み上げる
print (vv)               # 出力は: "[[1 0 1]
                         #          [1 0 1]
                         #          [1 0 1]
                         #          [1 0 1]]"
[[1 0 1]
 [1 0 1]
 [1 0 1]
 [1 0 1]]
In [10]:
y = x + vv  # xとvvを要素ごとに加算する
print (y)
[[ 2  2  4]
 [ 5  5  7]
 [ 8  8 10]
 [11 11 13]]

Numpyのブロードキャストにより、 このような計算を、実際にはvのコピーを作らずにすませることができる。. このような場合にブロードキャストを考えよう:

In [11]:
import numpy as np

# ベクトルvを行列xの各行に加え
# その結果を行列yで記憶する
x = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
v = np.array([1, 0, 1])
y = x + v  # ブロードキャストを使ってvをxのそれぞれの行に足す
print (y)
[[ 2  2  4]
 [ 5  5  7]
 [ 8  8 10]
 [11 11 13]]

y = x + vは、 たとえx(4, 3)という形状であり、 v(3,) という形状であっても、ブロードキャストのおかげで計算できます。 あたかもvが実際に(4, 3)という 形状をもっていて、各行がみな vのコピーであるかのように、和が要素ごとに計算されるのです。

2つの配列に対するブロードキャストは次の規則に従います:

  1. もしも配列が同じ階数でなければ、小さい階数の配列の形状に対し、両者の形状が同じ長さになるまで1を付け加える。
  2. 2つの配列がある次元において「両立可能 compatible」 であるとは、 それらがその次元において同じサイズを持つか、どちらかがその次元のサイズが1の場合をいう。
  3. どの次元においても両立可能ならば、配列はともにブロードキャストが可能である。
  4. ブロードキャストにより、2つの入力配列は、それらのうち、次元ごとに最大の形状をもっているように振る舞う
  5. 一方の配列がサイズ1で、もう一方の配列のサイズが1よりも大きい場合、前者の配列はその次元にそってコピーされたかのように振る舞う。

もしもこの説明で意味がわからなければ、 この解説か、 この解説nを読んでみよう。

ブロードキャストができる関数は 汎化関数と呼ばれている。どんなものがそうかは、 このドキュメント を参照すること.

以下に、ブロードキャストの使用例をいくつか示す:

In [12]:
import numpy as np

# ベクトルの外積計算
v = np.array([1,2,3])  # v の形状 (3,)
w = np.array([4,5])    # w の形状 (2,)
# 外積を計算するには、まずvの形状を変えて、
# 形状 (3, 1)の列ベクトルにする; 次にこれをwに対してブロードキャストし、
# 形状 (3, 2)の出力を得る。これが v と wの外積である
# [[ 4  5]
#  [ 8 10]
#  [12 15]]
print (np.reshape(v, (3, 1)) * w)

# ベクトルを行列のそれぞれの「列」に加える
x = np.array([[1,2,3], [4,5,6]])
# x は形状 (2, 3) で v は形状 (3,) であるから、それらをブロードキャストし
# 次のような形状 (2, 3)の行列を得る
# [[2 4 6]
#  [5 7 9]]
print (x + v)

# ベクトルを行列の各「行」に加える
# 行列x は形状 (2, 3) で、 w は形状 (2,)である。
# 行列 x を転置すると形状(3, 2) となり、これをwに対しブロードキャストすることで
# 形状(3, 2)の行列を得る;この結果を転置することで
# 形状 (2, 3)の行列を最終結果として得る。これは行列xの各列に
# ベクトル w を加えたものになっており、結果は:
# [[ 5  6  7]
#  [ 9 10 11]]
print ( (x.T + w).T )
# 別解:  wの形状を形状 (2, 1)のベクトルに変えると
# これを行列 x にブロードキャストして、同じ結果を得る
print (x + np.reshape(w, (2, 1)))

# 行列の定数倍:
# x は形状(2, 3) の行列とする. Numpy ではスカラーは形状 ()のベクトル扱いである
# これらは一緒にブロードキャストされ、形状(2, 3)の次の行列を得る:
# [[ 2  4  6]
#  [ 8 10 12]]
print (x * 2)
[[ 4  5]
 [ 8 10]
 [12 15]]
[[2 4 6]
 [5 7 9]]
[[ 5  6  7]
 [ 9 10 11]]
[[ 5  6  7]
 [ 9 10 11]]
[[ 2  4  6]
 [ 8 10 12]]

ブロードキャストを使うと、一般にコードが簡潔でしかも処理速度が速くなります。 ですから、可能な限りこれを使うよう、努めてください。

Numpyのドキュメント

この短い文書では、知らなければならない重要なことのホンのさわりしか紹介できていないので、 numpyの文献 を読んで、Numpyについて、よりよく知る努力をしてください。

SciPy

Numpyは高性能、多次元の配列を提供し、かつその配列の基本ツールや処理機能を提供している。 SciPyはNumpyをベースに、 その配列を用いた多くの関数や、いろいろな科学技術応用を提供している。

SciPyをよりよく知るには このドキュメントを見てほしい。 ここではSciPyについて役に立つほんの一部について紹介する。

画像処理

SciPyは画像を扱う基本関数を提供している。たとえば、 ディスクから画像を読み込みNumpyの配列に取り込んだり、その配列を画像としてディスクに書き込んだり、 画像のサイズを変えたりする関数がある。ここではそのような関数のいくつかについてお披露目しよう。

In [13]:
from scipy.misc import imread, imsave, imresize

# JPEG 画像ファイルを読み込み、numpy の配列として記憶する
img = imread('assets/cat.jpg')
print( img.dtype, img.shape ) # "uint8 (400, 248, 3)" と出力する

# 色のチャネルごとにいろいろな定数を掛けてスケーリングすることで
# 画像に色合いをつけることができる。この画像の形状は(400, 248, 3)である
# これに形状(3,) の定数ベクトル[1, 0.95, 0.9] をNumpyのブロードキャスト
# する。つまり赤色(第1チャネル)は変化させないが、緑(第2チャネル)と青
# (第3チャネル)の大きさをそれぞれ0.95倍、および0.9倍する

img_tinted = img * [1, 0.95, 0.9]

# 結果の画像を 300 x 300 ピクセルになるようサイズを変更する
img_tinted = imresize(img_tinted, (300, 300))

# この画像をディスクに書き込む
imsave('assets/cat_tinted.jpg', img_tinted)
uint8 (400, 248, 3)

<table border=0 >   </TR>

左: 原画像、 右: 色付けし、サイズ変更した画像 </TR> </TABLE>

MATLAB ファイル

scipy.io.loadmatscipy.io.savemat関数を使うと、 MATLABのファイルの読み書きができます。 これについては このドキュメントをご覧ください.

座標間の距離

SciPyは、点(座標)の集合の間の距離を計算するための有用な関数を用意しています、

関数 scipy.spatial.distance.pdistは、 集合中のすべての点と点のペアの間の距離を計算します

In [14]:
import numpy as np
from scipy.spatial.distance import pdist, squareform

# 各行が2D空間における点となっている配列を作る:
# [[0 1]
#  [1 0]
#  [2 0]]
x = np.array([[0, 1], [1, 0], [2, 0]])
print (x)

# xのすべての行の間のユークリッド距離を計算する 
# d[i, j] は x[i, :] と x[j, :] の間の距離となる
# 結果は次:
# [[ 0.          1.41421356  2.23606798]
#  [ 1.41421356  0.          1.        ]
#  [ 2.23606798  1.          0.        ]]
d = squareform(pdist(x, 'euclidean'))
print (d)
[[0 1]
 [1 0]
 [2 0]]
[[ 0.          1.41421356  2.23606798]
 [ 1.41421356  0.          1.        ]
 [ 2.23606798  1.          0.        ]]

この関数については この文書 で詳細を調べてください。

似たような関数として (scipy.spatial.distance.cdist) がある。 これは2つの点集合のすべての組み合わせに対して距離を計算する。これについては この文書 を参照のこと.

Matplotlib

Matplotlibは、描画のためのライブラリです。 ここでは、 matplotlib.pyplotモジュールについて簡単に紹介します。 これは、MATLABの描画機能と類似のシステムを提供しています。

プロット

matplotlibで最も重要な関数が plotです。これにより 2Dデータをプロットすることができます。例を示します:

In [16]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt

# x座標に対するsin関数の値を計算しy座標の値とする
x = np.arange(0, 3 * np.pi, 0.1)
y = np.sin(x)

# matplotlibを使ってプロットする
plt.plot(x, y)
plt.show()  # この plt.show() を呼ばなければ図が見えない

ちょっとだけ余計に作業することで、簡単にいくつもの線を同時に描画したり、題目をつけたり、 凡例をつけたり、軸のラベルをつけたりできます:

In [17]:
import numpy as np
import matplotlib.pyplot as plt

# x座標に対するsin関数の値を計算しy座標の値とする
x = np.arange(0, 3 * np.pi, 0.1)
y_sin = np.sin(x)
y_cos = np.cos(x)

# matplotlibを使ってプロットする
plt.plot(x, y_sin)
plt.plot(x, y_cos)
plt.xlabel('x axis label')  # ラベルに日本語は使えないと思って良い
plt.ylabel('y axis label')
plt.title('Sine and Cosine')
plt.legend(['Sine', 'Cosine'])
plt.show()     #  これを最後に呼ぶこと

plot 関数について、詳しくは この文書をご覧ください

Subplot

いろいろな図を同じ画面に表示するにはsubplot関数を使います。 次の例をみてください:

In [18]:
import numpy as np
import matplotlib.pyplot as plt

# x座標に対応するsinとcos関数の値を計算する 
x = np.arange(0, 3 * np.pi, 0.1)
y_sin = np.sin(x)
y_cos = np.cos(x)

# subplotの枠を設定:高さ2、幅1とする,
# そして、最初の枠をactiveにする
plt.subplot(2, 1, 1)

# 最初の図を描画する
plt.plot(x, y_sin)
plt.title('Sine')

# 2番めの枠をactiveにして、別な図を描画する
plt.subplot(2, 1, 2)
plt.plot(x, y_cos)
plt.title('Cosine')

# 図を表示する(これを最後に呼ぶこと)
plt.show()

subplot 関数について、詳しくは この文書をご覧ください

画像

imshow 関数を使って画像を表示できます。次の例をみてください:

In [19]:
import numpy as np
from scipy.misc import imread, imresize
import matplotlib.pyplot as plt

img = imread('assets/cat.jpg')
img_tinted = img * [1, 0.95, 0.9]

# 原画像を表示
plt.subplot(1, 2, 1)
plt.imshow(img)

# 色を変えた画像を表示
plt.subplot(1, 2, 2)

# A slight gotcha with imshow is that it might give strange results
# if presented with data that is not uint8. To work around this, we
# explicitly cast the image to uint8 before displaying it.
plt.imshow(np.uint8(img_tinted))
plt.show()
In [ ]: