PythonでNumpyを使ったベクトル・行列の演算や外積について解説していく

スポンサーリンク

今回は、「ベクタライズ」について学びます。

最近の複雑なシステムの多くは、大量のデータを扱います。

Pythonでこのような大量のデータを処理すると、C/C++などの他の言語と比較して、処理速度が遅くなることがあります。

そこで登場するのがベクトル化です。

この記事では、Pythonプログラムの実行時間を比較して高速化する、NumPyにおける配列のベクトル化演算について学びます。

スポンサーリンク

Pythonでのベクトル化

ベクタライズとは、forループを使わずに配列の演算を実装する技術です。

その代わりに、高度に最適化された様々なモジュールで定義された関数を使用し、コードの実行と実行時間を短縮します。

ベクトル化された配列操作は、純粋なPythonの同等品よりも高速になり、あらゆる種類の数値計算で最大の効果を発揮します。

Pythonのfor-loopは、C/C++の対応するものよりも遅いです。

Pythonはインタプリタ言語であり、ほとんどの実装は遅いです。

この遅い計算の主な理由は、Pythonの動的な性質と、メモリのオーバーヘッドを発生させるコンパイラレベルの最適化がないことに起因しています。

Pythonの配列のC実装であるNumPyは、NumPyの配列に対してベクトル化されたアクションを提供します。

NumPyを用いたベクトル演算

1. スカラーによる加算・減算・乗算・除算

スカラー量による配列の加算、減算、乗算、除算は、配列の全要素を与えられたスカラーで更新しながら、同じ寸法の配列になる。

この操作は、変数と同じように適用します。

このコードは、for-loopの実装と比較して、小さく、かつ高速に実行することができます

実行時間の計算には、timeitモジュールに含まれる Timer クラスを使用し、実行するステートメントを受け取り、そのステートメントを何回繰り返すかを受け取る timeit() メソッドを呼び出します。

なお、出力の計算時間はハードウェアなどに依存し、常に全く同じになるわけではない。

import numpy as np
from timeit import Timer
 
# Creating a large array of size 10**6
array = np.random.randint(1000, size=10**6)
 
# method that adds elements using for loop
def add_forloop():
  new_array = [element + 1 for element in array]
 
# method that adds elements using vectorization
def add_vectorized():
  new_array = array + 1
 
# Finding execution time using timeit
computation_time_forloop = Timer(add_forloop).timeit(1)
computation_time_vectorized = Timer(add_vectorized).timeit(1)
 
print("Computation time is %0.9f using for-loop"%execution_time_forloop)
print("Computation time is %0.9f using vectorization"%execution_time_vectorized)
Computation time is 0.001202600 using for-loop
Computation time is 0.000236700 using vectorization

2. 配列の和と最大値

配列の要素の合計と最大値を求めるには、forループとpythonの組み込みメソッドである sum()max()をそれぞれ使用することができます

この2つの方法をnumpyの演算と比較してみましょう。

import numpy as np
from timeit import Timer
 
# Creating a large array of size 10**5
array = np.random.randint(1000, size=10**5)
 
def sum_using_forloop():
  sum_array=0
  for element in array:
    sum_array += element
 
def sum_using_builtin_method():
  sum_array = sum(array)
 
def sum_using_numpy():
  sum_array = np.sum(array)
 
time_forloop = Timer(sum_using_forloop).timeit(1)
time_builtin = Timer(sum_using_builtin_method).timeit(1)
time_numpy = Timer(sum_using_numpy).timeit(1)
 
print("Summing elements takes %0.9f units using for loop"%time_forloop)
print("Summing elements takes %0.9f units using builtin method"%time_builtin)
print("Summing elements takes %0.9f units using numpy"%time_numpy)
 
print()
 
def max_using_forloop():
  maximum=array[0]
  for element in array:
    if element > maximum:
      maximum = element
 
def max_using_builtin_method():
  maximum = max(array)
 
def max_using_numpy():
  maximum = np.max(array)
 
time_forloop = Timer(max_using_forloop).timeit(1)
time_builtin = Timer(max_using_built-in_method).timeit(1)
time_numpy = Timer(max_using_numpy).timeit(1)
 
print("Finding maximum element takes %0.9f units using for loop"%time_forloop)
print("Finding maximum element takes %0.9f units using built-in method"%time_builtin)
print("Finding maximum element takes %0.9f units using numpy"%time_numpy)
Summing elements takes 0.069638600 units using for loop
Summing elements takes 0.044852800 units using builtin method
Summing elements takes 0.000202500 units using numpy
 
Finding maximum element takes 0.034151200 units using for loop
Finding maximum element takes 0.029331300 units using builtin method
Finding maximum element takes 0.000242700 units using numpy

ここで、numpyの演算は組み込みメソッドよりも高速であり、forループよりも高速であることがわかります。

3. ドットプロダクト

内積とも呼ばれ、2つのベクトルの内積は、同じ長さの2つのベクトルを取り、1つのスカラー量を返す代数演算です。

両ベクトルの要素ごとの積の和として計算される。

行列の場合、サイズ nx1 の 2 つの行列 a と b が与えられると、内積は最初の行列の転置を取り、次に aT (a の転置) と b の数学的な行列乗算を行うことで実現されます。

NumPy では、2つのベクトルの内積を求めるために、以下のように dot() メソッドを使用します。

import numpy as np
from timeit import Timer
 
# Create 2 vectors of same length
length = 100000
vector1 = np.random.randint(1000, size=length)
vector2 = np.random.randint(1000, size=length)
 
# Finds dot product of vectors using for loop
def dotproduct_forloop():
  dot = 0.0
  for i in range(length):
    dot += vector1[i] * vector2[i]
 
# Finds dot product of vectors using numpy vectorization
def dotproduct_vectorize():
  dot = np.dot(vector1, vector2)
   
 
# Finding execution time using timeit
time_forloop = Timer(dotproduct_forloop).timeit(1)
time_vectorize = Timer(dotproduct_vectorize).timeit(1)
 
print("Finding dot product takes %0.9f units using for loop"%time_forloop)
print("Finding dot product takes %0.9f units using vectorization"%time_vectorize)
Finding dot product takes 0.155011500 units using for loop
Finding dot product takes 0.000219400 units using vectorization

4. 外積

2つのベクトルの外積は、直方体の行列を生成します。

サイズ nx1 と mx1 の 2 つのベクトル a と b があるとき、これらのベクトルの外積はサイズ nxm の行列を生成します。

NumPy では、以下のように outer() メソッドを使用して 2 つのベクトルの外積を求めます。

import numpy as np
from timeit import Timer
 
# Create 2 vectors of same length
length1 = 1000
length2 = 500
vector1 = np.random.randint(1000, size=length1)
vector2 = np.random.randint(1000, size=length2)
 
# Finds outer product of vectors using for loop
def outerproduct_forloop():
  outer_product = np.zeros((length1, length2), dtype='int')
  for i in range(length1):
    for j in range(length2):
      outer_product[i, j] = vector1[i] * vector2[j]
 
# Finds outer product of vectors using numpy vectorization
def outerproduct_vectorize():
  outer_product = np.outer(vector1, vector2)
   
# Finding execution time using timeit
time_forloop = Timer(outerproduct_forloop).timeit(1)
time_vectorize = Timer(outerproduct_vectorize).timeit(1)
 
print("Finding outer product takes %0.9f units using for loop"%time_forloop)
print("Finding outer product takes %0.9f units using vectorization"%time_vectorize)
Finding outer product takes 0.626915200 units using for loop
Finding outer product takes 0.002191900 units using vectorization

5. 行列の乗算

行列の乗算は、第1の行列の行と第2の行列の列を掛け合わせる代数演算です。

次元がp×q、r×sの2つの行列が乗算されるためには、q==rが必要条件です。

乗算後の行列は、p×sの次元となります。

行列の乗算は、機械学習などの数学モデルで広く使われている演算です。

行列の掛け算の計算は計算コストが高く、システムが高速に実行するためには、高速な処理が必要です。

NumPyでは、以下のようにmatmul()メソッドを使って2つの行列の掛け算を求めます。

import numpy as np
from timeit import Timer
 
# Create 2 vectors of same length
n = 100
k = 50
m = 70
matrix1 = np.random.randint(1000, size=(n, k))
matrix2 = np.random.randint(1000, size=(k, m))
 
# Multiply 2 matrices using for loop
def matrixmultiply_forloop():
  product = np.zeros((n, m), dtype='int')
  for i in range(n):
    for j in range(m):
      for z in range(k):
        product[i, j] += matrix1[i, z] * matrix2[z, j]
 
# Multiply 2 matrices using numpy vectorization
def matrixmultiply_vectorize():
  product = np.matmul(matrix1, matrix2)
   
# Finding execution time using timeit
time_forloop = Timer(matrixmultiply_forloop).timeit(1)
time_vectorize = Timer(matrixmultiply_vectorize).timeit(1)
 
print("Multiplying matrices takes %0.9f units using for loop"%time_forloop)
print("Multiplying matrices takes %0.9f units using vectorization"%time_vectorize)
Multiplying matrices takes 0.777318300 units using for loop
Multiplying matrices takes 0.000984900 units using vectorization

6. 行列の要素ワイズプロ

2つの行列の要素ごとの積は、最初の行列の各要素に、2番目の行列の対応する要素を乗じる代数演算です。

行列の次元は同じでなければならない。

NumPy では、以下のように * 演算子を使用して 2 つのベクトルの要素毎の積を求めます。

import numpy as np
from timeit import Timer
 
# Create 2 vectors of same length
n = 500
m = 700
matrix1 = np.random.randint(1000, size=(n, m))
matrix2 = np.random.randint(1000, size=(n, m))
 
# Multiply 2 matrices using for loop
def multiplication_forloop():
  product = np.zeros((n, m), dtype='int')
  for i in range(n):
    for j in range(m):
      product[i, j] = matrix1[i, j] * matrix2[i, j]
 
# Multiply 2 matrices using numpy vectorization
def multiplication_vectorize():
  product = matrix1 * matrix2
 
# Finding execution time using timeit
time_forloop = Timer(multiplication_forloop).timeit(1)
time_vectorize = Timer(multiplication_vectorize).timeit(1)
 
print("Element Wise Multiplication takes %0.9f units using for loop"%time_forloop)
print("Element Wise Multiplication takes %0.9f units using vectorization"%time_vectorize)
Element Wise Multiplication takes 0.543777400 units using for loop
Element Wise Multiplication takes 0.001439500 units using vectorization

まとめ

ベクトル化は、複雑なシステムや数学的モデルにおいて、実行速度やコードサイズの削減のために広く使用されています。

これで Python でのベクトル化の使い方がわかったので、これを応用してプロジェクトの実行を高速化することができます

読んでくれてありがとうございます。

タイトルとURLをコピーしました