[Перевод] Как сократить время выполнения ресурсоемких задач в Python
Всем привет! На связи Spectr и рубрика «Что читают наши разработчики». Сегодня разберем практические методы ускорения тяжелых вычислений с помощью оптимизации на GPU в Python.
Одна из самых больших проблем, с которой сталкиваются специалисты по данным, это длительное время выполнения Python-кода при работе с чрезвычайно большими наборами данных или сверхсложными моделями машинного/глубокого обучения. Существует множество методов, которые доказали свою эффективность в повышении производительности кода. Например, снижение размерности, оптимизация моделей и отбор признаков — это алгоритмические решения. В некоторых случаях еще одним вариантом решения этой проблемы может быть использование другого языка программирования. В сегодняшней статье мы не будем сосредотачиваться на алгоритмических методах повышения эффективности кода. Вместо этого расскажем о практических техниках, которые удобны и просты в освоении.
Для иллюстрации мы используем набор данных Online Retail, который является общедоступным и распространяется по лицензии Creative Commons Attribution 4.0 International (CC BY 4.0). Вы можете скачать оригинальный набор данных Online Retail из репозитория UCI Machine Learning Repository. Этот набор данных содержит все транзакционные данные, происходящие в определенный период времени для зарегистрированного в Великобритании онлайн-ретейлера без физического магазина. Цель заключается в том, чтобы обучить модель предсказывать, совершит ли клиент повторную покупку, и для достижения этой цели используется следующий Python-код:
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from itertools import product
# Load dataset from Excel file
data = pd.read_excel('Online Retail.xlsx', engine='openpyxl')
# Data preprocessing
data = data.dropna(subset=['CustomerID'])
data['InvoiceYearMonth'] = data['InvoiceDate'].astype('datetime64[ns]').dt.to_period('M')
# Feature Engineering
data['TotalPrice'] = data['Quantity'] * data['UnitPrice']
customer_features = data.groupby('CustomerID').agg({
'TotalPrice': 'sum',
'InvoiceYearMonth': 'nunique', # Count of unique purchase months
'Quantity': 'sum'
}).rename(columns={'TotalPrice': 'TotalSpend', 'InvoiceYearMonth': 'PurchaseMonths', 'Quantity': 'TotalQuantity'})
# Create the target variable
customer_features['Repurchase'] = (customer_features['PurchaseMonths'] > 1).astype(int)
# Train-test split
X = customer_features.drop('Repurchase', axis=1)
y = customer_features['Repurchase']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Model training
clf = RandomForestClassifier()
clf.fit(X_train, y_train)
# Define different values for parameters
n_estimators_options = [50, 100, 200]
max_depth_options = [None, 10, 20]
class_weight_options = [None, 'balanced']
# Train the RandomForestClassifier with different combinations of parameters
results = []
for n_estimators, max_depth, class_weight in product(n_estimators_options, max_depth_options, class_weight_options):
clf = RandomForestClassifier(n_estimators=n_estimators, max_depth=max_depth, class_weight=class_weight, random_state=42)
clf.fit(X_train, y_train)
accuracy = clf.score(X_test, y_test)
results.append((n_estimators, max_depth, class_weight, accuracy))
Выполнение кода занимает некоторое время из-за обработки 541 909 строк данных. В таких отраслях, как электронная коммерция или социальные сети, специалисты по данным часто работают с еще большими наборами данных — иногда с миллиардами или даже триллионами строк с большим количеством признаков. Кроме того, существуют комбинации структурированных и неструктурированных данных, текста, изображений или видео, — эти различные типы данных, безусловно, увеличивают нагрузку. Поэтому крайне важно применять методы для оптимизации эффективности кода. Чтобы упростить объяснения, продолжим работать с данными Online Retail. Перед тем как представить эти методы, измеряем время, необходимое для выполнения всего Python-скрипта, чтения данных Online Retail и обучения модели машинного обучения.
import time
# Function to calculate and print elapsed time
def time_execution(func, *args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
elapsed_time = time.time() - start_time
return result, elapsed_time
# 1. Full Python code execution timing
def complete_process():
# Load dataset from Excel file
data = pd.read_excel('Online Retail.xlsx', engine='openpyxl')
# Data preprocessing
data = data.dropna(subset=['CustomerID'])
data['InvoiceYearMonth'] = data['InvoiceDate'].astype('datetime64[ns]').dt.to_period('M')
# Feature Engineering
data['TotalPrice'] = data['Quantity'] * data['UnitPrice']
customer_features = data.groupby('CustomerID').agg({
'TotalPrice': 'sum',
'InvoiceYearMonth': 'nunique',
'Quantity': 'sum'
}).rename(columns={'TotalPrice': 'TotalSpend', 'InvoiceYearMonth': 'PurchaseMonths', 'Quantity': 'TotalQuantity'})
customer_features['Repurchase'] = (customer_features['PurchaseMonths'] > 1).astype(int)
# Train-test split
X = customer_features.drop('Repurchase', axis=1)
y = customer_features['Repurchase']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Model training with parameter combinations
results = []
for n_estimators, max_depth, class_weight in product(n_estimators_options, max_depth_options, class_weight_options):
clf = RandomForestClassifier(n_estimators=n_estimators, max_depth=max_depth, class_weight=class_weight, random_state=42)
clf.fit(X_train, y_train)
accuracy = clf.score(X_test, y_test)
results.append((n_estimators, max_depth, class_weight, accuracy))
return results
# Measure total execution time
results, total_time = time_execution(complete_process)
print(f"Total execution time for the entire process: {total_time} seconds")
# 2. Timing the Excel file reading
def read_excel():
return pd.read_excel('Online Retail.xlsx', engine='openpyxl')
# Measure time taken to read the Excel file
_, read_time = time_execution(read_excel)
print(f"Time taken to read the Excel file: {read_time} seconds")
# 3. Timing the model training
def train_model(X_train, y_train):
results = []
for n_estimators, max_depth, class_weight in product(n_estimators_options, max_depth_options, class_weight_options):
clf = RandomForestClassifier(n_estimators=n_estimators, max_depth=max_depth, class_weight=class_weight, random_state=42)
clf.fit(X_train, y_train)
accuracy = clf.score(X_test, y_test)
results.append((n_estimators, max_depth, class_weight, accuracy))
return results
# Measure time taken to train the model
_, train_time = time_execution(train_model, X_train, y_train)
print(f"Time taken to train the model: {train_time} seconds")
Весь процесс занимает примерно 20 секунд, из которых почти 18 секунд уходит на чтение файла данных.
Решение первое. Включение GPU и настройка роста оперативной памяти
По сравнению с процессорами, графические процессоры (GPU) идеально подходят для обработки больших наборов данных и сложных моделей, таких как глубокое обучение, поскольку они поддерживают параллельную обработку. Иногда разработчики забывают настроить рост памяти, что приводит к попытке GPU выделить всю память для модели с самого начала.
Итак, что такое рост памяти? Почему это так важно при использовании GPU? Рост памяти — это механизм, который позволяет GPU выделять память постепенно по мере необходимости, а не резервировать большой блок памяти заранее. Если рост памяти не настроен, а модель большая, может не хватить доступной памяти, что приведет к ошибке «out-of-memory» (OOM). В случаях, когда несколько моделей работают одновременно, одна модель может занять всю память GPU и не дать доступ другим моделям.
Таким образом, правильная настройка роста памяти позволяет эффективно использовать GPU, повышает гибкость и улучшает устойчивость процесса обучения для больших наборов данных и сложных моделей. После включения GPU и настройки роста памяти код работает следующим образом:
import tensorflow as tf
from sklearn.model_selection import train_test_split
import pandas as pd
from itertools import product
import time
# Enable GPU and Set Memory Growth
gpus = tf.config.experimental.list_physical_devices('GPU')
if gpus:
try:
for gpu in gpus:
tf.config.experimental.set_memory_growth(gpu, True)
except RuntimeError as e:
print(e)
# Function to calculate and print elapsed time
def time_execution(func, *args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
elapsed_time = time.time() - start_time
return result, elapsed_time
# Read Excel File
def read_excel():
return pd.read_excel('Online Retail.xlsx', engine='openpyxl')
# Complete Process Function
def complete_process():
# Load dataset from Excel file
data = read_excel()
# Data preprocessing
data = data.dropna(subset=['CustomerID'])
data['InvoiceYearMonth'] = data['InvoiceDate'].astype('datetime64[ns]').dt.to_period('M')
# Feature Engineering
data['TotalPrice'] = data['Quantity'] * data['UnitPrice']
customer_features = data.groupby('CustomerID').agg({
'TotalPrice': 'sum',
'InvoiceYearMonth': 'nunique',
'Quantity': 'sum'
}).rename(columns={'TotalPrice': 'TotalSpend', 'InvoiceYearMonth': 'PurchaseMonths', 'Quantity': 'TotalQuantity'})
customer_features['Repurchase'] = (customer_features['PurchaseMonths'] > 1).astype(int)
# Train-test split
X = customer_features.drop('Repurchase', axis=1)
y = customer_features['Repurchase']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Model training with parameter combinations
results = []
n_estimators_options = [50, 100]
max_depth_options = [None, 10]
class_weight_options = [None, 'balanced']
for n_estimators, max_depth, class_weight in product(n_estimators_options, max_depth_options, class_weight_options):
clf = RandomForestClassifier(n_estimators=n_estimators, max_depth=max_depth, class_weight=class_weight, random_state=42)
clf.fit(X_train, y_train)
accuracy = clf.score(X_test, y_test)
results.append((n_estimators, max_depth, class_weight, accuracy))
return results
# Measure total execution time
results, total_time = time_execution(complete_process)
print(f"Total execution time for the entire process: {total_time} seconds")
# Measure time taken to read the Excel file
_, read_time = time_execution(read_excel)
print(f"Time taken to read the Excel file: {read_time} seconds")
# Measure time taken to train the model
def train_model(X_train, y_train):
results = []
n_estimators_options = [50, 100]
max_depth_options = [None, 10]
class_weight_options = [None, 'balanced']
for n_estimators, max_depth, class_weight in product(n_estimators_options, max_depth_options, class_weight_options):
clf = RandomForestClassifier(n_estimators=n_estimators, max_depth=max_depth, class_weight=class_weight, random_state=42)
clf.fit(X_train, y_train)
accuracy = clf.score(X_test, y_test)
results.append((n_estimators, max_depth, class_weight, accuracy))
return results
_, train_time = time_execution(train_model, X_train, y_train)
print(f"Time taken to train the model: {train_time} seconds")
Время, затраченное на обучение модели, значительно сократилось — с 1,9 секунды до 0,6 секунды. Однако было замечено, что время, затраченное на чтение файла Excel, сократилось несущественно. Поэтому требуется другая техника для улучшения эффективности загрузки и обработки данных — оптимизация ввода/вывода с предварительной загрузкой данных в конвейере.
Решение второе. Оптимизация Disk I/O с предварительной загрузкой данных в конвейере
Ввод/вывод на диске может стать узким местом при чтении очень больших наборов данных. API tf.data в TensorFlow эффективно оптимизирует конвейеры ввода и улучшает эффективность загрузки и обработки данных. Это позволяет выполнять асинхронные операции и параллельную обработку. Причина, по которой это решение сокращает время загрузки и обработки данных, заключается в том, что оно создает непрерывный, оптимизированный поток данных от диска к конвейеру обработки, минимизируя задержки, связанные с чтением больших наборов данных, и синхронизируя с параллельной обработкой данных. Обновленный код для загрузки данных Online Retail.xlsx с использованием tf.data выглядит следующим образом:
import time
import pandas as pd
import tensorflow as tf
# Function to calculate and print elapsed time
def time_execution(func, *args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
elapsed_time = time.time() - start_time
return result, elapsed_time
# Function to load and preprocess dataset using tf.data
def load_data_with_tfdata(file_path, batch_size):
# Define a generator function to yield data from the Excel file
def data_generator():
data = pd.read_excel(file_path, engine='openpyxl')
for _, row in data.iterrows():
yield dict(row)
# Create a tf.data.Dataset from the generator
dataset = tf.data.Dataset.from_generator(
data_generator,
output_signature={col: tf.TensorSpec(shape=(), dtype=tf.float32) for col in data.columns}
)
# Apply shuffle, batch, and prefetch transformations
dataset = dataset.shuffle(buffer_size=1000).batch(batch_size).prefetch(tf.data.experimental.AUTOTUNE)
return dataset
# Load and preprocess dataset using tf.data.Dataset
file_path = 'Online Retail.xlsx'
batch_size = 32
dataset, data_load_time = time_execution(load_data_with_tfdata, file_path, batch_size)
print(f"Time taken to load and preprocess data with tf.data: {data_load_time} seconds")
Время, затраченное на загрузку данных, значительно сократилось — с 18 секунд до 0,05 секунды.
Необходимо правильно определить размер батча, поскольку размер данных, обрабатываемых на каждом шаге, влияет на потребление памяти и эффективность вычислений. Если размер батча не задан, он может по умолчанию быть равен 1, что делает загрузку и обработку данных или обучение модели очень неэффективными. Если размер батча установлен слишком большим или слишком маленьким, это может привести к неэффективному обучению, ошибкам памяти, более медленной сходимости или субоптимальной производительности модели.
Заключение
GPU хорошо подходят для обработки чрезвычайно больших наборов данных и сложных моделей, но без правильных настроек параметров их преимущества трудно будет эффективно использовать. Включение роста памяти GPU оптимизирует использование GPU и предотвращает ошибки памяти. Оптимизация ввода/вывода с предварительной загрузкой данных в конвейере значительно сокращает время загрузки и обработки данных. Эти техники вместе предоставляют практичные и эффективные решения для преодоления проблем в повседневной работе.