目录
1.前言
逻辑回归是一种广泛应用于分类问题的机器学习算法。尽管其名称中包含"回归"一词,但实际上逻辑回归是一种分类算法,主要用于解决二分类问题。
2.逻辑回归的原理
2.1什么是逻辑回归?
逻辑回归是一种基于概率的分类算法,用于预测一个事物属于某一类别的概率。它的输出是介于0和1之间的概率值,通常用于解决二分类问题。
2.2基本原理
回归是如何变成分类的呢?
在线性回归问题中,得到了具体的回归值,如果此时任务要做一个二分类该怎么办呢?
如果可以将连续的数值转换成对应的区间,这样就可以完成分类任务了,逻辑回归中借助sigmoid函数完成了数值映射 把线性回归的结果(-∞,+∞)
映射到(0,1)
,通过概率值比较来完成分类任务
2.2.1Sigmoid函数
函数表达式:
sigmoid函数是⼀个⾃然的选择,因为它是⼀个平滑的、可微的阈值单元近似。当我们想要将输出视作⼆元分类问题的概率下面为sigmoid函数的图像表示,当输入接近0时,sigmoid更接近线形变换。
import torch
from d2l import torch as d2l
%matplotlib inline
x=torch.arange(-8.0,8.0,0.1,requires_grad=True)
sigmoid=torch.nn.Sigmoid()
y=sigmoid(x)
d2l.plot(x.detach(),y.detach(),'x','sigmoid(x)',figsize=(5,2.5))
sigmoid函数的导数为下面的公式:
sigmoid函数的导数图像如下所示。当输入值为0时,sigmoid函数的导数达到最大值0.25;而输入在任一方向上越远离0点时,导数越接近0。
#清除以前的梯度
#retain_graph如果设置为False,计算图中的中间变量在计算完后就会被释放。
y.backward(torch.ones_like(x),retain_graph=True)
d2l.plot(x.detach(),x.grad,'x','grad of sigmoid')
所以这个函数很好描述我们日常生活中,平时碰到的概率。我们可能认为明天下雨的概率为0.3,天晴的概率为0.7。抛一枚硬币正面的概率为0.5,反面的概率也为0.5。概率也是介于0到1之间的一些数,很自然我们可以把sigmod函数的值域和概率联系起来。
2.2.2训练过程
逻辑回归的训练过程主要涉及到最大似然估计,通过最大化观测数据的似然来确定模型参数。通常采用梯度下降等优化算法来最小化损失函数,使模型更好地拟合训练数据。
2.2.2.1最大似然估计
最大似然估计是在总体的分布类型己知的前提下使用的一种参数估计法。
在自然生活中,观察到的某种现象产生的原因可能有很多种.但要判断出到底是哪种原因时,
人们往往选择可能性最大的一种或者说是概率最大的。
这就是最大似然估计的思想:所有集合中的小事件发生概率连乘再取最大值。
似然函数:
给定一个训练数据集,假设有 m 个样本,每个样本的标签为 y (i),对应的特征为 X (i)。我们可以将这些样本的出现概率写成一个似然函数 L(β),
即在给定参数 β 的情况下,观测到这些样本的概率:
为了方便计算,通常采用对数似然函数
最大似然估计:
最大似然估计的目标是找到一组参数 β,使得似然函数 L(β) 或对数似然函数
logL(β) 最大。这等价于找到使得观测到数据的概率最大的模型参数。
在实际计算中,通常使用梯度下降等优化算法来最大化对数似然函数。优化算法的目标是调整模型参数,使得对数似然函数的梯度为零,即:
解这个方程,找到最大似然估计的参数。
2.2.2.2正则化
为防止过拟合,逻辑回归通常会使用正则化项,将正则化项添加到对数似然函数中。这可以通过在目标函数中添加一个正则化项,如 :
其中,λ 是正则化参数。
最大似然估计的优化目标变为:
最终,通过梯度下降等方法,我们可以找到使得似然函数最大化的参数值,从而得到逻辑回归模型的最佳参数。
最大似然估计在逻辑回归以及其他统计和机器学习问题中都是一种常用的估计方法,它充分利用了观测到的数据信息,寻找使得数据出现的概率最大的模型参数。
2.2.2.3梯度下降
1.什么是梯度下降
梯度下降过程类似一个人下山过程:
step1:明确自己现在所处的位置;
step2:找到现在所处位置下降最快的方向;
step3:沿着第二步找到的方向走一个步长,到达新的位置,且新位置低于刚才的位置;
step4:判断是否下山,如果还没有到最低点继续步骤—,如果已经到最低点,则停止。
从上面的分析知,用梯度下降法求解参数最重要的是找到下降最快的方向和确定要走的步长。
那么什么是函数下降最快的方向?
一元函数的导数的几何意义是某点切线的斜率。除此之外导数还可以表示函数在该点的变化率,导数越大,表示函数在该点的变化越大。
从图中可以发现p2点的斜率大于p1点的斜率,即p2点的导数大于p1点的导数。对于多维向量,它的导数叫做梯度(偏导数),当求某个变量的导数时,把其它变量视为常量,对整个函数求导,也就是分别对于它的每个分量求导数,即对于函数的某个特定点,它的梯度就表示从该点出发,函数值变化最为迅猛的方向。至此梯度下降法求解参数的方向已经找到,那就是函数的梯度方向。
2.梯度下降实现步骤
逻辑回归中的梯度下降是一种常用的优化算法,用于最小化模型的损失函数。梯度下降通过沿着损失函数的负梯度方向来更新模型参数,以逐步接近或达到损失函数的最小值。以下是逻辑回归中梯度下降方法的基本步骤:
1. 损失函数
逻辑回归的损失函数通常选择为对数似然损失函数。对于二分类问题,其数学表达式为:
其中,m 是样本数量,y(i) 是第 i 个样本的真实标签,y^(i) 是模型对第 i 个样本的预测值。
2.梯度下降更新规则
梯度下降的目标是通过迭代调整参数 β,使得损失函数 J(β) 达到最小值。更新规则如下:
其中,α 是学习率,控制更新步长。 ∂βj∂J(β) 是损失函数对参数 βj 的偏导数,也称为梯度。
3. 计算梯度
对于对数似然损失函数,损失函数对参数的偏导数的计算如下:
其中,是第 i 个样本的第 j 个特征。
4.梯度下降迭代
重复执行梯度下降更新规则,直到损失函数收敛或达到预定的迭代次数。迭代过程中,参数 β 不断调整,使得损失函数逐渐降低,最终收敛到一个局部最小值。
5. 学习率的选择
学习率 α 的选择对梯度下降的性能至关重要。如果学习率过大,可能导致梯度下降无法收敛,甚至发散;如果学习率过小,收敛速度会很慢。通常需要尝试不同的学习率,并观察损失函数的变化情况来选择合适的学习率。
6. 批量梯度下降、随机梯度下降和小批量梯度下降
梯度下降算法有几种变体,包括批量梯度下降(Batch Gradient Descent)、随机梯度下降(Stochastic Gradient Descent)和小批量梯度下降(Mini-batch Gradient Descent)。它们的区别在于每次迭代更新参数时所使用的样本数量。
- 批量梯度下降: 使用整个训练数据集进行参数更新。
- 随机梯度下降: 每次迭代只使用一个样本进行参数更新。
- 小批量梯度下降: 每次迭代使用一小部分样本进行参数更新。
不同的梯度下降变体适用于不同规模的数据集,选择取决于实际问题和计算资源。
梯度下降是逻辑回归模型训练中常用的优化算法之一,通过不断调整参数,使模型逼近最优解。合适的学习率和迭代次数的选择对梯度下降的性能至关重要。
3.逻辑回归实现鸢尾花二分类
使用逻辑回归模型对鸢尾花数据集进行二分类,并通过可视化决策边界和概率曲线来理解模型的预测效果。
3.1实验步骤
1.加载数据集
from sklearn import datasets
iris = datasets.load_iris()
list(iris.keys())from sklearn import datasets
iris = datasets.load_iris()
list(iris.keys())
2.查看数据集的键和描述
X = iris['data'][:,3:]
y = (iris['target'] == 2).astype(np.int)
3.提取特征和标签
X = iris['data'][:,3:]
y = (iris['target'] == 2).astype(np.int)
里选择了数据集中的第4个特征(花瓣宽度)作为输入特征 X
,并将目标变量 y
设置为对应的类别是否为Iris-Virginica。
4.创建逻辑回归模型进行训练
# 定义逻辑回归模型
class LogisticRegression:
def __init__(self, learning_rate=0.01, n_iterations=1000):
self.learning_rate = learning_rate
self.n_iterations = n_iterations
self.weights = None
self.bias = None
#激活函数
def sigmoid(self, z):
return 1 / (1 + np.exp(-z))
def fit(self, X, y):
m, n = X.shape
self.weights = np.zeros(n)
self.bias = 0
for _ in range(self.n_iterations):
# 计算模型预测值
y_pred = self.sigmoid(np.dot(X, self.weights) + self.bias)
# 计算梯度
dw = (1/m) * np.dot(X.T, (y_pred - y))
db = (1/m) * np.sum(y_pred - y)
# 更新权重和偏置
self.weights -= self.learning_rate * dw
self.bias -= self.learning_rate * db
def predict(self, X):
y_pred = self.sigmoid(np.dot(X, self.weights) + self.bias)
return (y_pred >= 0.5).astype(int)
# 使用手动实现的逻辑回归模型
log_res = LogisticRegressionManual()
log_res.fit(X, y)
创建一个逻辑回归模型,并使用 fit
方法进行训练。
5.生成新的数据并进行预测:
X_new = np.linspace(0,3,1000).reshape(-1,1)
y_proba = log_res.predict_proba(X_new)
6.绘制决策边界和概率曲线:
plt.figure(figsize=(12, 5))
设置图的大小。
decision_boundary = X_new[y_proba[:, 1] >= 0.5][0]
通过判定概率是否大于等于0.5来确定决策边界。
plt.plot(X_new, y_proba[:, 1], 'g-', label='Iris-Virginica')
plt.plot(X_new, y_proba[:, 0], 'b--', label='Not Iris-Virginica')
plt.arrow(decision_boundary, 0.08, -0.3, 0, head_width=0.05, head_length=0.1, fc='b', ec='b')
plt.arrow(decision_boundary, 0.92, 0.3, 0, head_width=0.05, head_length=0.1, fc='g', ec='g')
print(help(plt.arrow))
绘制图像曲线。
运行效果:
7.绘制另一个二维特征空间的决策边界:
X = iris['data'][:, (2, 3)]
y = (iris['target'] == 2).astype(np.int)
这里选择了数据集中的第3和第4个特征作为输入特征,并重新加载标签。
log_res = LogisticRegression(C=10000)
log_res.fit(X, y)
训练逻辑回归模型,并使用较大的正则化参数 C
进行训练。
x0, x1 = np.meshgrid(np.linspace(2.9, 7, 500).reshape(-1, 1), np.linspace(0.8, 2.7, 200).reshape(-1, 1))
X_new = np.c_[x0.ravel(), x1.ravel()]
生成了一个新的二维特征空间。
y_proba = log_res.predict_proba(X_new)
使用逻辑回归模型预测新的数据点的概率。
plt.figure(figsize=(10, 4))
plt.plot(X[y == 0, 0], X[y == 0, 1], 'bs')
plt.plot(X[y == 1, 0], X[y == 1, 1], 'g^')
绘制原始数据点。
zz = y_proba[:, 1].reshape(x0.shape)
contour = plt.contour(x0, x1, zz, cmap=plt.cm.brg)
plt.clabel(contour, inline=1)
绘制决策边界的等高线。
plt.axis([2.9, 7, 0.8, 2.7])
plt.text(3.5, 1.5, 'NOT Virginica', fontsize=16, color='b')
plt.text(6.5, 2.3, 'Virginica', fontsize=16, color='g')
运行效果:
3.2实验结论
此次试验主要实现逻辑回归模型在不同特征下的分类效果,以及如何通过可视化来理解模型的预测结果和决策边界。
3.2.1通过选择Iris数据集的第4个特征,即花瓣宽度,进行二分类(Iris-Virginica与非Iris-Virginica)。创建逻辑回归模型并训练后,通过绘制概率曲线和决策边界,可以观察到模型在花瓣宽度上的分类效果。
3.2.2决策边界和概率曲线:
通过绘制决策边界,可以看到模型根据概率判断的决策边界,即当概率超过0.5时,判断为Iris-Virginica。
概率曲线展示了在不同花瓣宽度下,判断为Iris-Virginica的概率。
3.2.3使用另一组特征进行分类:
第二部分部分代码使用数据集中的第3和第4个特征进行逻辑回归训练,并绘制了决策边界。
这展示了不同特征对分类效果的影响,以及模型在不同特征组合下的表现。
3.2.3参数调整:
在第二部分中,通过设置 C = 10000 超参数,调整了逻辑回归模型的正则化参数。
超参数的调整可以对模型性能产生影响,但此次实验完成得略为粗糙,在实际应用中,我们通常需要更全面的评估和调参来确保模型的性能和泛化能力。
4.逻辑回归不同训练方法测试
4.1获取数据集
我的数据集是获取用户个人信息(年龄、教育情况、工龄、地址、收入、负债率、银行卡负债等情况),利用这些信息,最后判断用户是否违约。
4.2数据预处理
我们使用了最大最小归一化对数据进行处理,将特征值映射到[0, 1]的范围,以确保模型在训练过程中更好地收敛。
预处理时,先将用户信息存入一个二维数组中,其返回值data_arr
为数据特征,label_arr
是每个数据对应标签 :
def load_data_set(fileName):
data_arr = []
label_arr = []
f = open(fileName, 'r')
for line in f.readlines():
line_arr = line.strip().split()
data_arr.append([np.float(line_arr[0]),
np.float(line_arr[1]),
np.float(line_arr[2]),
np.float(line_arr[3]),
np.float(line_arr[4]),
np.float(line_arr[5]),
np.float(line_arr[6]),
np.float(line_arr[7])])
label_arr.append(int(line_arr[8]))
data_arr = maxminnorm(data_arr)
return data_arr, label_arr
data_arr, class_labels = load_data_set()
在预处理时要注意如果有的数据太大,要将数据归一化,如果不归一化会导致数据溢出。根据sigmoid函数:
当z过大时,e^-z数值是很大的,所以要将器归一化处理,根据归一化公式:
根据公式可以将数据全部保证在0-1之间,并且不丢失数据代表的特征。带入到sigmoid函数后,就不会有数据溢出的异常了。
归一化处理:
def maxminnorm(array):
array = np.array(array)
maxcols=array.max(axis=0)
mincols=array.min(axis=0)
data_shape = array.shape
data_rows = data_shape[0]
data_cols = data_shape[1]
t=np.empty((data_rows,data_cols))
for i in range(data_cols):
t[:,i]=(array[:,i]-mincols[i])/(maxcols[i]-mincols[i])
return t
4.3利用梯度上升计算回归系数
4.3.1梯度上升法
梯度上升法其实就是因为使用了极大似然估计,传入的就是一个普通的数组param data_arr,当然传入一个二维的ndarray也行。class_labels 是类别标签,它是一个的行向量。为了便于矩阵计算,需要将该行向量转换为列向量,做法是将原向量转置,再将它赋值给label_mat。
def grad_ascent(data_arr, class_labels):
data_mat = np.mat(data_arr)
# 变成矩阵之后进行转置
label_mat = np.mat(class_labels).transpose()
# m->数据量,样本数 n->特征数
m, n = np.shape(data_mat)
# 学习率,learning rate
alpha = 0.001
# 最大迭代次数
max_cycles = 500
# 生成一个长度和特征数相同的矩阵
# weights 代表回归系数, 此处的 ones((n,1)) 创建一个长度和特征数相同的矩阵,其中的数全部都是 1
weights = np.ones((n, 1))
for k in range(max_cycles):
h = sigmoid(data_mat * weights)
error = label_mat - h
weights = weights + alpha * data_mat.transpose() * error
return weights
4.3.2随机梯度上升
梯度上升算法在更新回归系数是都要遍历整个数据集,如果处理的样本数和标签数太多,那计算的复杂度就太大了。如果一次只用一个样本点来更新回归系数,这种方法叫做随机梯度上升算法。随机梯度上升算法和梯度上升算法区别有:
(1)后者的变量和误差都是向量,而前者全是数值;
(2)前者没有矩阵转换过程,所有变量都是numpy数组。
#随机梯度上升
def stoc_grad_ascent0(data_mat, class_labels):
m, n = np.shape(data_mat)
alpha = 0.01
weights = np.ones(n)
for i in range(m):
# sum(data_mat[i]*weights)为了求 f(x)的值, f(x)=a1*x1+b2*x2+..+nn*xn,
# 此处求出的 h 是一个具体的数值,而不是一个矩阵
h = sigmoid(sum(data_mat[i] * weights))
error = class_labels[i] - h
weights = weights + alpha * error * data_mat[i]
return weights
4.3.3改进随机梯度上升算法
相对于随机梯度上升算法,这个算法改进了几个方面:(1)aplha在每次迭代的时候都会调整,这会缓解数据波动。另外,虽然aplha会随着迭代次数不断减少,但永远不会减少到0;(2)通过随机选取样本更新回归系数,这种方法减少波动;(3)可以修改默认的迭代次数,第三个参数可以将迭代次数传入,没有默认为150次。
def stoc_grad_ascent1(data_mat, class_labels, num_iter=150):
m, n = np.shape(data_mat)
weights = np.ones(n)
for j in range(num_iter):
# 这里必须要用list,不然后面的del没法使用
data_index = list(range(m))
for i in range(m):
# i和j的不断增大,导致alpha的值不断减少,但是不为0
alpha = 4 / (1.0 + j + i) + 0.01
# 随机产生一个 0~len()之间的一个值
# random.uniform(x, y) 方法将随机生成下一个实数,它在[x,y]范围内,x是这个范围内的最小值,y是这个范围内的最大值。
rand_index = int(np.random.uniform(0, len(data_index)))
h = sigmoid(np.sum(data_mat[data_index[rand_index]] * weights))
error = class_labels[data_index[rand_index]] - h
weights = weights + alpha * error * data_mat[data_index[rand_index]]
del(data_index[rand_index])
return weights
4.4训练和验证
- 根据回归系数和特征向量来计算 Sigmoid 的值,大于0.5函数返回1,否则返回0
def classify_vector(in_x, weights): # print(np.sum(in_x * weights)) prob = sigmoid(np.sum(in_x * weights)) if prob > 0.5: return 1.0 return 0.0
-
训练:
def colic_test(): f_train = open('data/loandata.txt', 'r') f_test = open('data/loandata_test.txt', 'r') training_set = [] training_labels = [] # trainingSet 中存储训练数据集的特征,trainingLabels 存储训练数据集的样本对应的分类标签 for line in f_train.readlines(): curr_line = line.strip().split('\t') if len(curr_line) == 1: continue # 这里如果就一个空的元素,则跳过本次循环 line_arr = [float(curr_line[i]) for i in range(8)] training_set.append(line_arr) training_labels.append(float(curr_line[8])) # 使用 改进后的 随机梯度下降算法 求得在此数据集上的最佳回归系数 trainWeights train_weights0 = grad_ascent(np.array(training_set), training_labels) train_weights1 = stoc_grad_ascent0(np.array(training_set), training_labels) train_weights2 = stoc_grad_ascent1(np.array(training_set), training_labels) error_count0 = 0 error_count1 = 0 error_count2 = 0 num_test_vec = 0.0 # 读取 测试数据集 进行测试,计算分类错误的样本条数和最终的错误率 for line in f_test.readlines(): num_test_vec += 1 curr_line = line.strip().split('\t') if len(curr_line) == 1: continue # 这里如果就一个空的元素,则跳过本次循环 line_arr = [float(curr_line[i]) for i in range(8)] if int(classify_vector(np.array(line_arr), train_weights0)) != int(curr_line[8]): error_count0 += 1 if int(classify_vector(np.array(line_arr), train_weights1)) != int(curr_line[8]): error_count1 += 1 if int(classify_vector(np.array(line_arr), train_weights2)) != int(curr_line[8]): error_count2 += 1 right_rate = 1 - (error_count0 / num_test_vec) print('梯度下降法正确率:{}'.format(right_rate)) right_rate = 1 - (error_count1 / num_test_vec) print('随机梯度下降法正确率:{}'.format(right_rate)) right_rate = 1 - (error_count2 / num_test_vec) print('改进随机梯度下降法正确率:{}'.format(right_rate)) return
结果:
4.5实验总结
该实验我们测试三个逻辑回归的不同优化算法:
1.grad_ascent(梯度上升算法):
使用整个训练集的所有样本进行权重更新。
通过计算整个数据集的梯度来更新权重。
固定学习率 alpha 和迭代次数 max_cycles。
2.stoc_grad_ascent0(简单随机梯度上升算法):
每次迭代只使用一个随机选择的样本进行权重更新。
通过计算单个样本的梯度来更新权重。
固定学习率 alpha。
3.stoc_grad_ascent1(改进的随机梯度上升算法):
引入动态学习率 alpha。
每次迭代使用整个训练集的所有样本,但是样本的选择是随机的。
学习率 alpha 随着迭代次数的增加而逐渐减小,增加了对全局最优解的稳定性。
区别总结:
grad_ascent (批量梯度上升算法)使用整个数据集的全部信息,计算全局梯度,更新权重。
stoc_grad_ascent0 (简单梯度上升算法)使用一个随机样本的信息,计算梯度,更新权重。
stoc_grad_ascent1(改进梯度上升算法) 使用整个数据集的信息,但每次迭代使用随机样本,引入了动态学习率。
这三种方法在计算代价函数梯度和更新权重的方式上有所不同,对于大型数据集,简单梯度上升和 改进梯度上升可以更快地收敛,因为它们每次只使用一个样本进行更新,而不需要计算整个数据集的梯度。
5.逻辑回归实现多分类
5.1SoftMax激活函数
在二分类任务时,经常使用sigmoid激活函数。而在处理多分类问题的时候,需要使用softmax函数。它通常用于神经网络的输出层,将原始的输出转换为表示概率分布的形式。它的输出有两条规则。
- 将一个含有任意实数的K维向量“压缩”到0到1之间的K维向量
- 所有项相加的和为1
Softmax函数的定义如下:
对于给定的K维输入向量 z=(z1,z2,…,zK),Softmax函数的输出 σ=(σ1,σ2,…,σK) 计算如下:
其中,e 是自然对数的底数(约等于2.71828)。
计算过程:
假设输入的数组为[3,1,-3]。那么每项的计算过程为:
当输入为3时,计算公式为
当输入为1时,计算公式为
当输入为-3时,计算公式为
softmax函数的主要特点是将输入转换为概率分布,使得输出向量中每个元素表示对应类别的概率。因此,Softmax广泛用于多类别分类问题,特别是在神经网络中。
在深度学习中,Softmax函数通常作为神经网络的输出层的激活函数,用于产生表示类别概率的输出。交叉熵损失函数常与Softmax函数一起使用,用于衡量模型输出概率分布与实际类别的差异,从而进行模型的训练。 Softmax函数的求导和计算相对简单,因此在梯度下降等优化算法中也很容易进行反向传播。
5.2交叉熵损失函数
交叉熵损失函数(Cross-Entropy Loss),也被称为对数损失函数(Log Loss),是用于衡量分类问题中模型输出与实际标签之间差异的一种常用损失函数。
对于二分类问题,交叉熵损失函数的定义如下:
其中:
- y 是实际标签(0或1)。
- y^ 是模型输出的概率。
对于多分类问题,交叉熵损失函数的定义如下:
其中:
- yi 是实际标签的独热编码(one-hot encoding)形式,表示样本属于第 i 类的概率。
- y^i 是模型输出的第 i 类的概率。
交叉熵损失函数的主要特点是能够衡量两个概率分布之间的相似性,它在分类问题中被广泛应用。当模型的输出概率分布与真实分布(标签的独热编码)越接近时,交叉熵损失越小。
在训练分类模型或者神经网络模型时,通常使用梯度下降或其变种来最小化交叉熵损失,从而调整模型的参数以提高分类准确性。
5.3逻辑回归实现多分类任务
使用Softmax激活函数和交叉熵损失函数来进行多分类任务
5.3.1softmax回归和交叉熵损失函数
核心步骤:
1.初始化权重矩阵和偏置向量。
2.定义 Softmax 函数,将输入转换为类别概率。
3.定义交叉熵损失函数。
4.使用梯度下降或其他优化算法最小化损失函数。
# 加载数据集
iris = load_iris()
# 提取特征 X 和标签 y
X = iris['data'][:, (2, 3)] # 选择花瓣长度和花瓣宽度作为特征
y = iris['target']
# 添加一列常数项到 X 中
X_with_bias = np.c_[np.ones((X.shape[0], 1)), X]
# 初始化权重矩阵,每一列对应一个类别
W = np.random.rand(X_with_bias.shape[1], len(np.unique(y)))
# 定义 Softmax 函数
def softmax(logits):
exp_logits = np.exp(logits)
sum_exp_logits = np.sum(exp_logits, axis=1, keepdims=True)
return exp_logits / sum_exp_logits
# 定义交叉熵损失函数
def cross_entropy_loss(y_true, y_proba):
m = len(y_true)
loss = -np.sum(np.log(y_proba[np.arange(m), y_true])) / m
return loss
# 定义学习率和训练轮数
learning_rate = 0.01
n_iterations = 1000
# 使用梯度下降更新权重
for iteration in range(n_iterations):
logits = X_with_bias.dot(W)
y_proba = softmax(logits)
loss = cross_entropy_loss(y, y_proba)
error = y_proba - np.eye(len(np.unique(y)))[y]
gradients = X_with_bias.T.dot(error) / len(y)
W = W - learning_rate * gradients
5.3.2对样本中每个点进行预测并绘制决策边界和分类区域
plt.figure(figsize=(10, 4))
plt.plot(X[y==2, 0], X[y==2, 1], "g^", label="Iris-Virginica")
plt.plot(X[y==1, 0], X[y==1, 1], "bs", label="Iris-Versicolor")
plt.plot(X[y==0, 0], X[y==0, 1], "yo", label="Iris-Setosa")
from matplotlib.colors import ListedColormap
custom_cmap = ListedColormap(['#fafab0','#9898ff','#a0faa0'])
plt.contourf(x0, x1, zz, cmap=custom_cmap)
contour = plt.contour(x0, x1, zz1, cmap=plt.cm.brg)
plt.clabel(contour, inline=1, fontsize=12)
plt.xlabel("Petal length", fontsize=14)
plt.ylabel("Petal width", fontsize=14)
plt.legend(loc="center left", fontsize=14)
plt.axis([0, 7, 0, 3.5])
plt.show()
5.3.3结果
5.3.4总结
Softmax回归是解决多分类问题的模型,通过逻辑回归的扩展实现。以上实验中手动实现softmax回归可能不如现有库(如Scikit-Learn)的实现高效,但有助于理解 Softmax 回归的内部工作原理。在今后实际应用中,准备数据、特征标准化后,用Scikit-Learn库的LogisticRegression
模型构建,拟合模型后,可实现多分类任务。若数据为二维,可可视化决策边界。Softmax回归适用于各种多类别场景,通过交叉熵损失函数优化模型,取得优异性能。