От данных к решениям: сквозной ML‑workflow с интерпретируемостью и помощью Gemini

Коротко о задаче

В этом руководстве показан практический сквозной процесс работы с данными: загрузка и предобработка, построение надежного scikit-learn пайплайна, оценка модели и применение методов интерпретируемости. Gemini используется как AI‑сотрудник для объяснений, выдачи идей для экспериментов и генерации коротких pandas‑сниппетов для EDA.

Окружение, импорты и настройка Gemini

Начните с установки/обновления необходимых пакетов и настройки ключа для Gemini. Ниже — импорты, конфигурация и вспомогательная функция для обращения к модели Gemini.

!pip -qU google-generativeai scikit-learn matplotlib pandas numpy
from getpass import getpass
import os, json, numpy as np, pandas as pd, matplotlib.pyplot as plt


if not os.environ.get("GOOGLE_API_KEY"):
   os.environ["GOOGLE_API_KEY"] = getpass(" Enter your Gemini API key (hidden): ")


import google.generativeai as genai
genai.configure(api_key=os.environ["GOOGLE_API_KEY"])
LLM = genai.GenerativeModel("gemini-1.5-flash")


def ask_llm(prompt, sys=None):
   p = prompt if sys is None else f"System:\n{sys}\n\nUser:\n{prompt}"
   r = LLM.generate_content(p)
   return (getattr(r, "text", "") or "").strip()

Загрузка датасета и первичный осмотр

Используется встроенный датасет диабета, целевая колонка переименовывается для ясности, выполняется быстрый осмотр данных.

from sklearn.datasets import load_diabetes
raw = load_diabetes(as_frame=True)
df  = raw.frame.rename(columns={"target":"disease_progression"})
print("Shape:", df.shape); display(df.head())

Пайплайн и модель

Пайплайн включает масштабирование и квантильное ранжирование в ColumnTransformer, затем HistGradientBoostingRegressor с ранней остановкой. Делаем разбиение, кросс‑валидацию и финальную подгонку.

from sklearn.model_selection import train_test_split, KFold, cross_val_score
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, QuantileTransformer
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.pipeline import Pipeline


X = df.drop(columns=["disease_progression"]); y = df["disease_progression"]
num_cols = X.columns.tolist()
pre = ColumnTransformer(
   [("scale", StandardScaler(), num_cols),
    ("rank",  QuantileTransformer(n_quantiles=min(200, len(X)), output_distribution="normal"), num_cols)],
   remainder="drop", verbose_feature_names_out=False)
model = HistGradientBoostingRegressor(max_depth=3, learning_rate=0.07,
                                     l2_regularization=0.0, max_iter=500,
                                     early_stopping=True, validation_fraction=0.15)
pipe  = Pipeline([("prep", pre), ("hgbt", model)])


Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.20, random_state=42)
cv = KFold(n_splits=5, shuffle=True, random_state=42)
cv_mse = -cross_val_score(pipe, Xtr, ytr, scoring="neg_mean_squared_error", cv=cv).mean()
cv_rmse = float(cv_mse ** 0.5)
pipe.fit(Xtr, ytr)

Оценка и остатки

После обучения считаем метрики на train/test, смотрим остатки (residuals) для поиска системных ошибок.

pred_tr = pipe.predict(Xtr); pred_te = pipe.predict(Xte)
rmse_tr = mean_squared_error(ytr, pred_tr) ** 0.5
rmse_te = mean_squared_error(yte, pred_te) ** 0.5
mae_te  = mean_absolute_error(yte, pred_te)
r2_te   = r2_score(yte, pred_te)
print(f"CV RMSE={cv_rmse:.2f} | Train RMSE={rmse_tr:.2f} | Test RMSE={rmse_te:.2f} | Test MAE={mae_te:.2f} | R²={r2_te:.3f}")


plt.figure(figsize=(5,4))
plt.scatter(pred_te, yte - pred_te, s=12)
plt.axhline(0, lw=1); plt.xlabel("Predicted"); plt.ylabel("Residual"); plt.title("Residuals (Test)")
plt.show()

Permutation importance и визуализация

Permutation importance показывает, какие признаки важны, измеряя рост ошибки при перестановке значений признака. Выводим топ‑10 и строим горизонтальную диаграмму.

from sklearn.inspection import permutation_importance
imp = permutation_importance(pipe, Xte, yte, scoring="neg_mean_squared_error", n_repeats=10, random_state=0)
imp_df = pd.DataFrame({"feature": X.columns, "importance": imp.importances_mean}).sort_values("importance", ascending=False)
display(imp_df.head(10))


plt.figure(figsize=(6,4))
top10 = imp_df.head(10).iloc[::-1]
plt.barh(top10["feature"], top10["importance"])
plt.title("Permutation Importance (Top 10)"); plt.xlabel("Δ(MSE)"); plt.tight_layout(); plt.show()

Частичная зависимость (ручной PDP)

Реализована простая функция для PDP: по сетке значений признака подставляем одно значение для всех строк и усредняем предсказания. Это помогает визуализировать маргинальное влияние признаков.

def compute_pdp(pipe, Xref: pd.DataFrame, feat: str, grid=40):
   xs = np.linspace(np.percentile(Xref[feat], 5), np.percentile(Xref[feat], 95), grid)
   Xtmp = Xref.copy()
   ys = []
   for v in xs:
       Xtmp[feat] = v
       ys.append(pipe.predict(Xtmp).mean())
   return xs, np.array(ys)


top_feats = imp_df["feature"].head(3).tolist()
plt.figure(figsize=(6,4))
for f in top_feats:
   xs, ys = compute_pdp(pipe, Xte.copy(), f, grid=40)
   plt.plot(xs, ys, label=f)
plt.legend(); plt.xlabel("Feature value"); plt.ylabel("Predicted target"); plt.title("Manual PDP (Top 3)")
plt.tight_layout(); plt.show()

Сбор отчета и запрос к Gemini

Формируем JSON‑объект с метриками и важностями, затем просим Gemini подготовить краткое резюме, список рисков, приоритетные эксперименты и идеи для фичеринга.

report_obj = {
   "dataset": {"rows": int(df.shape[0]), "cols": int(df.shape[1]-1), "target": "disease_progression"},
   "metrics": {"cv_rmse": float(cv_rmse), "train_rmse": float(rmse_tr),
               "test_rmse": float(rmse_te), "test_mae": float(mae_te), "r2": float(r2_te)},
   "top_importances": imp_df.head(10).to_dict(orient="records")
}
print(json.dumps(report_obj, indent=2))


sys_msg = ("You are a senior data scientist. Return: (1) ≤120-word executive summary, "
          "(2) key risks/assumptions bullets, (3) 5 prioritized next experiments w/ rationale, "
          "(4) quick-win feature engineering ideas as Python pseudocode.")
summary = ask_llm(f"Dataset + metrics + importances:\n{json.dumps(report_obj)}", sys=sys_msg)
print("\n Gemini Executive Brief\n" + "-"*80 + f"\n{summary}\n")

Безопасное исполнение сгенерированного кода и EDA

Определена простая песочница, которая запрещает опасные операции. Gemini генерирует короткие pandas‑сниппеты по вопросам на естественном языке; песочница выполняет их и возвращает ответы.

SAFE_GLOBALS = {"pd": pd, "np": np}
def run_generated_pandas(code: str, df_local: pd.DataFrame):
   banned = ["__", "import", "open(", "exec(", "eval(", "os.", "sys.", "pd.read", "to_csv", "to_pickle", "to_sql"]
   if any(b in code for b in banned): raise ValueError("Unsafe code rejected.")
   loc = {"df": df_local.copy()}
   exec(code, SAFE_GLOBALS, loc)
   return {k:v for k,v in loc.items() if k not in ("df",)}


def eda_qa(question: str):
   prompt = f"""You are a Python+Pandas analyst. DataFrame `df` columns:
{list(df.columns)}. Write a SHORT pandas snippet (no comments/prints) that computes the answer to:
"{question}". Use only pd/np/df; assign the final result to a variable named `answer`."""
   code = ask_llm(prompt, sys="Return only code. No prose.")
   try:
       out = run_generated_pandas(code, df)
       return code, out.get("answer", None)
   except Exception as e:
       return code, f"[Execution error: {e}]"


questions = [
   "What is the Pearson correlation between BMI and disease_progression?",
   "Show mean target by tertiles of BMI (low/med/high).",
   "Which single feature correlates most with the target (absolute value)?"
]
for q in questions:
   code, ans = eda_qa(q)
   print("\nQ:", q, "\nCode:\n", code, "\nAnswer:\n", ans)

Анализ рисков и what‑if тесты

Gemini помогает найти потенциальные проблемы (утечка данных, переобучение, проблемы калибровки, OOD и fairness) и предлагает быстрые проверки. Также реализованы простые what‑if оценки для топ‑признаков, чтобы увидеть эффект небольших изменений на предсказание.

crossitique = ask_llm(
   f"""Metrics: {report_obj['metrics']}
Top importances: {report_obj['top_importances']}
Identify risks around leakage, overfitting, calibration, OOD robustness, and fairness (even proxy-only).
Propose quick checks (concise Python sketches)."""
)
print("\n Gemini Risk & Robustness Review\n" + "-"*80 + f"\n{critique}\n")


def what_if(pipe, Xref: pd.DataFrame, feat: str, delta: float = 0.05):
   x0 = Xref.median(numeric_only=True).to_dict()
   x1, x2 = x0.copy(), x0.copy()
   if feat not in x1: return np.nan
   x2[feat] = x1[feat] + delta
   X1 = pd.DataFrame([x1], columns=X.columns)
   X2 = pd.DataFrame([x2], columns=X.columns)
   return float(pipe.predict(X2)[0] - pipe.predict(X1)[0])


for f in top_feats:
   print(f"Estimated Δtarget if {f} increases by +0.05 ≈ {what_if(pipe, Xte, f, 0.05):.2f}")

Коротко о дальнейших шагах

Проект легко адаптируется: меняйте датасеты, тюньте гиперпараметры, добавляйте фичи по подсказкам Gemini или расширяйте правила песочницы. Сочетание воспроизводимого пайплайна с интерактивным AI ускоряет исследование данных, интерпретацию моделей и выбор приоритетных экспериментов.