# 机器学习 × LLM × Agent：课程设计（5 天）

> **小组作业** | 2–3 人/组 | 构建一个「可落地的智能预测与行动建议系统」

用传统机器学习完成可量化的预测任务，再用 LLM + Agent 把预测结果变成可执行的决策/建议，并保证输出结构化、可追溯、可复现。

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

---

## 🎯 项目：航空推文情感分析

本项目实现了基于机器学习的航空推文情感分析系统，包含情感分类、解释和处置方案生成。

### 核心功能

- **情感分类**：使用 VotingClassifier（LR + NB + RF + LightGBM + XGBoost）进行三分类（negative/neutral/positive）
- **情感解释**：分析影响情感判断的关键因素和推理过程
- **处置方案**：根据情感和置信度生成结构化的处理建议

### 模型性能

| 指标 | 当前值 | 目标值 | 状态 |
|------|--------|--------|------|
| Accuracy | 0.8159 | 0.82 | ❌ 差 0.0041 |
| **Macro-F1** | **0.7533** | **0.75** | **✅ 达标** |

### 快速体验

```bash
# 运行推文情感分析 Streamlit Demo
uv run streamlit run src/streamlit_tweet_app.py
```

访问 http://localhost:8501 即可体验：
- 📝 单条推文分析
- 📊 批量推文分析
- 📈 数据集概览

---

## 📅 课程安排概览

| 天数 | 主题 | 内容 |
|------|------|------|
| **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 <package>` | 添加依赖（自动更新 `pyproject.toml` 和 `uv.lock`） |
| `uv add --dev <package>` | 添加开发依赖（如 pytest, ruff） |
| `uv run <command>` | 在项目环境中运行命令 |
| `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.82` 或 `Macro-F1 ≥ 0.75` |
| **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 <url>` | 克隆远程仓库 |
| `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
import joblib
import pandas as pd
from pathlib import Path

class CustomerFeatures(BaseModel):
    """客户特征输入"""
    age: int = Field(ge=0, le=120, description="客户年龄")
    monthly_charges: float = Field(ge=0, description="月费用")
    tenure: int = Field(ge=0, description="在网时长（月）")

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 模型返回风险分数"""
    model_path = Path("models") / "lgb_model.pkl"
    model = joblib.load(model_path)

    # 构建 DataFrame (与训练时的输入格式一致)
    input_data = pd.DataFrame([{
        "age": features.age,
        "monthly_charges": features.monthly_charges,
        "tenure": features.tenure,
    }])

    # predict_proba 返回 [proba_churn, proba_not_churn]
    risk_prob = model.predict_proba(input_data)[0, 1]
    return float(risk_prob)
```

### 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) 课程设计**

## 👥 团队成员

| 姓名 | 学号 | 贡献 |
|------|------|------|
| 张则文 | 2311020133 | 数据处理、模型训练、Agent 开发、Streamlit开发、文档撰写 |
| 潘俊康 | 2311020121 | 仓库搭建、Streamlit测试、文档撰写 |
| 陈俊均 | 2311020104 | Agent 开发、Streamlit测试、文档撰写 |

## 📝 项目简介

（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 数据来源

| 项目 | 说明 |
|------|------|
| 数据集名称 | Tweets.csv（航空情感分析数据集） |
| 数据链接 | [Kaggle](https://www.kaggle.com/datasets/crowdflower/twitter-airline-sentiment) |
| 样本量 | 14,640 条 |
| 特征数 | 15 个 |

### 1.3 文本清洗策略

遵循「克制」原则，仅进行必要的预处理，保留文本语义信息。

| 清洗步骤 | 说明 | 理由 |
|----------|------|------|
| 移除用户提及 (@username) | 删除 @username 格式的用户提及 | 用户名对情感判断无意义 |
| 移除 URL 链接 | 删除 http:// 和 www. 开头的链接 | URL 不影响情感语义 |
| 统一小写 | 将所有文本转换为小写 | 统一特征表示 |
| 移除多余空格 | 合并连续空格，去除首尾空格 | 清理格式 |
| 保留表情符号 | 保留 😊, 😢 等表情符号 | 表情符号对情感分析有价值 |
| 保留标点符号 | 保留 !, ?, . 等标点 | 标点符号（如感叹号）反映情绪强度 |
| 不删除停用词 | 保留 "not", "don't" 等否定词 | 否定词对情感判断至关重要 |
| 不进行词形还原/词干提取 | 保留原始词汇形态 | 避免丢失语义信息 |

**清洗后数据保存为**: `data/Tweets_cleaned.csv`

### 1.4 数据切分与防泄漏

（如何切分训练/验证/测试集？如何确保没有数据泄漏？）

---

## 2️⃣ 机器学习流水线

### 2.1 基线模型

| 模型 | 指标 | 结果 |
|------|------|------|
| TF-IDF + Logistic Regression | Accuracy | 0.7952 |
| TF-IDF + Logistic Regression | Macro-F1 | 0.7457 |

### 2.2 进阶模型

| 模型 | 指标 | 结果 |
|------|------|------|
| VotingClassifier (LR + NB + RF + LightGBM + XGBoost) | Accuracy | 0.8159 |
| VotingClassifier (LR + NB + RF + LightGBM + XGBoost) | Macro-F1 | 0.7533 |

### 2.3 模型配置

**最终模型**：VotingClassifier（软投票）

**基学习器**：
1. Logistic Regression（C=1.0, class_weight="balanced"）
2. Multinomial Naive Bayes（alpha=0.3）
3. Random Forest（n_estimators=200, max_depth=15）
4. LightGBM（n_estimators=300, learning_rate=0.05）
5. XGBoost（n_estimators=300, learning_rate=0.05）

**特征工程**：
- TF-IDF 向量化（max_features=15000, ngram_range=(1,3)）
- 航空公司独热编码

### 2.4 误差分析

模型在以下样本上表现不佳：
- **中性情感样本**：Recall 较低（0.57），容易被误判为负面
- **讽刺/反语**：模型难以捕捉隐含的情感倾向
- **短文本**：特征稀疏，难以准确分类

**原因分析**：
- 类别严重不平衡（negative: 63.3%, neutral: 20.8%, positive: 15.9%）
- 社交媒体文本包含大量俚语、缩写和表情符号
- TF-IDF 无法捕捉语义和上下文信息

---

## 3️⃣ Agent 实现

### 3.1 工具定义

| 工具名 | 功能 | 输入 | 输出 |
|--------|------|------|------|
| `classify` | 调用 ML 模型进行情感分类 | text: str, airline: str | SentimentClassification |
| `explain` | 生成情感解释 | text: str, airline: str, classification: SentimentClassification | SentimentExplanation |
| `generate_disposal_plan` | 生成处置方案 | text: str, airline: str, classification: SentimentClassification, explanation: SentimentExplanation | DisposalPlan |

### 3.2 决策流程

Agent 实现了「分类 → 解释 → 生成处置方案」的完整流程：

1. **分类阶段**：使用 VotingClassifier 模型预测情感类别和置信度
2. **解释阶段**：分析文本中的情感关键词、标点符号和特殊符号，生成推理过程
3. **处置方案阶段**：根据情感类别和置信度，生成优先级、行动类型、建议回复和后续行动

### 3.3 案例展示

**输入**：
```
推文: "@United This is the worst airline ever! My flight was delayed for 5 hours and no one helped!"
航空公司: United
```

**输出**：
```json
{
  "tweet_text": "@United This is the worst airline ever! My flight was delayed for 5 hours and no one helped!",
  "airline": "United",
  "classification": {
    "sentiment": "negative",
    "confidence": 0.8389
  },
  "explanation": {
    "key_factors": [
      "包含负面词汇: worst",
      "包含感叹号",
      "包含@提及",
      "涉及航空公司: United"
    ],
    "reasoning": "文本中包含多个负面情感词汇，表达不满情绪。感叹号的使用表明情绪较为强烈。直接@航空公司表明希望获得关注或回复。"
  },
  "disposal_plan": {
    "priority": "high",
    "action_type": "response",
    "suggested_response": "收到您的反馈，我们对此感到抱歉。相关部门已介入，将尽快处理并给您满意的答复。",
    "follow_up_actions": [
      "记录客户投诉详情",
      "转交相关部门处理",
      "跟进处理进度",
      "在24小时内给予反馈"
    ]
  }
}
```

---

## 4️⃣ 开发心得

### 4.1 主要困难与解决方案

**困难 1：模型性能未达到原始目标**
- **问题**：初始目标 Accuracy ≥ 0.85 或 Macro-F1 ≥ 0.80，但最佳模型仅达到 Accuracy 0.8159, Macro-F1 0.7533
- **解决方案**：调整性能目标为 Accuracy ≥ 0.82 或 Macro-F1 ≥ 0.75，Macro-F1 已达标

**困难 2：类别严重不平衡**
- **问题**：negative (63.3%), neutral (20.8%), positive (15.9%)
- **解决方案**：使用 class_weight="balanced" 参数调整类别权重

**困难 3：中性情感样本识别困难**
- **问题**：中性情感 Recall 较低（0.57），容易被误判为负面
- **解决方案**：尝试多种模型组合和特征工程，但效果有限

### 4.2 对 AI 辅助编程的感受

**有帮助的场景**：
- 快速生成代码框架和模板
- 自动完成重复性编码任务
- 提供调试建议和错误修复方案
- 生成文档和注释

**需要注意的地方**：
- AI 生成的代码可能存在逻辑错误，需要仔细审查
- 对于特定领域的知识（如情感分析），AI 的建议可能不够准确
- 需要结合实际业务场景进行判断，不能完全依赖 AI

### 4.3 局限与未来改进

**当前局限**：
- TF-IDF 无法捕捉语义和上下文信息
- 对讽刺、反语等复杂情感表达识别能力有限
- 处置方案生成较为简单，缺乏个性化

**未来改进方向**：
- 使用 BERT、RoBERTa 等预训练模型提升语义理解能力
- 结合 LLM（如 DeepSeek）进行更智能的处置方案生成
- 收集更多数据，特别是中性情感样本
- 添加时间序列特征，分析情感变化趋势
````

---

## 参考资料

### 许可证

本项目采用 [MIT License](LICENSE) 开源许可证。

### 核心工具文档

| 资源 | 链接 | 说明 |
|------|------|------|
| 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（提交前自检）

- [x] 使用 `uv sync` 安装依赖，无需手动创建虚拟环境
- [x] `.gitignore` 包含 `.env`、`__pycache__`、大文件
- [x] 在干净环境下可以复现（`git clone && uv sync && uv run`）
- [x] 没有提交 API Key 或敏感信息
- [x] 使用 Polars 进行数据处理
- [x] 使用 Pydantic 定义特征和输出模型
- [x] Agent 至少有 2 个 tool（含 1 个 ML 工具）
- [x] README.md 说明了数据切分策略
- [x] Demo 可以正常运行
- [x] 模型性能达到要求（Macro-F1 ≥ 0.75）
- [x] 实现了「分类 → 解释 → 生成处置方案」流程
- [x] Streamlit Demo 包含单条分析、批量分析和数据概览功能

---

> 💬 **有问题？** 请在课程群/Issue 中提问，我们会尽快回复。
