just-wine / model.py
Tuana's picture
initial app commit
c34e1c0
Raw
History Blame Contribute Delete
3.38 kB
from typing import Optional
import os
import pandas as pd
import numpy as np
import tabpfn_client
from tabpfn_client import TabPFNClassifier
from tabpfn_client.constants import ModelVersion
def init_tabpfn(token: Optional[str] = None) -> None:
"""Configure tabpfn-client. Pass token explicitly or set TABPFN_API_TOKEN in the environment."""
resolved = (token or "").strip() or os.environ.get("TABPFN_API_TOKEN", "").strip()
if not resolved:
raise RuntimeError("TabPFN API token is required.")
tabpfn_client.set_access_token(resolved)
FEATURES = [
"fixed_acidity", "volatile_acidity", "citric_acid", "residual_sugar",
"chlorides", "free_sulfur_dioxide", "total_sulfur_dioxide", "density",
"pH", "sulphates", "alcohol", "wine_type",
]
FEATURE_DISPLAY_NAMES = {
"fixed_acidity": "Fixed Acidity",
"volatile_acidity": "Volatile Acidity",
"citric_acid": "Citric Acid",
"residual_sugar": "Residual Sugar (g/L)",
"chlorides": "Chlorides",
"free_sulfur_dioxide": "Free SO₂ (mg/L)",
"total_sulfur_dioxide": "Total SO₂ (mg/L)",
"density": "Density (g/cm³)",
"pH": "pH",
"sulphates": "Sulphates",
"alcohol": "Alcohol (%)",
"wine_type": "Wine Type (0=Red, 1=White)",
}
QUALITY_LABELS = {0: "Low (≤5)", 1: "Medium (6)", 2: "High (≥7)"}
# Representative UCI quality scores (0–10) used to estimate an index from class probs.
QUALITY_INDEX_BY_BIN = {0: 4.5, 1: 6.0, 2: 7.5}
def quality_index_from_probs(probs: np.ndarray) -> float:
return float(sum(probs[i] * QUALITY_INDEX_BY_BIN[i] for i in range(len(probs))))
RED_URL = "https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv"
WHITE_URL = "https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-white.csv"
def bin_quality(q: int) -> int:
if q <= 5:
return 0
if q == 6:
return 1
return 2
def load_data() -> pd.DataFrame:
red = pd.read_csv(RED_URL, sep=";")
red["wine_type"] = 0
white = pd.read_csv(WHITE_URL, sep=";")
white["wine_type"] = 1
df = pd.concat([red, white], ignore_index=True)
df.columns = [c.strip().replace(" ", "_") for c in df.columns]
df["quality_bin"] = df["quality"].apply(bin_quality)
return df
def prepare_split(df: pd.DataFrame, test_size: int = 1000, random_state: int = 42):
df = df.sample(frac=1, random_state=random_state).reset_index(drop=True)
test_df = df.iloc[:test_size].reset_index(drop=True)
train_df = df.iloc[test_size:].reset_index(drop=True)
return train_df, test_df
def fit_model(train_df: pd.DataFrame) -> TabPFNClassifier:
X = train_df[FEATURES].values.astype(float)
y = train_df["quality_bin"].values
clf = TabPFNClassifier.create_default_for_version(ModelVersion.V3)
clf.fit(X, y)
return clf
def predict(clf: TabPFNClassifier, features: dict) -> tuple[str, dict, float, int]:
"""Returns (predicted label, {label: probability}, estimated quality index, class index)."""
row = np.array([[features[f] for f in FEATURES]], dtype=float)
probs = clf.predict_proba(row)[0]
pred_idx = int(np.argmax(probs))
prob_map = {QUALITY_LABELS[i]: float(probs[i]) for i in range(len(probs))}
quality_index = round(quality_index_from_probs(probs), 1)
return QUALITY_LABELS[pred_idx], prob_map, quality_index, pred_idx