452 lines
19 KiB
Python
452 lines
19 KiB
Python
import streamlit as st
|
||
import numpy as np
|
||
import polars as pl
|
||
from pathlib import Path
|
||
import sys
|
||
sys.path.insert(0, str(Path(__file__).parent))
|
||
from agent_app import create_agent
|
||
from features import TransactionFeatures, DecisionResult, TransactionClass, ConfidenceLevel, Priority
|
||
|
||
st.set_page_config(
|
||
page_title="信用卡欺诈检测系统",
|
||
page_icon="💳",
|
||
layout="wide"
|
||
)
|
||
|
||
st.title("💳 信用卡欺诈检测系统")
|
||
st.markdown("基于机器学习的实时欺诈检测与决策支持系统")
|
||
|
||
@st.cache_resource
|
||
def load_agent():
|
||
return create_agent()
|
||
|
||
agent = load_agent()
|
||
|
||
@st.cache_data
|
||
def load_csv_file(uploaded_file):
|
||
if uploaded_file is not None:
|
||
try:
|
||
df = pl.read_csv(uploaded_file, schema_overrides={"Time": pl.Float64})
|
||
return df
|
||
except Exception as e:
|
||
st.error(f"读取CSV文件失败: {e}")
|
||
return None
|
||
return None
|
||
|
||
st.sidebar.header("系统信息")
|
||
st.sidebar.info(f"""
|
||
**模型信息**
|
||
- 模型类型: RandomForest
|
||
- 特征数量: 30
|
||
- 支持工具: 2个
|
||
- predict_fraud (ML工具)
|
||
- analyze_transaction
|
||
""")
|
||
|
||
st.header("输入交易数据")
|
||
|
||
input_mode = st.radio(
|
||
"选择输入方式",
|
||
["📁 上传CSV文件", "✏️ 手动输入特征"],
|
||
horizontal=True
|
||
)
|
||
|
||
if input_mode == "📁 上传CSV文件":
|
||
st.subheader("上传CSV文件")
|
||
|
||
uploaded_file = st.file_uploader(
|
||
"选择CSV文件",
|
||
type=['csv'],
|
||
help="上传包含交易数据的CSV文件"
|
||
)
|
||
|
||
if uploaded_file is not None:
|
||
df = load_csv_file(uploaded_file)
|
||
|
||
if df is not None:
|
||
st.success(f"✅ 成功加载CSV文件,共 {df.height} 条交易记录")
|
||
|
||
st.write("### 数据预览")
|
||
st.dataframe(df.head(10), use_container_width=True)
|
||
|
||
st.write("### 选择交易")
|
||
if "Class" in df.columns:
|
||
df = df.drop("Class")
|
||
|
||
row_index = st.number_input(
|
||
"选择交易行号(从0开始)",
|
||
min_value=0,
|
||
max_value=df.height - 1,
|
||
value=0,
|
||
step=1
|
||
)
|
||
|
||
if st.button("📋 加载选中的交易", type="primary"):
|
||
feature_names = [
|
||
'Time', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6', 'V7', 'V8', 'V9',
|
||
'V10', 'V11', 'V12', 'V13', 'V14', 'V15', 'V16', 'V17', 'V18', 'V19',
|
||
'V20', 'V21', 'V22', 'V23', 'V24', 'V25', 'V26', 'V27', 'V28', 'Amount'
|
||
]
|
||
transaction = [df[row_index, col] for col in feature_names]
|
||
|
||
st.session_state.transaction = transaction
|
||
st.success(f"✅ 已加载第 {row_index} 行的交易数据")
|
||
|
||
st.write("### 选中的交易数据")
|
||
feature_data = {
|
||
"特征名称": feature_names,
|
||
"特征值": [f"{v:.6f}" for v in transaction]
|
||
}
|
||
st.dataframe(
|
||
pl.DataFrame(feature_data),
|
||
use_container_width=True
|
||
)
|
||
|
||
else:
|
||
st.subheader("手动输入特征")
|
||
|
||
col1, col2 = st.columns(2)
|
||
|
||
with col1:
|
||
st.write("基础信息")
|
||
time = st.number_input("Time (交易时间)", value=0.0, step=1.0)
|
||
amount = st.number_input("Amount (交易金额)", value=100.0, step=1.0)
|
||
|
||
with col2:
|
||
st.write("PCA特征 V1-V14")
|
||
v1 = st.number_input("V1", value=0.0, step=0.1)
|
||
v2 = st.number_input("V2", value=0.0, step=0.1)
|
||
v3 = st.number_input("V3", value=0.0, step=0.1)
|
||
v4 = st.number_input("V4", value=0.0, step=0.1)
|
||
v5 = st.number_input("V5", value=0.0, step=0.1)
|
||
v6 = st.number_input("V6", value=0.0, step=0.1)
|
||
v7 = st.number_input("V7", value=0.0, step=0.1)
|
||
v8 = st.number_input("V8", value=0.0, step=0.1)
|
||
v9 = st.number_input("V9", value=0.0, step=0.1)
|
||
v10 = st.number_input("V10", value=0.0, step=0.1)
|
||
v11 = st.number_input("V11", value=0.0, step=0.1)
|
||
v12 = st.number_input("V12", value=0.0, step=0.1)
|
||
v13 = st.number_input("V13", value=0.0, step=0.1)
|
||
v14 = st.number_input("V14", value=0.0, step=0.1)
|
||
|
||
col3, col4 = st.columns(2)
|
||
|
||
with col3:
|
||
st.write("PCA特征 V15-V21")
|
||
v15 = st.number_input("V15", value=0.0, step=0.1)
|
||
v16 = st.number_input("V16", value=0.0, step=0.1)
|
||
v17 = st.number_input("V17", value=0.0, step=0.1)
|
||
v18 = st.number_input("V18", value=0.0, step=0.1)
|
||
v19 = st.number_input("V19", value=0.0, step=0.1)
|
||
v20 = st.number_input("V20", value=0.0, step=0.1)
|
||
v21 = st.number_input("V21", value=0.0, step=0.1)
|
||
|
||
with col4:
|
||
st.write("PCA特征 V22-V28")
|
||
v22 = st.number_input("V22", value=0.0, step=0.1)
|
||
v23 = st.number_input("V23", value=0.0, step=0.1)
|
||
v24 = st.number_input("V24", value=0.0, step=0.1)
|
||
v25 = st.number_input("V25", value=0.0, step=0.1)
|
||
v26 = st.number_input("V26", value=0.0, step=0.1)
|
||
v27 = st.number_input("V27", value=0.0, step=0.1)
|
||
v28 = st.number_input("V28", value=0.0, step=0.1)
|
||
|
||
st.divider()
|
||
|
||
if st.button("🔍 检测欺诈", type="primary", use_container_width=True):
|
||
if input_mode == "📁 上传CSV文件":
|
||
if "transaction" in st.session_state:
|
||
transaction = st.session_state.transaction
|
||
else:
|
||
st.warning("⚠️ 请先上传CSV文件并选择交易")
|
||
st.stop()
|
||
else:
|
||
transaction = [
|
||
time, v1, v2, v3, v4, v5, v6, v7, v8, v9,
|
||
v10, v11, v12, v13, v14, v15, v16, v17, v18, v19,
|
||
v20, v21, v22, v23, v24, v25, v26, v27, v28, amount
|
||
]
|
||
|
||
with st.spinner("正在分析交易..."):
|
||
result = agent.process_transaction(transaction)
|
||
|
||
st.success("分析完成!")
|
||
|
||
col5, col6, col7 = st.columns(3)
|
||
|
||
with col5:
|
||
st.metric(
|
||
label="预测类别",
|
||
value=result.evaluation.class_name.value,
|
||
delta=f"置信度: {result.evaluation.confidence.value}"
|
||
)
|
||
|
||
with col6:
|
||
fraud_prob = result.evaluation.fraud_probability * 100
|
||
st.metric(
|
||
label="欺诈概率",
|
||
value=f"{fraud_prob:.2f}%",
|
||
delta=f"{100 - fraud_prob:.2f}% 正常"
|
||
)
|
||
|
||
with col7:
|
||
st.metric(
|
||
label="行动建议数量",
|
||
value=len(result.action_plan.actions),
|
||
delta="已生成"
|
||
)
|
||
|
||
st.divider()
|
||
|
||
tab1, tab2, tab3 = st.tabs(["📊 评估结果", "🔍 特征解释", "📋 行动计划"])
|
||
|
||
with tab1:
|
||
st.subheader("模型评估结果")
|
||
|
||
eval_col1, eval_col2 = st.columns(2)
|
||
|
||
with eval_col1:
|
||
st.info(f"""
|
||
**预测信息**
|
||
- 预测类别: {result.evaluation.class_name.value}
|
||
- 预测标签: {result.evaluation.predicted_class}
|
||
- 置信度: {result.evaluation.confidence.value}
|
||
""")
|
||
|
||
with eval_col2:
|
||
st.info(f"""
|
||
**概率分布**
|
||
- 欺诈概率: {result.evaluation.fraud_probability:.4f}
|
||
- 正常概率: {result.evaluation.normal_probability:.4f}
|
||
""")
|
||
|
||
if result.evaluation.class_name == TransactionClass.FRAUD:
|
||
st.error(f"⚠️ 该交易被识别为**欺诈交易**,请立即采取行动!")
|
||
else:
|
||
st.success(f"✅ 该交易被识别为**正常交易**")
|
||
|
||
with tab2:
|
||
st.subheader("🔍 特征解释")
|
||
|
||
st.markdown("""
|
||
<div style="background-color: #e3f2fd; padding: 15px; border-radius: 10px; margin-bottom: 20px;">
|
||
<h4 style="margin: 0; color: #1565c0;">💡 什么是特征解释?</h4>
|
||
<p style="margin: 10px 0 0 0; color: #424242;">
|
||
就像医生看病时会检查各项指标一样,我们的AI模型也通过分析交易的各项"特征"来判断是否为欺诈。
|
||
下面这些特征是影响判断结果最重要的因素,让我们来看看它们是如何"告诉"模型这个交易是否有问题的。
|
||
</p>
|
||
</div>
|
||
""", unsafe_allow_html=True)
|
||
|
||
st.info(f"""
|
||
**使用的模型**: {result.explanation.model_type}
|
||
|
||
**整体判断依据**: {result.explanation.overall_explanation}
|
||
""")
|
||
|
||
st.write("### 📊 关键影响因素分析")
|
||
st.caption("这些特征对判断结果影响最大,就像破案时的关键线索")
|
||
|
||
feature_descriptions = {
|
||
"Time": "交易发生的时间距离第一次交易经过的秒数。欺诈交易往往在特定时间段更频繁,比如深夜或节假日。",
|
||
"Amount": "交易金额。异常高额或异常低额的交易都可能引起怀疑,特别是与用户历史消费习惯不符时。",
|
||
"V1": "经过PCA(主成分分析)转换后的特征1,代表交易数据的某种模式。PCA将原始数据转换成更易分析的形式。",
|
||
"V2": "经过PCA转换后的特征2,捕捉交易数据的另一种模式。",
|
||
"V3": "经过PCA转换后的特征3,反映交易数据的特定维度。",
|
||
"V4": "经过PCA转换后的特征4,可能代表交易频率或模式。",
|
||
"V5": "经过PCA转换后的特征5,可能涉及交易的时间或空间特征。",
|
||
"V6": "经过PCA转换后的特征6,可能反映交易的某种统计特性。",
|
||
"V7": "经过PCA转换后的特征7,可能代表交易的异常程度。",
|
||
"V8": "经过PCA转换后的特征8,可能涉及交易的上下文信息。",
|
||
"V9": "经过PCA转换后的特征9,可能反映交易的时间序列特征。",
|
||
"V10": "经过PCA转换后的特征10,可能代表交易的某种模式。",
|
||
"V11": "经过PCA转换后的特征11,可能涉及交易的频率特征。",
|
||
"V12": "经过PCA转换后的特征12,可能反映交易的异常模式。",
|
||
"V13": "经过PCA转换后的特征13,可能代表交易的某种统计特性。",
|
||
"V14": "经过PCA转换后的特征14,可能涉及交易的时间特征。",
|
||
"V15": "经过PCA转换后的特征15,可能反映交易的某种模式。",
|
||
"V16": "经过PCA转换后的特征16,可能代表交易的异常程度。",
|
||
"V17": "经过PCA转换后的特征17,可能涉及交易的上下文信息。",
|
||
"V18": "经过PCA转换后的特征18,可能反映交易的时间序列特征。",
|
||
"V19": "经过PCA转换后的特征19,可能代表交易的某种模式。",
|
||
"V20": "经过PCA转换后的特征20,可能涉及交易的频率特征。",
|
||
"V21": "经过PCA转换后的特征21,可能反映交易的异常模式。",
|
||
"V22": "经过PCA转换后的特征22,可能代表交易的某种统计特性。",
|
||
"V23": "经过PCA转换后的特征23,可能涉及交易的时间特征。",
|
||
"V24": "经过PCA转换后的特征24,可能反映交易的某种模式。",
|
||
"V25": "经过PCA转换后的特征25,可能代表交易的异常程度。",
|
||
"V26": "经过PCA转换后的特征26,可能涉及交易的上下文信息。",
|
||
"V27": "经过PCA转换后的特征27,可能反映交易的时间序列特征。",
|
||
"V28": "经过PCA转换后的特征28,可能代表交易的某种模式。"
|
||
}
|
||
|
||
for i, feature in enumerate(result.explanation.key_features, 1):
|
||
importance_percent = min(feature.importance * 100, 100)
|
||
|
||
impact_emoji = "📈" if feature.impact == "正向影响" else "📉"
|
||
impact_color = "#ff5252" if feature.impact == "正向影响" else "#4caf50"
|
||
|
||
feature_desc = feature_descriptions.get(feature.feature_name, "该特征是经过PCA转换后的数据特征,用于帮助模型识别交易模式。")
|
||
|
||
with st.expander(f"{i}. {feature.feature_name} {impact_emoji}"):
|
||
st.markdown(f"""
|
||
<div style="padding: 15px; border-radius: 8px; background-color: #f5f5f5;">
|
||
|
||
<p style="margin: 10px 0; color: #616161;">
|
||
<strong>影响程度:</strong>
|
||
</p>
|
||
<div style="background-color: #e0e0e0; border-radius: 5px; height: 20px; margin: 5px 0;">
|
||
<div style="background-color: {impact_color}; height: 100%; width: {importance_percent}%; border-radius: 5px;"></div>
|
||
</div>
|
||
<p style="margin: 5px 0; font-size: 14px; color: {impact_color}; font-weight: bold;">
|
||
{importance_percent:.1f}% 的影响力
|
||
</p>
|
||
|
||
<p style="margin: 15px 0 10px 0; color: #616161;">
|
||
<strong>影响方向:</strong> {feature.impact}
|
||
</p>
|
||
|
||
<div style="padding: 12px; background-color: #e3f2fd; border-left: 4px solid #2196f3; border-radius: 4px; margin-bottom: 10px;">
|
||
<p style="margin: 0; color: #1565c0;">
|
||
<strong>📖 这个特征是什么?</strong><br>
|
||
{feature_desc}
|
||
</p>
|
||
</div>
|
||
|
||
<div style="padding: 10px; background-color: #fff3e0; border-left: 4px solid #ff9800; border-radius: 4px;">
|
||
<p style="margin: 0; color: #e65100;">
|
||
<strong>💡 简单来说:</strong>
|
||
{"这个特征让模型更倾向于认为这是欺诈交易" if feature.impact == "正向影响" else "这个特征让模型更倾向于认为这是正常交易"}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
""", unsafe_allow_html=True)
|
||
|
||
with tab3:
|
||
st.subheader("📋 行动计划")
|
||
|
||
st.markdown("""
|
||
<div style="background-color: #e8f5e9; padding: 15px; border-radius: 10px; margin-bottom: 20px;">
|
||
<h4 style="margin: 0; color: #2e7d32;">🎯 为什么需要行动计划?</h4>
|
||
<p style="margin: 10px 0 0 0; color: #424242;">
|
||
根据检测结果,我们为您准备了具体的行动建议。这些建议按照紧急程度排序,
|
||
帮助您快速、有效地应对可能的风险。请根据实际情况选择合适的处理方式。
|
||
</p>
|
||
</div>
|
||
""", unsafe_allow_html=True)
|
||
|
||
st.write("### 🚀 建议采取的行动")
|
||
st.caption("按优先级排序,从高到低依次处理")
|
||
|
||
for action in result.action_plan.actions:
|
||
priority_info = {
|
||
Priority.URGENT: {
|
||
"emoji": "🔴",
|
||
"color": "#d32f2f",
|
||
"bg_color": "#ffcdd2",
|
||
"description": "紧急 - 需要立即处理,不能拖延"
|
||
},
|
||
Priority.HIGH: {
|
||
"emoji": "🟠",
|
||
"color": "#f57c00",
|
||
"bg_color": "#ffe0b2",
|
||
"description": "高优先级 - 尽快处理"
|
||
},
|
||
Priority.MEDIUM: {
|
||
"emoji": "🟡",
|
||
"color": "#fbc02d",
|
||
"bg_color": "#fff9c4",
|
||
"description": "中等优先级 - 适时处理"
|
||
},
|
||
Priority.LOW: {
|
||
"emoji": "🟢",
|
||
"color": "#388e3c",
|
||
"bg_color": "#c8e6c9",
|
||
"description": "低优先级 - 可以稍后处理"
|
||
},
|
||
Priority.ROUTINE: {
|
||
"emoji": "⚪",
|
||
"color": "#757575",
|
||
"bg_color": "#e0e0e0",
|
||
"description": "常规 - 按正常流程处理"
|
||
}
|
||
}.get(action.priority, {
|
||
"emoji": "⚪",
|
||
"color": "#757575",
|
||
"bg_color": "#e0e0e0",
|
||
"description": "常规"
|
||
})
|
||
|
||
with st.container():
|
||
st.markdown(f"""
|
||
<div style="padding: 20px; border-radius: 10px; background-color: {priority_info['bg_color']}; margin-bottom: 15px; border-left: 5px solid {priority_info['color']};">
|
||
<div style="display: flex; align-items: center; margin-bottom: 10px;">
|
||
<span style="font-size: 24px; margin-right: 10px;">{priority_info['emoji']}</span>
|
||
<div>
|
||
<h4 style="margin: 0; color: {priority_info['color']};">{action.action}</h4>
|
||
<p style="margin: 5px 0 0 0; font-size: 14px; color: #616161;">
|
||
<strong>优先级:</strong> {action.priority.value} - {priority_info['description']}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="background-color: white; padding: 12px; border-radius: 6px; margin-top: 10px;">
|
||
<p style="margin: 0; color: #424242;">
|
||
<strong>💡 为什么要这样做?</strong><br>
|
||
{action.reason}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
""", unsafe_allow_html=True)
|
||
|
||
st.divider()
|
||
|
||
st.header("📝 使用说明")
|
||
|
||
with st.expander("查看使用说明"):
|
||
st.markdown("""
|
||
### 如何使用本系统
|
||
|
||
#### 方式1:上传CSV文件
|
||
1. **上传文件**: 点击"选择CSV文件"按钮上传包含交易数据的CSV文件
|
||
2. **查看数据**: 系统会显示数据预览
|
||
3. **选择交易**: 输入行号选择要分析的交易
|
||
4. **加载数据**: 点击"加载选中的交易"按钮
|
||
5. **开始检测**: 点击"检测欺诈"按钮开始分析
|
||
|
||
#### 方式2:手动输入
|
||
1. **输入特征**: 在表单中输入30个特征值
|
||
- Time: 交易发生时间(秒)
|
||
- V1-V28: PCA转换后的特征
|
||
- Amount: 交易金额
|
||
|
||
2. **点击检测**: 点击"检测欺诈"按钮开始分析
|
||
|
||
3. **查看结果**: 系统会返回三个部分的结果
|
||
- **评估结果**: 模型的预测类别和概率
|
||
- **特征解释**: 影响预测的关键特征
|
||
- **行动计划**: 建议的处理措施
|
||
|
||
### CSV文件格式要求
|
||
|
||
CSV文件必须包含以下列:
|
||
- Time, V1, V2, V3, V4, V5, V6, V7, V8, V9
|
||
- V10, V11, V12, V13, V14, V15, V16, V17, V18, V19
|
||
- V20, V21, V22, V23, V24, V25, V26, V27, V28, Amount
|
||
- Class (可选,如果存在会被自动删除)
|
||
|
||
### 系统特点
|
||
|
||
- ✅ 使用随机森林模型进行预测
|
||
- ✅ 支持CSV文件批量处理
|
||
- ✅ 提供特征重要性分析
|
||
- ✅ 根据置信度生成行动建议
|
||
- ✅ 实时分析,快速响应
|
||
|
||
### 注意事项
|
||
|
||
- 本系统仅供演示使用
|
||
- 实际应用中需要结合人工审核
|
||
- 建议定期更新模型以保持准确性
|
||
""")
|