二元分类
二元分类(Binary Classification)是机器学习中最常见的任务之一,目标是将数据分为两个互斥的类别。
核心概念
二元分类的输出通常是两个类别标签(如0/1、正/负、是/否)。常见的应用场景包括垃圾邮件检测、疾病诊断、客户流失预测等。关键在于构建一个模型,能够通过学习历史数据中的模式,对新样本进行准确分类。
常用算法
-
逻辑回归(Logistic Regression)
通过Sigmoid函数将线性回归的输出映射到0,1区间,表示概率。
-
支持向量机(SVM)
寻找一个超平面最大化两类数据间的间隔。核函数(如RBF)可用于处理非线性可分数据。
-
决策树与随机森林
决策树通过递归分割数据实现分类;随机森林通过集成多棵决策树提升泛化能力。
-
梯度提升树(如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。