【完全保存版】Databricks環境でHyperoptが死んだ日 - Python 3.12の衝撃とOptunaへの移行を実測データで徹底検証
🚨 今すぐ確認!あなたのHyperoptコードは動いていますか?
「ModuleNotFoundError: No module named 'imp'」
このエラーメッセージを目にしたことはありませんか?もしあなたがPython 3.12環境でHyperoptを使おうとしているなら、このエラーは避けられない運命なのです。実は、これは単なるバージョン互換性の問題ではなく、Pythonエコシステムの大きな転換点を示す重要なシグナルなのです。
なぜ今、この記事を読むべきなのか
Databricksは2025年、ついにHyperoptのサポートを終了し、Optunaへの移行を公式に推奨しました。しかし、ここで衝撃的な事実をお伝えしなければなりません。私が実際に行った検証では、Hyperoptの方が処理速度で1.8倍速いという結果が出たのです。
「え?速いのに使えなくなるの?」
そうです。まさにこれが、技術選択の難しさと面白さなのです。
📊 処理速度の真実:予想外の検証結果が示すもの
では、Databricksが推奨するOptunaに移行した場合、最も気になる処理速度はどうなるのでしょうか。多くの方が「新しいツールの方が速いだろう」と期待されるかもしれませんが、私が実際にDatabricks環境で行った検証結果は、その予想を覆すものでした。
検証では、5000サンプル・20特徴量の3クラス分類問題に対して、ランダムフォレストのハイパーパラメータチューニングを20回試行しました。その結果、並列処理(4ワーカー)での高速化率は以下のようになりました:
- Hyperopt: 4.52倍の高速化(79.50秒 → 17.58秒)
- Optuna: 2.55倍の高速化(79.26秒 → 31.04秒)
つまり、純粋な処理速度の観点では、Hyperoptの方が並列化効率において約1.8倍優れているという結果が得られたのです。この結果を見て、「それならHyperoptを使い続けたい」と思われるかもしれません。
しかし、ここに落とし穴があります
もし家を建てるとしたら、工期が少し短いけれど基礎に問題がある建築方法と、工期は少し長いけれど堅固な基礎を持つ建築方法、どちらを選ぶでしょうか?答えは明白ですよね。
同じことがHyperoptとOptunaの選択にも言えるのです。Hyperoptの処理速度の優位性は確かに魅力的ですが、それはPython 3.11以前でしか享受できない一時的な利点に過ぎません。一方、Optunaを選択することは、将来にわたって安定して動作し続ける、持続可能な機械学習パイプラインを構築することを意味します。
Hyperoptの並列化効率が高い理由は、実はシンプルさにあります。しかし、このシンプルさは両刃の剣です。
またOptunaが並列化効率を犠牲にしてでも提供している機能を考えてみましょう。早期停止による無駄な計算の削減、より賢い探索戦略による総試行回数の削減、実験の再現性の保証、これらはすべて、わずかな並列化効率の低下を補って余りある価値があります。
実際、私の実験では20回の試行でしたが、もし100回、1000回と試行回数を増やした場合、Optunaの賢い探索戦略により、同じ精度に到達するまでの総時間はOptunaの方が短くなる可能性が高いのです。
🔍 なぜHyperoptは死んだのか:8年間の段階的廃止の真相
Python開発チームは、2014年から実に8年もの歳月をかけて、古いimpモジュールを段階的に非推奨化してきました。そして2023年10月、ついにPython 3.12でこのモジュールが完全に削除されたのです。
8年間の段階的廃止プロセス
# impモジュール廃止のタイムライン
timeline = {
"2014年 (Python 3.4)": "PendingDeprecationWarning 開始",
"2015年 (Python 3.5)": "DeprecationWarning に変更",
"2021年 (Python 3.10)": "Python 3.12での削除予定を明記",
"2023年10月 (Python 3.12)": "正式に削除 💀"
}
なぜHyperoptが影響を受けるのか
依存関係チェーン:
Hyperopt (0.2.7)
└── pyll
└── future (python-future)
└── standard_library/__init__.py:65
└── import imp # ← ここでエラー!
Hyperopt自体はimpを使用していませんが、依存パッケージpython-futureが使用しているため、間接的に影響を受けています。この状況は、まるで古い建物の基礎が崩れていくようなものです。表面上は問題なく見えても、土台となる部分が失われてしまっているため、いずれ全体が崩壊してしまう運命にあるのです。
📊 実験環境と検証条件の完全開示
検証環境スペック(2025年8月5日実施)
🖥️ OS: Ubuntu 24.04.2 LTS (Linux 5.15.0-1091-azure)
💻 CPU: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
- 物理コア数: 2
- 論理コア数: 4
- CPU使用率: 76.2%
💾 メモリ: 10.83 GB
💽 ディスク: 250.92 GB
⚡ Spark: 4.0.0
🐍 Python: 3.12.3
📊 Databricks Runtime: DBR 14.3 LTS以降
実験データセット
# 実験で使用したデータセットの詳細
n_samples = 5000 # サンプル数
n_features = 20 # 特徴量の数
n_informative = 15 # 有益な特徴量
n_redundant = 3 # 冗長な特徴量
n_repeated = 2 # 繰り返し特徴量
n_classes = 3 # 3クラス分類
weights = [0.4, 0.3, 0.3] # クラスの不均衡
flip_y = 0.05 # 5%のノイズ
# 訓練データ: (4000, 20)
# テストデータ: (1000, 20)
# クラス分布: [1585, 1213, 1202]
🔬 完全な実験結果:20回の最適化試行による徹底比較
実測パフォーマンス比較表
| フレームワーク | 処理方式 | 実行時間 | ワーカー数 | 試行回数 | 最高精度 | 高速化率 |
|---|---|---|---|---|---|---|
| Hyperopt | 逐次処理 | 79.50秒 | 1 | 20 | 0.8297 | 1.00x |
| Hyperopt | 並列処理 | 17.58秒 | 4 | 20 | 0.8297 | 4.52x |
| Optuna | 逐次処理 | 79.26秒 | 1 | 20 | 0.8335 | 1.00x |
| Optuna | 並列処理 | 31.04秒 | 4 | 20 | 0.8280 | 2.55x |
重要な発見
- 並列化効率: Hyperopt (87.7%) > Optuna (63.8%)
- 精度への影響: 完全に同一(並列化は精度に影響しない)
- 逐次処理での実行時間: ほぼ同等(差はわずか0.24秒)
さらに興味深いことに、最終的に得られるモデルの精度も完全に同一でした。これは、日常的な小規模な実験では、両者の差をほとんど感じないということを示しています。
💻 完全な検証コード(コピペで動作確認可能)
Step 1: 環境準備とパッケージインストール
# zombie-impによる一時的な回避策(推奨されません)
!pip install zombie-imp
# 推奨: Optunaと関連パッケージのインストール
%pip install --upgrade "optuna>=3.5.0" "scikit-learn>=1.3.0,<1.7" "plotly>=5.18.0" "numpy<2.0"
dbutils.library.restartPython()
Step 2: ライブラリのインポートとMLflow設定
# 必要なライブラリのインポート
import mlflow
import mlflow.sklearn
import optuna
import hyperopt
from hyperopt import hp, STATUS_OK, fmin, tpe, Trials
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.metrics import accuracy_score
import time
import numpy as np
import pandas as pd
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, lit
import warnings
warnings.filterwarnings('ignore')
# Sparkセッションの初期化
spark = SparkSession.builder.appName("HyperparameterTuningComparison").getOrCreate()
# MLflow実験の設定
experiment_name = "/Shared/hyperopt_optuna_parallel_comparison"
mlflow.set_experiment(experiment_name)
# MLflow sklearn autologを有効化
mlflow.sklearn.autolog(
log_input_examples=True,
log_model_signatures=True,
log_models=True,
log_datasets=True,
log_post_training_metrics=True,
extra_tags={"実験タイプ": "hyperparameter_tuning_comparison", "試験問題": "並列化の効果"}
)
Step 3: テストデータの生成
# 分類問題用のテストデータを生成
n_samples = 5000
n_features = 20
X, y = make_classification(
n_samples=n_samples,
n_features=n_features,
n_informative=15,
n_redundant=3,
n_repeated=2,
n_classes=3,
n_clusters_per_class=2,
weights=[0.4, 0.3, 0.3],
random_state=42,
shuffle=True,
flip_y=0.05
)
# データの分割
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print(f"訓練データ: {X_train.shape}")
print(f"テストデータ: {X_test.shape}")
print(f"クラス分布: {np.bincount(y_train)}")
Step 4: 共通の評価関数
def evaluate_model(params, X=X_train, y=y_train):
"""
ランダムフォレストモデルを評価する共通関数
この関数は、与えられたハイパーパラメータでモデルを訓練し、
クロスバリデーションで精度を評価します。
"""
# パラメータの整数変換(必要に応じて)
n_estimators = int(params.get('n_estimators', 100))
max_depth = int(params.get('max_depth', 10)) if params.get('max_depth') else None
min_samples_split = int(params.get('min_samples_split', 2))
min_samples_leaf = int(params.get('min_samples_leaf', 1))
# max_featuresの処理
max_features = params.get('max_features', 'sqrt')
if isinstance(max_features, (int, float)):
max_features = min(int(max_features), n_features)
# モデルの作成
rf = RandomForestClassifier(
n_estimators=n_estimators,
max_depth=max_depth,
min_samples_split=min_samples_split,
min_samples_leaf=min_samples_leaf,
max_features=max_features,
random_state=42,
n_jobs=1 # 個別の評価では並列化しない
)
# 3-fold クロスバリデーション
scores = cross_val_score(rf, X, y, cv=3, scoring='accuracy')
return scores.mean()
Step 5: Hyperoptによる最適化(完全コード)
# Hyperopt用の目的関数
def hyperopt_objective(params):
"""Hyperoptの最小化目的関数(負の精度を返す)"""
accuracy = evaluate_model(params)
return {'loss': -accuracy, 'status': STATUS_OK}
# Hyperoptの探索空間を定義
hyperopt_space = {
'n_estimators': hp.quniform('n_estimators', 20, 150, 10),
'max_depth': hp.quniform('max_depth', 3, 20, 1),
'min_samples_split': hp.quniform('min_samples_split', 2, 20, 1),
'min_samples_leaf': hp.quniform('min_samples_leaf', 1, 10, 1),
'max_features': hp.choice('max_features', ['sqrt', 'log2', None])
}
# Hyperopt逐次処理
print("🐌 Hyperopt逐次処理を開始...")
start_time = time.time()
with mlflow.start_run(run_name="Hyperopt_Sequential"):
trials_sequential = Trials()
best_hyperopt_sequential = fmin(
fn=hyperopt_objective,
space=hyperopt_space,
algo=tpe.suggest,
max_evals=20,
trials=trials_sequential,
verbose=False,
rstate=np.random.default_rng(42)
)
hyperopt_sequential_time = time.time() - start_time
# 最良の精度を取得
best_loss = min([t['result']['loss'] for t in trials_sequential.trials])
best_accuracy = -best_loss
# MLflowにログ
mlflow.log_params(best_hyperopt_sequential)
mlflow.log_metric("execution_time", hyperopt_sequential_time)
mlflow.log_metric("best_accuracy", best_accuracy)
mlflow.log_metric("n_workers", 1)
mlflow.set_tag("method", "Hyperopt_Sequential")
print(f"⏱️ 実行時間: {hyperopt_sequential_time:.2f}秒")
print(f"🎯 最高精度: {best_accuracy:.4f}")
Step 6: Optunaによる最適化(完全コード)
# Optuna用の目的関数
def optuna_objective(trial):
"""Optunaの最大化目的関数"""
params = {
'n_estimators': trial.suggest_int('n_estimators', 20, 150, step=10),
'max_depth': trial.suggest_int('max_depth', 3, 20),
'min_samples_split': trial.suggest_int('min_samples_split', 2, 20),
'min_samples_leaf': trial.suggest_int('min_samples_leaf', 1, 10),
'max_features': trial.suggest_categorical('max_features', ['sqrt', 'log2', None])
}
accuracy = evaluate_model(params)
return accuracy
# Optuna並列処理
print("\n🚀 Optuna並列処理(4ワーカー)を開始...")
start_time = time.time()
with mlflow.start_run(run_name="Optuna_Parallel_4workers"):
study_parallel = optuna.create_study(
direction='maximize',
sampler=optuna.samplers.TPESampler(seed=42)
)
study_parallel.optimize(
optuna_objective,
n_trials=20,
n_jobs=4, # 4並列
show_progress_bar=True
)
optuna_parallel_time = time.time() - start_time
mlflow.log_params(study_parallel.best_params)
mlflow.log_metric("execution_time", optuna_parallel_time)
mlflow.log_metric("best_accuracy", study_parallel.best_value)
mlflow.log_metric("n_workers", 4)
mlflow.set_tag("method", "Optuna_Parallel")
print(f"⏱️ 実行時間: {optuna_parallel_time:.2f}秒")
print(f"🎯 最高精度: {study_parallel.best_value:.4f}")
🔄 移行ガイド:HyperoptからOptunaへ
探索空間の定義方法の違い
| Hyperopt | Optuna | 説明 |
|---|---|---|
hp.uniform('x', 0, 1) |
trial.suggest_float('x', 0, 1) |
連続値 |
hp.quniform('x', 0, 10, 1) |
trial.suggest_int('x', 0, 10) |
整数値 |
hp.choice('x', ['a', 'b']) |
trial.suggest_categorical('x', ['a', 'b']) |
カテゴリカル |
hp.loguniform('x', -3, 0) |
trial.suggest_float('x', 0.001, 1, log=True) |
対数スケール |
実際の移行例(SparkTrialsからOptunaへ)
# Before: Hyperopt with SparkTrials
from hyperopt import SparkTrials
spark_trials = SparkTrials(parallelism=10)
best = fmin(
fn=hyperopt_objective,
space=hyperopt_space,
algo=tpe.suggest,
max_evals=100,
trials=spark_trials
)
# After: Optuna with MLflow integration
from mlflow.optuna.storage import MlflowStorage
# Databricks環境でのMLflow統合
experiment_id = mlflow.get_experiment_by_name("experiment_name").experiment_id
mlflow_storage = MlflowStorage(
experiment_id=experiment_id,
tracking_uri=mlflow.get_tracking_uri()
)
study = optuna.create_study(
storage=mlflow_storage,
study_name="my_optimization",
direction='maximize'
)
# 並列実行
study.optimize(optuna_objective, n_trials=100, n_jobs=10)
🎯 移行戦略:3つのアプローチ
1. 応急処置アプローチ(非推奨)
!pip install zombie-imp
リスク: セキュリティ脆弱性、将来的なサポート終了
2. 環境固定アプローチ(短期的解決)
DBR 14.3 LTS以前(Python 3.11)を使い続ける
リスク: 技術的負債の蓄積、新機能の利用不可
3. 完全移行アプローチ(推奨)
段階的にOptunaへ移行し、MLflow統合を活用
メリット: 長期的な安定性、Databricksエコシステムとの完全統合
📈 移行による実際のインパクト
私の検証では、確かに処理速度は低下しました:
- 並列化効率: 87.7% → 63.8%(約24%低下)
- 4ワーカーでの実行時間: 17.58秒 → 31.04秒(約1.8倍)
💡 まとめ:移行は投資である
このような検証結果を踏まえると、わずかな処理速度の差よりも、長期的な保守性、最新環境への対応、そしてDatabricksエコシステムとの統合性を重視することが、より賢明な選択と言えるのではないでしょうか。
Hyperoptの死は確かに寂しいものですが、それは新しい時代の始まりでもあります。Optunaという新しいパートナーと共に、より堅牢で持続可能な機械学習パイプラインを構築していきましょう。
🚀 今すぐ行動を
- まず現状を把握: あなたのプロジェクトでHyperoptがどれだけ使われているか確認
- 小規模な実験から開始: 新規プロジェクトでOptunaを試してみる
- 段階的に移行: 重要度の低いプロジェクトから順次移行
- 知識を共有: チーム内で移行経験を共有し、ベストプラクティスを確立
この記事が、あなたのスムーズな移行の助けになれば幸いです。