一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

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

时间:2026-06-27 08:21:02 编辑:袖梨 来源:一聚教程网

学习代码记录仓库

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

Scikit-learn

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

安装:

 复制代码pip install scikit-learn

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

 复制代码import pandas as pd# 加载数据
df = pd.read_csv("train.csv")# 预处理数据
# ... 省略上一章的数据清洗部分

核心概念

训练集/验证集/测试集

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

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

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

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

 复制代码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. 类别特征编码

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

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

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

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

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

 复制代码# 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 。大多数情况下首选标准化。

 复制代码from sklearn.preprocessing import StandardScalerscaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
# 注意:测试集用 transform,不用 fit,防止数据泄露
X_test_scaled = scaler.transform(X_test)

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

 复制代码from sklearn.preprocessing import MinMaxScalerscaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

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

3. 特征选择

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

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

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

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

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

 复制代码from sklearn.ensemble import RandomForestClassifiermodel = RandomForestClassifier()
model.fit(X_train, y_train)
importance = pd.Series(model.feature_importances_, index=X.columns)
print(importance.sort_values(ascending=False))

过拟合 & 欠拟合

 复制代码欠拟合(Underfitting)          合适                    过拟合(Overfitting
模型太简单                      刚刚好                   模型太复杂
学不到规律                      泛化好                   记住了训练数据
训练/测试都差                   训练/测试都不错           训练很好,测试很差类比:                          类比:                   类比:
只学了 HTML                     学了 React               背下了面试题
啥都做不了                      能开发项目               遇到新题就不会

怎么判断?

 复制代码# 模型评估
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 → 过拟合

执行结果为:

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

说明模型过拟合。

怎么解决?

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

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

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

 复制代码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}")  

执行结果为:

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

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

交叉验证

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

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

 复制代码from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifiermodel = 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}")

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

 复制代码1折:[验] [训] [训] [训] [训] → 准确率 0.83177572折:[训] [验] [训] [训] [训] → 准确率 0.813084113折:[训] [训] [验] [训] [训] → 准确率 0.775700934折:[训] [训] [训] [验] [训] → 准确率 0.794392525折:[训] [训] [训] [训] [验] → 准确率 0.74528302
平均:0.792 ± 0.030

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

 复制代码from sklearn.model_selection import cross_val_score, StratifiedKFoldskf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X_train, y_train, cv=skf)

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

模型评估指标

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

分类指标

 复制代码from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
from sklearn.metrics import classification_report, confusion_matrixy_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

 复制代码              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.87F1 0.85
类别 1(存活):精确率 0.77,召回率 0.70F1 0.73
整体准确率:0.80

查看混淆矩阵输出confusion_matrix

 复制代码[[96 14]
 [21 48]]// 数据说明
             预测遇难    预测存活
实际遇难       96         1414 人被误判为存活
实际存活       21         4821 人被漏判为遇难

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

回归指标

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

 复制代码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)

经典算法

线性回归

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

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

 复制代码房价
  ↑
  │        ●
  │      ●   ●
  │    ●   ●
  │  ●   ●
  │●   ●
  └────────────→ 面积
        y = wx + b
 复制代码from sklearn.linear_model import LinearRegressionmodel = LinearRegression()
model.fit(X_train_scaled, y_train)# 预测
y_pred = model.predict(X_test_scaled)# 查看系数(每个特征的权重)
print(f"系数:{model.coef_}")
print(f"截距:{model.intercept_}")

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

逻辑回归

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

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

 复制代码from sklearn.linear_model import LogisticRegressionmodel = 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

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

决策树

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

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

 复制代码                    [票价 > 50?]
                    /           
                是               否
                /                 
        [年龄 > 30?]          [性别 = 女?]
        /                    /          
      存活       遇难       存活         遇难
 复制代码from sklearn.tree import DecisionTreeClassifier
import matplotlib.pyplot as plt
from sklearn import treemodel = 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节点结果

随机森林

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

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

 复制代码决策树1 → 存活
决策树2 → 遇难
决策树3 → 存活
决策树4 → 存活
决策树5 → 遇难
─────────────────
最终结果 → 存活(3:2
 复制代码from sklearn.ensemble import RandomForestClassifiermodel = 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 个中心点,让每个点到最近中心点的距离最小

 复制代码步骤1:随机放 K 个中心点
步骤2:每个点归到最近的中心点
步骤3:重新计算中心点位置
步骤4:重复 2-3 直到不再变化
 复制代码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. 数据加载与清洗

 复制代码import pandas as pddf = 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. 特征工程:划分数据集/特征缩放

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

 复制代码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. 模型训练与评估

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

 复制代码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. 交叉验证

 复制代码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. 详细评估

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

 复制代码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=["遇难", "存活"]))

结果输出:

 复制代码=== 混淆矩阵 ===
[[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. 特征重要性

 复制代码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. 预测测试集

 复制代码# 加载测试数据集
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 可以自动搜索参数,找到最佳参数组合。

 复制代码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"
过拟合训练集表现远好于测试集交叉验证、限制模型复杂度

热门栏目