机器学习听起来很复杂,但它的基本过程并不神秘。我们先准备数据,让模型从数据中寻找规律,再用没有参与训练的数据检查模型是否可靠。

对于初学者来说,线性回归和逻辑回归是两个非常适合入门的模型。线性回归可以预测房价、销量等连续数值;逻辑回归可以判断邮件是不是垃圾邮件、客户会不会流失等类别问题。

本文将从线性回归开始,依次学习拟合优度、逻辑回归、模型评价、正则化和交叉验证,并使用 Python 完成简单的代码实现。


一、认识线性回归

线性回归主要用于预测连续数值。

例如:

  • 根据房屋面积预测房价;
  • 根据广告投入预测商品销量;
  • 根据学习时间预测考试成绩;
  • 根据温度预测用电量。

如果只有一个输入特征,线性回归可以写成下面的形式:

y = wx + b

其中:

  • x 表示输入的数据;
  • y 表示模型的预测结果;
  • w 表示特征的权重;
  • b 表示截距。

假设我们要根据学习时间预测考试成绩,得到下面的公式:

考试成绩 = 5 × 学习时间 + 50

如果每天学习 6 小时,模型预测的成绩就是:

5 × 6 + 50 = 80

真实数据通常不会完全分布在一条直线上。线性回归要做的,就是寻找一条尽量靠近所有样本的直线。


二、使用 Python 实现线性回归

Python 中可以使用 scikit-learn 提供的 LinearRegression 创建线性回归模型。

下面使用糖尿病数据集进行演示:

from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

# 读取数据
X, y = load_diabetes(return_X_y=True) #糖尿病数据集,sklearn 内置经典回归数据集。

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

# 创建线性回归模型
model = LinearRegression()

# 训练模型
model.fit(X_train, y_train)

# 使用测试集进行预测
y_pred = model.predict(X_test)

# 评价模型
print("均方误差:", mean_squared_error(y_test, y_pred))
print("R²:", r2_score(y_test, y_pred))

这段代码中,X 保存输入特征,y 保存需要预测的目标值。

train_test_split() 用于把数据分成训练集和测试集。其中:

  • test_size=0.2 表示拿出 20% 的数据作为测试集;
  • random_state=42 用于固定随机结果,方便重复实验。

fit() 表示训练模型,predict() 表示使用训练好的模型进行预测。

需要注意,测试集不能提前参与训练。如果模型已经见过测试数据,最后得到的成绩就不能代表它处理新数据的能力。


三、均方误差与拟合优度 R²

模型完成预测后,需要使用评价指标判断效果。

线性回归常见的评价指标包括均方误差和拟合优度 R²。

均方误差会计算预测值与真实值之间的差距,再对误差进行平方并求平均。

误差需要平方,主要有两个原因:

  1. 避免正误差和负误差互相抵消;
  2. 让较大的预测错误受到更加明显的惩罚。

均方误差越小,通常说明预测值越接近真实值。

不过,均方误差会受到数据单位的影响。例如预测房价时,误差可能是几万元;预测温度时,误差可能只有几度。因此,不同任务之间不能只看均方误差进行比较。

R² 可以理解为模型对数据变化的解释程度:

  • R² 越接近 1,模型的拟合效果通常越好;
  • R² 等于 0,模型和直接使用平均值预测差不多;
  • R² 小于 0,模型甚至不如直接预测平均值。

初学者容易认为 R² 一定在 0 到 1 之间。实际上,如果模型在测试集上的表现很差,R² 完全可能是负数。

另外,R² 很高也不代表模型一定没有问题。如果训练集得分很高,测试集得分却明显下降,模型可能记住了训练数据中的噪声,这种现象叫作过拟合。


四、逻辑回归解决分类问题

线性回归适合预测连续数值,而逻辑回归主要用于分类。

常见的二分类问题包括:

  • 一封邮件是不是垃圾邮件;
  • 一位客户会不会流失;
  • 一次交易是否存在风险;
  • 一个检测结果是阳性还是阴性。

虽然名字中带有“回归”,但逻辑回归的最终任务是分类。

线性模型可以先计算一个结果:

z = wx + b

但是,z 可能是任意数字,无法直接表示概率。

因此,逻辑回归会使用 Sigmoid 函数,把结果压缩到 0 和 1 之间:

p = 1 / (1 + e^(-z))

得到的 p 可以理解为样本属于正类的概率。

例如,模型预测某封邮件是垃圾邮件的概率为 0.8。如果分类阈值是 0.5,它就会被判断为垃圾邮件。

阈值并不一定永远是 0.5。

在疾病筛查中,为了减少漏诊,可以降低阈值,让模型找出更多可能的阳性样本;在审核任务中,如果更担心误判,也可以适当提高阈值。


五、使用 Python 实现逻辑回归

下面使用乳腺癌数据集演示逻辑回归。

需要说明的是,这段代码仅用于学习机器学习流程,不能代替真实的医疗判断。

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

# 读取分类数据
X, y = load_breast_cancer(return_X_y=True)

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

# 建立数据处理和模型训练流程
model = Pipeline([
    ("scaler", StandardScaler()),
    ("logistic", LogisticRegression(max_iter=1000))
])

# 训练模型
model.fit(X_train, y_train)

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

# 预测属于正类的概率
y_prob = model.predict_proba(X_test)[:, 1]

print("前 5 个预测类别:", y_pred[:5])
print("前 5 个正类概率:", y_prob[:5])

StandardScaler() 用于标准化数据。

不同特征的数值范围可能相差很大。例如,一个特征的范围是 0 到 1,另一个特征的范围可能是几千。如果直接训练,数值较大的特征可能产生不合理的影响。

标准化可以让不同特征处在比较接近的尺度上。

Pipeline 则把标准化和逻辑回归组合在一起。这样做可以保证训练时只使用训练集计算标准化参数,避免测试集信息提前泄漏。

代码中的 stratify=y 表示按照原来的类别比例划分数据。分类数据不平衡时,这个参数尤其有用。


六、分类模型的常用评价指标

分类模型不能只看准确率。

假设 100 个样本中有 95 个正常样本,只有 5 个异常样本。如果模型把所有样本都预测为正常,准确率仍然有 95%。

但是,这个模型一个异常样本也没有找到,显然没有实际价值。

因此,我们还需要查看混淆矩阵、精确率、召回率、F1 值

from sklearn.metrics import (
    accuracy_score,
    confusion_matrix,
    classification_report,
    roc_auc_score
)

# 准确率
print("准确率:", accuracy_score(y_test, y_pred))

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

# 精确率、召回率和 F1 值
print("分类报告:")
print(classification_report(y_test, y_pred))

# ROC-AUC
print("ROC-AUC:", roc_auc_score(y_test, y_prob))

这些指标的含义如下:

  • 准确率:全部样本中,预测正确的比例;

  • 精确率:被预测为正类的样本中,真正为正类的比例;

  • 召回率:所有真正的正类样本中,被模型成功找出的比例;

  • F1 值:综合考虑精确率和召回率;

不同指标适合不同任务。

垃圾邮件识别可能更在意不要把正常邮件误判为垃圾邮件;疾病筛查可能更重视少漏掉阳性样本。

因此,不能只追求某个指标最高,而要根据实际任务选择重点指标。


七、使用正则化减少过拟合

如果特征很多、样本较少,模型容易把训练数据中的偶然现象也当成规律。

这种情况下,模型在训练集上的表现可能很好,但面对新的测试数据时,表现却明显下降。这种现象叫作过拟合。

正则化可以理解为给模型增加一个限制:模型不仅要减少预测错误,学习到的系数也不能变得过大。

常见的正则化方式有 L1 和 L2。

1. L1 正则化

L1 正则化会惩罚模型中所有系数绝对值的总和。

它可能把部分不重要特征的系数直接变成 0,因此具有一定的特征选择作用。

from sklearn.linear_model import LogisticRegression

model_l1 = LogisticRegression(
    penalty="l1",
    solver="liblinear",
    C=1.0,
    max_iter=1000
)

使用 L1 正则化时,需要选择支持它的求解器,例如 liblinear。

2. L2 正则化

L2 正则化会惩罚所有系数平方后的总和。

它通常不会直接把系数变成 0,而是让系数整体变小,使模型更加平滑和稳定。

model_l2 = LogisticRegression(
    penalty="l2",
    C=1.0,
    max_iter=1000
)

在逻辑回归中,参数 C 用于控制正则化程度。

需要特别注意:

  • C 越小,正则化越强;
  • C 越大,正则化越弱。

这一点很容易记反。

正则化过弱,模型可能继续过拟合;正则化过强,又可能限制模型学习真正的规律,最终造成欠拟合。


八、交叉验证让结果更加可靠

只划分一次训练集和测试集,得到的成绩可能带有偶然性。

如果测试集刚好比较简单,模型成绩会偏高;如果测试集中刚好有较多难样本,成绩又可能偏低。

交叉验证可以减少这种偶然影响。

以 5 折交叉验证为例:

  1. 把训练数据分成 5 份;
  2. 每次使用其中 4 份训练;
  3. 使用剩下的 1 份进行验证;
  4. 一共训练和验证 5 次;
  5. 最后计算 5 次成绩的平均值。

可以使用 cross_val_score() 完成交叉验证:

from sklearn.model_selection import cross_val_score

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

print("每一折的准确率:", scores)
print("平均准确率:", scores.mean())

这里的 cv=5 表示使用 5 折交叉验证,scoring="accuracy" 表示使用准确率进行评价。

交叉验证应该在训练集内部完成。最终测试集仍然要保留下来,用于最后一次客观检查。

如果反复观察测试集成绩,并根据测试集结果调整模型,测试集实际上也间接参与了训练,最后的评价就会变得不可靠。


九、使用网格搜索选择参数

正则化参数 C 应该设置成多少?应该使用 L1 还是 L2?

这些参数可以通过网格搜索和交叉验证一起选择。

from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import StratifiedKFold

# 设置需要尝试的参数
params = {
    "logistic__C": [0.01, 0.1, 1, 10],
    "logistic__penalty": ["l1", "l2"],
    "logistic__solver": ["liblinear"]
}

# 创建分层交叉验证
cv = StratifiedKFold(
    n_splits=5,
    shuffle=True,
    random_state=42
)

# 创建网格搜索
search = GridSearchCV(
    model,
    param_grid=params,
    cv=cv,
    scoring="accuracy"
)

# 开始搜索
search.fit(X_train, y_train)

print("最佳参数:", search.best_params_)
print("交叉验证最佳成绩:", search.best_score_)
print("测试集成绩:", search.score(X_test, y_test))

GridSearchCV 会尝试参数表中的不同组合,并通过交叉验证比较这些组合的效果。

StratifiedKFold 表示分层交叉验证,它会尽量保持每一折中的类别比例一致。

网格搜索并不是尝试的参数越多越好。参数组合太多,会明显增加训练时间。

初学时可以先选择少量有代表性的参数,观察结果后再逐步缩小范围。


十、完整的机器学习流程

把前面的内容连起来,一个基础机器学习项目通常包含以下步骤:

明确问题
   ↓
读取并检查数据
   ↓
划分训练集与测试集
   ↓
数据预处理
   ↓
训练基础模型
   ↓
选择合适的评价指标
   ↓
使用正则化和交叉验证调参
   ↓
在测试集上进行最终评价

首先需要明确问题类型。

如果预测的是房价、销售额、温度等连续数值,通常属于回归问题;如果判断的是正常或异常、成功或失败等类别,通常属于分类问题。

然后检查数据中是否存在缺失值、异常值、特征范围差异和类别不平衡等问题。

模型训练完成后,不能只看训练集成绩,还要关注模型面对新数据时的表现。

如果训练集成绩很高,验证集成绩却比较低,可能发生了过拟合;如果训练集和验证集成绩都很低,可能出现了欠拟合,也可能是现有特征无法提供足够的信息。


十一、总结

通过本文,我们学习了机器学习中的一组基础知识:

  • 线性回归用于预测连续数值;
  • fit() 用于训练模型,predict() 用于进行预测;
  • 均方误差越小,通常说明预测误差越小;
  • R² 用于衡量模型对数据变化的解释能力;
  • 逻辑回归虽然名字中有“回归”,实际主要用于分类;
  • Sigmoid 函数可以把结果转换到 0 和 1 之间;
  • 分类模型不能只看准确率;
  • 精确率、召回率、F1 和 AUC 可以从不同角度评价模型;
  • L1 和 L2 正则化可以限制模型复杂度;
  • 参数 C 越小,正则化越强;
  • 交叉验证可以让模型评价更加稳定;
  • 网格搜索可以帮助我们选择较合适的参数。

更多推荐