补充材料
围绕同一个逻辑回归案例,补充变量含义、预处理、验证与代码。
A1 数据字典与信息边界
本课响应变量为Survived。下表以衍生基准模型为起点;进阶方案另用分组年龄、FareLog、Deck、FamilyBand与明确的交互列。
| 字段 | 含义 | 课堂处理 |
|---|---|---|
| PassengerId | 乘客记录标识 | 对齐记录,不作为本课预测特征 |
| Survived | 0遇难,1生还 | 响应变量 |
| Pclass | 舱位等级1、2、3 | 按类别编码 |
| Sex | 性别 | 按类别编码 |
| Age | 年龄,可缺失 | 基准用训练折中位数;可比较Sex×Pclass分组与回退 |
| SibSp | 同行兄弟姐妹或配偶数 | 构造FamilySize |
| Parch | 同行父母或子女数 | 构造FamilySize |
| Fare | 票价 | 数值特征;本课不假定它必然等于单人的票价 |
| Cabin | 船舱记录,可缺失 | 基准用HasCabin;进阶以Deck替换 |
| Embarked | 登船港口S、C、Q | 训练部分众数填补后编码 |
| Name | 姓名文本 | 提取头衔Title,使用固定的合并规则 |
| Ticket | 票号 | 不进入默认生还模型;候选频数在训练侧学习,并用于分组验证 |
HasCabin只表示记录有无;FamilySize由已记录的同行亲属数构造。数据中的关联不能自动解释为因果效应。
A2 怎样选择缺失值策略
中位数填补简单、可解释,适合作为起点。删除记录、分组填补与模型填补各有条件,不能仅凭方法更复杂就认定预测更好。
| 策略 | 需要考虑的问题 |
|---|---|
| 删除缺失记录 | 剩余样本是否还能代表目标群体?样本量会损失多少? |
| 中位数或众数填补 | 规则是否仅从训练部分学习?填补是否掩盖了缺失模式? |
| 分组或模型填补 | 能否在每个训练折内独立拟合?下游预测是否确有改善? |
| 缺失指示变量 | 缺失本身是否有预测信息?这种记录机制将来是否仍存在? |
单次填补没有恢复真实的未知值,也通常没有传播填补不确定性。若任务重点是统计推断,可能还需要多重填补等方法,并检查缺失机制与分析目标。
A3 独热编码与参考类别
对港口使用0、1、2作为一个连续预测变量,会让逻辑回归假定相同的编码增量对应相同的线性评分变化。独热编码允许各类别相对参考类别具有独立效应。
| 港口 | 是否C | 是否Q |
|---|---|---|
| S 参考类别 | 0 | 0 |
| C | 1 | 0 |
| Q | 0 | 1 |
系数解释须同时说明参考类别与其他纳入模型的变量。数值变量经过标准化后,增加1单位对应增加一个训练部分的标准差。相关特征、编码和正则化都会影响系数,不能把绝对值直接当作通用重要性。
A4 从似然到正则化
在常用的条件独立建模下,逻辑回归将每名乘客真实结果的概率相乘。最大化似然,也可转化为最小化负对数似然,即二分类对数损失。
L2正则化对系数大小加入惩罚,限制过度复杂的估计。它不保证每次都提高预测,也不能替代样本外验证。本课使用带L2正则化的逻辑回归,数值特征先标准化。进阶方案使用Female×Class2与Female×Class3,并保留Sex与Pclass主效应,不额外加入六类SexClass。
同一似然函数中的系数,不是概率的百分点变化。二元特征由0变为1时,exp(β)表示其他模型变量相同条件下的优势比。
A5 划分方式怎样匹配预测对象
| 预测目标 | 划分重点 | 例子 |
|---|---|---|
| 同一总体中的新个体 | 在样本近似可交换的条件下使用随机或分层划分 | 本课分层五折演示 |
| 全新客户或家庭 | 同一群组的记录不要跨入训练和验证 | 按客户、家庭或票号分组 |
| 未来时间 | 尊重时间顺序与预测时可用信息 | 用过去训练,向未来验证 |
Titanic存在家庭与同行组关联。本课分层五折用于展示验证流程;若目标是对全新家庭进行外推,应进一步采用匹配该目标的分组验证。对同一批数据反复改变随机划分的结果,也不能当作彼此独立的实验。
五折均值和折间标准差是一种描述;它们不能直接构成模型泛化能力的95%置信区间。反复根据同一验证结果选择特征、参数或阈值,还会产生选择偏差。
打开16单元可运行Python实验室 ↗ · 下载完整Notebook与数据包 ↓
A6 最小复现代码与数据
解压完整下载包,将训练数据、course_model.py与validation-folds.csv放在同一目录。下面的流程在每个训练折内拟合填补、编码、缩放与模型。
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler, FunctionTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from course_model import course_splits
df = pd.read_csv("train.csv")
def features(x):
x = x.copy()
x["FamilySize"] = x["SibSp"] + x["Parch"] + 1
x["HasCabin"] = x["Cabin"].notna().astype(int)
title = x["Name"].str.extract(r"([A-Za-z]+)\.", expand=False)
title = title.replace({"Mlle": "Miss", "Ms": "Miss", "Mme": "Mrs"})
x["Title"] = title.where(title.isin(["Mr", "Miss", "Mrs", "Master"]), "Rare")
return x
numeric = ["Age", "Fare", "FamilySize", "HasCabin"]
categorical = ["Pclass", "Sex", "Embarked", "Title"]
preprocess = ColumnTransformer([
("num", Pipeline([
("fill", SimpleImputer(strategy="median")),
("scale", StandardScaler())
]), numeric),
("cat", Pipeline([
("fill", SimpleImputer(strategy="most_frequent")),
("encode", OneHotEncoder(drop="first", handle_unknown="ignore"))
]), categorical)
])
model = Pipeline([
("features", FunctionTransformer(features)),
("preprocess", preprocess),
("logit", LogisticRegression(max_iter=2000))
])
cv = course_splits(df) # 按PassengerId复用已发布的折编号
scores = cross_val_score(model, df.drop(columns="Survived"), df["Survived"],
cv=cv, scoring="accuracy")
print("各折准确率:", scores)
print("平均准确率:", scores.mean())
print("折间标准差:", scores.std())课堂参考结果:平均准确率约83.05%,折间标准差约1.32个百分点。不同软件版本与数值求解条件下,结果可能有小幅差异。
验证划分由validation-folds.csv固定,并按PassengerId对齐。环境与数据版本记录参考环境、浏览器环境及校验值。重新独立生成分组划分可能产生不同分配;本课默认计算共同复用这份划分。