Suscripción mediante IA: ingeniería de funciones y puntuación de riesgo en los seguros modernos
La suscripción es el corazón de cualquier empresa de seguridad: y el proceso mediante el cual Decide si aceptas un riesgo, a qué precio y en qué condiciones. Durante décadas, este proceso dependía de la experiencia de los inspectores humanos que analizaban documentos en papel y aplicaban Normativa actual codificada en tablas. ¿El resultado? Decisiones en 3-5 días laborables, costes altos costos operativos y variabilidad subjetiva entre las aseguradoras.
La inteligencia artificial está reescribiendo radicalmente estas reglas. Según McKinsey, el Las inversiones globales en soluciones de IA lo superarán de forma segura 6 millones de dolares para 2025, y BCG estima que el 36% del valor total de la seguridad de la IA se centra precisamente en la función de suscripción. Las cifras operativas son igualmente impresionantes: El tiempo de decisión de inscripción se ha reducido de 3 a 5 días para 12,4 minutos para ellos pólizas estándar, manteniendo un índice de precisión en la evaluación de riesgos del 99,3%.
Pero, ¿cómo funciona realmente un sistema de suscripción de IA? Esta guía lo deconstruirá todo. pila técnica: después de la recuperación e ingeniería de características, hasta ahora tienes los modelos correctos, ahora hasta interpretabilidad y gestión de mensajes, con ejemplos de listas de códigos reales para producción.
lo que aprendes
- Arquitectura de un sistema de suscripción de IA de extremo a extremo
- Ingeniería de funciones específica para el ámbito de los seguros.
- Modelos de ML para puntuación de riesgo: XGBoost, dos etapas de frecuencia/gravedad
- Interpretabilidad con SHAP para decisiones auditables y listas para el cumplimiento
- Detección de excesos y mitigación de la equidad en el contexto regulatorio de la UE
- MLOps para modelos de suscripción y producción con MLflow
- Seguimiento de la deriva de datos con el Índice de Estabilidad de la Población (PSI)
El proceso de suscripción: del legado al nativo de la IA
Antes de diseñar un sistema de IA, es fundamental comprender el flujo de trabajo tradicional al que nos enfrentamos. automatizando. El proceso de suscripción se divide en cuatro fases fundamentales:
- Recopilación de información: El solicitante proporciona datos sobre sí mismo y el riesgo (cuestionario, documentos, posible inspección física del activo)
- Análisis de riesgos: El asegurador evalúa la probabilidad y gravedad de cualquier reclamación futura.
- Pricing: Determinazione del premio in base al rischio valutato e agli obiettivi di combined ratio del portfolio
- Decisión: Aceptación, devolución o aceptación con condiciones (exclusiones, gratis, primero)
Un sistema de IA nativo no elimina estas fases, pero las transforma profundamente: recopilación de datos el análisis de riesgos es automático a partir de fuentes heterogéneas (datos abiertos, telemática, buró de crédito). y fabricado para modelos ML en milisegundos, el precio es dinámico y personalizado para cada uno solicitante, y la decisión se automatiza para casos estándar con supervisión humana para el Casos completos o dobles.
Quadro Normativo: AI Act EU e Underwriting
La Ley Europea de IA (plenamente vigente desde agosto de 2027) clasifica los sistemas puntuación crédito y seguridad cómo IA alto riesgo (Anexo III). esto implica obligaciones específicas: transparencia de las decisiones automatizadas, cumplimiento de la revisión humana, Documentación técnica detallada y evaluación de conformidad previa a la comercialización. El diseño de Los sistemas de suscripción IA deben incorporar estos requisitos directamente desde la arquitectura, sin importar cuán modernización trasera.
Funciones de ingeniería para una suscripción segura
La calidad de la ingeniería de funciones es el factor que más diferencia un modelo de suscripción excelente que mediocre. Una diferencia entre dominios como la visión por computadora, donde las características son Eliminado automáticamente de los límites convolucionales, se requieren datos tabulares seguros Ingeniería manual profunda basada en el conocimiento del dominio actuarial.
Las características para el ramo de automoción se agrupan en cinco categorías principales:
- Características demográficas: edad, estado civil, tipo de residencia
- Funciones de conducción: años de permiso de conducir, edad en la que se obtuvo por primera vez, historial de accidentes e infracciones
- Características del vehículo: marca, modelo, año, valor, potencia, año de matriculación
- Características geográficas: densidad urbana, índice de criminalidad de la zona, riesgo climático
- Características económicas: puntaje crediticio, tipo de contrato de seguro requerido
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)
),
}
Modelos de calificación de riesgos: enfoques y compensaciones
La selección del modelo de aprendizaje automático para la exactitud de los resultados debe equilibrar la precisión predictivo, interpretabilidad (fundamental para el cumplimiento), velocidad de inferencia y facilidad de mantenimiento. Estos son los principales focos de la industria de la seguridad:
Modelos de comparación para la precisión del riesgo de seguridad.
| Modello | Accuratezza | Interpretabilita | Use Case Ideale |
|---|---|---|---|
| GLM (Poisson/Gamma) | Media | muy alto | Baseline attuariale, regulatory acceptance |
| Random Forest | Alta | Media | Feature importance, robustezza agli outlier |
| XGBoost / LightGBM | muy alto | Media | Producción estándar, SOTA sobre datos tabulares. |
| Neural Network Tabulare | Alta | Bassa | Funciones completas con incrustaciones de categorías. |
El enfoque más establecido en la industria es este modelo de dos pasos: un modelo de frecuencia (probabilidad de tener al menos un accidente) y de gravedad (costes esperados del accidente). dados que ocurre). La primera esperanza pura es: Frecuencia x Gravedad.
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, # stabilita 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)
Interpretabilidad con SHAP: decisiones auditables
En un contexto regulado como el de la seguridad, un modelo de caja negra no es suficiente. La legislación exige que las decisiones de suscripción sean explicables: para el cliente (ver a la explicación del RGPD), para inspectores (revisión limitada de casos) y para reguladores (Solvencia II Pilar 3, ORSA). SHAP (explicaciones de aditivos de SHapley) es el hardware de referencia industria para la interpretación post-hoc de modelos conjuntos.
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"
Detección de equidad y sesgos en el contexto de la UE
El uso de variables proxy (código postal, tarjeta de crédito) puede introducir discriminación indirecta prohibido por la ley. En Europa, la directiva sobre igualdad de género (confirmada por La sentencia Test-Achats del Tribunal de Justicia de la UE (2011) prohíbe el uso del género para fijar precios seguro. La Ley de IA añade restricciones a los sistemas altamente clasificados del Anexo III, solicitar evaluaciones acumulativas obligatorias antes de la implementación.
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 e Monitoring in Produzione
Los modelos de suscripción son adecuados para ti. deriva del concepto frecuente: el perfil de los solicitantes de cambios (nuevos modelos de vehículos eléctricos, cambios demográficos), costes de reparaciones por inflación, los fenómenos climáticos extremos alteran los patrones de pérdidas. Un sistema de seguimiento continuo con Índice de estabilidad de la población (PSI) yo Es fundamental identificar cuándo es necesario retirar el modelo.
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)))
Best Practices e Anti-pattern
Mejores prácticas para la suscripción a IA
- Arquitectura de dos etapas (frecuencia/severidad): y el estándar actuarial produce precios más precisos que un solo modelo en el momento de la reclamación
- Plazo obligatorio: los datos de seguridad se correlacionan a lo largo del tiempo; nunca utilice la reproducción aleatoria para la división de entrenamiento/vista previa
- Exposición como compensación: Utilice siempre la duración de la póliza (exposición en años) como compensación en el modelo de Poisson para normalizar el recuento de reclamaciones.
- Mantener un GLM de referencia: Los reguladores validan más fácilmente los modelos lineales generalizados y proporcionan puntos de referencia para evaluar el valor agregado del ML.
- Modo sombra antes del lanzamiento: Ejecute el modelo en paralelo con el script humano durante 30 a 90 días, comparando las decisiones antes de la automatización.
- Monitoree PSI semanalmente: La deriva en el sector del automóvil se debe frecuentemente a los nuevos modelos de vehículos, la inflación de los costes de reparación y los cambios regulatorios.
Antipatrones a evitar
- Fuga de características: nunca use variables disponibles solo después del reclamo (monto del reclamo, reserva) como características de entrenamiento del modelo de frecuencia
- Optimice AUC únicamente: En el sector de seguros, las métricas relevantes son el coeficiente de Gini, el índice combinado y el aumento en el decil superior de riesgo.
- Modelos con más de 500 funciones: imposible de validar actuarialmente y justificar ante el regulador; Prefiera una selección rigurosa de funciones (máximo 40-60 funciones)
- Ignorando la concentración del papel: un modelo que solo acepta archivos de bajo perfil crea antiselección y una lista desequilibrada
- Discriminación por poder: variables como el código postal pueden ser indicadores del origen étnico; Siempre verifique el impacto dispar antes de la implementación.
Conclusiones Próximos pasos
La suscripción a la IA no sustituye al inspector humano, hasta que éste la amplifique: decisiones para Las pólizas estándar (80-90% del volumen) se pueden automatizar completamente con precisión más alto que el remedio humano, liberando a los especialistas para casos complejos de los cuales la experiencia de dominio e irremplazable.
Las claves para un sistema exitoso son: ingeniería funcional profunda basada en el conocimiento actuarial, arquitectura de frecuencia/severidad de dos etapas, interpretación SHAP para cumplimiento, Auditoría patrimonial obligatoria y seguimiento continuo con PSI para gestión de deriva.
El siguiente artículo de la serie explora laautomatización de quejas con Visión por Computador PNL: desde FNOL digital hasta evaluación fotográfica automática de daños, hasta liquidación acelerada de extremo a extremo.
Serie InsurTech Engineering
- 01 - Dominio Assicurativo per Developer: Prodotti, Attori e Data Model
- 02 - Policy Management Cloud-Native: Architettura API-First
- 03 - Canal telemático: procesamiento de datos UBI a escala
- 04 - AI Underwriting: Feature Engineering e Risk Scoring (questo articolo)
- 05 - Automazione Sinistri: Computer Vision e NLP
- 06 - Fraud Detection: Graph Analytics e Behavioral Signal
- 07 - Standard ACORD e API Integration Assicurative
- 08 - Compliance Engineering: Solvency II e IFRS 17







