二元分类和逻辑回归

二元分类

二元分类(Binary Classification)是机器学习中最常见的任务之一,目标是将数据分为两个互斥的类别。

核心概念

二元分类的输出通常是两个类别标签(如0/1、正/负、是/否)。常见的应用场景包括垃圾邮件检测、疾病诊断、客户流失预测等。关键在于构建一个模型,能够通过学习历史数据中的模式,对新样本进行准确分类。

常用算法

  1. 逻辑回归(Logistic Regression)

    通过Sigmoid函数将线性回归的输出映射到0,1区间,表示概率。

  2. 支持向量机(SVM)

    寻找一个超平面最大化两类数据间的间隔。核函数(如RBF)可用于处理非线性可分数据。

  3. 决策树与随机森林

    决策树通过递归分割数据实现分类;随机森林通过集成多棵决策树提升泛化能力。

  4. 梯度提升树(如XGBoost、LightGBM)

    通过迭代训练弱分类器(通常是决策树)并优化损失函数,逐步提升模型性能。

逻辑回归

逻辑回归是一种用于解决二分类问题的统计方法,通过拟合一个逻辑函数(如Sigmoid函数)预测事件发生的概率。尽管名称中包含"回归",但它实际是一种分类算法,适用于输出为离散值(如0或1)的场景。

逻辑回归通过线性组合输入特征并应用Sigmoid函数将结果映射到0,1区间,表示概率。Sigmoid函数公式为:

其中 ,w 为权重向量,b 为偏置项。

成本函数

为了训练回归的参数w和b,需要定义一个成本函数

在定义成本函数之前,我们需要先定义**损失函数(Loss function),**损失函数衡量的是单个样本的预测值和实际标签值之间的误差。

一般通过最大似然估计或梯度下降法优化损失函数 ,我们希望误差函数越小越好,因为这表明我们的预测很准确。

当y=1时,因为第二项为0 ,所以损失函数变为,所以我们希望尽可能小,所以就要尽可能大,即a要尽可能大,但是a是由σ函数所得,即a最大不超过1,也就是说,当实际标签y=1时,我们希望预测值a也尽可能接近1,这符合我们的要求。

如果y=0,第一项变为0,所以损失函数为,我们希望尽可能小,即尽可能大,所以这使得a尽可能小,a最小不低于0,所以,实际标签为0时,我们希望预测值a尽可能接近0。

损失函数衡量的是单个样本的表现,而成本函数衡量的是全体训练样本的表现,下面是成本函数的定义

梯度下降

为了根据训练集学习得到参数w和b,我们需要使用优化算法,梯度下降法就是优化算法中的一种。梯度下降是一种用于优化目标函数的迭代算法,通过计算目标函数的梯度并沿负梯度方向更新参数,逐步逼近最小值。广泛应用于机器学习模型的参数训练

我们要做的就是随机任意初始化w和b,然后不断用下面的式子更新w和b的值。其中,α表示学习率,它控制更新的速率。

导数的直观理解就是曲线的斜率。假设初始参数w位于最低点右侧,由于此时导数为正,根据更新公式会使w逐渐减小,即损失函数J(w)向左侧最低点移动。反之,若w初始位于左侧,导数为负会使w不断增大,推动J(w)向右移动直至达到最低点。

为了求解使J(w,b)最小化的参数w和b,需要计算J(w,b)对w和b的偏导数。为便于理解,我们先从单个训练样本的损失函数开始推导导数计算。

首先采用反向计算的方式,从后向前逐步求导,先计算损失函数L对输出a的偏导数。

在逻辑回归中,我们通常处理的是多个训练样本而非单个样本,因此需要探讨如何对m个训练样本进行梯度下降更新。

由于成本函数J(w,b)是损失函数的平均值,因此J(w,b)对w和b的偏导数也应该是单个样本梯度计算结果的均值。

从上述三个导数计算公式可以看出,理论上需要采用两层循环结构:外层循环遍历每个训练样本,内层循环对所有w参数进行累加。然而在实际的深度学习算法实现中,显式使用循环结构效率极低,特别是在处理大规模数据时。为了提高计算效率,我们需要采用向量化技术来避免不必要的显式循环。

向量化

python 复制代码
import time

a = np.random.rand(1000000)
b = np.random.rand(1000000)

past = time.time()
c = np.dot(a,b)
now = time.time()

print("向量化版本的计算时长:" + str(1000*(now-past)) + "ms")

c = 0
past = time.time()
for i in range(1000000):
    c += a[i] * b[i]
now = time.time()

print("非向量化版本的计算时长:" + str(1000*(now-past)) + "ms")
复制代码
向量化版本的计算时长:1.0833740234375ms
非向量化版本的计算时长:727.9131412506104ms

图中展示了一个计算向量内积的示例,可以看出向量化操作显著提升了运算速度。当处理大规模数据时,这种性能优势会更加突出。

所有其实求成本函数使用向量化进行计算时,内部结构应该是这样,类似于矩阵乘法。

接下来我将给出之前所用到的一些函数代码

  • sigmoid函数
python 复制代码
def sigmoid(z):
    '''
    σ函数
    '''
    a = 1 / (1 + np.exp(-z))
    
    return a
  • w和b参数的初始化
python 复制代码
def initialize_parameters(dim):
    w = np.random.randn(dim,1)
    b = 0
    
    return w,b

dim代表 w 的维度,等于输入特征的数量,需要注意的是,逻辑回归中通常也可以把 w 初始化为全 0,这里用随机初始化也能运行,但对普通逻辑回归来说不是必须的。

  • 正反向传播
python 复制代码
def propagate(w,b,X,Y):
    m = X.shape[1]
    A = sigmoid(np.dot(w.T,X)+b)
    cost = -1/m*np.sum(Y*np.log(A)+(1-Y)*np.log(1-A))
    
    dw = 1/m*np.dot(X,(A-Y).T)
    db = 1/m*np.sum(A-Y)
    
    cost = np.squeeze(cost)
    grads = {"dw":dw, "db":db}
    
    return grads,cost

X 表示输入特征矩阵,形状为:(n_x, m) ,n_X表示特征数量。m表示训练样本的个数。

Y 训练集的标签,形状为(1,m),每个标签通常是 0 或 1。

因为 X 的每一列是一个样本,所以 X.shape[1] 是样本数量。

  • 梯度下降算法
python 复制代码
def optimize(w,b,X,Y,num_iterations,learning_rate,print_cost = False):
    costs = []
    
    for i in range(num_iterations):
        grads,cost = propagate(w,b,X,Y)
        
        dw = grads["dw"]
        db = grads["db"]
        
        w = w - learning_rate*dw
        b = b - learning_rate*db
        
        if i%100 == 0:
            costs.append(cost)
        
        if print_cost and i % 100 == 0:
            print("Cost after iteration %i: %f" %(i, cost))
            
    params = {"w":w,
              "b":b}
    
    grads = {"dw":dw,
             "db":db}
    
    return params,grads,costs

每次迭代都调用 propagate(),计算当前参数下的损失和梯度。

learning_rate就是学习率 α,学习率控制每次更新的步子大小:

学习率 影响
太大 可能震荡,甚至损失变大
太小 收敛很慢
合适 损失逐渐下降

每 100 次迭代记录一次成本值,方便后面画图观察模型是否在学习。最后返回优化后的参数、最后一次梯度和训练过程中的损失记录。

python 复制代码
def predict(w,b,X):
    m = X.shape[1]
    A = sigmoid(np.dot(w.T,X)+b)
    Y_prediction = np.zeros((1,m))
    
    for i in range(m):
        Y_prediction[0,i]=A[0,i]>0.5
    
    return Y_prediction

这个函数用训练好的参数进行预测。先得到每个样本属于类别 1 的概率。

如果概率大于 0.5,预测为 True,存入数组后会变成 1.0

如果概率小于等于 0.5,预测为 False,存入数组后会变成 0.0。