文章目录
一、理论基础
1.拟合与回归
拟合: 形象的说,拟合就是把平面上一系列的点,用一条光滑的曲线连接起来。因为这条曲线有无数种可能,从而有各种拟合方法。拟合的曲线一般可以用函数表示,根据这个函数的不同有不同的拟合名字。(针对本实验,拟合可以理解为对于给定的空间中的一些点,找到一条连续的直线来最大限度地逼近这些点)
回归: 假设现在有一些数据点,我们使用一条直线对这些点进行拟合(该线被称为最佳拟合直线),这个拟合过程称为回归。
注: 此处需要注意回归与分类的区别。回归问题通常是对结果进行范围预测;而分类问题则是将事物打上一个标签,通常结果为离散值,最终正确结果只有一个。
2. Sigmoid函数
我们想要的函数应该是能接受所有的输入然后预测出类别。例如,在两个类的情况下,函数输出0或1。这样的函数被称为海维赛德阶跃函数(Heaviside step function)或者直接称为单位阶跃函数。然而,海维赛德阶跃函数的问题在于:该函数在跳跃点从0的瞬间跳跃到1,这个瞬间跳跃过程有时很难处理。幸好,另一个函数也有相同的性质,且在数学上更容易处理,这个函数就是Sigmoid函数。该函数的具体计算公式如下:下边还给出了Sigmoid函数在不同尺度下的两个图像。当x为0时,该函数的函数值为0.5。随着x的增大,对应的函数值将逼近于1;而随着x的减小,对应的函数值将逼近与0。当函数的横坐标刻度足够大时,Sigmoid函数看起来就像是一个阶跃函数。
因此,为了实现Logistic回归分类器,我们可以在每个特征上都乘以一个回归系数,然后把所有结果相加,将这个总和代入Sigmoid函数中,进而得到一个范围在0和1之间的数值。任何大于0.5的数据都会被分入1类,小于0.5的数据都会被分入0类。所以,Logistic回归问题也可以看做是一种概率估计。
3.基于最优化方法的最佳回归系数确定
Sigmoid函数的输入记为z,由下边公式得出:
如果采用向量的写法,可以写成:
其中向量x是分类器输入的数据,向量w是我们要找的最佳回归系数。
3.1梯度上升法
现在,我们介绍的第一个最优化算法叫做梯度上升法。梯度上升法基于的思想是:要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。如果梯度记为∇,则函数法 f(x,y) 的梯度由下边的式子表示:
梯度上升算法到达每个点后都会重新估计移动的方向。从P0开始,计算完该点的梯度,函数就根据梯度移动到下一点P1。在P1点,梯度再次被重新计算,并沿着新的梯度方向移动到P2。如此循环迭代,直到满足停止条件。 在迭代的过程中,梯度算法总是保证我们能选取到最佳的移动方向。
从上图可以发现,梯度算法总是指向函数值增长最快的方向。这里所说的是移动方向,而未提到移动量的大小。该量值称为步长,记作α。用向量来表示的话,梯度算法的迭代公式如下:
该公式将一直被迭代执行,直到达到某个停止条件为止,比如迭代次数达到某个指定值或算法达到某个可以允许的误差范围。
最后得到更新权重的公式为:
可将该公式改写成矢量化版本:
注:详细的公式推导可参考博客https://blog.csdn.net/yjw123456/article/details/103333558
二、代码实现
1.数据集可视化
实验数据集的部分数据如下:
该数据集共100个样本点,每个点包含对应横纵坐标和类别标签,对试验数据集进行可视化:
import numpy as np
import matplotlib.pyplot as plt
'''
函数说明:读取数据集
Returns:
dataMat:包含了数据特征值的矩阵
dataLabel:数据集的标签
'''
def loadDataSet():
dataMat = []
labelMat = []
fr = open('Ch05/testSet.txt')
for line in fr.readlines():
# strip()方法不传参时表示移除字符串首尾空格
lineArr = line.strip().split()
dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])
labelMat.append(int(lineArr[2]))
fr.close()
return dataMat, labelMat
"""
函数说明:绘制数据集
Parameters:
无
Returns:
无
"""
def plotDataSet():
dataMat, labelMat = loadDataSet()
# 转换成numpy的array数组
dataArr = np.array(dataMat)
n = np.shape(dataMat)[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=20, c='red', marker='s', alpha=.5)
ax.scatter(xcord2, ycord2, s=20, c='green', alpha=.5)
plt.title('DataSet')
plt.xlabel('x');
plt.ylabel('y')
plt.show()
if __name__ == '__main__':
plotDataSet()
运行结果如下:
2.使用梯度上升寻找最佳的参数
添加Sigmoid函数和Logistic回归梯度上升算法代码:
'''
函数说明:sigmoid函数
Parameters:
inx:经历一次前向传播还没有激活的值
Returns:
激活后的值
'''
def sigmoid(inx):
return 1.0/(1+np.exp(-inx))
"""
函数说明:梯度上升算法
Parameters:
dataMatIn:数据集
classLabels:数据标签
Returns:
weights:求得的权重数组(最优参数)
"""
def gradAscent(dataMatIn,classLabels):
#将输入数据集转换为矩阵
dataMatrix = np.mat(dataMatIn)
LabelsMat = np.mat(classLabels).transpose()
m,n = np.shape(dataMatrix)
#初始化系数矩阵,因为数据集为m行n列,所以权重矩阵为n行
weights = np.ones((n,1))
#迭代次数
maxIter = 500
#步长(学习率)
alpha = 0.001
for k in range(maxIter):
h = sigmoid(dataMatrix * weights)
#计算误差
error = LabelsMat - h
#更新权重矩阵
weights = weights + alpha * dataMatrix.transpose() * error
return weights.getA()
if __name__ == '__main__':
dataArr,labelMat = loadDataSet()
print(gradAscent(dataArr,labelMat))
运行结果:
该结果为使用梯度上升算法寻找到的一组回归系数。
3.画出决策边界
在代码中添加绘制数据集和最佳拟合直线的函数:
"""
函数说明:绘制数据集
Parameters:
weights:权重参数数组
Returns:
无
"""
def plotBestFit(wei):
dataMat,dataLabel = loadDataSet()
dataArray = np.array(dataMat)
xcord1 = [];ycord1 = []
xcord2 = [];ycord2 = []
n = np.shape(dataArray)[0]
for i in range(n):
if int(dataLabel[i]) == 0:
xcord1.append(dataArray[i,1])
ycord1.append(dataArray[i,2])
else:
xcord2.append(dataArray[i,1])
ycord2.append(dataArray[i,2])
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xcord1,ycord1,color = 'red',marker = 's',s = 30)
ax.scatter(xcord2,ycord2,color = 'blue',s = 30)
x = np.arange(-3.0,3.0,0.1)
y = (-wei[0,0] - wei[1,0]*x)/wei[2,0]
ax.plot(x,y)
plt.xlabel('X1');plt.ylabel('X2')
plt.show()
if __name__ == '__main__':
dataArr,labelMat = loadDataSet()
weights = gradAscent(dataArr,labelMat)
plotBestFit(weights)
运行结果:
由上图的分类结果可以看出只有两个点被分错(该方法对数据集进行了大量的运算,大约有300次乘法),下一步将对该算法进行改进。
4.随机梯度上升算法
梯度上升算法在每次更新回归系数时都要遍历整个数据集,该方法在处理100个左右的数据集时(需要300次乘法)尚可,但如果有数十亿样本和成千上万的特征,那么该方法的计算复杂度就太高了。
一种改进方法是一次只用一个样本点来更新回归系数,这样就可以大大减少计算量了,该方法称为随机梯度上升算法。
代码如下:
# 初始随机梯度上升法
def stocGradAscent0(dataMatrix, classLabels):
m, n = np.shape(dataMatrix)
alpha = 0.01
weights = np.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
if __name__ == '__main__':
plotDataSet()
dataArr,labelMat = loadDataSet()
weights = stocGradAscent0(np.array(dataArr),labelMat)
plotBestFit(weights)
运行结果:
根据上图可以发现,大约三分之一的样本点被错分,即使用随机梯度上升算法进行分类的错误率明显高于梯度上升算法,这是由于一些样本点不能被正确的分类(数据集并非线性可分),在每次迭代时会引发系数的剧烈改变。因此我们希望该算法能避免来回波动,从而收敛到某个值,且收敛速度也需要加快,下面将对随机梯度上升算法进行改进。
5.算法改进:改进随机梯度上升算法
def stocGradAscent1(dataMatrix, classLabels, numIter=150):
m, n = np.shape(dataMatrix)
# 存储每次更新的回归系数
weights = np.ones(n)
for j in range(numIter):
dataIndex = list(range(m))
for i in range(m):
# 降低alpha的大小,每次减小1/(j+i)
alpha = 4 / (1.0 + j + i) + 0.01
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
运行结果:
改进方面:
(1)alpha每次迭代的时候都会调整,并且永远不会减小到0.
因为这里还存在一个常数项。必须这样做的原因是为了保证在多次迭代之后新数据仍然具有一定的影响。如果需要处理的问题是动态变化的,那么可以适当加大上述常数项,来确保新的值获得更大的回归系数。另一点值得注意的是,在降低alpha的函数中,alpha每次减少1/(j+i),其中j是迭代次数,i是样本点的下标。
(2)更新回归系数(最优参数)时,只使用一个样本点,并且选择的样本点是随机的,每次迭代不使用已经用过的样本点。
这样的方法,就有效地减少了计算量,并保证了回归效果。100个样本就可以更新100次回归系数,而梯度上升算法只能更新一次,这就是为什么收敛的快一些。并且由于样本的选取是随机的,也就是更新顺序是随机的,这会影响整个分类的准确的,导致每次运行得出的准确率都不一样。
6.回归系数与迭代次数的关系
我们可以看到分类效果是不错的,但是无法看出迭代次数与回归系数的关系,也不能直观的看到每个回归方法的收敛情况。因此,我们来绘制出系数与迭代次数的关系:
# -*- coding: utf-8 -*
import random as random
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
def loadDataSet():
dataMat = []
labelMat = []
fr = open('Ch05/testSet.txt')
for line in fr.readlines():
lineArr = line.strip().split()
dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])
labelMat.append(int(lineArr[2]))
fr.close()
return dataMat, labelMat
def plotDataSet():
"""
绘制数据集
:return:
"""
dataMat, labelMat = loadDataSet()
dataArr = np.array(dataMat)
n = np.shape(dataMat)[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=20, c='red', marker='s', alpha=.5) # 绘制正样本
ax.scatter(xcord2, ycord2, s=20, c='green', alpha=.5) # 绘制负样本
plt.title('DataSet') # 绘制title
plt.xlabel('x')
plt.ylabel('y')
plt.show()
def sigmoid(inX):
return 1.0 / (1 + np.exp(-inX))
def gradAscent(dataMatIn, classLabels):
dataMatrix = np.mat(dataMatIn)
labelMat = np.mat(classLabels).transpose() # 转置
m, n = np.shape(dataMatrix)
alpha = 0.01
maxCycles = 500
weights = np.ones((n, 1))
weights_array = np.array([])
# theta := theta + alpha X^T(y^ - g(X(\theta))
for k in range(maxCycles):
h = sigmoid(dataMatrix * weights) # 1/(1 + \theta^T x)
error = labelMat - h
weights = weights + alpha * dataMatrix.transpose() * error
weights_array = np.append(weights_array, weights)
weights_array = weights_array.reshape(maxCycles, n)
return weights.getA(), weights_array
def stocGradAscent1(dataMatrix, classLabels, numIter=150):
m, n = np.shape(dataMatrix) # 100 X 3
weights = np.ones(n) # 3 X 1
weights_array = np.array([]) # 存储每次更新的回归系数
for j in range(numIter): # 迭代次数
dataIndex = list(range(m)) # 保存可用的索引
for i in range(m):
alpha = 4 / (1.0 + j + i) + 0.01 # 每次迭代时需要调整
randIndex = int(random.uniform(0, len(dataIndex))) # 随机选取更新
h = sigmoid(sum(dataMatrix[randIndex] * weights)) # 计算h
error = classLabels[randIndex] - h
weights = weights + alpha * error * dataMatrix[randIndex]
weights_array = np.append(weights_array, weights, axis=0) # 添加回归系数到数组中
del (dataIndex[randIndex]) # 删除已用样本
weights_array = weights_array.reshape(numIter * m, n) # 改变维度
return weights, weights_array
def plotBestFit(weights):
dataMat, labelMat = loadDataSet()
dataArr = np.array(dataMat)
n = np.shape(dataMat)[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 = np.arange(-3.0, 3.0, 0.1)
y = (-weights[0] - weights[1] * x) / weights[
2] # 0 = w0 + w1x1 + w2y -> y = (-w0 -w1x1)/w2 z = 0时,sigmoid函数取中间值,也就是0.5
ax.plot(x, y)
plt.title('BestFit') # 绘制title
plt.xlabel('X1')
plt.ylabel('X2')
plt.show()
def plotWeights(weights_array1, weights_array2):
font = matplotlib.font_manager.FontProperties(fname='C:/Windows/Fonts/simkai.ttf') #中文字体,不然会出现乱码
# 将fig画布分隔成1行1列,不共享x轴和y轴,fig画布的大小为(13,8)
# 当nrow=3,nclos=2时,代表fig画布被分为六个区域,axs[0][0]
fig, axs = plt.subplots(nrows=3, ncols=2, sharex=False, sharey=False, figsize=(20, 10))
x1 = np.arange(0, len(weights_array1), 1)
# 绘制w0与迭代次数的关系
axs[0][0].plot(x1, weights_array1[:, 0])
axs0_title_text = axs[0][0].set_title(u'改进的随机梯度上升算法:回归系数与迭代关次数系', FontProperties=font)
axs0_ylabel_text = axs[0][0].set_ylabel(u'W0', FontProperties=font)
plt.setp(axs0_title_text, size=20, weight='bold', color='black')
plt.setp(axs0_ylabel_text, size=20, weight='bold', color='black')
# 绘制w1与迭代次数的关系
axs[1][0].plot(x1, weights_array1[:, 1])
axs1_ylabel_text = axs[1][0].set_ylabel(u'W1', FontProperties=font)
plt.setp(axs1_ylabel_text, size=20, weight='bold', color='black')
# 绘制w2与迭代次数的关系
axs[2][0].plot(x1, weights_array1[:, 2])
axs2_xlabel_text = axs[2][0].set_xlabel(u'迭代次数', FontProperties=font)
axs2_ylabel_text = axs[2][0].set_ylabel(u'W2', FontProperties=font)
plt.setp(axs2_xlabel_text, size=20, weight='bold', color='black')
plt.setp(axs2_ylabel_text, size=20, weight='bold', color='black')
x2 = np.arange(0, len(weights_array2), 1)
# 绘制w0与迭代次数的关系
axs[0][1].plot(x2, weights_array2[:, 0])
axs0_title_text = axs[0][1].set_title(u'梯度上升算法:回归系数与迭代次数关系', FontProperties=font)
axs0_ylabel_text = axs[0][1].set_ylabel(u'W0', FontProperties=font)
plt.setp(axs0_title_text, size=20, weight='bold', color='black')
plt.setp(axs0_ylabel_text, size=20, weight='bold', color='black')
# 绘制w1与迭代次数的关系
axs[1][1].plot(x2, weights_array2[:, 1])
axs1_ylabel_text = axs[1][1].set_ylabel(u'W1', FontProperties=font)
plt.setp(axs1_ylabel_text, size=20, weight='bold', color='black')
# 绘制w2与迭代次数的关系
axs[2][1].plot(x2, weights_array2[:, 2])
axs2_xlabel_text = axs[2][1].set_xlabel(u'迭代次数', FontProperties=font)
axs2_ylabel_text = axs[2][1].set_ylabel(u'W2', FontProperties=font)
plt.setp(axs2_xlabel_text, size=20, weight='bold', color='black')
plt.setp(axs2_ylabel_text, size=20, weight='bold', color='black')
plt.show()
if __name__ == '__main__':
dataMat, labelMat = loadDataSet()
weights1, weights_array1 = stocGradAscent1(np.array(dataMat), labelMat)
weights2, weights_array2 = gradAscent(dataMat, labelMat)
plotWeights(weights_array1, weights_array2)
运行结果:
从上图可以看到,我们改进的随机梯度上升算法收敛效果更好。我们一共有100个样本点,改进的随机梯度上升算法迭代次数是150次,但上图显示15000次迭代次数,原因是使用一次样本就更新了一下回归系数。
迭代150次,每次使用100个样本,也就更新15000次。从上图左侧的改进的随机梯度上升算法可以看出,在更新2000次时回归系统已经收敛了。相当于遍历整个数据集20次的时候,回归系统已经收敛,训练完成。
在看右侧的梯度上升算法的效果,梯度上升算法每次更新回归系数要遍历整个数据集。当迭代次数为300多次的时候(遍历数据集第300次),回归系数才收敛。
并且在大的波动停止后,都会有一些小的周期性波动。因为存在一些不能正确分类的样本点,在每次迭代时引发系数的剧烈改变。
而改进的随机梯度上升算法可以减少周期性的波动。
三、实际案例:从疝气病症预测病马死亡率
本节将使用逻辑回归来预测患有疝病的马的存活问题。这里的数据包含368个样本和28个特征。这种并不一定源自马的胃肠问题,其他问题也可能引发马疝病。该数据集中包含了医院检测马疝病的一些指标,有的指标比较主观,有的指标难以测量,例如马的疼痛级别。另外需要说明的是,除了主观和难以测量的问题外,还有30%的值是缺失的。下面将首先介绍如何处理数据集中的数据缺失问题,然后再利用逻辑回归和随机梯度上升算法来预测病马的生死。
1.数据处理
数据中的缺失值是个非常棘手的问题。数据缺失究竟带来了什么问题?假设有100个样本和20个特征,这些数据都是机器收集回来的。若机器上的某个传感器损坏导致一个特征无效时该怎么办?此时是否要扔掉整个数据?这种情况下,另外19个特征怎么办?它们是否还可用?答案是肯定的。因为有时候数据相当昂贵,扔掉和重新获取都是不可取的,所以必须采用一些方法来解决这个问题。
下面给出了一些可选的做法:
(1)使用可用特征的均值来填补缺失值
(2)使用特殊值来填补缺失值,如-1
(3)忽略有缺失值的样本
(4)使用相似样本的均值填补缺失值
(5)使用另外的机器学习算法预测缺失值
现在,我们对数据集进行预处理,使其可以顺利地使用分类算法。在预处理阶段需要做两件时间:第一,所有的缺失值必须用一个实数值来替换,因为我们使用的Numpy数据类型不允许包含缺失值。这里选择0来替换所有的缺失值,恰好能适用于逻辑回归。这样做的直觉在于,我们需要的是一个在更新时不会影响系数的值。回归系数的更新公式如下:
weights = weights + alpha * error * dataMatrix[randIndex]
如果某特征对应值为0,那么该特征的系数将不做更新,即:
weights = weights
另外,由于sigmod(0)=0.5,即它对结果的预测不具有任何倾向性,因此不会对误差造成任何影响。此外,该数据集中的特征值一般不为0,因此在某种意义上说它也满足特殊值这个要求。
第二件事情是,如果在测试数据集中发现了一条数据的类别标签已经缺失,那么我们的简单做法是将该条数据丢弃。
因为类别标签和特征不同,很难确定采用某个适合的值来替换。采用逻辑回归进行分类时这种做法是合理的。
原始的数据集经过预处理后保存成两个文件:horseColicTraining.txt和horseColicTest.txt。
现在我们有一个干净可用的数据集合一个不错的优化算法,下面将这些部分融合在一起训练处一个分类器,然后利用该分类器来预测病马的生死问题。
2.测试算法:用逻辑回归进行分类
使用逻辑回归方法进行分类并不需要做很多工作,所需做的只是把测试集上每个特征向量乘以最优化方法得来的回归系数,再将该乘积结果求和,
最后输入到Sigmoid函数中即可。如果对应的Sigmoid值大于0.5就预测类别标签为1,否则为0。
代码如下:
"""
函数说明:分类函数
Parameters:
inx:输入的特征向量
weights:回归系数
Returns:
类别标签
"""
def classifyVector(inx,weights):
prob = sigmoid(sum(inx*weights))
if prob > 0.5:
return 1.0
else:
return 0.0
"""
函数说明:使用Logistic分类器进行预测
Parameters:
无
Returns:
无
"""
def colicTest():
frTrain = open("python/ch05/horseColicTraining.txt")
frTest = open("python/ch05/horseColicTest.txt")
trainList = [];trainLabels = []
for line in frTrain.readlines():
lineArr = []
currLine = line.strip().split('\t')
for i in range(21):
lineArr.append(float(currLine[i]))
trainList.append(lineArr)
trainLabels.append(float(currLine[21]))
weight = stocGradAscent1(np.array(trainList),trainLabels,500)
errorCount = 0;numTest = 0.0
for line in frTest.readlines():
lineArr = []
numTest += 1.0
currLine = line.strip().split('\t')
for i in range(21):
lineArr.append(float(currLine[i])) #处理测试集数据
if int(classifyVector (np.array(lineArr), weight)) != int( currLine[21] ):
errorCount += 1
errorRate = (float(errorCount) / numTest) *100
print("单次分类测试的错误率为:%.2f%%" % errorRate)
return errorRate
"""
函数说明:计算迭代numtests次后的错误率
"""
def multiTest():
numtests = 10;errorsum = 0.0
for k in range(numtests):
errorsum += colicTest()
print("%d次分类测试的平均错误率为:%.2f%%" % (numtests, errorsum/float(numtests)))
运行结果:
由运行结果可知,迭代10后的平均错误率为36.27%。每次分类的错误率不同是由于样本的选取是随机的,即回归系数的更新顺序是随机的,这会影响整个分类的准确度,导致每次运行得出的准确率都不一样。
四、总结
1.Logistic回归的优缺点
1.1优点
简单易理解,模型的可解释性好,从特征的权重可以看到不同的特征对最后结果的影响
分类时计算量小,速度快,存储资源低
适合二分类问题,不需要缩放输入特征
1.2缺点
容易欠拟合,分类准确率可能不高(因为形式较为简单(类似线性模型),很难拟合数据的真实分布)
对于非线性特征需要进行转换
不能用Logistic回归去解决非线性问题,因为Logistic的决策面是线性的
2.算法选择
逻辑回归的目的是寻找一个非线性函数Sigmoid的最佳拟合参数,求解过程可以由最优化算法来完成。常用的是梯度上升算法,而它又可以简化为随机梯度上升算法。
当数据集较小时,我们使用梯度上升算法。当数据集较大时,使用随机梯度上升算法。
3.如何处理数据缺失
具体的选择做法已经在上文中列举出来了,但是这个问题并没有标准答案。根据查阅资料,目前现有的一些方法包括上述的方法都有优缺点,根据具体问题具体分析。
4.相关参考
(1)《机器学习实战》读书笔记——Logistic回归https://helloai.blog.csdn.net/article/details/103427828
(2)李宏毅机器学习——逻辑回归https://blog.csdn.net/yjw123456/article/details/103333558
(3)本文使用到的代码和相关数据集已上传至Gitee仓库