AI 引受業務: 現代の保険における特徴エンジニアリングとリスク スコアリング
引受業務は保険会社の心臓部であり、そのプロセスです。 リスクを受け入れるかどうか、どのような価格で、どのような条件で受け入れるかを決定します。何十年にもわたって、このプロセスは それは紙文書を分析し、申請を行う人間の保険会社の専門知識に依存していました。 表に成文化された保険数理規則。結果? 3 ~ 5 営業日以内に決定、費用はかかります 高い営業コストと引受会社間の主観的なばらつき。
人工知能はこれらのルールを根本的に書き換えています。マッキンゼーによれば、 保険向け AI ソリューションへの世界的な投資は、 60億ドル 2025年に、BCG は次のように推定しています。 保険AIの総額の36% 引受機能に正確に集中しています。稼働数も同様に印象的です。 引受決定にかかる平均時間は 3 ~ 5 日から 12.4分 彼らのために 標準的なポリシーを採用し、リスク評価の正確性率 99.3% を維持しています。
しかし、AI 引受システムは実際にどのように機能するのでしょうか?このガイドでは全体を分解します 技術スタック: 機能の収集とエンジニアリングからリスク スコアリング モデルまで 解釈可能性とバイアス管理について — 本番環境で使用できる実際のコード例を使用して説明します。
何を学ぶか
- エンドツーエンドの AI 引受システムのアーキテクチャ
- 保険分野に特化した特徴エンジニアリング
- リスク スコアリング用の ML モデル: XGBoost、2 つの頻度/重大度段階
- SHAP による解釈可能性により、監査可能でコンプライアンスに対応した意思決定を実現
- EU 規制の文脈におけるバイアス検出と公平性の軽減
- MLflow を使用した本番環境での引受モデルのための MLOps
- 人口安定指数(PSI)によるデータドリフト監視
引受プロセス: レガシーから AI ネイティブへ
AI システムを設計する前に、私たちが扱っている従来のワークフローを理解することが不可欠です 自動化すること。引受プロセスは 4 つの基本的なフェーズに分かれています。
- 情報収集: 申請者は自分自身とリスクに関するデータを提供します(アンケート、書類、資産の物理的検査の可能性あり)
- リスク分析: 引受会社は、将来の保険金請求の可能性と重大度を評価します。
- 価格設定: 評価されたリスクとポートフォリオのコンバインドレシオ目標に基づいた保険料の決定
- 決断: 承認、拒否、または条件付きでの承認 (除外、フランチャイズ、プレミアム)
AI ネイティブ システムは、これらのフェーズを排除するのではなく、大きく変革します: データ収集 リスク分析は、異種ソース (オープン データ、テレマティクス、信用調査機関) から自動的に行われます。 ML モデルによってミリ秒単位で実行され、価格設定は動的であり、それぞれにパーソナライズされます。 標準的なケースでは、決定は人間の監督下で自動化されます。 複雑なケースや境界線に近いケース。
規制の枠組み: AI 法 EU と引受業務
欧州 AI 法 (2027 年 8 月から完全施行) では、システムが分類されています 得点 信用と保険 として 高リスク AI (付録 III)。これ 特定の義務を意味します: 自動化された決定の透明性、人間によるレビューの権利、 詳細な技術文書と市場投入前の適合性評価。のデザイン AI 引受システムは、これらの要件をどのように組み込むかではなく、アーキテクチャから直接組み込む必要があります。 その後の改造。
保険引受業務のための特徴エンジニアリング
特徴量エンジニアリングの品質は、引受モデルを最も差別化する要素です 平凡から素晴らしい。コンピューター ビジョンのようなドメインとは異なり、機能は 畳み込み層から自動的に抽出される保険表データには、 保険数理分野の知識に基づいた深いマニュアルエンジニアリング。
自動車分野の機能は、次の 5 つの主要カテゴリに分類されます。
- 人口統計上の特徴: 年齢、婚姻状況、住居の種類
- 運転機能: 運転免許証取得年数、初取得年齢、事故歴、違反歴
- 車両の特徴: メーカー、モデル、年、値、出力、登録年
- 地形: 都市密度、その地域の犯罪指数、気象リスク
- 経済的な特徴: 信用スコア、必要な保険契約の種類
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)
),
}
リスクスコアリングモデル: アプローチとトレードオフ
リスクスコアリングのための機械学習モデルの選択は、精度のバランスをとる必要があります 予測性、解釈可能性 (コンプライアンスの基本)、推論の速度と容易さ メンテナンスのこと。保険業界の主なアプローチは次のとおりです。
保険リスクスコアリングモデルの比較
| モデル | 正確さ | 解釈可能性 | 理想的な使用例 |
|---|---|---|---|
| GLM (ポアソン/ガンマ) | 平均 | 非常に高い | 保険数理ベースライン、規制当局の承認 |
| ランダムフォレスト | 高い | 平均 | 特徴の重要性、外れ値に対する堅牢性 |
| XGブースト / ライトGBM | 非常に高い | 平均 | 標準的なプロダクション、表形式データの SOTA |
| 表形式ニューラル ネットワーク | 高い | 低い | カテゴリカル埋め込みを使用した複雑な特徴 |
業界で最も確立されたアプローチは、 2段モデル:モデル 頻度 (少なくとも 1 回の事故が発生する確率) と重大度 (事故の予想コスト) の 1 つ それが発生することを前提とします)。予想される純粋なプレミアムは次のとおりです。 頻度×重大度.
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)
SHAP による解釈可能性: 監査可能な決定
保険などの規制された状況では、ブラックボックス モデルでは十分ではありません。 法律では、引受決定は顧客にとって説明可能であることが求められています(右) GDPR の説明に準拠)、引受会社向け(境界例のレビュー)および規制当局向け (ソルベンシー II の柱 3、ORSA)。 SHAP (SHApley Additive exPlanations) はリファレンス ツールです アンサンブル モデルの事後解釈可能性に関して業界に貢献します。
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"
EU の文脈における公平性とバイアスの検出
代理変数 (郵便番号、信用スコア) の使用は、間接的な差別を引き起こす可能性があります 法律で禁止されています。ヨーロッパでは、男女平等指令(によって確認されています) 2011 年の EU 司法裁判所の Test-Achats 判決) では、価格設定に性別を使用することを禁止しています。 保険。 AI 法は、附属書 III に分類される高リスクシステムに対する制約を追加します。 導入前に必須のコンプライアンス評価が必要です。
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 とモニタリング
引受モデルの対象となるのは、 コンセプトドリフト 頻繁に: プロフィール 申請者の変化(電気自動車の新モデル、人口動態の変化)、費用 修理の割合はインフレに見舞われ、極端な気象現象により損失パターンが変化します。 継続的な監視システム 人口安定指数 (PSI) e モデルをいつ再牽引する必要があるかを特定するために不可欠です。
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)))
ベストプラクティスとアンチパターン
AI 引受業務のベスト プラクティス
- 2 段階のアーキテクチャ (頻度/重大度): および保険数理基準に基づいて、請求額に関する単一のモデルよりも正確な価格設定を生成します。
- 必須の時間分割: 保険データは時間の経過とともに自己相関します。トレーニング/テストの分割にはランダム シャッフルを使用しないでください
- オフセットとしての露出: 保険金請求件数を正規化するために、ポアソン モデルのオフセットとして常に保険期間 (保険期間 (年単位)) を使用します。
- ベースライン GLM を維持します。 一般化された線形モデルは規制当局によってより簡単に検証され、ML の付加価値を評価するためのベンチマークを提供します。
- 稼働前のシャドウ モード: 人間による引受業務と並行してモデルを 30 ~ 90 日間実行し、自動化する前に意思決定を比較します。
- PSI を毎週監視します。 新しい車両モデル、修理費の高騰、規制の変更により、自動車部門のドリフトが頻繁に発生しています
避けるべきアンチパターン
- 特徴リーク: 請求後にのみ利用可能な変数 (請求金額、引当金) を頻度モデルのトレーニング特徴として決して使用しないでください。
- AUC のみを最適化します。 保険部門の関連指標は、ジニ係数、複合比率、およびリスクの上位十分位の上昇率です。
- 500 以上の機能を備えたモデル: 保険数理的に検証し、規制当局に正当化することが不可能。厳密な特徴選択を好む (最大 40 ~ 60 の特徴)
- ポートフォリオの集中を無視する: 非常に低いリスクプロファイルのみを受け入れるモデルは、反選択と不均衡なポートフォリオを生み出します。
- 代理の差別: 郵便番号などの変数は民族性の代用となる可能性があります。導入前に常にさまざまな影響を確認する
結論と次のステップ
AI 引受業務は人間の引受業務に代わるものではありませんが、それを増幅させます。 標準ポリシー (ボリュームの 80 ~ 90%) を正確に完全に自動化できます 人間の平均よりも高いため、専門家が複雑なケースに対応できるようになります。 支配力とかけがえのないもの。
システムを成功させる鍵は次のとおりです。 知識ベースの深層特徴エンジニアリング 保険数理、2 段階の頻度/重大度アーキテクチャ、コンプライアンスのための SHAP 解釈可能性、 ドリフト管理のための必須の公平性監査と PSI による継続的モニタリング。
シリーズの次の記事では、Computer Vision による請求の自動化 とNLP: デジタル FNOL から自動写真損傷評価まで、 エンドツーエンドの決済を加速します。
インシュアテックエンジニアリングシリーズ
- 01 - 開発者向けの保険ドメイン: 製品、アクター、データ モデル
- 02 - クラウドネイティブのポリシー管理: API ファーストのアーキテクチャ
- 03 - テレマティクス パイプライン: 大規模な UBI データ処理
- 04 - AI 引受: 特徴エンジニアリングとリスク スコアリング (この記事)
- 05 - 請求の自動化: コンピューター ビジョンと NLP
- 06 - 不正行為の検出: グラフ分析と行動シグナル
- 07 - ACORD Standard と保険 API の統合
- 08 - コンプライアンス エンジニアリング: ソルベンシー II および IFRS 17







