13 Risk-Aware Question Selection¶
这一份 notebook 的目标是把安全控制前移到“问什么”这一步,而不是等问完以后再拒答。
我们现在已经知道:
- 有些问题会提高正确率
- 有些问题虽然会提高置信度,但也会带来危险高置信错误
所以这一份实验要回答的是:
在
age / sex / location三个候选问题中,哪个问题既有收益,又更安全?
核心思路:
- 基于已有病例级结果,统计每个问题的收益和风险
- 为每个问题定义一个风险感知得分
- 用这个得分来决定下一步问什么
- 和之前的 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 |
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 先把风险感知提问策略的统计基础搭好。下一步我们会继续做:
- 基于这个顺序做第一版
risk-aware fixed-order agent - 和之前的
fixed_order / uncertainty / lookahead对比 - 如果有效,再升级到病例级 risk-aware question selection