AI Underwriting: Feature Engineering a hodnocení rizik v moderním pojištění
Upisování je tlukoucím srdcem každé pojišťovny: a procesem, kterým to probíhá rozhoduje, zda přijmout riziko, za jakou cenu a za jakých podmínek. Po celá desetiletí tento proces záleželo na odbornosti lidských upisovatelů, kteří analyzovali papírové dokumenty a aplikovali je pojistně-matematická pravidla kodifikovaná v tabulkách. Výsledek? Rozhodnutí do 3-5 pracovních dnů, náklady vysoké provozní náklady a subjektivní variabilita mezi upisovateli.
Umělá inteligence tato pravidla radikálně přepisuje. Podle McKinsey, globální investice do řešení AI pro pojištění předčí 6 miliard dolarů v roce 2025, přičemž BCG odhaduje, že 36 % z celkové hodnoty pojištění AI je soustředěna právě do funkce upisování. Operační čísla jsou stejně působivá: průměrná doba rozhodování o upisování klesla ze 3–5 dnů na 12,4 minuty pro ně standardní politiky, udržující míru přesnosti v hodnocení rizik 99,3 %.
Jak ale systém upisování AI vlastně funguje? Tento průvodce celou věc dekonstruuje technický zásobník: od shromažďování a navrhování funkcí až po modely bodování rizik, až na interpretovatelnost a řízení zkreslení – s příklady reálného kódu připraveného pro produkci.
Co se naučíte
- Architektura komplexního upisovacího systému AI
- Funkční inženýrství specifické pro oblast pojištění
- Modely ML pro hodnocení rizik: XGBoost, dvě fáze frekvence/závažnosti
- Interpretovatelnost s SHAP pro auditovatelná a připravená rozhodnutí
- Detekce zkreslení a zmírňování spravedlnosti v regulačním kontextu EU
- MLOps pro upisování modelů ve výrobě s MLflow
- Monitorování posunu dat pomocí indexu stability populace (PSI)
Proces upisování: od dědictví po AI-Native
Před návrhem systému AI je nezbytné porozumět tradičnímu pracovnímu postupu, se kterým se zabýváme automatizace. Proces upisování je rozdělen do čtyř základních fází:
- Sběr informací: Žadatel poskytne údaje o sobě a riziku (dotazník, doklady, případná fyzická kontrola majetku)
- Analýza rizik: Upisovatel vyhodnocuje pravděpodobnost a závažnost jakýchkoli budoucích nároků
- Cena: Stanovení pojistného na základě vyhodnoceného rizika a kombinovaných ukazatelových cílů portfolia
- Rozhodnutí: Přijetí, odmítnutí nebo přijetí s podmínkami (výjimky, franšíza, prémie)
Nativní systém AI tyto fáze neodstraňuje, ale hluboce je transformuje: sběr dat analýza rizik se stává automatickou z heterogenních zdrojů (otevřená data, telematika, úvěrový úřad). a prováděné modely ML v milisekundách, ceny jsou dynamické a přizpůsobené pro každou z nich žadatele a rozhodování je u standardních případů automatizované s lidským dohledem pro i složité nebo hraniční případy.
Regulační rámec: AI Act EU and Underwriting
Evropský zákon o umělé inteligenci (plná účinnost od srpna 2027) klasifikuje systémy bodování úvěr a pojištění jako Vysoce riziková umělá inteligence (příloha III). Toto znamená konkrétní povinnosti: transparentnost automatizovaných rozhodnutí, právo na kontrolu člověkem, podrobnou technickou dokumentaci a posouzení shody před uvedením na trh. Návrh Systémy upisování AI musí zahrnovat tyto požadavky přímo z architektury, nikoli jak následné dodatečné vybavení.
Funkce inženýrství pro upisování pojištění
Kvalita technického inženýrství je faktorem, který nejvíce odlišuje upisovací model vynikající od průměrného. Na rozdíl od domén, jako je počítačové vidění, kde funkce jsou automaticky extrahované z konvolučních vrstev, vyžadují tabulková data pojištění hluboké manuální inženýrství založené na znalostech pojistné matematiky.
Funkce pro automobilový průmysl jsou seskupeny do pěti hlavních kategorií:
- Demografické charakteristiky: věk, rodinný stav, typ bydliště
- Jízdní vlastnosti: roky řidičského průkazu, věk při prvním získání, historie nehod a přestupků
- Vlastnosti vozidla: značka, model, rok, hodnota, výkon, rok registrace
- Zeměpisné vlastnosti: hustota měst, index kriminality oblasti, riziko počasí
- Ekonomické vlastnosti: kreditní skóre, požadovaný typ pojistné smlouvy
import pandas as pd
import numpy as np
from typing import Dict, Optional
from dataclasses import dataclass
from datetime import date
@dataclass
class PolicyApplicant:
"""Rappresenta i dati grezzi di un richiedente polizza auto."""
applicant_id: str
birth_date: date
license_date: date
zip_code: str
vehicle_make: str
vehicle_year: int
vehicle_value: float
annual_mileage: int
claims_3yr: int
violations_3yr: int
credit_score: Optional[int] = None
marital_status: str = "single"
housing_type: str = "tenant"
class AutoInsuranceFeatureEngineer:
"""
Feature engineering per underwriting auto.
Produce 40+ feature da dati grezzi del richiedente,
includendo feature derivate, interazioni e encoding
domain-specific.
"""
VEHICLE_MAKE_RISK: Dict[str, int] = {
"Ferrari": 5, "Lamborghini": 5, "Porsche": 4,
"BMW": 3, "Mercedes": 3, "Audi": 3,
"Toyota": 1, "Honda": 1, "Volkswagen": 2,
"Ford": 2, "Fiat": 2, "Renault": 2,
}
def __init__(self, reference_date: Optional[date] = None):
self.reference_date = reference_date or date.today()
def engineer_features(self, applicant: PolicyApplicant) -> Dict[str, float]:
features: Dict[str, float] = {}
features.update(self._demographic_features(applicant))
features.update(self._driving_experience_features(applicant))
features.update(self._vehicle_features(applicant))
features.update(self._claims_features(applicant))
features.update(self._geographic_features(applicant))
if applicant.credit_score is not None:
features.update(self._credit_features(applicant))
features.update(self._interaction_features(features))
return features
def _demographic_features(self, applicant: PolicyApplicant) -> Dict[str, float]:
age = (self.reference_date - applicant.birth_date).days / 365.25
return {
"age": age,
"age_squared": age ** 2,
"age_under_25": float(age < 25),
"age_over_70": float(age > 70),
"age_risk_young": max(0.0, (25 - age) / 25) if age < 25 else 0.0,
"age_risk_senior": max(0.0, (age - 70) / 20) if age > 70 else 0.0,
"is_married": float(applicant.marital_status == "married"),
"is_homeowner": float(applicant.housing_type == "owner"),
}
def _driving_experience_features(self, applicant: PolicyApplicant) -> Dict[str, float]:
years_licensed = (self.reference_date - applicant.license_date).days / 365.25
age = (self.reference_date - applicant.birth_date).days / 365.25
age_at_license = age - years_licensed
return {
"years_licensed": years_licensed,
"years_licensed_squared": years_licensed ** 2,
"age_at_first_license": age_at_license,
"late_license_ratio": max(0.0, (age_at_license - 18) / 10),
"is_new_driver": float(years_licensed < 2),
"is_experienced_driver": float(years_licensed > 10),
}
def _vehicle_features(self, applicant: PolicyApplicant) -> Dict[str, float]:
vehicle_age = self.reference_date.year - applicant.vehicle_year
make_risk = self.VEHICLE_MAKE_RISK.get(applicant.vehicle_make, 2)
return {
"vehicle_age": float(vehicle_age),
"vehicle_value": applicant.vehicle_value,
"vehicle_value_log": np.log1p(applicant.vehicle_value),
"vehicle_make_risk_score": float(make_risk),
"is_high_performance": float(make_risk >= 4),
"is_new_vehicle": float(vehicle_age <= 2),
"is_old_vehicle": float(vehicle_age > 10),
"annual_mileage": float(applicant.annual_mileage),
"annual_mileage_log": np.log1p(applicant.annual_mileage),
"high_mileage": float(applicant.annual_mileage > 20000),
}
def _claims_features(self, applicant: PolicyApplicant) -> Dict[str, float]:
claims = applicant.claims_3yr
violations = applicant.violations_3yr
return {
"claims_3yr": float(claims),
"violations_3yr": float(violations),
"has_any_claim": float(claims > 0),
"has_multiple_claims": float(claims > 1),
"has_violations": float(violations > 0),
# Score combinato ponderato: sinistri pesano 3x rispetto a infrazioni
"incident_score": claims * 3.0 + violations * 1.5,
"claims_x_violations": float(claims * violations),
}
def _geographic_features(self, applicant: PolicyApplicant) -> Dict[str, float]:
# In produzione: lookup su DB geografici (ISTAT, OpenStreetMap, criminalita)
zip_hash = hash(applicant.zip_code) % 100
urban_score = (zip_hash % 5) / 4.0
crime_index = (zip_hash % 3) / 2.0
weather_risk = (zip_hash % 4) / 3.0
return {
"urban_density_score": urban_score,
"area_crime_index": crime_index,
"area_weather_risk": weather_risk,
"composite_geo_risk": (urban_score + crime_index + weather_risk) / 3,
}
def _credit_features(self, applicant: PolicyApplicant) -> Dict[str, float]:
score = applicant.credit_score or 0
return {
"credit_score": float(score),
"credit_score_normalized": (score - 300) / (850 - 300),
"poor_credit": float(score < 580),
"fair_credit": float(580 <= score < 670),
"good_credit": float(670 <= score < 740),
"excellent_credit": float(score >= 740),
}
def _interaction_features(self, features: Dict[str, float]) -> Dict[str, float]:
return {
# Giovane + auto sportiva = rischio molto alto
"young_high_perf": (
features.get("age_risk_young", 0) *
features.get("is_high_performance", 0)
),
# Sinistri + area ad alto crimine amplificano il rischio
"claims_urban": (
features.get("claims_3yr", 0) *
features.get("urban_density_score", 0)
),
# Mileage alto + veicolo vecchio = rischio meccanico aumentato
"mileage_old_vehicle": (
features.get("annual_mileage_log", 0) *
features.get("is_old_vehicle", 0)
),
}
Modely hodnocení rizik: Přístupy a kompromisy
Výběr modelu strojového učení pro bodování rizik musí vyvažovat přesnost prediktivní, interpretovatelnost (zásadní pro shodu), rychlost vyvozování a snadnost údržby. Zde jsou hlavní přístupy pojišťovnictví:
Porovnání modelů hodnocení pojistného rizika
| Model | Přesnost | Interpretovatelnost | Ideální případ použití |
|---|---|---|---|
| GLM (Poisson/Gamma) | Průměrný | Velmi vysoká | Pojistně-matematický základ, regulační akceptace |
| Náhodný les | Vysoký | Průměrný | Význam rysů, odolnost vůči odlehlým hodnotám |
| XGBoost / LightGBM | Velmi vysoká | Průměrný | Standardní výroba, SOTA na tabulkových datech |
| Tabelární neuronová síť | Vysoký | Nízký | Komplexní funkce s kategorickými vložkami |
Nejznámějším přístupem v oboru je dvoustupňový model: modelka frekvence (pravděpodobnost, že dojde k alespoň jedné nehodě) a jedna podle závažnosti (očekávané náklady na nehodu). vzhledem k tomu, že k tomu dojde). Očekávaná čistá prémie je: Frekvence x Závažnost.
import xgboost as xgb
from sklearn.metrics import mean_absolute_error, mean_squared_error
import numpy as np
import pandas as pd
from typing import Dict, Optional
class TwoStageRiskScorer:
"""
Modello a due stadi per pricing assicurativo auto.
Stage 1: Frequency model (Poisson regression con XGBoost)
Target = numero sinistri per polizza
Stage 2: Severity model (Tweedie/Gamma con XGBoost)
Target = importo sinistro, addestrato solo su polizze con sinistri
Pure Premium = E[Frequency] * E[Severity | has_claim]
"""
FREQUENCY_PARAMS: Dict = {
"objective": "count:poisson",
"eval_metric": "poisson-nloglik",
"max_depth": 6,
"learning_rate": 0.05,
"n_estimators": 500,
"min_child_weight": 50, # stabilità attuariale: min sinistri per leaf
"subsample": 0.8,
"colsample_bytree": 0.8,
"reg_alpha": 0.1,
"reg_lambda": 1.0,
"tree_method": "hist",
"early_stopping_rounds": 50,
}
SEVERITY_PARAMS: Dict = {
"objective": "reg:tweedie",
"tweedie_variance_power": 1.5, # 1=Poisson, 2=Gamma
"eval_metric": "tweedie-nloglik@1.5",
"max_depth": 5,
"learning_rate": 0.05,
"n_estimators": 300,
"min_child_weight": 30,
"subsample": 0.8,
"colsample_bytree": 0.7,
"reg_alpha": 0.1,
"reg_lambda": 1.0,
"tree_method": "hist",
"early_stopping_rounds": 30,
}
def __init__(self) -> None:
self.frequency_model = xgb.XGBRegressor(**self.FREQUENCY_PARAMS)
self.severity_model = xgb.XGBRegressor(**self.SEVERITY_PARAMS)
self.feature_names: list = []
def fit(
self,
X: pd.DataFrame,
y_claims: pd.Series,
y_amounts: pd.Series,
exposure: pd.Series,
eval_fraction: float = 0.2,
) -> "TwoStageRiskScorer":
"""
Addestra entrambi i modelli.
IMPORTANTE: usa split temporale, non random shuffle.
I dati assicurativi sono autocorrelati nel tempo.
"""
self.feature_names = X.columns.tolist()
split_idx = int(len(X) * (1 - eval_fraction))
X_train, X_val = X.iloc[:split_idx], X.iloc[split_idx:]
freq_train = y_claims.iloc[:split_idx]
freq_val = y_claims.iloc[split_idx:]
# Stage 1: Frequency
self.frequency_model.fit(
X_train, freq_train,
sample_weight=exposure.iloc[:split_idx],
eval_set=[(X_val, freq_val)],
verbose=50,
)
# Stage 2: Severity - solo su polizze con sinistri
has_claim = y_amounts > 0
X_sev = X[has_claim]
y_sev = y_amounts[has_claim]
sev_split = int(len(X_sev) * (1 - eval_fraction))
self.severity_model.fit(
X_sev.iloc[:sev_split], y_sev.iloc[:sev_split],
eval_set=[(X_sev.iloc[sev_split:], y_sev.iloc[sev_split:])],
verbose=30,
)
return self
def predict_pure_premium(
self, X: pd.DataFrame, exposure: float = 1.0
) -> np.ndarray:
"""Calcola il pure premium: E[Freq] * E[Severity]."""
freq = self.frequency_model.predict(X) * exposure
sev = self.severity_model.predict(X)
return freq * sev
def evaluate(self, X: pd.DataFrame, y_claims: pd.Series) -> Dict[str, float]:
pred = self.frequency_model.predict(X)
mae = mean_absolute_error(y_claims, pred)
rmse = float(np.sqrt(mean_squared_error(y_claims, pred)))
gini = self._gini_coefficient(y_claims.values, pred)
lift = self._lift_at_decile(y_claims.values, pred, 0.1)
return {
"mae": round(mae, 6),
"rmse": round(rmse, 6),
"gini_coefficient": round(gini, 4),
"lift_top_decile": round(lift, 4),
}
def _gini_coefficient(self, actual: np.ndarray, predicted: np.ndarray) -> float:
"""Gini coefficient: metrica attuariale standard per modelli di frequenza."""
idx = np.argsort(predicted)
cum = np.cumsum(actual[idx])
cum_norm = cum / cum[-1]
n = len(actual)
lorenz_area = float(np.sum(cum_norm)) / n
return 2 * (lorenz_area - 0.5)
def _lift_at_decile(
self, actual: np.ndarray, predicted: np.ndarray, decile: float
) -> float:
k = max(1, int(len(actual) * decile))
top_idx = np.argsort(predicted)[-k:]
base_rate = actual.mean()
if base_rate == 0:
return 0.0
return float(actual[top_idx].mean() / base_rate)
Interpretovatelnost s SHAP: Auditovatelná rozhodnutí
V regulovaném kontextu, jako je pojištění, model černé skříňky nestačí. Legislativa vyžaduje, aby rozhodnutí o upisování byla vysvětlitelná: pro zákazníka (vpravo k vysvětlení GDPR), pro upisovatele (přezkoumání hraničních případů) a pro regulátory (Solvency II Pilíř 3, ORSA). Referenčním nástrojem je SHAP (SHapley Additive exPlanations). průmyslu pro post-hoc interpretovatelnost modelů souborů.
import shap
import pandas as pd
import numpy as np
from typing import Dict, List, Tuple
class UnderwritingExplainer:
"""
Spiegazioni SHAP per decisioni underwriting.
Genera output a tre livelli: cliente, underwriter, compliance.
"""
FEATURE_LABELS: Dict[str, str] = {
"age": "eta del guidatore",
"years_licensed": "anni di patente",
"claims_3yr": "sinistri negli ultimi 3 anni",
"violations_3yr": "infrazioni negli ultimi 3 anni",
"vehicle_make_risk_score": "categoria rischio veicolo",
"vehicle_age": "anzianita del veicolo",
"vehicle_value": "valore del veicolo",
"annual_mileage": "chilometraggio annuo dichiarato",
"composite_geo_risk": "rischio della zona geografica",
"credit_score": "score creditizio",
"young_high_perf": "combinazione giovane + veicolo sportivo",
}
def __init__(self, model, feature_names: List[str]) -> None:
self.feature_names = feature_names
self.explainer = shap.TreeExplainer(model)
def explain(
self, X_row: pd.DataFrame, risk_score: float
) -> Dict:
"""Spiegazione completa per una singola valutazione."""
shap_values = self.explainer.shap_values(X_row)
impacts: List[Tuple[str, float]] = sorted(
zip(self.feature_names, shap_values[0]),
key=lambda x: abs(x[1]),
reverse=True
)
return {
"risk_score": round(risk_score, 2),
"decision": self._score_to_decision(risk_score),
"customer_message": self._customer_message(impacts, risk_score),
"top_risk_factors": [
{
"name": name,
"label": self.FEATURE_LABELS.get(name, name),
"direction": "aumenta rischio" if shap > 0 else "riduce rischio",
"magnitude": round(abs(shap), 4),
}
for name, shap in impacts[:5]
],
"audit_trail": {
"base_expected_value": float(self.explainer.expected_value),
"all_shap_values": {
n: round(float(s), 6)
for n, s in zip(self.feature_names, shap_values[0])
},
"input_features": X_row.to_dict(orient="records")[0],
},
}
def _customer_message(
self, impacts: List[Tuple[str, float]], score: float
) -> str:
high = [(n, v) for n, v in impacts if abs(v) > 0.1]
if not high:
return "Il tuo profilo rientra nella fascia di rischio standard."
positivi = [self.FEATURE_LABELS.get(n, n) for n, v in high[:3] if v < 0]
negativi = [self.FEATURE_LABELS.get(n, n) for n, v in high[:3] if v > 0]
parts = []
if negativi:
parts.append(f"Fattori che aumentano il profilo di rischio: {', '.join(negativi)}.")
if positivi:
parts.append(f"Fattori a tuo favore: {', '.join(positivi)}.")
return " ".join(parts)
def _score_to_decision(self, score: float) -> str:
if score < 30:
return "ACCEPT_PREFERRED"
elif score < 60:
return "ACCEPT_STANDARD"
elif score < 80:
return "ACCEPT_SUBSTANDARD"
return "DECLINE_OR_MANUAL_REVIEW"
Spravedlnost a detekce zkreslení v kontextu EU
Použití zástupných proměnných (PSČ, kreditní skóre) může zavést nepřímou diskriminaci zákonem zakázáno. V Evropě platí směrnice o rovnosti žen a mužů (potvrzená Rozhodnutí Soudního dvora EU ve věci Test-Achats z roku 2011) zakazuje použití pohlaví pro stanovení cen pojištění. Zákon o umělé inteligenci přidává omezení pro vysoce rizikové systémy klasifikované v příloze III, vyžadující povinné posouzení shody před nasazením.
import pandas as pd
import numpy as np
from sklearn.metrics import confusion_matrix
from typing import Dict, List
class FairnessAuditor:
"""
Auditor di fairness per modelli underwriting (EU-compliant).
Metriche implementate:
- Disparate Impact (regola 80%)
- Demographic Parity Gap
- Equal Opportunity (TPR parity)
- Calibration by group
"""
DISPARATE_IMPACT_THRESHOLD = 0.8 # EEOC 80% rule
MAX_DP_GAP = 0.1 # linee guida EIOPA
def __init__(
self,
predictions: np.ndarray,
true_labels: np.ndarray,
sensitive_df: pd.DataFrame,
) -> None:
self.predictions = predictions
self.true_labels = true_labels
self.sensitive_df = sensitive_df
def full_audit(self) -> Dict:
results: Dict = {}
for attr in self.sensitive_df.columns:
groups = self.sensitive_df[attr].unique()
attr_results: Dict = {}
for group in groups:
mask = self.sensitive_df[attr] == group
g_pred = self.predictions[mask]
g_true = self.true_labels[mask]
attr_results[str(group)] = {
"count": int(mask.sum()),
"acceptance_rate": float((g_pred < 0.6).mean()),
"avg_score": round(float(g_pred.mean()), 4),
"tpr": self._tpr(g_true, g_pred),
}
di = self._disparate_impact(attr_results)
dp = self._dp_gap(attr_results)
attr_results["_metrics"] = {
"disparate_impact": round(di, 4),
"demographic_parity_gap": round(dp, 4),
"passes_di_rule": di >= self.DISPARATE_IMPACT_THRESHOLD,
"passes_dp_rule": dp <= self.MAX_DP_GAP,
"overall_fair": di >= self.DISPARATE_IMPACT_THRESHOLD and dp <= self.MAX_DP_GAP,
}
results[attr] = attr_results
return results
def _tpr(self, labels: np.ndarray, preds: np.ndarray, thr: float = 0.5) -> float:
if len(labels) < 10:
return float("nan")
binary = (preds >= thr).astype(int)
try:
tn, fp, fn, tp = confusion_matrix(labels, binary, labels=[0, 1]).ravel()
return round(tp / (tp + fn), 4) if (tp + fn) > 0 else 0.0
except ValueError:
return float("nan")
def _disparate_impact(self, groups: Dict) -> float:
rates = [v["acceptance_rate"] for k, v in groups.items()
if not k.startswith("_") and isinstance(v, dict)]
if not rates or max(rates) == 0:
return 1.0
return min(rates) / max(rates)
def _dp_gap(self, groups: Dict) -> float:
rates = [v["acceptance_rate"] for k, v in groups.items()
if not k.startswith("_") and isinstance(v, dict)]
return (max(rates) - min(rates)) if rates else 0.0
MLOps a monitorování ve výrobě
Modely upisování podléhají koncept drift časté: profil změny žadatelů (nové modely elektromobilů, demografické změny), náklady opravy trpí inflací, extrémní klimatické jevy mění vzorce ztrát. Nepřetržitý monitorovací systém s Index stability populace (PSI) e nezbytné pro identifikaci, kdy je třeba model znovu odtáhnout.
from scipy import stats
import numpy as np
import pandas as pd
from typing import Dict, List
from datetime import datetime
class DriftMonitor:
"""
Monitora data drift per modelli underwriting.
Usa PSI (Population Stability Index) come metrica primaria.
PSI interpretation:
- PSI < 0.1: Nessun cambiamento significativo
- PSI 0.1-0.25: Cambiamento moderato, monitorare
- PSI > 0.25: Cambiamento significativo, retraining consigliato
"""
def __init__(self, reference_df: pd.DataFrame, features: List[str]) -> None:
self.reference_df = reference_df
self.features = features
def check_drift(self, current_df: pd.DataFrame) -> Dict:
feature_results: Dict = {}
critical_features = []
for feat in self.features:
if feat not in current_df.columns:
continue
psi = self._psi(self.reference_df[feat], current_df[feat])
ks_stat, ks_p = stats.ks_2samp(
self.reference_df[feat].dropna(),
current_df[feat].dropna()
)
status = "ok" if psi < 0.1 else ("warning" if psi < 0.25 else "critical")
feature_results[feat] = {
"psi": round(psi, 4),
"ks_statistic": round(ks_stat, 4),
"ks_pvalue": round(ks_p, 4),
"status": status,
}
if status == "critical":
critical_features.append(feat)
avg_psi = float(np.mean([v["psi"] for v in feature_results.values()]))
return {
"checked_at": datetime.now().isoformat(),
"overall_psi": round(avg_psi, 4),
"retraining_recommended": avg_psi > 0.1,
"critical_features": critical_features,
"feature_details": feature_results,
}
def _psi(self, ref: pd.Series, cur: pd.Series, bins: int = 10) -> float:
ref_clean = ref.dropna().values
cur_clean = cur.dropna().values
edges = np.percentile(ref_clean, np.linspace(0, 100, bins + 1))
edges = np.unique(edges)
ref_counts, _ = np.histogram(ref_clean, bins=edges)
cur_counts, _ = np.histogram(cur_clean, bins=edges)
ref_pct = (ref_counts + 1e-10) / len(ref_clean)
cur_pct = (cur_counts + 1e-10) / len(cur_clean)
return float(np.sum((cur_pct - ref_pct) * np.log(cur_pct / ref_pct)))
Osvědčené postupy a anti-vzory
Nejlepší postupy pro upisování AI
- Dvoustupňová architektura (frekvence/závažnost): a pojistně-matematický standard a vytváří přesnější ceny než jeden model na výši nároku
- Povinný časový úsek: údaje o pojištění jsou v průběhu času autokorelované; nikdy nepoužívejte náhodné míchání pro rozdělení vlaku/testu
- Expozice jako offset: vždy použijte trvání pojistky (expozice v letech) jako kompenzaci v Poissonově modelu k normalizaci počtu nároků
- Udržujte základní GLM: zobecněné lineární modely jsou snadněji ověřovány regulačními orgány a poskytují referenční hodnoty pro hodnocení přidané hodnoty ML
- Režim stínu před spuštěním: Spusťte model paralelně s lidským upisováním po dobu 30–90 dní a porovnávejte rozhodnutí před automatizací
- Monitorujte PSI týdně: drift v automobilovém sektoru je častý kvůli novým modelům vozidel, inflaci nákladů na opravy a regulačním změnám
Anti-vzory, kterým je třeba se vyhnout
- Únik funkce: nikdy nepoužívejte proměnné dostupné pouze po uplatnění nároku (výše nároku, rezerva) jako tréninkové funkce frekvenčního modelu
- Optimalizovat pouze AUC: v pojišťovnictví jsou relevantní metriky Gini koeficient, Combined Ratio a Lift v horním decilu rizika
- Modely s více než 500 funkcemi: nemožné pojistně-matematické ověření a zdůvodnění regulačnímu orgánu; preferujte pečlivý výběr funkcí (max. 40–60 funkcí)
- Ignorování koncentrace portfolia: model, který akceptuje pouze velmi nízké rizikové profily, vytváří antiselekci a nevyvážené portfolio
- Diskriminace proxy: proměnné, jako je PSČ, mohou být proxy pro etnický původ; vždy před nasazením zkontrolujte nesourodý dopad
Závěry a další kroky
Upisování AI nenahrazuje lidského upisovatele, ale zesiluje ho: rozhodnutí pro Standardní zásady (80–90 % objemu) lze s přesností plně automatizovat vyšší než je lidský průměr, což uvolňuje specialisty pro složité případy, kdy mají zkušenosti z panství a nenahraditelné.
Klíče k úspěšnému systému jsou: znalostní inženýrství hlubokých funkcí pojistná matematika, dvoustupňová architektura frekvence/závažnosti, interpretovatelnost SHAP pro shodu, povinný audit poctivosti a průběžné monitorování s PSI pro řízení driftu.
Další článek v sérii zkoumáautomatizace nároků pomocí Computer Vision a NLP: od digitálního FNOL po automatické hodnocení poškození fotografií, až zrychlené vypořádání end-to-end.
InsurTech Engineering Series
- 01 – Pojistná doména pro vývojáře: Produkty, herci a datové modely
- 02 - Cloud-Native Policy Management: API-First Architecture
- 03 - Telematics Pipeline: Zpracování dat UBI v měřítku
- 04 – AI Underwriting: Feature Engineering a hodnocení rizik (tento článek)
- 05 - Automatizace pohledávek: Počítačové vidění a NLP
- 06 - Detekce podvodů: Analýza grafů a behaviorální signál
- 07 - Integrace standardu ACORD a Insurance API
- 08 – Compliance Engineering: Solvency II a IFRS 17







