こんにちは!スタビジ編集部です!
AIやデータ分析、自動化などを実現するため、”Python“を使ってプログラムやサービスを開発する人が増えています。
Pythonは機能が充実していて、初心者にも学習がしやすい面から利用者の多いプログラミング言語です。
一方で実際にコーディングすると以下の悩みを持つことがあるのではないでしょうか。
Pythonは直感的な文法や豊富なライブラリがある一方で、他のプログラミング言語と比べると実行速度が遅いとされることがあります。
今回はそんなPythonの高速化テクニックについて見ていくよ!
Pythonについて基礎から体系的に学びたい人は当メディアが運営する「スタアカ」の以下のコースをチェックしてみて下さい。
目次
Pythonで高速化が必要な理由
まずはPythonで高速化が必要な背景について以下の観点から見ていきましょう。
・Pythonの課題
・Pythonで高速化が求められる場面
Pythonの課題
Pythonの課題として以下が挙げられます。
- インタプリタ型言語でコードを逐次翻訳しながら実行するため実行速度が遅い
- 動的型付けにより、裏での処理が余分で発生するため時間がかかる
- メタデータを多く含むことによるメモリ効率の悪さ
インタプリタ型言語のため実行速度が遅い
プログラミングでは、人間が書いたコードをコンピューターが理解できる言語に翻訳(コンパイル)してプログラムを実行します。
プログラミング言語でその翻訳の処理方法が大きく以下の2つに分かれます
- コンパイラ言語:事前に一括して翻訳し、翻訳したファイルを実行する言語
- インタプリタ型言語:1行ずつコードを逐次翻訳しながら実行する言語
Pythonは「インタプリタ型言語」に分類され、細かく処理を確認しながらコーディングが出来る反面、「コンパイラ言語」と比較すると実行処理が遅いデメリットがあります。
動的型付けにより、裏での処理が余分で発生するため時間がかかる
Pythonでは他のプログラミング言語で見られるデータ型を明示的に指定する必要がありません。
x = 10 # 整数
y = "Hello" # 文字列
これはPythonが自動的に「これは整数」、「これは文字列」と判断してくれるためで、この機能を”動的型付け“と呼ばれます。
この機能のおかげで初心者でも簡単にデータ型を扱える一方で、Python側で型の変換作業を行っているため、プログラムの実行時間としては長くなってしまうデメリットがあります。
メタデータを多く含むことによるメモリ効率の悪さ
Pythonではデータ(例:x=10)の中にデータそのもの(10)に加えて”メタデータ“が含まれます。
x = 10
print(dir(x))
>>実行結果
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
メタデータには”型情報”や”算術用メソッド”などが含まれていて一つのデータに様々な情報が付与されています。
Pythonではメタデータにより柔軟なデータの扱いが出来る一方で、一つのデータに大量の情報が含まれるため、メモリ消費が増えてしまいます。
そのため、Pythonで大量のデータにアクセスする場合、データ内のメタデータとの処理がも発生するため、実行速度の低下やメモリ不足になるデメリットがあります。
Pythonで高速化が求められる場面
Pythonはデータ分析やWebアプリケーションなど様々な場面で利用されます。
普通にプログラムを実行する場合ではそこまで実行速度は気になりませんが、以下のような場面で高速化が求められます。
・大量のデータを扱うデータサイエンスや機械学習
・リアルタイム性が求められるWebアプリケーションやIoT
大量のデータを使用する場合にはメタデータや動的型付けが実行速度に大きく影響を与えてしまいます。
また、ユーザーが入力した値や取得したデータを元に即座に結果を求められる場面でも、実行速度は重要になります。
Pythonの高速化テクニック
ここからPythonの「高速化テクニック」について見ていきます。
今回は以下のテクニックについて解説していきます。
- ベクトル演算ライブラリの使用
- ライブラリを使用したJITコンパイル
- 並列化処理
一つ一つサンプルを実装しながら進めていくので、自身の環境でも試してみて下さい。
本記事でのPythonの作業は「Jupyter Notebook」で行っています。
Jupyter NotebookはWebブラウザ上でPythonプログラムを書いて・実行出来る環境です。
プログラムの実行結果を毎回確認できるので初心者におすすめのツールになります。
Jupyter Notebookについて以下の記事で解説しているので、参考にしてみて下さい。
ベクトル演算ライブラリの使用
Pythonではデータ分析やデータサイエンスで大量のデータをリストやループ処理で扱うことが多いです。
その際、単純にPython記法で記述するより「NumPy」や「Pandas」といったベクトル演算ライブラリを使用した方が、効率的に処理できます。
NumPyやPandasについては下記の記事で解説しているので、参考にしてみて下さい。
それぞれサンプルを見ていきましょう。
NumPy
まずは「NumPy」について見ていきます。
import numpy as np
import time
size = 10**6
# Pythonリスト
start_time = time.time()
data = [i for i in range(size)]
result = [x**2 for x in data]
end_time = time.time()
print(f"Pythonリストの計算時間: {end_time - start_time:.4f}秒")
# NumPy配列
start_time = time.time()
np_data = np.arange(size)
np_result = np_data**2
end_time = time.time()
print(f"NumPy配列の計算時間: {end_time - start_time:.4f}秒")
ここでは100万個のデータをリストに格納し、さらにその各要素を2乗する処理を行っています。
Pythonのリストを使う場合とNumPy配列を使った場合の処理時間の結果を比較しており、実際の処理時間の結果は以下のようになりました。
どちらの処理の内容自体は同じですが、NumPyでは”ベクトル化”と呼ばれる配列全体に対して一括処理を行っているため、処理時間が短くなります。
また、NumPyでは内部の演算処理はC言語を使って効率的に行っているため、単純なPythonの演算よりも早く処理できます。
Pandas
続いて、「Pandas」での処理を見ていきます。
import random
import pandas as pd
import numpy as np
import time
# データサイズ
size = 10**6
# Pythonリストを使ったフィルタリング
data_list = [random.randint(0, 100) for _ in range(size)]
start = time.time()
filtered_data_list = [x for x in data_list if x >= 50]
end = time.time()
print(f"Pythonリストのフィルタリング時間: {end - start:.4f}秒")
# Pandasを使ったフィルタリング
data_pandas = pd.DataFrame({'A': np.random.randint(0, 100, size)})
start = time.time()
filtered_data_pandas = data_pandas[data_pandas['A'] >= 50]
end = time.time()
print(f"Pandasのフィルタリング時間: {end - start:.4f}秒")
# Python辞書を使ったグループ化、平均値処理
data_list = [(random.randint(0, 10), random.random()) for _ in range(size)]
start = time.time()
grouped_dict = {}
for key, value in data_list:
if key not in grouped_dict:
grouped_dict[key] = []
grouped_dict[key].append(value)
averages_dict = {k: sum(v) / len(v) for k, v in grouped_dict.items()}
end = time.time()
print(f"Python辞書でのグループ化時間: {end - start:.4f}秒")
# Pandasを使ったグループ化、平均値処理
data_pandas = pd.DataFrame({
'A': np.random.randint(0, 10, size),
'B': np.random.rand(size)
})
start = time.time()
grouped_pandas = data_pandas.groupby('A')['B'].mean()
end = time.time()
print(f"Pandasのグループ化時間: {end - start:.4f}秒")
ここでは100万個のデータに対して、フィルタリングの時間と辞書型配列のグループ化・平均値処理にかかる時間を計測しています。
Pythonでは要素一つ一つに対してフィルタリングするのに対し、Pandasではベクトル化により列全体に対して一括処理が出来るため、早くなります。
また、各列でデータ型が固定化されているため、型チェックといった処理がなくなることも処理時間の
グループ化や平均値計算では、これらの演算処理がPandas側であらかじめ最適なアルゴリズムで実装されているため、それを使用することで高速に処理をすることが出来ます。
また、Pandas以外にもデータ加工集計処理に「Polars」と呼ばれるライブラリもあるので、下記記事でチェックしてみて下さい。
詳しくPolarsについて知りたい方は以下のUdemyのコースで私が解説していますのでチェックしてみてください!
【初心者向け】PythonのPolarsを使ってデータ加工集計・データ分析を高速化!Pandasと比較してみよう!
【時間】 | 3時間 |
---|---|
【レベル】 | 初級 |
Pythonの高速化ライブラリであるPolarsについて知りたいならこのコース!
今なら購入時に「GT34R35G27DD」という講師クーポンコードを入れると94%OFFになりますのでぜひご受講ください!
ライブラリを使用したJITコンパイル
Pythonでは課題にインタプリタ型言語のため、一つ一つコードを翻訳しながら実行するため、処理時間が遅くなることがありました。
その課題を解決するために用いるのが「JIT(Just-In-Time)コンパイル」と呼ばれる手法になります。
「JIT(Just-In-Time)コンパイル」とはプログラムの一部を実行時に機械語に変換して実行速度を向上させる技術です。
Pythonでは、「Numba」といったJITコンパイル用のライブラリを利用することで計算速度を向上させることができます。
from numba import njit
import time
# 再帰的にフィボナッチ数列を計算
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# 実行と計測
start_time = time.time()
result = fibonacci(35)
end_time = time.time()
print(f"フィボナッチ数列(Python標準): {result}")
print(f"実行時間: {end_time - start_time:.4f}秒")
# JITコンパイルを適用したフィボナッチ関数
@njit
def fibonacci_jit(n):
if n <= 1:
return n
return fibonacci_jit(n - 1) + fibonacci_jit(n - 2)
# 実行と計測
start_time = time.time()
result = fibonacci_jit(35)
end_time = time.time()
print(f"フィボナッチ数列(Numba JIT): {result}")
print(f"実行時間: {end_time - start_time:.4f}秒")
再帰的にフィボナッチ数列の計算を行う関数をそのまま実行する場合とNumbaでJITコンパイルする場合を比較しています。
JITコンパイル自体はNumbaを使用して”@njit“をデコレータとして関数につけるだけで行われます。
処理結果を見ていきます。
JITコンパイルした方が約2秒ほど処理時間が短いことがわかります。
またこのプログラムを再度実行すると、すでにフィボナッチ数列の計算を行う関数がJITコンパイルされているため、より短い処理結果が得られます。
そのため、再帰的な処理を行う場合や配列で繰り返し処理を行う場合、常にプログラムを実行しているWebサービスでJITコンパイルが効果的です。
一方で、JITコンパイルは最初の関数実行時にコンパイル処理を行うため、1回しか処理を行わない関数はPython標準で実行した方が速いので注意が必要です。
並列化処理
プログラミングの高速化テクニックとしてPythonに限らずよく利用されるのが「並列化処理」になります。
「並列化処理」とは複数の独立したタスクを同時に実行することで処理を効率化する技術です。
Pythonでは逐次コードが実行されますが、「multiprocessing」や「concurrent.futures」といったライブラリを使用することで並列化処理を行うことが可能です。
ただし、Juyptyer Notebookで並列化処理を行う場合は制約があるため、今回は”multiprocess.py”というファイルを作成して実行します。
import os
import time
from concurrent.futures import ProcessPoolExecutor
from PIL import Image
# 入力画像フォルダと出力フォルダ
INPUT_DIR = "input_images"
OUTPUT_DIR_SEQUENTIALLY = "output_images_sequentially"
OUTPUT_DIR_PARALLEL = "output_images_parallel"
# リサイズする画像の新しいサイズ
NEW_SIZE = (256, 256)
def resize_image(file_name, output_dir):
"""画像をリサイズして保存する"""
input_path = os.path.join(INPUT_DIR, file_name)
output_path = os.path.join(output_dir, file_name)
try:
with Image.open(input_path) as img:
img = img.resize(NEW_SIZE)
img.save(output_path)
except Exception as e:
print(f"{file_name} の処理でエラー: {e}")
if __name__ == "__main__":
# 処理対象の画像ファイル一覧を取得
image_files = [f for f in os.listdir(INPUT_DIR) if f.endswith(('.jpg', '.png'))]
print("==== 逐次処理 ====")
start_time = time.time()
for file_name in image_files:
resize_image(file_name, 'output_images_sequentially')
sequential_time = time.time() - start_time
print(f"逐次処理時間: {sequential_time:.4f} 秒\n")
print("==== 並列処理 ====")
start_time = time.time()
with ProcessPoolExecutor(max_workers=4) as executor: # 最大4プロセスで並列実行
for file_name in image_files:
future = executor.submit(resize_image, file_name, 'output_images_parallel') # 並列処理でリサイズ
result = future.result()
parallel_time = time.time() - start_time
print(f"並列処理時間: {parallel_time:.4f} 秒")
ここでは、160枚の画像をリサイズして指定されたフォルダに保存する処理を行います。
並列化処理では「concurrent.futures」ライブラリの「ProcessPoolExecutor」クラスを用いて実行します。
「ProcessPoolExecutor」クラスでは、複数プロセスを管理し、指定した関数を指定された最大プロセスまで並列に実行することが出来ます。
実行した結果が以下になります。
並列処理の実行結果の方が約2秒ほど速い結果となりました。
ただ並列化処理を実行する上で以下の点に注意すると良いです。
・並列化処理は大量のPCリソースを使用するので、実行数に注意
・並列するプロセスは互いに独立させる(プロセス間でデータ共有に注意)
・Pythonは標準で、GIL(Global Interpreter Lock)という仕組みにより、同時に1つのスレッドしか実行できないことに注意
小さいタスクや依存関係があるタスクの場合はエラーになることがあるので、実装の際は意識してみて下さい。
Python高速化テクニック まとめ
Pythonの「高速化テクニック」について見ていきました。
今回は高速化テクニックとして以下の手法を実装してみました。
- ベクトル演算ライブラリの使用
- ライブラリを使用したJITコンパイル
- 並列化処理
既存のPythonプログラムに対してひと工夫を加えることで、処理時間が短くなったことを確認できたと思います。
他にもプログラムのアルゴリズムの工夫や別言語との組み合わせによっても高速化をすることが出来るので、ぜひいろいろ調べて試してみて下さい。
また、初心者だけど本格的にPythonでアプリ開発をやってみたい方は、当メディアが運営する教育サービス「スタアカ(スタビジアカデミー)」を以下の講座チェックしてみてください。
AIデータサイエンス特化スクール「スタアカ」
【価格】 | ライトプラン:1280円/月 プレミアムプラン:149,800円 |
---|---|
【オススメ度】 | |
【サポート体制】 | |
【受講形式】 | オンライン形式 |
【学習範囲】 | データサイエンスを網羅的に学ぶ 実践的なビジネスフレームワークを学ぶ SQLとPythonを組み合わせて実データを使った様々なワークを行う マーケティングの実行プラン策定 マーケティングとデータ分析の掛け合わせで集客マネタイズ |
データサイエンティストとしての自分の経験をふまえてエッセンスを詰め込んだのがこちらのスタビジアカデミー、略して「スタアカ」!!
24時間以内の質問対応と現役データサイエンティストによる複数回のメンタリングを実施します!
カリキュラム自体は、他のスクールと比較して圧倒的に良い自信があるのでぜひ受講してみてください!
他のスクールのカリキュラムはPythonでの機械学習実装だけに焦点が当たっているものが多く、実務に即した内容になっていないものが多いです。
そんな課題感に対して、実務で使うことの多いSQLや機械学習のビジネス導入プロセスの理解なども合わせて学べるボリューム満点のコースになっています!
Pythonが初めての人でも学べるようなカリキュラムしておりますので是非チェックしてみてください!
ウォルマートのデータを使って商品の予測分析をしたり、実務で使うことの多いGoogleプロダクトのBigQueryを使って投球分析をしたり、データサイエンティストに必要なビジネス・マーケティングの基礎を学んでマーケティングプランを作ってもらったり・Webサイト構築してデータ基盤構築してWebマーケ×データ分析実践してもらったりする盛りだくさんの内容になってます!
・BigQuery上でSQL、Google Colab上でPythonを使い野球の投球分析
・世界最大手小売企業のウォルマートの実データを用いた需要予測
・ビジネス・マーケティングの基礎を学んで実際の企業を題材にしたマーケティングプランの策定
・Webサイト構築してデータ基盤構築してWebマーケ×データ分析実践して稼ぐ