13 Risk-Aware Question Selection¶

这一份 notebook 的目标是把安全控制前移到“问什么”这一步,而不是等问完以后再拒答。

我们现在已经知道:

  • 有些问题会提高正确率
  • 有些问题虽然会提高置信度,但也会带来危险高置信错误

所以这一份实验要回答的是:

在 age / sex / location 三个候选问题中,哪个问题既有收益,又更安全?

核心思路:

  1. 基于已有病例级结果,统计每个问题的收益和风险
  2. 为每个问题定义一个风险感知得分
  3. 用这个得分来决定下一步问什么
  4. 和之前的 fixed-order / uncertainty / lookahead 做对比
In [1]:
from pathlib import Path
from datetime import datetime
import ast
import json

import numpy as np
import pandas as pd
from sklearn.metrics import accuracy_score, balanced_accuracy_score, f1_score

PROJECT_ROOT = Path('/Users/applesues01/Documents/Medical_Agent')
SUPPORT_DIR = PROJECT_ROOT / 'supports'

DYNAMIC_CASES_PATH = SUPPORT_DIR / '2026-08-04_104443_dynamic_agent_cases.csv'
IMAGE_ONLY_CASE_PATH = SUPPORT_DIR / 'image_only_case_level_predictions.csv'
MODEL_REGISTRY_PATH = SUPPORT_DIR / '2026-08-03_194704_all_saved_metadata_models.csv'

print(DYNAMIC_CASES_PATH)
print(IMAGE_ONLY_CASE_PATH)
print(MODEL_REGISTRY_PATH)
/Users/applesues01/Documents/Medical_Agent/supports/2026-08-04_104443_dynamic_agent_cases.csv
/Users/applesues01/Documents/Medical_Agent/supports/image_only_case_level_predictions.csv
/Users/applesues01/Documents/Medical_Agent/supports/2026-08-03_194704_all_saved_metadata_models.csv

1. 读取现有病例级结果¶

这里先不重新训练任何模型,而是利用我们已经跑出来的病例级结果,先把“每种提问的风险画像”统计清楚。

In [2]:
dynamic_df = pd.read_csv(DYNAMIC_CASES_PATH)
image_only_case_df = pd.read_csv(IMAGE_ONLY_CASE_PATH)
model_registry_df = pd.read_csv(MODEL_REGISTRY_PATH)

def parse_list_cell(value):
    if pd.isna(value):
        return []
    if isinstance(value, list):
        return value
    text = str(value).strip()
    if text == '' or text == '[]':
        return []
    try:
        return ast.literal_eval(text)
    except Exception:
        return [text]

bool_cols = [
    'initial_correct', 'final_correct', 'changed_prediction', 'improved',
    'worsened', 'still_wrong', 'still_correct', 'initial_high_conf_wrong',
    'final_high_conf_wrong', 'unsafe_confidence_increase'
]
for col in bool_cols:
    dynamic_df[col] = dynamic_df[col].astype(str).str.lower().map({'true': True, 'false': False})

dynamic_df['asked_questions_list'] = dynamic_df['asked_questions'].apply(parse_list_cell)
dynamic_df['known_keys_list'] = dynamic_df['known_keys'].apply(parse_list_cell)
dynamic_df['confidence_gain'] = dynamic_df['final_max_prob'] - dynamic_df['initial_max_prob']
dynamic_df['num_questions'] = dynamic_df['num_questions'].astype(int)

dynamic_df[['image_id', 'true_label', 'asked_questions_list', 'known_keys_list', 'confidence_gain']].head()
Out[2]:
image_id true_label asked_questions_list known_keys_list confidence_gain
0 ISIC_0025837 bkl [] [] 0.000000
1 ISIC_0025209 bkl [age, sex] [age, sex] 0.467302
2 ISIC_0029161 bkl [sex] [sex] 0.194568
3 ISIC_0026273 bkl [] [] 0.000000
4 ISIC_0025819 bkl [] [] 0.000000

2. 先统计单步提问的收益和风险¶

这里我们先粗看:

  • 问了 age 的病例,平均有没有更容易改善?
  • 问了 sex 的病例,平均是不是更危险?
  • 问了 location 的病例,风险和收益分别如何?

注意:这一步是经验统计,不是最终策略。

In [3]:
question_stats = []

for q in ['age', 'sex', 'location']:
    subset = dynamic_df[dynamic_df['asked_questions_list'].apply(lambda xs: q in xs)].copy()
    if len(subset) == 0:
        continue

    question_stats.append({
        'question': q,
        'cases': len(subset),
        'improved_rate': subset['improved'].mean(),
        'worsened_rate': subset['worsened'].mean(),
        'unsafe_rate': subset['unsafe_confidence_increase'].mean(),
        'final_correct_rate': subset['final_correct'].mean(),
        'avg_confidence_gain': subset['confidence_gain'].mean(),
    })

question_stats_df = pd.DataFrame(question_stats)
question_stats_df
Out[3]:
question cases improved_rate worsened_rate unsafe_rate final_correct_rate avg_confidence_gain
0 age 122 0.188525 0.081967 0.278689 0.639344 0.288338
1 sex 344 0.119186 0.127907 0.334302 0.537791 0.324628
2 location 81 0.123457 0.123457 0.407407 0.469136 0.265222

3. 定义风险感知得分¶

我们不再只问“哪个问题最可能提高置信度”,而是同时考虑:

  • 收益:improved_rate
  • 风险:unsafe_rate
  • 副作用:worsened_rate

一个简单起点:

score = improved_rate - alpha * unsafe_rate - beta * worsened_rate

后面我们可以调 alpha 和 beta。

In [4]:
def build_question_score_table(alpha=2.0, beta=1.0):
    df = question_stats_df.copy()
    df['risk_aware_score'] = (
        df['improved_rate']
        - alpha * df['unsafe_rate']
        - beta * df['worsened_rate']
    )
    return df.sort_values('risk_aware_score', ascending=False)

score_table_df = build_question_score_table(alpha=2.0, beta=1.0)
score_table_df
Out[4]:
question cases improved_rate worsened_rate unsafe_rate final_correct_rate avg_confidence_gain risk_aware_score
0 age 122 0.188525 0.081967 0.278689 0.639344 0.288338 -0.450820
1 sex 344 0.119186 0.127907 0.334302 0.537791 0.324628 -0.677326
2 location 81 0.123457 0.123457 0.407407 0.469136 0.265222 -0.814815

4. 先做第一版结论¶

如果某个问题:

  • 改善率不高
  • 但危险率很高

那它在风险感知策略里就应该被降权。

In [5]:
score_table_df[['question', 'cases', 'improved_rate', 'worsened_rate', 'unsafe_rate', 'risk_aware_score']]
Out[5]:
question cases improved_rate worsened_rate unsafe_rate risk_aware_score
0 age 122 0.188525 0.081967 0.278689 -0.450820
1 sex 344 0.119186 0.127907 0.334302 -0.677326
2 location 81 0.123457 0.123457 0.407407 -0.814815

5. 做一个最简单的风险感知提问顺序¶

先不做复杂病例级模型,先做一个最简单、可解释的版本:

  • 根据全局风险感知得分,给出一个问题优先级顺序
  • 例如:age -> location -> sex

这会形成我们的第一个 risk-aware fixed order baseline。

In [6]:
risk_aware_order = score_table_df['question'].tolist()
risk_aware_order
Out[6]:
['age', 'sex', 'location']

6. 和导师原始思路的关系¶

你导师最初的设想是:

  • 主动问问题
  • 问最有价值的问题
  • 注意安全

这一版 risk-aware question selection,实际上就是把“价值”和“安全”显式放到同一个打分里。

7. 下一步要接什么¶

这份 notebook 先把风险感知提问策略的统计基础搭好。下一步我们会继续做:

  1. 基于这个顺序做第一版 risk-aware fixed-order agent
  2. 和之前的 fixed_order / uncertainty / lookahead 对比
  3. 如果有效,再升级到病例级 risk-aware question selection