|
import streamlit as st |
|
import pandas as pd |
|
import numpy as np |
|
import matplotlib.pyplot as plt |
|
import japanize_matplotlib |
|
import seaborn as sns |
|
import joblib |
|
import base64 |
|
import io |
|
import shap |
|
from pycaret.regression import * |
|
from sklearn.tree import plot_tree |
|
from sklearn.inspection import permutation_importance |
|
import common |
|
|
|
import matplotlib.font_manager as fm |
|
|
|
|
|
font_path = 'ipaexg.ttf' |
|
|
|
|
|
font_prop = fm.FontProperties(fname=font_path) |
|
|
|
|
|
plt.rcParams['font.family'] = font_prop.get_name() |
|
|
|
|
|
st.set_page_config( |
|
page_title="easyAutoML(回帰)", |
|
page_icon="📊", |
|
layout="wide" |
|
) |
|
|
|
|
|
if 'model_configs' not in st.session_state: |
|
st.session_state.model_configs = { |
|
'uploaded_data': None, |
|
'target_variable': None, |
|
'ignore_features': [], |
|
'setup_done': False, |
|
'model_trained': False, |
|
'current_model': None, |
|
'final_model': None, |
|
'feature_importance': None, |
|
'model_comparison': None, |
|
'pre_tuned_scores': None, |
|
'post_tuned_scores': None, |
|
'X_train': None, |
|
'features': None |
|
} |
|
|
|
|
|
st.title("easyAutoML(回帰)") |
|
common.display_header() |
|
|
|
with st.expander("このアプリケーションについて", expanded=False): |
|
st.markdown(""" |
|
### 📊 AutoMLアプリケーションの概要 |
|
このアプリケーションは、機械学習モデルの構築と評価を自動化し、詳細な分析結果を提供します。 |
|
""") |
|
|
|
st.markdown("### 🔍 主な機能") |
|
|
|
st.markdown("#### 1. データ分析") |
|
st.markdown(""" |
|
- データの基本統計量の確認 |
|
- 欠損値の分析と自動処理 |
|
- 外れ値の検出と処理オプション |
|
""") |
|
|
|
st.markdown("#### 2. モデル構築と最適化") |
|
st.markdown(""" |
|
- 複数の機械学習モデルの自動比較 |
|
- 最適なモデルの選択支援 |
|
- ハイパーパラメータの自動チューニング |
|
- 交差検証による性能評価 |
|
""") |
|
|
|
st.markdown("#### 3. モデル評価と解釈") |
|
st.markdown(""" |
|
- 特徴量重要度の分析 |
|
- モデルベースの重要度 |
|
- SHAP値による解釈 |
|
- 予測性能の詳細な評価 |
|
- 残差分析 |
|
- 予測値と実測値の比較 |
|
- クックの距離による影響度分析 |
|
- 決定木モデルの構造可視化(対応モデルのみ) |
|
""") |
|
|
|
st.markdown("#### 4. 最終モデルの生成") |
|
st.markdown(""" |
|
- モデルのファイナライズ |
|
- 最終評価結果の確認 |
|
- トレーニング済みモデルのダウンロード |
|
""") |
|
|
|
st.info("⚠️ 注意: データの前処理(欠損値の処理など)は自動的に行われますが、データの品質が結果に大きく影響します。") |
|
|
|
|
|
st.markdown("---") |
|
st.header("1. データのアップロード") |
|
st.write("データのアップロードについて") |
|
st.markdown(""" |
|
- CSVまたはExcelファイルをアップロードしてください |
|
- 欠損値や異常値は自動的に処理されます |
|
""") |
|
|
|
uploaded_file = st.file_uploader( |
|
"ファイルを選択してください(CSVまたはExcel)", |
|
type=['csv', 'xlsx'] |
|
) |
|
|
|
if uploaded_file is not None: |
|
try: |
|
|
|
if 'last_file_name' not in st.session_state or st.session_state.last_file_name != uploaded_file.name: |
|
st.session_state.model_configs = { |
|
'uploaded_data': None, |
|
'target_variable': None, |
|
'ignore_features': [], |
|
'setup_done': False, |
|
'model_trained': False, |
|
'current_model': None, |
|
'final_model': None, |
|
'feature_importance': None, |
|
'model_comparison': None, |
|
'pre_tuned_scores': None, |
|
'post_tuned_scores': None, |
|
'X_train': None, |
|
'features': None |
|
} |
|
st.session_state.last_file_name = uploaded_file.name |
|
|
|
|
|
if uploaded_file.name.endswith('.csv'): |
|
data = pd.read_csv(uploaded_file) |
|
else: |
|
data = pd.read_excel(uploaded_file) |
|
|
|
st.session_state.model_configs['uploaded_data'] = data |
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
with col1: |
|
st.metric("データ件数", f"{len(data):,}件") |
|
with col2: |
|
st.metric("項目数", f"{len(data.columns)}個") |
|
with col3: |
|
st.metric("欠損値を含む列", f"{data.isnull().any().sum()}個") |
|
|
|
|
|
st.subheader("データプレビュー") |
|
st.dataframe(data.head(), use_container_width=True) |
|
|
|
|
|
with st.expander("データの基本統計量を表示", expanded=False): |
|
st.dataframe(data.describe(), use_container_width=True) |
|
|
|
|
|
if data.isnull().any().sum() > 0: |
|
with st.expander("欠損値の詳細を表示", expanded=False): |
|
missing_data = pd.DataFrame({ |
|
'欠損値数': data.isnull().sum(), |
|
'欠損率(%)': (data.isnull().sum() / len(data) * 100).round(2) |
|
}).reset_index() |
|
missing_data.columns = ['列名', '欠損値数', '欠損率(%)'] |
|
st.dataframe(missing_data[missing_data['欠損値数'] > 0], use_container_width=True) |
|
|
|
st.markdown("---") |
|
|
|
st.header("2. モデル設定") |
|
|
|
col1, col2 = st.columns(2) |
|
with col1: |
|
target_variable = st.selectbox( |
|
'予測対象(目的変数)の選択', |
|
options=data.columns, |
|
help="予測したい項目を選択してください" |
|
) |
|
st.session_state.model_configs['target_variable'] = target_variable |
|
|
|
with col2: |
|
ignore_features = st.multiselect( |
|
'除外する項目の選択', |
|
options=[col for col in data.columns if col != target_variable], |
|
help="モデルの学習に使用しない項目を選択してください" |
|
) |
|
st.session_state.model_configs['ignore_features'] = ignore_features |
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
with col1: |
|
remove_outliers = st.checkbox('外れ値を除去する', value=False) |
|
with col2: |
|
pca_option = st.checkbox('PCAを適用する', value=False) |
|
with col3: |
|
if pca_option: |
|
max_components = len(data.columns) - 1 |
|
pca_components = st.slider('主成分の数', min_value=1, max_value=max_components, value=min(10, max_components)) |
|
else: |
|
pca_components = None |
|
|
|
|
|
if st.button('モデルの比較を開始', use_container_width=True): |
|
try: |
|
with st.spinner("モデルを比較中..."): |
|
|
|
progress_bar = st.progress(0) |
|
status_text = st.empty() |
|
|
|
|
|
status_text.text("データの前処理を実行中...") |
|
progress_bar.progress(20) |
|
|
|
|
|
setup_params = { |
|
'data': data, |
|
'target': target_variable, |
|
'ignore_features': ignore_features, |
|
'remove_outliers': remove_outliers, |
|
'session_id': 123, |
|
'verbose': False, |
|
'pca': pca_option |
|
} |
|
if pca_option: |
|
setup_params['pca_components'] = pca_components |
|
setup_data = setup(**setup_params) |
|
|
|
|
|
X_train = get_config('X_train') |
|
st.session_state.model_configs['X_train'] = X_train |
|
st.session_state.model_configs['features'] = X_train.columns.tolist() |
|
|
|
|
|
progress_bar.progress(40) |
|
status_text.text("モデルを比較中...") |
|
|
|
models_comparison = compare_models( |
|
exclude=['catboost'], |
|
fold=5, |
|
sort='MAE', |
|
n_select=15, |
|
verbose=False |
|
) |
|
|
|
|
|
comparison_df = pull() |
|
st.session_state.model_configs['model_comparison'] = comparison_df.copy() |
|
|
|
progress_bar.progress(100) |
|
status_text.text("モデルの比較が完了しました!") |
|
st.success("✅ モデルの比較が完了しました!") |
|
|
|
except Exception as e: |
|
st.error(f"エラーが発生しました: {str(e)}") |
|
st.stop() |
|
|
|
|
|
if 'model_comparison' in st.session_state.model_configs and st.session_state.model_configs['model_comparison'] is not None: |
|
st.markdown("---") |
|
st.header("モデルの選択とチューニング") |
|
|
|
|
|
st.subheader("モデル比較結果") |
|
st.dataframe(st.session_state.model_configs['model_comparison'], use_container_width=True) |
|
with st.expander("モデルの説明", expanded=False): |
|
st.markdown(""" |
|
**モデルの説明をここに記載**(省略) |
|
""") |
|
|
|
|
|
with st.expander("評価指標の説明", expanded=False): |
|
st.markdown(""" |
|
**評価指標の説明をここに記載**(省略) |
|
""") |
|
|
|
|
|
selected_model = st.selectbox( |
|
'チューニングするモデルを選択', |
|
options=st.session_state.model_configs['model_comparison'].index, |
|
help="比較結果から最適なモデルを選択してください" |
|
) |
|
|
|
|
|
st.subheader("ハイパーパラメータの設定") |
|
|
|
|
|
cv_folds = st.slider('交差検証のfold数', min_value=2, max_value=20, value=5) |
|
|
|
|
|
params = {} |
|
if selected_model in ['rf', 'et']: |
|
n_estimators = st.slider('決定木の数', min_value=50, max_value=500, value=100, step=50) |
|
max_depth = st.slider('決定木の最大深さ', min_value=3, max_value=20, value=3) |
|
min_samples_split = st.slider('分割のための最小サンプル数', min_value=2, max_value=20, value=2) |
|
min_samples_leaf = st.slider('葉となるための最小サンプル数', min_value=1, max_value=10, value=1) |
|
params = { |
|
'n_estimators': n_estimators, |
|
'max_depth': max_depth, |
|
'min_samples_split': min_samples_split, |
|
'min_samples_leaf': min_samples_leaf |
|
} |
|
elif selected_model in ['xgboost', 'lightgbm']: |
|
learning_rate = st.slider('学習率', min_value=0.01, max_value=0.3, value=0.1, step=0.01) |
|
n_estimators = st.slider('決定木の数', min_value=50, max_value=500, value=100, step=50) |
|
max_depth = st.slider('決定木の最大深さ', min_value=3, max_value=20, value=3) |
|
params = { |
|
'learning_rate': learning_rate, |
|
'n_estimators': n_estimators, |
|
'max_depth': max_depth |
|
} |
|
|
|
|
|
if st.button('選択したモデルでトレーニング開始', use_container_width=True): |
|
try: |
|
with st.spinner("モデルをトレーニング中..."): |
|
progress_bar = st.progress(0) |
|
status_text = st.empty() |
|
|
|
|
|
status_text.text("モデルを作成中...") |
|
progress_bar.progress(30) |
|
|
|
|
|
base_model = create_model(selected_model, fold=cv_folds, **params) |
|
pre_tuned_scores = pull() |
|
st.session_state.model_configs['pre_tuned_scores'] = pre_tuned_scores.copy() |
|
|
|
|
|
status_text.text("ハイパーパラメータをチューニング中...") |
|
progress_bar.progress(60) |
|
tuned_model = tune_model( |
|
base_model, |
|
n_iter=10, |
|
fold=cv_folds, |
|
optimize='MAE' |
|
) |
|
|
|
|
|
post_tuned_scores = pull() |
|
st.session_state.model_configs.update({ |
|
'current_model': tuned_model, |
|
'post_tuned_scores': post_tuned_scores.copy(), |
|
'model_trained': True |
|
}) |
|
|
|
|
|
st.subheader("チューニング結果") |
|
col1, col2 = st.columns(2) |
|
with col1: |
|
st.write("チューニング前") |
|
st.dataframe(pre_tuned_scores, use_container_width=True) |
|
with col2: |
|
st.write("チューニング後") |
|
st.dataframe(post_tuned_scores, use_container_width=True) |
|
|
|
progress_bar.progress(100) |
|
status_text.text("モデルのトレーニングが完了しました!") |
|
st.success("✅ モデルのトレーニングが完了しました!") |
|
|
|
except Exception as e: |
|
st.error(f"モデルのトレーニング中にエラーが発生しました: {str(e)}") |
|
st.stop() |
|
|
|
|
|
if st.session_state.model_configs['model_trained']: |
|
st.markdown("---") |
|
st.header("3. モデルの評価") |
|
|
|
|
|
st.subheader("3-1. 特徴量重要度") |
|
try: |
|
model = st.session_state.model_configs['current_model'] |
|
X_train_transformed = get_config('X_train_transformed') |
|
feature_names = X_train_transformed.columns |
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
st.write("モデルベースの特徴量重要度") |
|
with st.spinner('特徴量重要度を計算中...'): |
|
try: |
|
if hasattr(model, 'feature_importances_'): |
|
importance_df = pd.DataFrame({ |
|
'特徴量': feature_names, |
|
'重要度': model.feature_importances_ |
|
}).sort_values(by='重要度', ascending=False) |
|
|
|
fig, ax = plt.subplots(figsize=(10, 6)) |
|
sns.barplot(data=importance_df, x='重要度', y='特徴量', ax=ax) |
|
st.pyplot(fig) |
|
st.caption("**モデルが学習した結果、各特徴量が目的変数の予測にどれだけ寄与したかを示しています。重要度が高い特徴量ほど、モデルの予測に大きな影響を与えています。**") |
|
with st.expander("特徴量重要度データ", expanded=False): |
|
st.dataframe(importance_df, use_container_width=True) |
|
else: |
|
st.warning("このモデルでは特徴量重要度を直接取得できません。") |
|
except Exception as e: |
|
st.warning(f"特徴量重要度のプロットに失敗しました: {str(e)}") |
|
|
|
with col2: |
|
st.write("SHAP値による特徴量重要度") |
|
with st.spinner('SHAP値を計算中...'): |
|
try: |
|
explainer = shap.Explainer(model, X_train_transformed) |
|
shap_values = explainer(X_train_transformed) |
|
shap.summary_plot(shap_values, X_train_transformed, plot_type="bar", show=False) |
|
st.pyplot(plt.gcf()) |
|
st.caption("**SHAP値に基づく特徴量の重要度を示しています。各特徴量が予測結果に与える影響を定量的に評価できます。(青:正の影響 緑:負の影響)**") |
|
plt.clf() |
|
with st.expander("SHAP値データ", expanded=False): |
|
shap_df = pd.DataFrame({ |
|
'特徴量': feature_names, |
|
'SHAP値の平均絶対値': np.abs(shap_values.values).mean(axis=0) |
|
}).sort_values(by='SHAP値の平均絶対値', ascending=False) |
|
st.dataframe(shap_df, use_container_width=True) |
|
except Exception as e: |
|
st.warning(f"SHAP値の計算中にエラーが発生しました: {str(e)}") |
|
|
|
except Exception as e: |
|
st.error(f"特徴量重要度の分析中にエラーが発生しました: {str(e)}") |
|
st.markdown("---") |
|
|
|
|
|
st.subheader("3-2. 予測性能の分析と外れ値の検出") |
|
try: |
|
with st.spinner('予測性能を分析中...'): |
|
col1, col2 = st.columns(2) |
|
with col1: |
|
st.write("残差プロット") |
|
plot_model(model, plot='residuals', display_format="streamlit") |
|
st.caption("残差(予測値と実測値の差)と予測値の関係を示しています。パターンがなければ、モデルが適切にフィットしていることを示唆します。") |
|
|
|
with col2: |
|
st.write("予測誤差プロット") |
|
plot_model(model, plot='error', display_format="streamlit") |
|
st.caption("予測値と実測値の差を示しています。誤差が小さいほど、モデルの予測精度が高いことを示します。") |
|
|
|
col3, col4 = st.columns(2) |
|
with col3: |
|
st.write("学習曲線") |
|
plot_model(model, plot='learning', display_format="streamlit") |
|
st.caption("トレーニングデータと検証データに対するモデルのパフォーマンスを示します。学習曲線が収束していれば、モデルが適切に学習していることを示します。") |
|
|
|
with col4: |
|
st.write("クックの距離") |
|
plot_model(model, plot='cooks', display_format="streamlit") |
|
st.caption("クックの距離が大きいデータポイントは、**モデルに強い影響を与える可能性がある**ため、外れ値の検出に役立ちます。") |
|
except Exception as e: |
|
st.error(f"予測性能の分析中にエラーが発生しました: {str(e)}") |
|
|
|
|
|
if selected_model in ['dt', 'rf', 'et', 'gbr', 'xgboost', 'lightgbm']: |
|
st.markdown("---") |
|
st.subheader("3-4. 決定木の構造") |
|
try: |
|
with st.spinner('決定木を可視化中...'): |
|
if selected_model == 'dt': |
|
fig, ax = plt.subplots(figsize=(40, 20)) |
|
plot_tree( |
|
model, |
|
feature_names=feature_names, |
|
filled=True, |
|
rounded=True, |
|
fontsize=12, |
|
ax=ax |
|
) |
|
st.pyplot(fig) |
|
st.caption("決定木の構造を表示しています。") |
|
else: |
|
from sklearn.metrics import mean_squared_error |
|
X_test_transformed = get_config('X_test_transformed') |
|
y_test = get_config('y_test') |
|
|
|
if selected_model in ['rf', 'et']: |
|
estimators = model.estimators_ |
|
elif selected_model == 'gbr': |
|
estimators = [est[0] for est in model.estimators_] |
|
elif selected_model == 'xgboost': |
|
import xgboost as xgb |
|
estimators = model.get_booster().get_dump() |
|
elif selected_model == 'lightgbm': |
|
import lightgbm as lgb |
|
estimators = model.booster_.dump_model()['tree_info'] |
|
else: |
|
estimators = [] |
|
|
|
best_score = float('inf') |
|
best_estimator_index = 0 |
|
|
|
for idx, estimator in enumerate(estimators): |
|
if selected_model in ['rf', 'et', 'gbr']: |
|
y_pred = estimator.predict(X_test_transformed) |
|
elif selected_model == 'xgboost': |
|
y_pred = model.predict(X_test_transformed, ntree_limit=idx+1) |
|
elif selected_model == 'lightgbm': |
|
y_pred = model.predict(X_test_transformed, num_iteration=idx+1) |
|
else: |
|
continue |
|
|
|
mse = mean_squared_error(y_test, y_pred) |
|
if mse < best_score: |
|
best_score = mse |
|
best_estimator_index = idx |
|
|
|
if selected_model in ['rf', 'et', 'gbr']: |
|
best_tree = estimators[best_estimator_index] |
|
fig, ax = plt.subplots(figsize=(40, 20)) |
|
plot_tree( |
|
best_tree, |
|
feature_names=feature_names, |
|
filled=True, |
|
rounded=True, |
|
fontsize=12, |
|
ax=ax |
|
) |
|
st.pyplot(fig) |
|
st.caption(f"ベストな決定木(ツリー番号: {best_estimator_index})の構造を表示しています。") |
|
elif selected_model == 'xgboost': |
|
import xgboost as xgb |
|
booster = model.get_booster() |
|
fig, ax = plt.subplots(figsize=(40, 20)) |
|
xgb.plot_tree(booster, num_trees=best_estimator_index, ax=ax) |
|
st.pyplot(fig) |
|
st.caption(f"ベストな決定木(ツリー番号: {best_estimator_index})の構造を表示しています。") |
|
elif selected_model == 'lightgbm': |
|
import lightgbm as lgb |
|
graph = lgb.create_tree_digraph(model, tree_index=best_estimator_index) |
|
st.graphviz_chart(graph) |
|
st.caption(f"ベストな決定木(ツリー番号: {best_estimator_index})の構造を表示しています。") |
|
else: |
|
st.warning(f"{selected_model}モデルの決定木の可視化は現在サポートされていません。") |
|
except Exception as e: |
|
st.error(f"決定木の可視化中にエラーが発生しました: {str(e)}") |
|
|
|
|
|
st.markdown("---") |
|
st.header("4. モデルのファイナライズ") |
|
|
|
if st.button('モデルをファイナライズ', use_container_width=True): |
|
try: |
|
with st.spinner("モデルをファイナライズ中..."): |
|
progress_bar = st.progress(0) |
|
status_text = st.empty() |
|
|
|
status_text.text("モデルをファイナライズ中...") |
|
progress_bar.progress(30) |
|
final_model = finalize_model(model) |
|
|
|
status_text.text("最終評価を実行中...") |
|
progress_bar.progress(60) |
|
predictions = predict_model(final_model) |
|
final_scores = pull() |
|
|
|
st.subheader("ファイナルモデルの評価結果") |
|
col1, col2 = st.columns(2) |
|
with col1: |
|
st.write("チューニング後の評価結果") |
|
st.dataframe(st.session_state.model_configs['post_tuned_scores'], use_container_width=True) |
|
with col2: |
|
st.write("ファイナライズ後の評価結果") |
|
st.dataframe(final_scores, use_container_width=True) |
|
|
|
final_model.target_column = target_variable |
|
|
|
status_text.text("モデルを保存中...") |
|
progress_bar.progress(90) |
|
model_name = f"{target_variable}_finalized_model" |
|
|
|
save_model(final_model, model_name) |
|
|
|
with open(f"{model_name}.pkl", 'rb') as f: |
|
model_bytes = f.read() |
|
|
|
st.download_button( |
|
label="ファイナライズしたモデルをダウンロード", |
|
data=model_bytes, |
|
file_name=f"{model_name}.pkl", |
|
mime="application/octet-stream", |
|
use_container_width=True |
|
) |
|
|
|
progress_bar.progress(100) |
|
status_text.text("ファイナライズが完了しました!") |
|
st.success("✅ モデルのファイナライズが完了しました!") |
|
|
|
except Exception as e: |
|
st.error(f"モデルのファイナライズ中にエラーが発生しました: {str(e)}") |
|
|
|
except Exception as e: |
|
st.error(f"予期せぬエラーが発生しました: {str(e)}") |
|
|
|
|
|
common.display_copyright() |
|
|