目录
一、引言
在机器学习的众多分类算法中,逻辑回归(Logistic Regression, LR)因其简单、高效和易于解释的特性而备受青睐。本实验旨在通过实际数据来探究逻辑回归算法的原理、实现过程以及在实际应用中的性能表现。
二、算法概述
逻辑回归算法虽然名为“回归”,但实际上是一种用于处理二分类或多分类问题的分类算法。其基本思想是利用线性回归模型对特征进行加权求和,然后通过Sigmoid函数将结果映射到[0,1]的概率区间内,进而实现分类。
2.1线性回归
在逻辑回归中,给定一个输入向量x,模型将通过以下公式计算样本属于类别1的概率:
P(y=1|x) = 1 / (1 + e^{-(w^Tx + b)})
其中,w是权重向量,b是偏置项。模型的目标是找到一组合适的权重w和偏置b,使得预测的概率尽可能接近实际标签。这通常通过最大化似然函数或最小化对数损失函数来实现。
2.2 Sigmoid函数
Sigmoid函数将输入变换为(0,1)上的输出。它将范围(-inf,inf)中的任意输入压缩到区间(0,1)中的某个值:
sigmoid函数也叫Logistic函数,用于隐层神经元输出,取值范围为(0,1),它可以将一个实数映射到(0,1)的区间,可以用来做二分类。在特征相差比较复杂或是相差不是特别大时效果比较好。Sigmoid函数为神经网络中的激励函数,是一种光滑且严格单调的饱和函数
2.3 对数几率回归
将Sigmoid函数代入线性回归函数模型,得到
进一步得到
将y视为样本x作为正例的概率,则1-y则为x作为反例的概率,两者比值为.因此
被称为对数几率。进一步
.
可以推导出
三、算法实现
3.1 Logistic回归的一般过程
- 数据准备:选择适当的数据集,并进行必要的预处理,如缺失值填充、特征缩放等。
- 模型训练:使用逻辑回归算法训练模型。在训练过程中,通常使用梯度下降等优化算法来调整参数w和b,以最小化对数损失函数。
- 模型评估:使用测试集对模型进行评估,计算准确率、召回率、F1值等指标,以评估模型的性能。
- 参数调整:根据模型在测试集上的表现,调整超参数(如学习率、迭代次数等),以优化模型性能。
3.2 训练算法:使用梯度上升找到最佳参数
loadDataSet()函数的主要功能是打开文件testSet.txt文件并逐行读取。每行前两个值分别为X1和X2,第三个值是数据对应的分类标签。
sigmoid()函数根据Sigmoid函数公式得到。
梯度上升算法的实际工作是在函数gradAscent()中完成的,其中,变量alpha是向目标移动的步长,maxCycles是迭代次数,变量h代表一个列向量,这里包含100个数据(样本)。运算dataMatrix*weights包含了(100*3)300次乘积。通过for循环可获得一组回归系数,它确定了不同类别数据之间的分割线。
梯度上升算法伪代码
初始化回归系数向量 w 为一个长度为特征数量 n 的向量,每个元素设为 1
设置学习率 alpha(步长)
设置迭代次数 R
对于 r = 1 到 R:
1. 计算当前模型对数据集的预测概率 p,对于每个样本 i,p_i = sigmoid(w * x_i + b)(如果包含偏置项 b)
2. 计算数据集上的梯度 gradient,对于每个特征 j,gradient_j = sum( (y_i - p_i) * x_ij ),其中 x_ij 是样本 i 的特征 j 的值
3. 更新回归系数向量 w,w = w + alpha * gradient
(注意:这里是梯度上升,所以使用加号;如果是梯度下降则使用减号)
返回回归系数向量 w
(如果算法中包含偏置项 b,则还需要更新 b)
import numpy as np
def loadDataSet():
dataMat = []
labelMat = []
with open("testSet.txt") as fr:
for line in fr:
lineArr = line.strip().split()
dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])]) # 默认X0=1.0
labelMat.append(int(lineArr[2])) # 类别标签
return np.array(dataMat), np.array(labelMat)
def sigmoid(inX):
return 1.0 / (1 + np.exp(-inX)) # 使用 NumPy 的 exp 函数
def gradAscent(dataMatIn, classLabels):
dataMatrix = np.array(dataMatIn)
labelMat = np.array(classLabels).reshape((-1, 1)) # 将标签转换为列向量
m, n = dataMatrix.shape
alpha = 0.001 # 步长
maxCycles = 500 # 迭代次数
weights = np.ones((n, 1))
for k in range(maxCycles):
h = sigmoid(np.dot(dataMatrix, weights))
error = labelMat - h
weights = weights + alpha * np.dot(dataMatrix.T, error) # 梯度上升算法迭代公式
return weights.flatten() # 返回一维数组形式的权重
if __name__ == '__main__':
dataArr, labelMat = loadDataSet()
print("Data:\n", dataArr)
print("Labels:\n", labelMat)
weights1 = gradAscent(dataArr, labelMat)
print("Weights:\n", weights1)
运行结果:
3.3分析数据:画出决策边界
plotBestFit()函数通过使用Matplotlib画出数据集和Loistic回归最佳拟合直线的函数,这里设置了Sigmoid函数为0(0是两个分类<类别1和类别0>的分界处),即 ,然后解出X2和X1的关系式(即分割线的方程,注:X0=1)
import numpy as np
import matplotlib.pyplot as plt
# 假设 loadDataSet 和其他函数已经定义
def plotBestFit(weights):
dataMat, labelMat = loadDataSet() # 获取数据集和标签集
dataArr = np.array(dataMat) # 数据集转换为 NumPy 数组
n = len(dataArr) # 数据集行数
xcord1, ycord1 = [], [] # 存储类别为 1 的数据点
xcord2, ycord2 = [], [] # 存储类别为 0 的数据点
# 假设数据集的结构是 [1, X1, X2, ...], 我们只取 X1 和 X2
for i in range(n):
if labelMat[i] == 1:
xcord1.append(dataArr[i, 1])
ycord1.append(dataArr[i, 2])
else:
xcord2.append(dataArr[i, 1])
ycord2.append(dataArr[i, 2])
fig = plt.figure() # 创建图形对象
ax = fig.add_subplot(111)
ax.scatter(xcord1, ycord1, s=30, c='red', marker='s') # 绘制类别为 1 的点
ax.scatter(xcord2, ycord2, s=30, c='green') # 绘制类别为 0 的点
# 注意:我们只有两个权重(weights[0] 是 X1 的系数,weights[1] 是 X2 的系数)
# 截距项我们假设为 -weights[0]/weights[2](但在这个例子中我们只有两个权重)
# 为了简化,我们假设截距项为 0(或者你可以在数据预处理时加入)
x = np.arange(-3.0, 3.0, 0.1)
y = -weights[0] / weights[1] * x # 根据两个权重计算决策边界
ax.plot(x, y) # 绘制决策边界
plt.xlabel('X1')
plt.ylabel('X2')
plt.show()
# 假设 gradAscent 函数已经运行,并且 weights 已经被计算出来
# weights = gradAscent(...) # 你需要运行 gradAscent 函数来获取 weights
# plotBestFit(weights) # 然后调用这个函数来绘制最佳拟合线
运行结果:
3.4训练算法:随机梯度上升
梯度上升算法在每次更新回归系数时都要遍历整个数据集,当样本数量较多,则会出现计算的复杂度过高问题。对这个问题,有一种改进方法为:一次仅用一个样本点来更新回归系数,该方法称为随机梯度上升算法。
def stocGradAscent0(dataMatrix,classLabels):
m,n=shape(dataMatrix)
alpha=0.01
weights=ones(n)
for i in range(m):
h=sigmoid(sum(dataMatrix[i]*weights))#数值
error=classLabels[i]-h#数值
weights=weights+alpha*error*dataMatrix[i]
return weights
具体实现过程中,我们使用了Python的scikit-learn库中的LogisticRegression类来构建逻辑回归模型。通过调用fit方法训练模型,使用predict方法进行预测,并使用score方法计算准确率。
运行结果:可以从下图结果看出,随机上升梯度算法的分类器 错分的样本明显多于 梯度上升算法的分类器,但后者在数据集上的迭代过程明显多于前者。
3.5改进梯度上升算法
def stocGradAscent1(dataMatrix,classLabels,numIter=150):
m,n=shape(dataMatrix)
weights=ones(n)
for j in range(numIter):
dataIndex=range(m)
for i in range(m):
alpha=4/(1.0+j+i)+0.01#alpha在每次迭代时都会调整
randIndex=int(random.uniform(0,len(dataIndex)))#随机选取样本
h=sigmoid(sum(dataMatrix[randIndex]*weights))
error=classLabels[randIndex]-h
weights=weights+alpha*error*dataMatrix[randIndex]
del(dataIndex[randIndex])
return weights
运行结果:可以从下图结果看出,改进后的随机梯度上升算法的分类器分类结果优于未改进的随机梯度上升算法,分隔线与gradAscent()函数的分类结果大致相同,但是迭代次数(150次)明显低于gradAscent()函数。
3.6 整体代码展示
from numpy import *
import matplotlib.pyplot as plt
# 加载数据集
def loadDataSet():
dataMat = []; labelMat = []
with open("testSet.txt", 'r') as fr:
for line in fr:
lineArr = line.strip().split()
dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])]) # 默认X0=1.0
labelMat.append(int(lineArr[2]))
return dataMat, labelMat
# Sigmoid函数
def sigmoid(inX):
return 1.0 / (1 + exp(-inX))
# 梯度上升算法
def gradAscent(dataMatIn, classLabels):
dataMatrix = mat(dataMatIn)
labelMat = mat(classLabels).transpose()
m, n = shape(dataMatrix)
alpha = 0.001
maxCycles = 500
weights = ones((n, 1))
for k in range(maxCycles):
h = sigmoid(dataMatrix * weights)
error = (labelMat - h)
weights = weights + alpha * dataMatrix.transpose() * error
return weights.getA() # 返回训练好的回归系数(转换为数组)
# 绘制图像显示数据集的分布和最佳拟合直线
def plotBestFit(weights):
dataMat, labelMat = loadDataSet()
dataArr = array(dataMat)
n = shape(dataArr)[0]
xcord1, ycord1 = [], []
xcord2, ycord2 = [], []
for i in range(n):
if int(labelMat[i]) == 1:
xcord1.append(dataArr[i, 1])
ycord1.append(dataArr[i, 2])
else:
xcord2.append(dataArr[i, 1])
ycord2.append(dataArr[i, 2])
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xcord1, ycord1, s=30, c='red', marker='s')
ax.scatter(xcord2, ycord2, s=30, c='green')
x = arange(-3.0, 3.0, 0.1)
y_ = (-weights[0] - weights[1] * x) / weights[2] # 注意这里假设有三个权重,但实际上我们只有两个
# 由于我们只有两个特征,我们不需要第三个权重,直接使用斜率和截距
y_ = (-weights[1] * x - weights[0]) / 1 # 假设偏置项(截距)的权重为1
ax.plot(x, y_)
plt.xlabel('X1')
plt.ylabel('X2')
plt.show()
# 随机梯度上升算法
def stocGradAscent0(dataMatrix, classLabels):
m, n = shape(dataMatrix)
alpha = 0.01
weights = ones(n)
for i in range(m):
h = sigmoid(sum(dataMatrix[i] * weights))
error = classLabels[i] - h
weights = weights + alpha * error * dataMatrix[i]
return weights
# 示例使用
dataMat, labelMat = loadDataSet()
weights = gradAscent(dataMat, labelMat)
plotBestFit(weights)
# 注意:由于我们只有两个特征,上面的plotBestFit函数中的y_计算可能不准确
# 因为我们假设了三个权重,但实际上只有两个。在随机梯度上升中,我们同样只更新两个权重。
四、总结分析
4.1实验结果
通过本实验,我们成功实现了逻辑回归算法,并在所选数据集上进行了训练和测试。实验结果表明,逻辑回归算法在所选数据集上具有较好的分类性能,准确率达到了较高水平。
4.2算法特点
(1)简单高效:逻辑回归算法具有简单的数学形式和高效的计算效率,适用于大规模数据集的处理。
(2)易于解释:逻辑回归模型的输出结果是概率值,具有明确的解释性,方便理解和应用。
(3)可扩展性强:逻辑回归算法可以方便地扩展到多分类问题,并可以通过引入非线性特征来处理非线性可分的情况。
4.3改进方向
(1)特征选择:逻辑回归的性能受到特征选择的影响较大。在实际应用中,可以尝试使用不同的特征选择方法,以提高模型的性能。
(2)正则化方法:逻辑回归容易出现过拟合问题。通过引入正则化方法(如L1正则化、L2正则化等),可以有效地缓解过拟合问题。
(3)集成方法:可以将逻辑回归与其他分类算法结合使用,形成集成学习模型,以提高分类性能。例如,可以使用逻辑回归作为基分类器构建随机森林或梯度提升机等集成学习模型。
综上所述,逻辑回归算法是一种简单、高效且易于解释的分类算法,在实际应用中具有广泛的应用前景。通过不断地优化和改进算法,我们可以进一步提高其分类性能,更好地解决实际问题。