2311020116_CreditCardFraudD.../src/streamlit_app.py

452 lines
19 KiB
Python
Raw Normal View History

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文件批量处理
- 提供特征重要性分析
- 根据置信度生成行动建议
- 实时分析快速响应
### 注意事项
- 本系统仅供演示使用
- 实际应用中需要结合人工审核
- 建议定期更新模型以保持准确性
""")