AI工程师第三课 - 机器学习基础

学习代码记录仓库

使用Jupyter lab测试执行代码验证。

Scikit-learn

Scikit-learn(sklearn)是一个开源机器学习库,支持监督学习和非监督学习。提供了用于模型拟合、数据预处理、模型选择、模型评估以及许多其他实用工具。

安装:

sh 复制代码
pip install scikit-learn

继续使用上一章里的数据,先加载好数据:

py 复制代码
import pandas as pd

# 加载数据
df = pd.read_csv("train.csv")

# 预处理数据
# ... 省略上一章的数据清洗部分

核心概念

训练集/验证集/测试集

把数据分成三份,各司其职:

js 复制代码
数据集(100%)
├── 训练集(60-70%)  → 用来训练模型(学习规律)
├── 验证集(15-20%)  → 用来调参选模型(模拟考试)
└── 测试集(15-20%)  → 最终评估(高考,只用一次)

为什么要分? 就像前端不能用单元测试的数据来验收功能,模型也不能用训练数据来评估效果,否则会"作弊"。

使用train_test_split对数据进行划分,按照上述比例分割:

py 复制代码
from sklearn.model_selection import train_test_split

# 特征 X 比如年龄、性别等,除了标签之外的
X = df.drop(columns=["Survived"])
# 标签 Y 就是是否存活
y = df["Survived"]

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42,stratify=y
)

# 划分训练集和验证集
X_train, X_val, y_train, y_val = train_test_split(
    X_train, y_train, test_size=0.25, random_state=42,stratify=y_train
)
# 最终比例:60% 训练,20% 验证,20% 测试
  • random_state 是随机数种子,保证每次运行结果一致。
  • stratify 是对类别进行分层,保证训练集和测试集的类别分布一致。

特征工程

特征工程 = 把原始数据加工成模型更容易理解的形式。

1. 类别特征编码

模型只能处理数字,需要把文字转成数字。这属于数据清洗与处理,可以放到前面去。

py 复制代码
# 先删除无意义的列,比如 姓名、ID 等
df = df.drop(columns=["Name", "Ticket", "PassengerId"])

标签编码:直接转成数字,适合有序类别或二值特征:

py 复制代码
# Sex 只有两个值,用 map 或 LabelEncoder 都行
df["Sex"] = df["Sex"].map({"male": 0, "female": 1})

独热编码:每个类别一列,适合无序类别:

py 复制代码
# Embarked 有 S/C/Q 三个值,用独热编码避免模型误以为有大小关系
df = pd.get_dummies(df, columns=["Embarked"], drop_first=True)
# drop_first=True 丢掉第一列,避免多重共线性(知道两列就能推出第三列)

2. 特征缩放(归一化/标准化)

不同特征的量纲差异大(如年龄 0-100,票价 0-500),需要统一到同一尺度:

StandardScaler 是标准化,把数据缩放到均值为 0,标准差为 1 。大多数情况下首选标准化。

py 复制代码
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
# 注意:测试集用 transform,不用 fit,防止数据泄露
X_test_scaled = scaler.transform(X_test)

MinMaxScaler 是归一化,把数据缩放到 0-1 之间。适合固定范围的数据(如图像像素)。

py 复制代码
from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

对于存在极端值的数据(如票价、收入),标准化受极端值影响大,可以用 RobustScaler,它用中位数和四分位距缩放,更稳健。

3. 特征选择

不是所有特征都有用,选择有用的特征:

AI工程师第二课 - 数据处理中有输出过一张相关性热力图,根据相关性来选择有用的特征:

py 复制代码
# 相关性计算
corr = df.corr()
print(corr["Survived"].sort_values(ascending=False))

但是单凭相关性判断是有缺陷的, 它只能发现线性相关的特征。比如性别Sex是非线性关系,但相关性却很高。

使用模型评估特征重要性,这是sklearn提供的方法,它可以捕捉到非线性关系、更准确,但是要先训练模型。

py 复制代码
from sklearn.ensemble import RandomForestClassifier

model = RandomForestClassifier()
model.fit(X_train, y_train)
importance = pd.Series(model.feature_importances_, index=X.columns)
print(importance.sort_values(ascending=False))

不必太纠结具体选择哪些特征,实际项目中对模型效果影响更大的是:数据量 > 数据清洗质量 > 模型调参 > 特征选择。先把前面做好,再回头做特征选择优化。

过拟合 & 欠拟合

js 复制代码
欠拟合(Underfitting)          合适                    过拟合(Overfitting)
模型太简单                      刚刚好                   模型太复杂
学不到规律                      泛化好                   记住了训练数据
训练/测试都差                   训练/测试都不错           训练很好,测试很差

类比:                          类比:                   类比:
只学了 HTML                     学了 React               背下了面试题
啥都做不了                      能开发项目               遇到新题就不会

怎么判断?

py 复制代码
# 模型评估
train_score = model.score(X_train_scaled, y_train)
test_score = model.score(X_test_scaled, y_test)

print(f"训练集准确率:{train_score:.3f}")
print(f"测试集准确率:{test_score:.3f}")

# 情况判断:
# 训练 0.6,测试 0.6 → 欠拟合
# 训练 0.9,测试 0.9 → 刚好
# 训练 0.99,测试 0.7 → 过拟合

执行结果为:

js 复制代码
训练集准确率:0.987
测试集准确率:0.799

说明模型过拟合。

怎么解决?

问题 解决方案
欠拟合 增加特征、用更复杂的模型、减少正则化
过拟合 增加数据、减少特征、用更简单的模型、正则化、交叉验证

什么是正则化? 限制模型参数不要太大,防止过度拟合训练数据。C 越小,正则化越强,模型越"保守"。

通过使用sklearnLogisticRegression 模型,可以设置正则化参数 C 来控制正则化强度。

py 复制代码
from sklearn.linear_model import LogisticRegression

# 降低 C 值,加强正则化
model = LogisticRegression(C=0.1, max_iter=1000)
model.fit(X_train_scaled, y_train)
print(f"训练:{model.score(X_train_scaled, y_train):.3f}") 
print(f"测试:{model.score(X_test_scaled, y_test):.3f}")  

执行结果为:

js 复制代码
训练:0.800
测试:0.793

可以看到正则化生效了,他们之间的得分差距小了。但是准确率却变差了,从0.987降到了0.800,正则化把模型压的太简单了。

交叉验证

为了弥补固定分割的数据划分问题,使用交叉验证。它可以让每一份数据都参与训练和验证,使得结果更稳定。

把训练数据分成 K 份,轮流用其中 1 份做验证,K-1 份做训练,取平均分:

py 复制代码
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier

model = RandomForestClassifier()
scores = cross_val_score(model, X_train, y_train, cv=5, scoring="accuracy")

print(f"5折交叉验证:{scores}")
print(f"平均准确率:{scores.mean():.3f} ± {scores.std():.3f}")

由于每次执行分层的数据不同,导致执行结果有差异,这是某次执行的输出:

js 复制代码
第1折:[验] [训] [训] [训] [训] → 准确率 0.8317757
第2折:[训] [验] [训] [训] [训] → 准确率 0.81308411
第3折:[训] [训] [验] [训] [训] → 准确率 0.77570093
第4折:[训] [训] [训] [验] [训] → 准确率 0.79439252
第5折:[训] [训] [训] [训] [验] → 准确率 0.74528302
平均:0.792 ± 0.030

标准差 0.030 偏大,说明模型不够稳定。可以通过增加数据量、使用分层交叉验证(StratifiedKFold)来改善。

py 复制代码
from sklearn.model_selection import cross_val_score, StratifiedKFold

skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X_train, y_train, cv=skf)

执行结果可能略有差异,这是因为数据量太少了,只有几百条,效果并不是很明显。

模型评估指标

模型评估指标,用于评价模型性能。

分类指标

py 复制代码
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
from sklearn.metrics import classification_report, confusion_matrix

y_pred = model.predict(X_test)

# 基础指标
print(f"准确率(Accuracy):{accuracy_score(y_test, y_pred):.3f}")
print(f"精确率(Precision):{precision_score(y_test, y_pred):.3f}")
print(f"召回率(Recall):{recall_score(y_test, y_pred):.3f}")
print(f"F1 分数:{f1_score(y_test, y_pred):.3f}")

# 完整报告
print(classification_report(y_test, y_pred))

# 混淆矩阵
print(confusion_matrix(y_test, y_pred))

指标解读(以前端视角):

指标 含义 前端类比
准确率 整体预测对了多少 测试用例通过率
精确率 预测为正的里面有多少是真的 表单验证的准确性(报错了确实是错的)
召回率 真实为正的有多少被找到了 Bug 检测的覆盖率(所有 Bug 都找到了吗)
F1 精确率和召回率的平衡 综合评分

查看完整报告输出classification_report

js 复制代码
              precision    recall  f1-score   support

           0       0.82      0.87      0.85       110
           1       0.77      0.70      0.73        69

    accuracy                           0.80       179
   macro avg       0.80      0.78      0.79       179
weighted avg       0.80      0.80      0.80       179

// 数据说明
// support 为样本数量
类别 0(遇难):精确率 0.82,召回率 0.87,F1 0.85
类别 1(存活):精确率 0.77,召回率 0.70,F1 0.73
整体准确率:0.80

查看混淆矩阵输出confusion_matrix

js 复制代码
[[96 14]
 [21 48]]

// 数据说明
             预测遇难    预测存活
实际遇难       96         14      ← 14 人被误判为存活
实际存活       21         48      ← 21 人被漏判为遇难

模型更擅长预测"遇难"(F1=0.85),对"存活"容易漏判(召回率=0.70)。

回归指标

用途: 预测连续数值(价格、销量、温度)

py 复制代码
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score

# MSE:均方误差(对大误差惩罚更重)
mse = mean_squared_error(y_test, y_pred)

# MAE:平均绝对误差(更直观)
mae = mean_absolute_error(y_test, y_pred)

# R²:决定系数(越接近1越好)
r2 = r2_score(y_test, y_pred)

经典算法

线性回归

用途: 预测连续数值(房价、温度、销量)

原理: 找一条直线(或平面)拟合数据

js 复制代码
房价
  ↑
  │        ●
  │      ●   ●
  │    ●   ●
  │  ●   ●
  │●   ●
  └────────────→ 面积
        y = wx + b
py 复制代码
from sklearn.linear_model import LinearRegression

model = LinearRegression()
model.fit(X_train_scaled, y_train)

# 预测
y_pred = model.predict(X_test_scaled)

# 查看系数(每个特征的权重)
print(f"系数:{model.coef_}")
print(f"截距:{model.intercept_}")

系数: 每个特征对预测结果的影响, 预测结果 += 系数 * 特征值。 截距: 预测结果 += 截距。

逻辑回归

用途: 二分类问题(是/否、存活/死亡、垃圾邮件/正常)

名字有"回归",但实际是分类算法。

py 复制代码
from sklearn.linear_model import LogisticRegression

model = LogisticRegression(max_iter=1000)
model.fit(X_train_scaled, y_train)

# 预测类别
y_pred = model.predict(X_test_scaled)

# 预测概率
y_prob = model.predict_proba(X_test_scaled)
# [[0.8, 0.2],   → 80% 概率是类别 0
#  [0.3, 0.7]]   → 70% 概率是类别 1

类别 直接预测结果。 概率 预测结果为正的概率。 预测结果为正的概率越大,越可能为正。你可以自己判断。

决策树

用途: 分类和回归,可解释性强

原理: 像问问题一样做决策

css 复制代码
                    [票价 > 50?]
                    /           \
                是               否
                /                 \
        [年龄 > 30?]          [性别 = 女?]
        /        \            /          \
      存活       遇难       存活         遇难
py 复制代码
from sklearn.tree import DecisionTreeClassifier
import matplotlib.pyplot as plt
from sklearn import tree

model = DecisionTreeClassifier(max_depth=3)  # 限制深度防止过拟合
model.fit(X_train, y_train)  # 树模型不需要缩放

# 可视化决策树
fig, ax = plt.subplots(figsize=(15, 8))
tree.plot_tree(model, feature_names=X.columns, class_names=["遇难", "存活"], filled=True)
plt.show()

决策树节点属性说明:

  • 决策条件,比如性别、年龄等。
  • gini基尼指数,越小越纯
  • samples样本数量
  • value节点结果数量
  • class节点结果

随机森林

用途: 多棵决策树投票,效果更稳定

原理: 训练多棵不同的决策树,取多数投票结果

js 复制代码
决策树1 → 存活
决策树2 → 遇难
决策树3 → 存活
决策树4 → 存活
决策树5 → 遇难
─────────────────
最终结果 → 存活(3:2)
py 复制代码
from sklearn.ensemble import RandomForestClassifier

model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)  # 树模型不需要缩放

# 特征重要性
importance = pd.Series(model.feature_importances_, index=X.columns)
importance.sort_values(ascending=False).plot(kind="bar")
plt.title("特征重要性")
plt.show()

特征重要性: 每个特征对预测结果的影响,越大越重要。

K-Means 聚类

用途: 无监督学习,自动把数据分成 K 组。使用场景:聚类、图像处理、广告推荐、推荐引擎、 anomaly detection(异常检测)

原理: 不断调整 K 个中心点,让每个点到最近中心点的距离最小

js 复制代码
步骤1:随机放 K 个中心点
步骤2:每个点归到最近的中心点
步骤3:重新计算中心点位置
步骤4:重复 2-3 直到不再变化
py 复制代码
from sklearn.cluster import KMeans

# 全量缩放
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 创建模型
model = KMeans(n_clusters=3, random_state=42)
model.fit(X_scaled)

# 获取聚类结果
labels = model.labels_        # 每个点属于哪个簇
centers = model.cluster_centers_  # 中心点坐标

# 可视化
plt.scatter(X_scaled[:, 0], X_scaled[:, 1], c=labels, cmap="viridis")
plt.scatter(centers[:, 0], centers[:, 1], c="red", marker="x", s=200)
plt.title("K-Means 聚类结果")
plt.show()

K-Means 聚类结果: 每个点属于哪个簇,以及中心点坐标。对于如何分组,需要不断尝试最优的k。只能发现球形聚类,不能发现长条形、环形分区。受异常数据影响较大。

实战:Titanic 存活预测

在上一章【AI工程师第二课 - 数据处理】中对于数据的清理分析做了详细的阐述,这里重点完成kaggle的任务,通过真实的Titanic 存活数据训练模型,然后得出测试数据的存活预测。

前面的章节已经部分包含了所需要的代码逻辑,这里按照流程给出全部的代码。

1. 数据加载与清洗

py 复制代码
import pandas as pd

df = pd.read_csv("train.csv")

# 特征选择
features = ["Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "Embarked"]
df = df[features + ["Survived"]]

# 缺失值处理
df["Age"] = df["Age"].fillna(df["Age"].median())
df["Embarked"] = df["Embarked"].fillna(df["Embarked"].mode()[0])

# 类别编码
df["Sex"] = df["Sex"].map({"female": 0, "male": 1})
df = pd.get_dummies(df, columns=["Embarked"], drop_first=True)

2. 特征工程:划分数据集/特征缩放

这里只划分了训练集和测试集,没有划分验证集。因为总数据量少,后面使用了交叉验证代替了固定划分,让更多的数据参与训练,提高水利用率。

py 复制代码
X = df.drop("Survived", axis=1)
y = df["Survived"]

# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# 特征缩放
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

3. 模型训练与评估

是否存活就是典型的二分类问题,使用逻辑回归和随机森林模型训练和评估。

py 复制代码
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier

# 模型1:逻辑回归
lr = LogisticRegression(max_iter=1000)
lr.fit(X_train_scaled, y_train)
lr_score = lr.score(X_test_scaled, y_test)
print(f"\n逻辑回归准确率:{lr_score:.3f}")

# 模型2:随机森林
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)  # 随机森林不需要缩放
rf_score = rf.score(X_test, y_test)
print(f"随机森林准确率:{rf_score:.3f}")

没想到两个测试集的准确率都是0.81,说明模型效果都不错。

4. 交叉验证

py 复制代码
from sklearn.model_selection import cross_val_score

# 交叉验证
lr_cv = cross_val_score(lr, X_train_scaled, y_train, cv=5)
rf_cv = cross_val_score(rf, X_train, y_train, cv=5)

print(f"\n逻辑回归 5折交叉验证:{lr_cv.mean():.3f} ± {lr_cv.std():.3f}")
print(f"随机森林 5折交叉验证:{rf_cv.mean():.3f} ± {rf_cv.std():.3f}")

交叉验证的结果随机森林的准确率更高一点,而且误差更小,说明随机森林效果更好。

5. 详细评估

我们已经知道了随机森林的预测效果更好,下面详细评估。

py 复制代码
best_model = rf 
y_pred = best_model.predict(X_test)

print("\n=== 混淆矩阵 ===")
print(confusion_matrix(y_test, y_pred))

print("\n=== 分类报告 ===")
print(classification_report(y_test, y_pred, target_names=["遇难", "存活"]))

结果输出:

js 复制代码
=== 混淆矩阵 ===
[[90 15]        ← 105 个遇难:90 对,15 错
 [19 55]]       ← 74 个存活:55 对,19 错

=== 分类报告 ===
              precision    recall  f1-score   support

        遇难       0.83      0.86      0.84       105
        存活       0.79      0.74      0.76        74

    accuracy                           0.81       179
   macro avg       0.81      0.80      0.80       179
weighted avg       0.81      0.81      0.81       179

6. 特征重要性

py 复制代码
importance = pd.Series(best_model.feature_importances_, index=X.columns)
importance.sort_values(ascending=True).plot(kind="barh")
plt.title("特征重要性")
plt.xlabel("重要性")
plt.tight_layout()
plt.show()

7. 预测测试集

py 复制代码
# 加载测试数据集
test_df = pd.read_csv("test.csv")
passenger_ids = test_df["PassengerId"]

# 用同样的特征
test_df = test_df[features]

# 清洗数据
test_df["Age"] = test_df["Age"].fillna(test_df["Age"].median())
test_df["Fare"] = test_df["Fare"].fillna(test_df["Fare"].median())
test_df["Embarked"] = test_df["Embarked"].fillna(test_df["Embarked"].mode()[0])

# 类别编码
test_df["Sex"] = test_df["Sex"].map({"female": 0, "male": 1})
test_df = pd.get_dummies(test_df, columns=["Embarked"], drop_first=True)


# 预测
predictions = best_model.predict(test_df)

# 生成提交文件
submission = pd.DataFrame({
    "PassengerId": passenger_ids,
    "Survived": predictions
})
submission.to_csv("submission.csv", index=False)
print("提交文件已生成!")

得到测试集的预测结果,我们可以提交结果到kaggle进行评价。

可以看到结果分数为0.75, 排行榜排名11330。还有比我们低的,🤣 。

8. 优化

  1. 通过构造新特征来提升模型效果。比如:提取称谓(女士可能存活率高)、家庭人数。
  2. 换更好的模型。比如:KNN、SVM、XGBoost、LightGBM。
  3. 调参。
  4. 集成模型。多个模型投票,取最终结果。

我们先通过调参来试试能提升多少。GridSearchCV 可以自动搜索参数,找到最佳参数组合。

py 复制代码
from sklearn.model_selection import GridSearchCV

# 定义参数网格
params = {
    "n_estimators": [50, 100, 200], # 树数量
    "max_depth": [3, 5, 10, None], # 树深度
    "min_samples_split": [2, 5, 10], # 节点分裂所需的最小样本数
    "min_samples_leaf": [1, 2, 4], # 节点叶子所需最小样本数
}

# 网格搜索
grid = GridSearchCV(
    RandomForestClassifier(random_state=42),
    params,
    cv=5, # 交叉验证次数
    scoring="accuracy", # 评估标准
    n_jobs=-1  # 用所有 CPU 核心,加快速度
)

# 3 x 3 x 4 x 3 = 108 个参数组合 + 5 个交叉验证,共 108 x 5 = 540 个模型训练
grid.fit(X_train, y_train)

print(f"最优参数:{grid.best_params_}")
print(f"最优分数:{grid.best_score_:.3f}")

best_rf = grid.best_estimator_
predictions = best_rf.predict(test_df)

# 生成提交文件
submission = pd.DataFrame({
    "PassengerId": passenger_ids,
    "Survived": predictions
})
submission.to_csv("submission_optimized.csv", index=False)
print("提交文件已生成!")

再次提交查看结果排名,可以看到分数从0.75提升到0.78,虽然看似提升不大, 但还是能提升。而且排名飙升到了2513,可以想像0.01分之要甩掉多少人。

算法选择指南

任务类型 数据特点 推荐算法 理由
二分类 数据量小 逻辑回归 简单、可解释
二分类 数据量大 随机森林 效果好、不易过拟合
回归 线性关系 线性回归 简单、可解释
回归 非线性关系 随机森林 能捕捉复杂关系
聚类 不知道分几组 K-Means 最常用

一句话:先跑逻辑回归做 baseline,再用随机森林提升效果。

常见坑

说明 解决
数据泄露 测试集信息混入训练集 train_test_split 后再做特征工程
特征缩放 树模型不需要,线性模型需要 随机森林不缩放,逻辑回归要缩放
类别不平衡 正负样本比例悬殊 class_weight="balanced"
过拟合 训练集表现远好于测试集 交叉验证、限制模型复杂度
相关推荐
顾林海6 小时前
Agent入门阶段-编程基础-Python:流程控制
python·agent·ai编程
呱呱复呱呱9 小时前
Django CBV 源码解读:一个请求是怎么找到你的 get() 方法的
python·django
曲幽13 小时前
刚部署的 LibreTranslate 频频翻车?我掏出了 20 年前的 StarDict 词典,用 FastAPI 搭了个本地词典翻译 API
python·fastapi·web·translate·goldendict·libretranslate·stardict·pystardict
荣码14 小时前
用Streamlit给AI应用套个界面,10行代码出Web页面
java·python
兵慌码乱1 天前
基于Python+PyQt5+SQLite的药房管理系统实现:事务一致性与界面解耦全流程解析
python·sqlite·信号与槽·pyqt5·数据库设计·桌面应用开发·事务处理
金銀銅鐵1 天前
[Python] 体验用欧几里得算法计算最大公约数的过程
python·数学
FreakStudio1 天前
W55MH32L-EVB 上手测评:硬件 TCP/IP 加持的以太网单片机,MicroPython 零门槛开发
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy·电子计算机
用户0332126663671 天前
使用 Python 从零创建 Word 文档
python
Csvn1 天前
Python 两大经典坑点 —— 可变默认参数 & 闭包延迟绑定
后端·python