From b7ca3a5bc9a24257b6b3122fb4c8f13890c2642a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=98=89=E7=83=A8?= Date: Fri, 16 Jan 2026 19:22:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=20/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 875 ++++++++++++++++++++++++++++++++++++++++++++ agent_decision.py | 212 +++++++++++ model_artifacts.pkl | Bin 0 -> 250453 bytes smart_agent.py | 236 ++++++++++++ train_model.py | 90 +++++ 5 files changed, 1413 insertions(+) create mode 100644 README.md create mode 100644 agent_decision.py create mode 100644 model_artifacts.pkl create mode 100644 smart_agent.py create mode 100644 train_model.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae0c7de --- /dev/null +++ b/README.md @@ -0,0 +1,875 @@ +# 机器学习 × LLM × Agent:课程设计(5 天) + +> **小组作业** | 2–3 人/组 | 构建一个「可落地的智能预测与行动建议系统」 + +用传统机器学习完成可量化的预测任务,再用 LLM + Agent 把预测结果变成可执行的决策/建议,并保证输出结构化、可追溯、可复现。 + +--- + +## 📅 课程安排概览 + +| 天数 | 主题 | 内容 | +|------|------|------| +| **Day 1** | 项目启动 | 技术栈介绍 + 演示 + 选题分组 | +| **Day 2** | 自主设计 | 分组开发 | +| **Day 3** | 答疑 + Git 指导 | 集中答疑 + Git 提交教学 | +| **Day 4** | 自主设计 | 继续开发 + 准备展示 | +| **Day 5** | 小组展示 | 教师机运行 + 评分 | + +--- + +## 📑 目录 + +- [Day 1:项目启动](#day-1项目启动) + - [快速开始](#-快速开始) + - [技术栈要求](#技术栈要求2026-版) + - [选题指南](#选题指南) + - [可选扩展思路](#可选扩展思路) +- [Day 2:自主设计](#day-2自主设计) +- [Day 3:答疑 + Git 指导](#day-3答疑--git-指导) + - [Git 安装](#git-安装国内环境) + - [Git 基础操作](#git-基础操作) + - [.gitignore 详解](#gitignore-详解) +- [Day 4:自主设计](#day-4自主设计) +- [Day 5:小组展示](#day-5小组展示) + - [展示流程](#展示流程) + - [跨机运行检查清单](#跨机运行检查清单) + - [评分标准](#评分标准总分-100) +- [附录](#附录) + - [代码示例](#代码示例) + - [项目结构](#建议项目结构) + - [参考资料](#参考资料) + +--- + +# Day 1:项目启动 + +## 🚀 快速开始 + +> **2026 最佳实践**:使用 `uv` 替代 pip/venv/poetry 进行全流程项目管理 + +```bash +# 1. 安装 uv(如尚未安装) +# 方法 A:使用 pip 安装(推荐,国内可用) +pip install uv -i https://mirrors.aliyun.com/pypi/simple/ + +# 方法 B:使用 pipx 安装(隔离环境) +pipx install uv + +# 方法 C:官方脚本(需要科学上网) +# macOS / Linux: curl -LsSf https://astral.sh/uv/install.sh | sh +# Windows: powershell -c "irm https://astral.sh/uv/install.ps1 | iex" + +# 配置 PyPI 镜像(加速依赖下载) +uv config set index-url https://mirrors.aliyun.com/pypi/simple/ + +# 2. 克隆/Fork 本模板仓库 +git clone http://hblu.top:3000/MachineLearning2025/CourseDesign +cd CourseDesign + +# 3. 初始化项目并安装依赖(uv 自动创建虚拟环境) +uv sync + +# 4. 配置 DeepSeek API Key(不要提交到仓库!) +cp .env.example .env +# 编辑 .env 文件,填入你的 API Key +# DEEPSEEK_API_KEY="your-key-here" + +# 5. 运行示例 +# 方式 A:运行 Streamlit 可视化 Demo(推荐) +uv run streamlit run src/streamlit_app.py + +# 方式 B:运行命令行 Agent Demo +uv run python src/agent_app.py + +# 方式 C:运行训练脚本 +uv run python src/train.py +``` + +### uv 常用命令速查 + +| 命令 | 说明 | +|------|------| +| `uv sync` | 同步依赖(根据 `pyproject.toml` 和 `uv.lock`) | +| `uv add ` | 添加依赖(自动更新 `pyproject.toml` 和 `uv.lock`) | +| `uv add --dev ` | 添加开发依赖(如 pytest, ruff) | +| `uv run ` | 在项目环境中运行命令 | +| `uv lock` | 手动更新锁文件 | +| `uv python install 3.12` | 安装指定 Python 版本 | + +--- + +## 技术栈要求(2026 版) + +| 组件 | 要求 | 2026 最佳实践 | +|------|------|---------------| +| **人数** | 2–3 人/组 | — | +| **Python 版本** | ≥ 3.12 | 推荐 3.12/3.14 | +| **项目管理** | `uv` | 替代 pip/venv/poetry,10-100x 更快 | +| **数据处理** | `polars` + `pandas>=2.2` | polars 作为主力(Lazy API),pandas 用于兼容 | +| **数据可视化** | `seaborn>=0.13` | 使用 Seaborn Objects API(`so.Plot`) | +| **数据验证** | `pydantic` + `pandera` | pydantic 验证单行/配置,pandera 验证 DataFrame 清洗前后 | +| **机器学习** | `scikit-learn` + `lightgbm` | sklearn 做基线,LightGBM 做高性能模型 | +| **Agent 框架** | `pydantic-ai` | 结构化输出、类型安全的 Agent | +| **LLM 提供方** | `DeepSeek` | OpenAI 兼容 API | + +### 必须包含的三块能力 + +| 能力 | 说明 | +|------|------| +| **传统机器学习** | 可复现训练流程、离线评估指标、模型保存与加载 | +| **LLM** | 用于解释、归因、生成建议/回复、信息整合(不能凭空杜撰) | +| **Agent** | 用工具调用把系统串起来(至少 2 个 tool,其中 1 个必须是 ML 预测/评估相关工具) | + +--- + +## 选题指南 + +> ⚠️ **注意**:Level 1/2/3 **都可以拿满分**;高难度通常更容易体现"深度",但不会因为选 Level 1 就被封顶。 + +### Level 1|入门:表格预测 + 行动建议闭环 + +> 📌 **建议新手选择** + +**目标**:做一个结构化数据的分类/回归模型,并让 Agent 基于模型输出给出可执行建议。 + +#### 推荐数据集 + +| 数据集 | 链接 | +|--------|------| +| Telco Customer Churn | [Kaggle](https://www.kaggle.com/datasets/blastchar/telco-customer-churn) | +| German Credit Risk | [Kaggle](https://www.kaggle.com/datasets/uciml/german-credit) | +| Bank Marketing | [Kaggle](https://www.kaggle.com/datasets/janiobachmann/bank-marketing-dataset) | +| Heart Failure Prediction | [Kaggle](https://www.kaggle.com/datasets/fedesoriano/heart-failure-prediction) | + +#### ✅ 必做部分 + +| 模块 | 要求 | +|------|------| +| **数据处理** | 使用 Polars 完成可复现的数据清洗流水线;使用 Pandera 定义 Schema | +| **机器学习** | 至少 2 个模型对比(1 个基线如 LogReg,1 个强模型如 LightGBM);达到 `F1 ≥ 0.70` 或 `ROC-AUC ≥ 0.75` | +| **Agent** | 使用 Pydantic 定义输入输出;至少 2 个 tool(含 1 个 ML 预测工具) | + +--- + +### Level 2|进阶:文本任务 + 处置建议 + +> 📌 **NLP 向** + +**目标**:做文本分类/情感分析,并让 Agent 生成结构化处置方案。 + +#### 推荐数据集 + +| 数据集 | 链接 | 说明 | +|--------|------|------| +| Twitter US Airline Sentiment | [Kaggle](https://www.kaggle.com/datasets/crowdflower/twitter-airline-sentiment) | 航空公司情感分析 | +| IMDB 50K Movie Reviews | [Kaggle](https://www.kaggle.com/datasets/lakshmi25npathi/imdb-dataset-of-50k-movie-reviews) | 电影评论情感 | +| SMS Spam Collection | [Kaggle](https://www.kaggle.com/datasets/uciml/sms-spam-collection-dataset) | 垃圾短信分类 | +| Consumer Complaints | [Kaggle](https://www.kaggle.com/datasets/selener/consumer-complaint-database) | 投诉分流 | + +#### ✅ 必做部分 + +| 模块 | 要求 | +|------|------| +| **数据处理** | 文本清洗要「克制」,说明预处理策略;使用 Pandera 定义 Schema | +| **机器学习** | 基线 `TF-IDF + LogReg`;达到 `Accuracy ≥ 0.85` 或 `Macro-F1 ≥ 0.80` | +| **Agent** | 实现「分类 → 解释 → 生成处置方案」流程;输出结构化(Pydantic) | + +--- + +### Level 3|高阶:不平衡/多表/时序 + 多步决策 + +> 📌 **真实世界约束** + +**目标**:处理更复杂的数据特性(极度不平衡、多表关联、时序预测),实现多步决策 Agent。 + +#### 推荐数据集 + +| 数据集 | 链接 | 特点 | +|--------|------|------| +| Credit Card Fraud Detection | [Kaggle](https://www.kaggle.com/datasets/mlg-ulb/creditcardfraud) | 极度不平衡 | +| IEEE-CIS Fraud Detection | [Kaggle](https://www.kaggle.com/c/ieee-fraud-detection) | 多表/特征工程复杂 | +| M5 Forecasting - Accuracy | [Kaggle](https://www.kaggle.com/competitions/m5-forecasting-accuracy) | 时序预测 | +| Instacart Market Basket | [Kaggle](https://www.kaggle.com/c/instacart-market-basket-analysis) | 多表 + 推荐 | + +#### ✅ 必做部分 + +| 模块 | 要求 | +|------|------| +| **数据处理** | 明确主键/外键与 join 规则;写出「数据泄露风险点清单」 | +| **机器学习** | 使用合理指标(如 `PR-AUC`);必须使用时间切分评估(如时序) | +| **Agent** | 至少 3 步决策(评估 → 解释 → 行动计划);输出结构化 | + +--- + +### 自选题目标准 + +> 💡 **鼓励自选题目**,但必须满足以下硬标准 + +| 要求 | 说明 | +|------|------| +| **数据真实可获取** | 公开、可重复下载(Kaggle/UCI/OpenML 等),提供链接 | +| **可量化预测任务** | 有明确标签/目标变量与评价指标 | +| **业务闭环** | 能落到「下一步做什么」的决策/行动 | +| **Agent 工具调用** | 至少 2 个 tools,其中 1 个必须是 ML 工具 | +| **规模与复杂度** | 样本量建议 ≥ 5,000 | +| **合规性** | 禁止爬取受限数据;禁止提交密钥/隐私数据 | + +--- + +## 可选扩展思路 + +以下是一些可选的扩展方向,用于加深项目深度,**不作为评分硬性要求**: + +| 方向 | 思路 | +|------|------| +| **可解释性** | 添加特征重要性解释工具(如 `explain_top_features`),让 Agent 能解释决策依据 | +| **代价敏感策略** | 给每个动作定义成本/收益假设,让 Agent 输出最划算的动作组合 | +| **阈值策略** | 把"预测概率"转化为"干预策略"(高/中/低风险不同处理) | +| **相似案例检索** | 用 TF-IDF/Embedding 做 `retrieve_similar(text) -> top_k`,提供可追溯证据 | +| **合规检查** | 对 Agent 输出做规则检查(如不得泄露隐私、不得虚假承诺) | +| **误差分析** | Top 误判样本分析,找出模型薄弱点 | +| **消融实验** | 对比不同特征/模型配置,得出改进方向 | + +--- + +# Day 2:自主设计 + +**今日任务**: +- 分组进行项目设计与开发 +- 完成数据探索与清洗 +- 开始训练基线模型 + +**建议里程碑**: +- [ ] 数据下载并完成初步探索 +- [ ] 数据清洗流水线可运行 +- [ ] 基线模型训练完成 + +--- + +# Day 3:答疑 + Git 指导 + +## Git 安装(国内环境) + +### Windows + +1. 下载 Git for Windows: + - 官方镜像(推荐):https://registry.npmmirror.com/binary.html?path=git-for-windows/ + - 或官网:https://git-scm.com/download/win +2. 双击安装,全程默认设置即可 +3. 安装完成后,右键可看到「Git Bash Here」选项 + +### macOS + +```bash +# 方法 A:Xcode 命令行工具(推荐) +xcode-select --install + +# 方法 B:Homebrew +brew install git +``` + +### Linux (Ubuntu/Debian) + +```bash +sudo apt update +sudo apt install git +``` + +### 验证安装 + +```bash +git --version +# 输出类似:git version 2.43.0 +``` + +--- + +## Git 基础操作 + +### 首次配置 + +```bash +# 设置用户名和邮箱(提交记录会显示) +git config --global user.name "你的姓名" +git config --global user.email "你的邮箱@example.com" +``` + +### 克隆仓库 + +```bash +# 组长创建仓库后,所有组员克隆 +git clone http://hblu.top:3000/<用户名>/<项目名>.git +cd <项目名> +``` + +### 日常开发流程 + +```bash +# 1. 拉取最新代码(每次开始工作前) +git pull + +# 2. 查看当前状态 +git status + +# 3. 添加修改的文件 +git add . # 添加所有修改 +git add src/train.py # 或只添加特定文件 + +# 4. 提交修改 +git commit -m "feat: 添加数据预处理模块" + +# 5. 推送到远程仓库 +git push +``` + +### 常用命令速查 + +| 命令 | 说明 | +|------|------| +| `git clone ` | 克隆远程仓库 | +| `git pull` | 拉取远程更新 | +| `git status` | 查看当前状态 | +| `git add .` | 暂存所有修改 | +| `git commit -m "消息"` | 提交修改 | +| `git push` | 推送到远程 | +| `git log --oneline -5` | 查看最近 5 条提交 | + +### 团队协作注意事项 + +1. **每次开始工作前先 `git pull`**,避免冲突 +2. **提交信息要有意义**,如 `feat: 添加 Agent 工具` 而非 `update` +3. **小步提交**,不要把所有修改攒到最后一起提交 + +--- + +## .gitignore 详解 + +`.gitignore` 文件告诉 Git **哪些文件不要提交**。这非常重要,因为: +- **API Key 泄露会导致账户被盗用** +- **大文件会导致仓库臃肿** +- **临时文件没有提交意义** + +### 本项目必须忽略的文件 + +创建 `.gitignore` 文件,内容如下: + +```gitignore +# ===== 环境变量(绝对不能提交!)===== +.env + +# ===== Python 虚拟环境 ===== +.venv/ +venv/ +__pycache__/ +*.pyc +*.pyo +.pytest_cache/ + +# ===== IDE 配置 ===== +.vscode/ +.idea/ +*.swp + +# ===== macOS 系统文件 ===== +.DS_Store + +# ===== Jupyter ===== +.ipynb_checkpoints/ + +# ===== 超大文件(超过 10MB 需手动添加)===== +# 如果你的数据或模型文件超过 10MB,请在下面添加: +# data/large_dataset.csv +# models/large_model.pkl +``` + +> 💡 **关于 data/ 和 models/ 文件**: +> - **默认应该提交**,方便教师机直接运行 +> - 如果单个文件 **超过 10MB**,请添加到 `.gitignore` 并在 `data/README.md` 中说明下载方式 + +### 检查 .gitignore 是否生效 + +```bash +# 查看哪些文件会被 Git 忽略 +git status --ignored + +# 如果之前已经提交了不应提交的文件,需要先从 Git 中移除 +git rm --cached .env # 从 Git 移除但保留本地文件 +git rm --cached -r __pycache__ +git commit -m "chore: 移除不应提交的文件" +``` + +--- + +## 作业提交流程 + +### 1. 账号信息 + +账号已统一创建,请登录 [hblu.top:3000/MachineLearning2025](http://hblu.top:3000/MachineLearning2025) + +| 项目 | 说明 | +|------|------| +| **用户名** | `st` + 学号(如 `st2024001`) | +| **初始密码** | `12345678`(请登录后修改) | +| **组织** | MachineLearning2025 | + +> ⚠️ **首次登录后请立即修改密码** + +### 2. 组长创建仓库 + +在 [MachineLearning2025](http://hblu.top:3000/MachineLearning2025) 组织下创建新仓库,命名格式:`组号-项目名称`(如 `G01-ChurnPredictor`) + +### 3. 添加组员 + +Settings → Collaborators → 添加其他组员(使用 `st+学号` 搜索) + +### 4. 提交检查清单 + +- [ ] `.gitignore` 已创建且包含必要规则 +- [ ] `.env.example` 已提交,`.env` 未提交 +- [ ] 没有提交 API Key 或敏感信息 +- [ ] 没有提交大于 10MB 的文件 + +--- + +# Day 4:自主设计 + +**今日任务**: +- 继续完善项目 +- 完成 Agent 集成 +- 准备 Streamlit Demo +- 撰写项目报告 + +**建议里程碑**: +- [ ] ML 模型完成并保存 +- [ ] Agent 工具调用测试通过 +- [ ] Streamlit Demo 可运行 +- [ ] README.md 初稿完成 + +--- + +# Day 5:小组展示 + +## 展示流程 + +1. **教师机克隆你的仓库** + ```bash + git clone http://hblu.top:3000/MachineLearning2025/<项目名>.git + cd <项目名> + ``` + +2. **安装依赖并运行** + ```bash + uv sync + cp .env.example .env + # 教师填入测试用 API Key + uv run streamlit run src/streamlit_app.py + ``` + +3. **5-8 分钟 Demo 展示** + +--- + +## 跨机运行检查清单 + +> ⚠️ **避免「明明在我电脑上能跑」的问题** + +### 必须检查 + +| 检查项 | 说明 | 常见错误 | +|--------|------|----------| +| **依赖完整** | 所有依赖都在 `pyproject.toml` 中 | 忘记 `uv add` 新安装的包 | +| **相对路径** | 数据/模型使用相对路径 | `C:\Users\张三\data.csv` | +| **环境变量** | API Key 通过 `.env` 读取 | 硬编码 Key 在代码中 | +| **数据可获取** | 数据文件有下载说明或包含在仓库 | 数据只在本地,忘记上传 | +| **uv.lock** | 锁文件已提交 | 依赖版本不确定 | + +### 提交前测试方法 + +```bash +# 模拟干净环境测试 +cd /tmp +git clone <你的仓库地址> +cd <项目名> +uv sync +cp .env.example .env +# 填入 API Key +uv run streamlit run src/streamlit_app.py +``` + +### 常见问题排查 + +| 错误 | 原因 | 解决方案 | +|------|------|----------| +| `ModuleNotFoundError` | 缺少依赖 | `uv add <包名>` 后重新提交 | +| `FileNotFoundError` | 路径问题 | 使用 `Path(__file__).parent` 获取相对路径 | +| `DEEPSEEK_API_KEY not found` | 环境变量问题 | 检查 `.env` 格式和 `python-dotenv` | + +--- + +## 评分标准(总分 100) + +> ⚠️ 所有分析、对比、决策逻辑都必须在 `README.md` 中清晰体现。 + +### A. 问题与数据(10 分) + +| 维度 | 分值 | 要求 | +|------|------|------| +| 任务定义清晰 | 5 | 标签/目标、输入输出边界 | +| 数据说明与切分 | 5 | 来源链接、字段含义、切分策略 | + +### B. 传统机器学习(30 分) + +| 维度 | 分值 | 要求 | +|------|------|------| +| 基线与可复现训练 | 10 | 固定随机种子、训练脚本可跑通 | +| 指标与对比 | 10 | 达到指标要求,与基线对比 | +| 误差分析 | 10 | 展示错误样本/分桶,给出改进方向 | + +### C. LLM + Agent(30 分) + +| 维度 | 分值 | 要求 | +|------|------|------| +| 工具调用 | 10 | 至少 2 个 tools,能稳定调用 ML 工具 | +| 结构化输出 | 10 | Pydantic schema 清晰;字段有约束 | +| 建议可执行且有证据 | 10 | 能落地的动作清单,引用依据 | + +### D. 工程与演示(30 分) + +| 维度 | 分值 | 要求 | +|------|------|------| +| **Streamlit 演示** | **15** | 交互流畅;展示「预测→分析→建议」全流程 | +| **跨机运行** | **10** | 在教师机 `git clone && uv sync && uv run` 可直接运行 | +| 代码质量 | 5 | 结构清晰、有类型提示与文档 | + +### ❌ 常见扣分项 + +- 训练/推理无法在教师机跑通 +- 未使用 `uv` 管理项目 +- 数据泄露(尤其是时序/多表) +- Agent 编造数据集不存在的事实 +- **把密钥提交进仓库(严重扣分)** + +### ✅ 常见加分项 + +- 使用 Polars Lazy API 高效处理数据 +- 做了可解释性/阈值策略/代价敏感分析 +- 做了检索增强且引用可追溯证据 +- 做了消融/对比实验,结论清晰 + +--- + +# 附录 + +## 代码示例 + +### 数据处理:Polars 最佳实践 + +```python +import polars as pl + +# ✅ 推荐:使用 Lazy API(自动查询优化) +lf = pl.scan_csv("data/train.csv") +result = ( + lf.filter(pl.col("age") > 30) + .group_by("category") + .agg(pl.col("value").mean()) + .collect() # 最后一步才执行 +) + +# ✅ 推荐:从 Pandas 无缝迁移 +df_polars = pl.from_pandas(df_pandas) +df_pandas = df_polars.to_pandas() +``` + +### 数据验证:Pydantic + Pandera + +```python +from pydantic import BaseModel, Field + +class CustomerFeatures(BaseModel): + """客户特征数据模型""" + age: int = Field(ge=0, le=120, description="客户年龄") + tenure: int = Field(ge=0, description="客户任期(月)") + monthly_charges: float = Field(ge=0, description="月费用") + contract_type: str = Field(pattern="^(month-to-month|one-year|two-year)$") +``` + +```python +import pandera as pa +from pandera import Column, Check, DataFrameSchema + +# ✅ 定义清洗后 Schema +clean_data_schema = DataFrameSchema( + columns={ + "age": Column(pa.Int, checks=[Check.ge(0), Check.le(120)], nullable=False), + "tenure": Column(pa.Int, checks=[Check.ge(0)], nullable=False), + "monthly_charges": Column(pa.Float, checks=[Check.ge(0)], nullable=False), + }, + strict=True, + coerce=True, +) +``` + +### 机器学习:sklearn + LightGBM + +```python +from sklearn.model_selection import train_test_split +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import roc_auc_score +import lightgbm as lgb +import joblib + +# 基线模型 +baseline = LogisticRegression(max_iter=1000, random_state=42) +baseline.fit(X_train, y_train) +print("Baseline ROC-AUC:", roc_auc_score(y_test, baseline.predict_proba(X_test)[:, 1])) + +# 高性能模型 +lgb_model = lgb.LGBMClassifier(n_estimators=500, learning_rate=0.05, random_state=42) +lgb_model.fit(X_train, y_train) + +# 保存模型 +joblib.dump(lgb_model, "models/lgb_model.pkl") +``` + +### Agent:pydantic-ai 示例 + +```python +from pydantic import BaseModel, Field +from pydantic_ai import Agent, RunContext + +class Decision(BaseModel): + """Agent 输出的结构化决策""" + risk_score: float = Field(ge=0, le=1, description="预测风险概率") + decision: str = Field(description="建议策略") + actions: list[str] = Field(description="可执行动作清单") + rationale: str = Field(description="决策依据") + +agent = Agent( + "deepseek:deepseek-chat", + output_type=Decision, + system_prompt="你是业务决策助手。必须先调用工具获取预测结果,再给出结构化决策。", +) + +@agent.tool +def predict_risk(ctx: RunContext, features: CustomerFeatures) -> float: + """调用 ML 模型返回风险分数""" + # TODO: 实现模型调用 + pass +``` + +### API Key 配置 + +> ⚠️ **不要把 Key 写进代码、不要提交到仓库!** + +创建 `.env.example`(提交到仓库): +``` +DEEPSEEK_API_KEY=your-key-here +``` + +复制为 `.env` 并填入真实 Key(`.env` 在 `.gitignore` 中排除)。 + +--- + +## 建议项目结构 + +``` +ml_course_design/ +├── pyproject.toml # 项目配置与依赖 +├── uv.lock # 锁定的依赖版本 +├── README.md # 项目说明与报告 +├── .env.example # 环境变量模板 +├── .gitignore # Git 忽略规则 +│ +├── data/ # 数据目录 +│ └── README.md # 数据来源说明 +│ +├── models/ # 训练产物 +│ └── .gitkeep +│ +├── src/ # 核心代码 +│ ├── __init__.py +│ ├── data.py # 数据读取/清洗 +│ ├── features.py # Pydantic 特征模型 +│ ├── train.py # 训练与评估 +│ ├── infer.py # 推理接口 +│ ├── agent_app.py # Agent 入口 +│ └── streamlit_app.py # Demo 入口 +│ +└── tests/ # 测试 + └── test_*.py +``` + +--- + +## README.md 模板(你的项目) + +请将以下内容作为你项目 `README.md` 的模板 + +````markdown +# 项目名称 + +> **机器学习 (Python) 课程设计** + +## 👥 团队成员 + +| 姓名 | 学号 | 贡献 | +|------|------|------| +| 张三 | 2024001 | 数据处理、模型训练 | +| 李四 | 2024002 | Agent 开发、Streamlit | +| 王五 | 2024003 | 测试、文档撰写 | + +## 📝 项目简介 + +(1-2 段描述项目目标、选用的数据集、解决的问题) + +## 🚀 快速开始 + +```bash +# 克隆仓库 +git clone http://hblu.top:3000/MachineLearning2025/GXX-ProjectName.git +cd GXX-ProjectName + +# 安装依赖 +uv sync + +# 配置环境变量 +cp .env.example .env +# 编辑 .env 填入 API Key + +# 运行 Demo +uv run streamlit run src/streamlit_app.py +``` + +--- + +## 1️⃣ 问题定义与数据 + +### 1.1 任务描述 + +(描述预测任务类型:分类/回归/时序,以及业务目标) + +### 1.2 数据来源 + +| 项目 | 说明 | +|------|------| +| 数据集名称 | XXX | +| 数据链接 | [Kaggle](https://...) | +| 样本量 | X,XXX 条 | +| 特征数 | XX 个 | + +### 1.3 数据切分与防泄漏 + +(如何切分训练/验证/测试集?如何确保没有数据泄漏?) + +--- + +## 2️⃣ 机器学习流水线 + +### 2.1 基线模型 + +| 模型 | 指标 | 结果 | +|------|------|------| +| Logistic Regression | ROC-AUC | 0.XX | + +### 2.2 进阶模型 + +| 模型 | 指标 | 结果 | +|------|------|------| +| LightGBM | ROC-AUC | 0.XX | + +### 2.3 误差分析 + +(模型在哪些样本上表现不佳?为什么?) + +--- + +## 3️⃣ Agent 实现 + +### 3.1 工具定义 + +| 工具名 | 功能 | 输入 | 输出 | +|--------|------|------|------| +| `predict_risk` | 调用 ML 模型预测 | CustomerFeatures | float | +| `explain_features` | 解释特征影响 | CustomerFeatures | list[str] | + +### 3.2 决策流程 + +(Agent 如何使用工具?如:预测 → 解释 → 建议) + +### 3.3 案例展示 + +**输入**: +``` +请分析这位客户的流失风险:年龄 35,任期 2 个月,月费 89.99 +``` + +**输出**: +```json +{ + "risk_score": 0.72, + "decision": "高风险,建议主动挥留", + "actions": ["发送优惠短信", "客服回访"], + "rationale": "新客户 + 月付合同是流失高危特征" +} +``` + +--- + +## 4️⃣ 开发心得 + +### 4.1 主要困难与解决方案 + +(遇到的最大困难是什么?如何解决?) + +### 4.2 对 AI 辅助编程的感受 + +(使用 AI 工具的体验如何?哪些场景有帮助?哪些地方需要注意?) + +### 4.3 局限与未来改进 + +(如果有更多时间,还有哪些可以改进的地方?) +```` + +--- + +## 参考资料 + +### 核心工具文档 + +| 资源 | 链接 | 说明 | +|------|------|------| +| uv 官方文档 | https://docs.astral.sh/uv/ | Python 项目管理器 | +| Polars 用户指南 | https://pola.rs/ | 高性能 DataFrame | +| Pydantic 文档 | https://docs.pydantic.dev/ | 数据验证与设置 | +| Pandera 文档 | https://pandera.readthedocs.io/ | DataFrame Schema 验证 | +| pydantic-ai 文档 | https://ai.pydantic.dev/ | Agent 框架 | +| DeepSeek API | https://api.deepseek.com | OpenAI 兼容 | + +### 推荐学习资源 + +| 资源 | 链接 | +|------|------| +| Polars vs Pandas | https://pola.rs/user-guide/migration/pandas/ | +| Pydantic AI 快速入门 | https://ai.pydantic.dev/quick-start/ | +| Pandera 快速入门 | https://pandera.readthedocs.io/en/stable/try_pandera.html | +| uv 项目工作流 | https://docs.astral.sh/uv/concepts/projects/ | + +--- + +## 📋 Checklist(提交前自检) + +- [ ] 使用 `uv sync` 安装依赖,无需手动创建虚拟环境 +- [ ] `.gitignore` 包含 `.env`、`__pycache__`、大文件 +- [ ] 在干净环境下可以复现(`git clone && uv sync && uv run`) +- [ ] 没有提交 API Key 或敏感信息 +- [ ] 使用 Polars 进行数据处理 +- [ ] 使用 Pydantic 定义特征和输出模型 +- [ ] Agent 至少有 2 个 tool(含 1 个 ML 工具) +- [ ] README.md 说明了数据切分策略 +- [ ] Demo 可以正常运行 + +--- + +> 💬 **有问题?** 请在课程群/Issue 中提问,我们会尽快回复。 diff --git a/agent_decision.py b/agent_decision.py new file mode 100644 index 0000000..eea38cb --- /dev/null +++ b/agent_decision.py @@ -0,0 +1,212 @@ +import pandas as pd +import numpy as np +import joblib +import json +import random + +# ========================================== +# 1. 模拟 LLM 接口 (Mock Agent) +# ========================================== +def mock_llm_generate(prompt): + """ + 模拟 LLM 的生成过程。 + 在实际应用中,这里会调用 OpenAI/Anthropic/DeepSeek 的 API。 + """ + # 从 Prompt 中提取关键信息来生成“假”的智能回复 + # 这里我们用简单的规则来模拟 LLM 的“思考” + + # 提取意向度 (从 Prompt 文本中查找) + import re + # 匹配 "预测模型判定该客户订阅定存的概率为: 78.5%" 这种格式 + prob_match = re.search(r"预测模型判定该客户订阅定存的概率为:\s*([\d\.]+)%", prompt) + prob = float(prob_match.group(1)) if prob_match else 0 + + # 模拟 LLM 根据概率生成的建议 + if prob > 70: + strategy = "VIP 专属服务" + action = "资深理财经理致电" + script = "您好,鉴于您良好的信用记录,我们为您预留了一款高收益理财产品..." + reason = "客户属于高意向群体,且过往活动反馈良好。" + elif prob > 40: + strategy = "标准营销" + action = "普通客服致电或短信触达" + script = "您好,我行近期推出了几款稳健型存款产品,占用您一分钟..." + reason = "客户意向中等,建议通过低成本渠道试探。" + else: + strategy = "静默观察" + action = "发送月度邮件" + script = "(邮件内容) 本月财经摘要..." + reason = "客户意向较低,频繁打扰可能导致反感。" + + # 构造 JSON 输出 + response = { + "customer_id": "Unknown", # 实际中会从 Context 获取 + "analysis": { + "score": prob, + "segment": strategy + }, + "action_plan": { + "primary_action": action, + "backup_action": "记录反馈并更新标签", + "suggested_script": script + }, + "reasoning": reason + } + + return json.dumps(response, ensure_ascii=False, indent=2) + +# ========================================== +# 2. Agent 核心类 +# ========================================== +class MarketingAgent: + def __init__(self, artifact_path='model_artifacts.pkl'): + print(f"Agent 正在加载模型资产: {artifact_path} ...") + self.artifacts = joblib.load(artifact_path) + self.model = self.artifacts['model'] + self.encoders = self.artifacts['encoders'] + self.feature_meta = self.artifacts['feature_meta'] + + def preprocess(self, customer_data): + """将原始字典数据转换为模型可接受的 DataFrame""" + # 创建 DataFrame + df = pd.DataFrame([customer_data]) + + # 移除 duration (如果存在) + if 'duration' in df.columns: + df = df.drop('duration', axis=1) + + # 编码分类特征 + for col, le in self.encoders.items(): + if col in df.columns: + # 处理未知类别: 如果遇到训练集没见过的类别,设为出现最多的那个或报错 + # 这里简单处理:如果遇到未知,就用 transform 的第一个类别 (仅作演示) + try: + df[col] = le.transform(df[col]) + except ValueError: + # 遇到未知标签,使用众数填充或标记为 -1 (取决于模型训练时是否处理了未知) + # 这里为了演示不报错,我们假设数据是干净的,或者直接填 0 + df[col] = 0 + + # 确保列顺序一致 + # 注意:XGBoost 可能会报错如果列顺序不对,最好重新索引 + # 这里假设 feature_meta['all_cols'] 保存了训练时的特征顺序 + df = df[self.feature_meta['all_cols']] + + return df + + def analyze_customer(self, customer_data): + """ + Agent 的主工作流: + 1. 感知 (Perception): 接收数据,进行预处理。 + 2. 思考 (Cognition - Model): 调用 ML 模型预测概率。 + 3. 规划 (Planning - LLM): 构建 Prompt,调用 LLM 生成建议。 + 4. 行动 (Action): 输出结构化建议。 + """ + # 1. 预处理 + X_input = self.preprocess(customer_data) + + # 2. 模型预测 + prob = self.model.predict_proba(X_input)[0][1] # 获取属于类别 1 (yes) 的概率 + prob_percent = round(prob * 100, 2) + + # 获取特征重要性高的特征值,放入 Prompt (简单逻辑:列出所有特征) + # 实际中可以结合 SHAP 值只列出 Top 3 贡献特征 + feature_desc = ", ".join([f"{k}={v}" for k, v in customer_data.items() if k != 'duration']) + + # 3. 构建 Prompt + # 这是一个 "Prompt Engineering" 的过程 + system_prompt = """你是一个专业的银行营销决策 Agent。请根据客户数据和预测模型的结果,给出具体的执行建议。 +要求输出必须是 JSON 格式。""" + + user_prompt = f""" + 【输入数据】 + 客户特征: {feature_desc} + + 【模型分析】 + 预测模型判定该客户订阅定存的概率为: {prob_percent}% + + 【业务规则库】 + - 概率 > 70%: 高价值,高优先级,人工介入。 + - 概率 40%-70%: 潜在价值,自动化营销 + 人工辅助。 + - 概率 < 40%: 低价值,仅自动化触达。 + + 【任务】 + 请基于以上信息,生成该客户的营销建议 JSON,包含: + - 客户分群 (segment) + - 推荐行动 (primary_action) + - 话术建议 (suggested_script) + - 决策依据 (reasoning) + """ + + print(f"\n--- Agent 正在思考 (构建 Prompt) ---\n[Prompt 摘要] 预测概率: {prob_percent}%") + + # 4. 调用 LLM (模拟) + # 在真实场景中:response = openai.ChatCompletion.create(...) + llm_response = mock_llm_generate(user_prompt) + + return json.loads(llm_response) + +# ========================================== +# 3. 运行演示 +# ========================================== +if __name__ == "__main__": + # 加载原始数据取样 + df_raw = pd.read_csv('bank.csv') + + # 实例化 Agent + agent = MarketingAgent() + + print("\n" + "="*50) + print("开始模拟业务流程...") + print("="*50) + + # 随机抽取 3 个客户进行模拟 + sample_indices = [1, 20, 100] + + # 构造一个高意向客户 (VIP 模拟) + # 基于特征重要性: poutcome=success (最重要), contact=cellular, housing=no, balance=high + vip_customer = { + 'age': 35, + 'job': 'management', + 'marital': 'married', + 'education': 'tertiary', + 'default': 'no', + 'balance': 5000, + 'housing': 'no', + 'loan': 'no', + 'contact': 'cellular', + 'day': 15, + 'month': 'oct', + 'duration': 0, # 会被移除 + 'campaign': 1, + 'pdays': 90, + 'previous': 2, + 'poutcome': 'success', # 强特征 + 'deposit': 'yes' # 仅用于展示 + } + + # 将 VIP 客户加入测试列表 (使用特殊的 -1 索引标记) + test_cases = [(i, df_raw.iloc[i].to_dict()) for i in sample_indices] + test_cases.append((-1, vip_customer)) + + for idx, customer_dict in test_cases: + # 移除结果列 'deposit',模拟这是新客户 + if 'deposit' in customer_dict: + real_result = customer_dict.pop('deposit') + else: + real_result = "Unknown" + + if idx == -1: + print(f"\n>>> 处理客户 ID: VIP-Demo (人工构造的高意向客户)") + else: + print(f"\n>>> 处理客户 ID: {idx}") + + print(f"真实结果 (仅供参考): {real_result}") + + # Agent 工作 + decision = agent.analyze_customer(customer_dict) + + # 打印结果 + print("\n[Agent 最终建议]") + print(json.dumps(decision, ensure_ascii=False, indent=2)) + print("-" * 30) diff --git a/model_artifacts.pkl b/model_artifacts.pkl new file mode 100644 index 0000000000000000000000000000000000000000..5498c43b6dbf7af1fa111646ec8b59f46805b44b GIT binary patch literal 250453 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%0ANrr=Lzu~y&Zg8S2OnaK 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%0A_b4~MDwj0o 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 F{0|{WHLd^v literal 0 HcmV?d00001 diff --git a/smart_agent.py b/smart_agent.py new file mode 100644 index 0000000..f1bfc37 --- /dev/null +++ b/smart_agent.py @@ -0,0 +1,236 @@ +import pandas as pd +import joblib +import json +import re + +# ========================================== +# 0. 基础工具类定义 +# ========================================== +class BaseTool: + def __init__(self, name, description): + self.name = name + self.description = description + + def run(self, *args, **kwargs): + raise NotImplementedError + +# ========================================== +# 1. 工具实现 +# ========================================== + +class MLPredictionTool(BaseTool): + """ + 工具 1: 机器学习预测工具 + 功能: 加载预训练模型,预测客户转化概率,并提供特征归因。 + """ + def __init__(self, artifact_path='model_artifacts.pkl'): + super().__init__("ML_Predictor", "输入客户特征,输出购买概率和关键影响因素") + print(f"[{self.name}] 正在加载模型资产...") + self.artifacts = joblib.load(artifact_path) + self.model = self.artifacts['model'] + self.encoders = self.artifacts['encoders'] + self.feature_meta = self.artifacts['feature_meta'] + # 获取特征重要性,用于简单的归因解释 + self.feature_importances = pd.Series( + self.model.feature_importances_, + index=self.feature_meta['all_cols'] + ).sort_values(ascending=False) + + def preprocess(self, customer_data): + df = pd.DataFrame([customer_data]) + if 'duration' in df.columns: df = df.drop('duration', axis=1) + + for col, le in self.encoders.items(): + if col in df.columns: + try: + df[col] = le.transform(df[col]) + except: + df[col] = 0 # 简单处理未知值 + + # 补齐可能缺失的列(全0填充)并保持顺序 + for col in self.feature_meta['all_cols']: + if col not in df.columns: + df[col] = 0 + + return df[self.feature_meta['all_cols']] + + def run(self, customer_data): + # 1. 预处理 + X = self.preprocess(customer_data) + + # 2. 预测 + prob = float(self.model.predict_proba(X)[0][1]) # 强制转换为 python float + + # 3. 归因 (Attribution) + # 简单逻辑:找出该客户数据中,属于 Top 5 重要特征的字段及其值 + top_features = self.feature_importances.head(5).index.tolist() + attribution = {feat: customer_data.get(feat, 'N/A') for feat in top_features} + + return { + "probability": round(prob, 4), + "risk_level": "High" if prob < 0.3 else ("Medium" if prob < 0.7 else "Low"), + "key_factors": attribution + } + +class StrategyRetrievalTool(BaseTool): + """ + 工具 2: 策略检索工具 + 功能: 根据客户分群或意向等级,检索对应的营销话术和产品包。 + """ + def __init__(self): + super().__init__("Strategy_Retriever", "根据意向分检索营销策略") + # 模拟向量数据库或规则库 + self.knowledge_base = { + "High_Intent": { + "segment": "VIP_Growth", + "channel": "Personal_Call", + "product": "大额存单/结构性存款", + "script_template": "尊贵的{name},鉴于您良好的{key_factor},我们要为您推荐专属..." + }, + "Medium_Intent": { + "segment": "Potential_Saver", + "channel": "SMS_Web", + "product": "灵活理财/定投", + "script_template": "你好,发现您对{key_factor}感兴趣,这里有一份理财攻略..." + }, + "Low_Intent": { + "segment": "General_Mass", + "channel": "Email", + "product": "货币基金/新人礼包", + "script_template": "本月财经快讯:如何打理您的零钱..." + } + } + + def run(self, probability): + if probability > 0.7: + key = "High_Intent" + elif probability > 0.4: + key = "Medium_Intent" + else: + key = "Low_Intent" + + return self.knowledge_base[key] + +# ========================================== +# 2. Agent 定义 (Orchestrator) +# ========================================== + +class SalesAgent: + def __init__(self): + self.tools = { + "predictor": MLPredictionTool(), + "retriever": StrategyRetrievalTool() + } + + def mock_llm_inference(self, prompt): + """ + 模拟 LLM 的生成能力。 + 在真实场景中,这里调用 openai.ChatCompletion.create(model="gpt-4", messages=...) + """ + # 从 Prompt 中解析 Context + # 这是一个 Mock,所以我们用正则或简单的逻辑把 Prompt 里的信息“反刍”出来 + # 实际上 LLM 会进行语义理解和润色 + + # 提取关键信息用于 Mock 输出 + try: + context_str = re.search(r"【Context】(.*?)【Instruction】", prompt, re.S).group(1) + context = json.loads(context_str) + + pred_result = context['prediction'] + strategy_result = context['strategy'] + customer_info = context['customer_raw'] + + # 模拟 LLM 生成话术 + script = strategy_result['script_template'].format( + name="客户", + key_factor=list(pred_result['key_factors'].keys())[0] + ) + + response = { + "thought_process": f"模型预测概率为 {pred_result['probability']},属于 {strategy_result['segment']} 客群。已检索到对应策略,建议通过 {strategy_result['channel']} 触达。", + "final_decision": { + "action": strategy_result['channel'], + "product_recommendation": strategy_result['product'], + "personalized_script": script, + "attribution_explanation": f"预测模型显示该客户成交概率为 {pred_result['probability']*100}%,主要受 {json.dumps(pred_result['key_factors'], ensure_ascii=False)} 等因素影响。" + } + } + return json.dumps(response, ensure_ascii=False, indent=2) + except Exception as e: + return json.dumps({"error": f"LLM Mock Failed: {str(e)}"}) + + def process_request(self, customer_data): + print(f"\n[Agent] 收到新请求: {customer_data.get('job', 'Unknown')} | {customer_data.get('age')}岁") + + # --- Step 1: 调用 ML 工具进行预测 --- + print(f"[Agent] 调用工具: {self.tools['predictor'].name} ...") + pred_result = self.tools['predictor'].run(customer_data) + print(f" >>> 预测结果: 概率={pred_result['probability']}, 关键因素={list(pred_result['key_factors'].keys())}") + + # --- Step 2: 调用 检索工具获取策略 --- + print(f"[Agent] 调用工具: {self.tools['retriever'].name} ...") + strategy_result = self.tools['retriever'].run(pred_result['probability']) + print(f" >>> 检索结果: 渠道={strategy_result['channel']}, 产品={strategy_result['product']}") + + # --- Step 3: LLM 整合信息 --- + print(f"[Agent] 请求 LLM 进行最终决策与生成...") + + # 构建 Context + context = { + "customer_raw": customer_data, + "prediction": pred_result, + "strategy": strategy_result + } + + prompt = f""" + 你是一个智能营销助手。请根据以下上下文信息,生成结构化的营销建议。 + + 【Context】 + {json.dumps(context, ensure_ascii=False)} + + 【Instruction】 + 1. 解释模型预测结果。 + 2. 结合策略库,生成具体的话术。 + 3. 输出 JSON 格式。 + """ + + final_output = self.mock_llm_inference(prompt) + return final_output + +# ========================================== +# 3. 主程序入口 +# ========================================== +if __name__ == "__main__": + # 1. 准备数据 + df_raw = pd.read_csv('bank.csv') + + # 2. 初始化 Agent + agent = SalesAgent() + + # 3. 模拟场景 + print("\n" + "="*60) + print("场景演示: Agent 协调多个工具完成决策") + print("="*60) + + # 场景 A: 低意向客户 + customer_a = df_raw.iloc[1].to_dict() # 假设这是低概率 + if 'deposit' in customer_a: del customer_a['deposit'] + + result_a = agent.process_request(customer_a) + print("\n[Agent Final Output]") + print(result_a) + + print("-" * 60) + + # 场景 B: 高意向客户 (人工构造) + customer_b = customer_a.copy() + customer_b.update({ + 'poutcome': 'success', + 'duration': 1000, # 注意工具内部会移除 duration,这里只是模拟输入 + 'contact': 'cellular', + 'month': 'oct' + }) + + result_b = agent.process_request(customer_b) + print("\n[Agent Final Output]") + print(result_b) diff --git a/train_model.py b/train_model.py new file mode 100644 index 0000000..912a5ba --- /dev/null +++ b/train_model.py @@ -0,0 +1,90 @@ +import pandas as pd +import numpy as np +import xgboost as xgb +from sklearn.model_selection import train_test_split +from sklearn.preprocessing import LabelEncoder, StandardScaler +from sklearn.metrics import accuracy_score, classification_report +import joblib +import json + +# 1. 加载数据 +print("正在加载数据...") +df = pd.read_csv('bank.csv') + +# 2. 数据预处理 +print("正在进行数据预处理...") + +# 移除 duration 列 (避免数据泄露) +if 'duration' in df.columns: + df = df.drop('duration', axis=1) + +# 分离特征和目标 +X = df.drop('deposit', axis=1) +y = df['deposit'] + +# 处理目标变量 (yes -> 1, no -> 0) +le_target = LabelEncoder() +y = le_target.fit_transform(y) + +# 识别分类特征和数值特征 +categorical_cols = X.select_dtypes(include=['object']).columns.tolist() +numeric_cols = X.select_dtypes(include=['int64', 'float64']).columns.tolist() + +# 保存列名信息,供 Agent 使用 +feature_meta = { + 'numeric_cols': numeric_cols, + 'categorical_cols': categorical_cols, + 'all_cols': list(X.columns) +} + +# 对分类特征进行 Label Encoding +# 注意:XGBoost 可以处理类别特征,但通常需要转换为数值。 +# 为了简化 Agent 的推理流程,我们需要保存这些 Encoder。 +encoders = {} +for col in categorical_cols: + le = LabelEncoder() + X[col] = le.fit_transform(X[col]) + encoders[col] = le + +# 3. 划分数据集 +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + +# 4. 训练模型 +print("正在训练 XGBoost 模型...") +model = xgb.XGBClassifier( + n_estimators=100, + learning_rate=0.1, + max_depth=5, + use_label_encoder=False, + eval_metric='logloss' +) +model.fit(X_train, y_train) + +# 5. 评估模型 +y_pred = model.predict(X_test) +y_pred_proba = model.predict_proba(X_test)[:, 1] + +print("\n模型评估结果:") +print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}") +print("\nClassification Report:") +print(classification_report(y_test, y_pred)) + +# 6. 保存资产 +print("\n正在保存模型和预处理工具...") +artifacts = { + 'model': model, + 'encoders': encoders, + 'target_encoder': le_target, + 'feature_meta': feature_meta +} +joblib.dump(artifacts, 'model_artifacts.pkl') + +# 另外保存一份特征重要性,供参考 +importances = model.feature_importances_ +feature_names = X.columns +feat_imp_df = pd.DataFrame({'Feature': feature_names, 'Importance': importances}) +feat_imp_df = feat_imp_df.sort_values(by='Importance', ascending=False) +print("\n特征重要性 Top 5:") +print(feat_imp_df.head()) + +print("\n完成!模型已保存为 'model_artifacts.pkl'")