Week 07:统计机器学习项目启动、数据卡与 Baseline
Week 05-06 你已经能训练 sklearn baseline、做交叉验证和模型选择。Week 07 开始把这些能力组织成一个可以展示的统计机器学习项目。目标不是“写很多模型”,而是让一个陌生人打开你的仓库后能看懂:问题是什么、数据从哪里来、如何运行、baseline 是什么、目前错误在哪里。
本文默认 CachyOS + fish shell + VS Code + uv + Python + Git。命令按 fish 写。
0. 本周详细教学:语法、规范、验收
本节不是追加在尾部的复习,而是本周正文的入口。先读这里,再做后面的命令和项目。
0.1 本周真正要学会什么
| 维度 | 要求 |
|---|---|
| 知识点 | 问题定义、数据卡、baseline、特征工程计划 |
| 代码语法 | 能从空文件写出本周核心脚本,而不是只复制运行 |
| 程序规范 | 函数拆分、路径清楚、输入输出明确、错误能解释 |
| 交付物 | projects/stat-ml-baseline/README.md |
| 验收方式 | 从 fish 终端运行命令,得到可复查的文件或指标 |
0.2 代码语法精讲
下面的代码不是最终答案,而是本周必须理解的最小骨架:
problem = {
"target": "predict whether sample is positive",
"unit": "one row is one observation",
"baseline": "DummyClassifier + LogisticRegression",
"risk": "target leakage from future columns",
}
for k, v in problem.items():
print(f"{k}: {v}")
读代码时按四步检查:输入从哪里来;中间变量的类型和 shape 是什么;函数或脚本输出什么;哪些错误应该显式报出来。
0.3 本周程序规范
- 所有路径用相对路径或 `pathlib.Path`,不要写死 `/home/miku/...`。
- 核心逻辑进 `src/`,notebook 只做探索和解释。
- 每个脚本能从 fish 终端运行,并在 README 写出命令。
- 输出必须落盘到 `reports/`、`figures/` 或 `outputs/`,不能只在屏幕上看。
0.4 本周练习分层
| 层级 | 任务 | 不合格表现 | 合格验收 |
|---|---|---|---|
| 最小练习 | 手写上面的最小骨架 | 只在 notebook 里运行 | 终端运行成功 |
| 标准练习 | 把逻辑拆成函数/模块 | 一个大脚本从头写到尾 | 至少 2 个函数,职责清楚 |
| 项目练习 | 生成本周交付物 projects/stat-ml-baseline/README.md | 只有屏幕输出 | 文件落盘,可复查 |
| 复盘练习 | 写 3 个错误和修复 | 只写“已解决” | 写清报错、原因、修复、预防 |
0.5 本周和主线的连接
- 回到总计划:USTC AI / Quant 练习手册
- 查详细练习索引:技术练习详解
- 查质量评分:最终质量门槛
1. 本周目标
创建一个完整项目骨架:
projects/stat-ml-baseline/
├── README.md
├── data/
├── docs/
├── notebooks/
├── reports/
└── src/
并完成四个核心产物:
docs/data_card.md:说明数据来源、字段、目标变量、限制。src/train_baseline.py:能训练并输出 baseline 结果。reports/baseline_results.md:记录模型、指标、划分方式、初步结论。reports/error_analysis.md:分析错误样本与下一步改进。
本周你要从“脚本能跑”升级为“项目能被别人理解”。
2. 前置条件
你应该已经完成:
- Week 05:train / validation / test、多个 baseline、分类或回归指标。
- Week 06:KFold / StratifiedKFold、Pipeline、GridSearchCV、防泄漏。
- Week 03-04:pandas 清洗、EDA 图表、报告写作。
- Git 基础:
git status、git add、git commit。
检查工具:
python --version
uv --version
git --version
code --version
如果你刚打开新终端,后面每次运行项目前都先激活:
source .venv/bin/activate.fish
3. 选题原则
推荐题目:
基于统计学习的表格数据预测与误差分析
你可以选择:
- Kaggle 入门数据:Titanic、House Prices、Spaceship Titanic。
- UCI 数据集:Adult Income、Wine Quality、Bank Marketing。
- 金融或经济公开数据:指数收益、宏观指标、公司财务指标。
- 校内课程数据:只要不涉及隐私或不可公开数据。
选择标准:
| 标准 | 建议 |
|---|---|
| 数据规模 | 初学项目优先 500 到 100000 行,不要一开始选超大数据 |
| 数据类型 | 表格数据优先,特征包括数值和类别都可以 |
| 目标变量 | 明确:分类或回归,不要模糊 |
| 可解释性 | 能讲清楚每个字段大概含义 |
| 合法性 | 数据来源可引用,不含隐私或违反平台条款 |
如果不知道选什么,建议用 UCI Wine Quality 或 Kaggle Titanic。它们简单、可解释、适合展示完整流程。
4. 建立项目目录
在学习总目录下创建项目:
cd ~/Code/python-learning
mkdir -p projects
cd projects
mkdir -p stat-ml-baseline
cd stat-ml-baseline
pwd
初始化 Python 项目:
uv init
uv venv
source .venv/bin/activate.fish
uv add pandas numpy scikit-learn matplotlib seaborn tabulate
创建目录:
mkdir -p data/raw data/interim data/processed docs notebooks reports src
用 VS Code 打开:
code .
5. 文件布局
建议结构:
stat-ml-baseline/
├── .venv/ # 本地虚拟环境,不提交
├── data/
│ ├── raw/ # 原始数据,只读
│ ├── interim/ # 中间处理结果
│ └── processed/ # 建模用数据
├── docs/
│ └── data_card.md # 数据卡
├── notebooks/
│ └── 01_eda.ipynb # 可选:探索分析
├── reports/
│ ├── baseline_results.md # baseline 结果
│ └── error_analysis.md # 错误分析
├── src/
│ ├── train_baseline.py # 训练入口
│ └── make_dataset.py # 可选:数据清洗入口
├── pyproject.toml
├── uv.lock
└── README.md
目录职责要清楚:
| 目录 | 职责 |
|---|---|
data/raw/ |
放原始数据,不在里面手工改值 |
data/interim/ |
放临时清洗结果,可删除重建 |
data/processed/ |
放最终建模输入 |
docs/ |
放数据说明、设计说明 |
notebooks/ |
放探索性分析,不做最终训练入口 |
reports/ |
放实验结果和错误分析 |
src/ |
放可重复运行的 Python 脚本 |
6. 创建 README 初稿
打开 README.md,先写一个能说明项目方向的初稿。不要等项目结束才写 README,否则你会忘记很多设计选择。
建议结构:
# Statistical Machine Learning BaselineProblem
This project predicts … using tabular data. The task is classification / regression.
Data
- Source:
- Rows:
- Columns:
- Target:
- Unit of observation:
Method
- Baseline models:
- Validation strategy:
- Main metric:
Current Status
Week 07 project start. Baseline and error analysis are in progress.
How to Run
- Create and activate a uv virtual environment.
- Install dependencies.
Run the baseline script.
这不是最终版。Week 08 会继续打磨 README。本周的 README 只要能帮助你保持项目方向即可。
7. 创建数据卡
创建文件:
docs/data_card.md
建议内容:
# Data CardDataset Name
Write the dataset name here.
Source
- URL:
- License or access condition:
- Download date:
Prediction Target
- Target column:
- Task type: classification / regression
- Positive class definition if classification:
Unit of Observation
One row represents …
Columns
Column Type Meaning Notes example_column numeric … … Missing Values
Describe missing columns and possible reasons.
Known Limitations
- Sampling bias:
- Time period limitation:
- Measurement errors:
- Possible leakage columns:
Ethical or Privacy Notes
State whether the dataset contains personal, financial, medical, or sensitive information.
数据卡不是形式主义。它能防止你在 Week 08 写报告时讲不清数据来源。
8. 下载或放入数据
如果数据已经手动下载,把 CSV 放到:
data/raw/dataset.csv
用 fish 检查文件:
ls data/raw
如果你用 Kaggle,不建议本教程里展开认证流程。初学阶段可以手动从网页下载 zip,再解压到 data/raw/。如果使用 UCI 的公开 CSV,可以用浏览器下载,或者用 Python 下载。
重要原则:
原始数据进 data/raw/
脚本生成的数据进 data/processed/
不要在 Excel 里手改原始数据后覆盖 raw 文件
9. 如果暂时没有数据:使用 sklearn 数据集占位
为了先搭项目骨架,可以用 sklearn 自带数据集跑通流程。等你选定真实数据后再替换 load_data()。
本教程下面使用 breast cancer dataset 做可运行示例。它不是你的最终项目题目也没关系;Week 07 重点是项目结构。
10. 编写 baseline 训练脚本
创建文件:
src/train_baseline.py
粘贴下面代码:
from pathlib import Path
import pandas as pd
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, confusion_matrix, f1_score, precision_score, recall_score, roc_auc_score
from sklearn.model_selection import StratifiedKFold, cross_validate, train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
RANDOM_STATE = 42
RESULT_PATH = Path(“reports/baseline_results.md”)
ERROR_PATH = Path(“reports/error_analysis.md”)
def load_project_data():
dataset = load_breast_cancer(as_frame=True)
X = dataset.data
y = dataset.target
return X, y, list(dataset.target_names)
def build_models():
return {
“logistic_regression”: Pipeline(
steps=[
(“scaler”, StandardScaler()),
(“model”, LogisticRegression(max_iter=3000, random_state=RANDOM_STATE)),
]
),
“random_forest”: RandomForestClassifier(
n_estimators=200,
max_depth=None,
min_samples_leaf=1,
random_state=RANDOM_STATE,
),
}
def score_models(models, X_train, y_train):
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=RANDOM_STATE)
scoring = {
“accuracy”: “accuracy”,
“precision”: “precision”,
“recall”: “recall”,
“f1”: “f1”,
“roc_auc”: “roc_auc”,
}
rows = []
for name, model in models.items():
scores = cross_validate(model, X_train, y_train, cv=cv, scoring=scoring, n_jobs=-1)
row = {“model”: name}
for metric in scoring:
values = scores[f”test_{metric}”]
row[f”{metric}_mean”] = values.mean()
row[f”{metric}_std”] = values.std()
rows.append(row)
return sorted(rows, key=lambda item: item[“f1_mean”], reverse=True)
def evaluate_final_model(model, X_train, X_test, y_train, y_test):
model.fit(X_train, y_train)
predictions = model.predict(X_test)
probabilities = model.predict_proba(X_test)[:, 1]
metrics = {
“accuracy”: accuracy_score(y_test, predictions),
“precision”: precision_score(y_test, predictions),
“recall”: recall_score(y_test, predictions),
“f1”: f1_score(y_test, predictions),
“roc_auc”: roc_auc_score(y_test, probabilities),
}
matrix = confusion_matrix(y_test, predictions)
errors = X_test.assign(actual=y_test.to_numpy(), predicted=predictions, probability=probabilities)
errors = errors[errors[“actual”] != errors[“predicted”]]
return metrics, matrix, errors
def write_baseline_report(rows, test_metrics, target_names):
table_rows = []
for row in rows:
table_rows.append(
”| {model} | {f1_mean:.4f} | {f1_std:.4f} | {roc_auc_mean:.4f} | {recall_mean:.4f} |“.format(
**row
)
)
plain report = f"""# Baseline Results
Dataset
- Dataset: sklearn breast cancer dataset or project replacement dataset
- Target names:
- Task: binary classification
Validation Design
- Holdout test size: 20%
- Cross validation: StratifiedKFold with 5 folds on the training portion
- Selection metric: mean CV F1
- Random state:
Cross-Validation Results
Model Mean F1 Std F1 Mean ROC AUC Mean Recall {chr(10).join(table_rows)}
Final Test Metrics
Metric Value Accuracy {test_metrics[‘accuracy’]:.4f} Precision {test_metrics[‘precision’]:.4f} Recall {test_metrics[‘recall’]:.4f} F1 {test_metrics[‘f1’]:.4f} ROC AUC {test_metrics[‘roc_auc’]:.4f}
Notes
- Replace this section with your interpretation.
- Explain why the chosen metric matches the project goal.
- Compare the model against a naive baseline if possible.
"""
RESULT_PATH.parent.mkdir(parents=True, exist_ok=True)
RESULT_PATH.write_text(report, encoding=“utf-8”)
def write_error_analysis(matrix, errors):
preview = errors.head(10).round(4).to_markdown(index=False)
report = f"""# Error Analysis
Confusion Matrix
Rows are actual labels, columns are predicted labels.
{matrix}
</code></pre></div>
<h2>Wrong Prediction Preview</h2>
<p>{preview}</p>
<h2>Observations To Fill In</h2>
<ul>
<li>What type of mistake appears most often?</li>
<li>Are false positives or false negatives more costly?</li>
<li>Do wrong predictions have low confidence or high confidence?</li>
<li>Which features look unusual among wrong predictions?</li>
<li>What data cleaning or feature engineering should be tried next?
"""
ERROR_PATH.parent.mkdir(parents=True, exist_ok=True)
ERROR_PATH.write_text(report, encoding="utf-8")</li>
</ul>
<p>def main():
X, y, target_names = load_project_data()
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.2,
random_state=RANDOM_STATE,
stratify=y,
)
models = build_models()
rows = score_models(models, X_train, y_train)
best_name = rows[0]["model"]
test_metrics, matrix, errors = evaluate_final_model(models[best_name], X_train, X_test, y_train, y_test)</p>
<div class="omp-code-frame" data-lang="prompt"><div class="omp-code-bar"><span>omp</span><span>prompt</span></div><pre><code>write_baseline_report(rows, test_metrics, target_names)
write_error_analysis(matrix, errors)
print(pd.DataFrame(rows).round(4).to_string(index=False))
print()
print("Selected model:", best_name)
print("Reports written:", RESULT_PATH, ERROR_PATH)
</code></pre></div>
<p>if <strong>name</strong> == "<strong>main</strong>":
main()</p>
<div class="omp-code-frame" data-lang="prompt"><div class="omp-code-bar"><span>omp</span><span>prompt</span></div><pre><code>
## 11. 运行 baseline
```fish
source .venv/bin/activate.fish
python src/train_baseline.py
</code></pre></div>
<p>应该生成:</p>
<div class="omp-code-frame" data-lang="prompt"><div class="omp-code-bar"><span>omp</span><span>prompt</span></div><pre><code class="language-text">reports/baseline_results.md
reports/error_analysis.md
</code></pre></div>
<p>检查:</p>
<div class="omp-code-frame" data-lang="fish"><div class="omp-code-bar"><span>omp</span><span>fish</span></div><pre><code class="language-fish">test -f reports/baseline_results.md; and echo "baseline report exists"
test -f reports/error_analysis.md; and echo "error analysis exists"
</code></pre></div>
<p>打开报告:</p>
<div class="omp-code-frame" data-lang="fish"><div class="omp-code-bar"><span>omp</span><span>fish</span></div><pre><code class="language-fish">code reports/baseline_results.md reports/error_analysis.md
</code></pre></div>
<h2>12. 把示例数据替换成真实项目数据</h2>
<p>假设真实 CSV 是:</p>
<div class="omp-code-frame" data-lang="prompt"><div class="omp-code-bar"><span>omp</span><span>prompt</span></div><pre><code class="language-text">data/raw/train.csv
</code></pre></div>
<p>目标列叫:</p>
<div class="omp-code-frame" data-lang="prompt"><div class="omp-code-bar"><span>omp</span><span>prompt</span></div><pre><code class="language-text">target
</code></pre></div>
<p>可以把 <code>load_project_data()</code> 改成:</p>
<div class="omp-code-frame" data-lang="python"><div class="omp-code-bar"><span>omp</span><span>python</span></div><pre><code class="language-python">def load_project_data():
data = pd.read_csv("data/raw/train.csv")
X = data.drop(columns=["target"])
y = data["target"]
target_names = sorted(y.astype(str).unique().tolist())
return X, y, target_names
</code></pre></div>
<p>如果有类别变量,你需要在 Week 06 学过的 <code>ColumnTransformer</code> 中处理。不要用“随便把字符串替换成 0、1、2”的方式编码城市、行业、学校等无序类别。</p>
<h2>13. 明确问题定义</h2>
<p>在 README 的 <code>Problem</code> 部分回答:</p>
<div class="omp-code-frame" data-lang="prompt"><div class="omp-code-bar"><span>omp</span><span>prompt</span></div><pre><code class="language-text">我要预测什么?
为什么这个预测有意义?
每一行数据代表什么?
目标变量是如何定义的?
预测提前量是多少?
这个项目是分类还是回归?
</code></pre></div>
<p>示例:</p>
<div class="omp-code-frame" data-lang="prompt"><div class="omp-code-bar"><span>omp</span><span>prompt</span></div><pre><code class="language-text">This project predicts whether a passenger survived on the Titanic using passenger-level tabular features. Each row is one passenger. The target is Survived, where 1 means survived and 0 means not survived. This is a binary classification task.
</code></pre></div>
<p>统计学学生尤其要注意“单位”。一行是一个人、一家公司、一天、一笔交易,分析意义完全不同。</p>
<h2>14. 建立 naive baseline</h2>
<p>除了 sklearn 模型,还应该有 naive baseline。</p>
<p>分类任务:</p>
<div class="omp-code-frame" data-lang="prompt"><div class="omp-code-bar"><span>omp</span><span>prompt</span></div><pre><code class="language-text">永远预测训练集中最多的类别
</code></pre></div>
<p>回归任务:</p>
<div class="omp-code-frame" data-lang="prompt"><div class="omp-code-bar"><span>omp</span><span>prompt</span></div><pre><code class="language-text">永远预测训练集目标变量均值或中位数
</code></pre></div>
<p>你可以在报告中写:</p>
<div class="omp-code-frame" data-lang="prompt"><div class="omp-code-bar"><span>omp</span><span>prompt</span></div><pre><code class="language-text">Naive baseline: always predict the majority class.
F1 is low because the model never identifies minority-class cases.
</code></pre></div>
<p>这样可以证明 sklearn 模型不仅比随机好,也比最简单策略好。</p>
<h2>15. 初步错误分析怎么做</h2>
<p>不要只看总分。打开 <code>reports/error_analysis.md</code>,观察错误样本:</p>
<div class="omp-code-frame" data-lang="fish"><div class="omp-code-bar"><span>omp</span><span>fish</span></div><pre><code class="language-fish">code reports/error_analysis.md
</code></pre></div>
<p>写下:</p>
<table>
<thead>
<tr>
<th>问题</th>
<th>例子</th>
</tr>
</thead>
<tbody><tr>
<td>哪类错误更多</td>
<td>false positives 多,还是 false negatives 多</td>
</tr>
<tr>
<td>错误样本有什么共同点</td>
<td>某些特征极端、缺失多、靠近决策边界</td>
</tr>
<tr>
<td>指标是否符合目标</td>
<td>如果漏判代价高,recall 是否足够</td>
</tr>
<tr>
<td>是否需要新特征</td>
<td>比如比例、分组统计、时间窗口</td>
</tr>
<tr>
<td>是否可能有数据问题</td>
<td>标签噪声、重复样本、泄漏列</td>
</tr>
</tbody></table>
<p>错误分析的价值在于指导 Week 08 的改进,不是为了装饰报告。</p>
<h2>16. README 本周至少要写到什么程度</h2>
<p>Week 07 的 README 不要求完美,但至少包含:</p>
<div class="omp-code-frame" data-lang="prompt"><div class="omp-code-bar"><span>omp</span><span>prompt</span></div><pre><code class="language-text">项目标题
问题定义
数据来源
目标变量
运行命令
当前 baseline 结果链接
下一步计划
</code></pre></div>
<p>运行命令可以写成:</p>
<div class="omp-code-frame" data-lang="fish"><div class="omp-code-bar"><span>omp</span><span>fish</span></div><pre><code class="language-fish">uv venv
source .venv/bin/activate.fish
uv sync
python src/train_baseline.py
</code></pre></div>
<p>如果你用了手动下载数据,README 必须说明数据应该放在哪里:</p>
<div class="omp-code-frame" data-lang="prompt"><div class="omp-code-bar"><span>omp</span><span>prompt</span></div><pre><code class="language-text">Download the dataset and place the CSV at data/raw/train.csv.
</code></pre></div>
<p>不要让读者猜文件名。</p>
<h2>17. Git 提交</h2>
<p>先创建 <code>.gitignore</code>,内容至少包括:</p>
<div class="omp-code-frame" data-lang="prompt"><div class="omp-code-bar"><span>omp</span><span>prompt</span></div><pre><code class="language-text">.venv/
__pycache__/
.ipynb_checkpoints/
.DS_Store
data/raw/*.zip
</code></pre></div>
<p>如果原始 CSV 很大,或数据许可不允许发布,也把它加入 <code>.gitignore</code>。然后提交:</p>
<div class="omp-code-frame" data-lang="fish"><div class="omp-code-bar"><span>omp</span><span>fish</span></div><pre><code class="language-fish">git status --short
git add README.md docs reports src pyproject.toml uv.lock .gitignore
git commit -m "Start statistical machine learning baseline project"
</code></pre></div>
<p>如果这是你第一次在该目录使用 Git:</p>
<div class="omp-code-frame" data-lang="fish"><div class="omp-code-bar"><span>omp</span><span>fish</span></div><pre><code class="language-fish">git init
</code></pre></div>
<p>再执行上面的 <code>git add</code> 与 <code>git commit</code>。</p>
<h2>18. 本周练习</h2>
<ol>
<li>在 <code>docs/data_card.md</code> 中写完至少 10 个字段的说明。</li>
<li>在 README 中用 5 句话讲清楚问题定义。</li>
<li>加入 naive baseline,并在报告中比较 naive baseline 与 sklearn baseline。</li>
<li>把错误样本保存为 <code>reports/wrong_predictions.csv</code>,用 pandas 查看前 20 行。</li>
<li>尝试一个新特征,并说明它为什么可能有用。</li>
<li>用 Git 做两次提交:一次提交项目骨架,一次提交 baseline 结果。</li>
</ol>
<h2>19. 验收检查</h2>
<p>在项目根目录执行:</p>
<div class="omp-code-frame" data-lang="fish"><div class="omp-code-bar"><span>omp</span><span>fish</span></div><pre><code class="language-fish">source .venv/bin/activate.fish
python src/train_baseline.py
test -f docs/data_card.md; and echo "data card exists"
test -f reports/baseline_results.md; and echo "baseline report exists"
test -f reports/error_analysis.md; and echo "error analysis exists"
</code></pre></div>
<p>人工确认:</p>
<ul>
<li>项目目录结构完整。</li>
<li>README 能说明问题、数据、运行方式。</li>
<li><code>docs/data_card.md</code> 写明数据来源、字段、目标、限制。</li>
<li>baseline 脚本能从头运行。</li>
<li>报告有模型、指标、验证方式、结果表。</li>
<li>错误分析不是空模板,至少有 5 条观察。</li>
<li>Git 提交不包含 <code>.venv/</code>。</li>
</ul>
<h2>20. 常见错误</h2>
<h3>错误 1:项目没有明确目标</h3>
<p>“分析 Titanic 数据”不是明确目标。“预测乘客是否生还,并分析哪些群体错误率高”才是明确目标。</p>
<h3>错误 2:README 最后才写</h3>
<p>README 是项目导航,不是交作业时补的封面。Week 07 就写初稿,Week 08 再打磨。</p>
<h3>错误 3:notebook 里能跑,但脚本不能跑</h3>
<p>Notebook 适合探索,最终训练入口应该是 <code>src/train_baseline.py</code>。否则别人无法稳定复现。</p>
<h3>错误 4:数据来源不清楚</h3>
<p>报告里必须能回答:数据从哪里来、下载日期、许可或引用方式、是否可以公开。</p>
<h3>错误 5:只写模型分数,不写错误分析</h3>
<p>模型项目的价值不只在分数,还在你能否解释错误、提出下一步改进。</p>
<h2>21. 下一步</h2>
<p>进入 Week 08:<a href="/post/ustc-stat-ai-quant-plan/">统计建模项目收尾</a>。你会把 Week 07 的项目整理成可以给老师、导师或实习面试官看的版本:README 打磨、报告成稿、复现命令、提交历史清理与成果展示。</p>
</article>
</div>
</section>
喜欢的话,留下你的评论吧~