逻辑回归-梯度下降迭代次数足够的情况下得到的全域最小值解不唯一

32 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天,点击查看活动详情

文章有问题......

  在前面的实验,我们发现

features_temp = pd.read_csv('iris.csv').iloc[:, 1: 3].values
labels_temp = pd.read_csv('iris.csv').iloc[:, -1].values
labels_temp[labels_temp != 'Iris-setosa'] = 0
labels_temp[labels_temp == 'Iris-setosa'] = 1
labels = labels_temp.astype(float).reshape(-1, 1)
features = np.concatenate([features_temp, np.ones(shape=labels.shape)], 1)

np.random.seed(24)  

batch_size = 10
num_epoch = 200
lr_init = 0.5
n = features.shape[1]
w = np.random.randn(n, 1)
lr_lambda = lambda epoch: 0.95 ** epoch

for i in range(num_epoch):
    w = sgd_cal(features, w, labels, logit_gd, batch_size=batch_size, epoch=1, lr=lr_init*lr_lambda(i))
w
---
array([[ 2.8630463 ],
       [-3.19680442],
       [ 0.01148845]])

logit_acc(features, w, labels, thr=0.5)
---
1.0

这里我们是用np.random.randn(n, 1),来随机选择初始值w的,后面我们又手动指定了w的初始值

w = np.array([[10.0],[-1],[0]])
for i in range(num_epoch):
    w = sgd_cal(features, w, labels, logit_gd, batch_size=batch_size, epoch=1, lr=lr_init*lr_lambda(i))
w
---
array([[ 6.79050273],
       [-6.44217682],
       [-1.0895957 ]])

logit_acc(features, w, labels, thr=0.5)
---
1.0

我们又得到了一组w的值,并且如果我们用一个列表在循环中间记录w的变化可以发现,两次梯度下降都已经收敛,但是我们仍然得到了两个不同的值,这是因为我们此时全域最小值不是一个单独的解,而是一个解集。

  我们先看一下散点图

plt.scatter(features[(labels == 0).flatten(), 0], features[(labels == 0).flatten(), 1], color='red')
plt.scatter(features[(labels == 1).flatten(), 0], features[(labels == 1).flatten(), 1], color='blue')

![[附件/Pasted image 20221229161938.png|300]]

显然在中间,我们可以画出很多条直线使得逻辑回归的损失函数为0,因此其解为一个集合

  进一步的,我们可以绘制一下当d=0时解集

x1 = np.linspace(-1,10,110)
x2 = np.linspace(-10,1,110)
x11,x22 = np.meshgrid(x1,x2)

xx = np.concatenate([x11.reshape(-1, 1),x22.reshape(-1, 1),np.zeros((x1.shape[0] * x2.shape[0],1))],1)
xx_df = pd.DataFrame(xx)
xx_df[3] = xx_df.apply(lambda x:logit_acc(features,[[x[0]],[x[1]],[x[2]]],labels),axis=1)
xx = xx_df.values
plt.contourf(xx[:,0].reshape(x11.shape),xx[:,1].reshape(x11.shape),xx[:,3].reshape(x11.shape))

![[附件/Pasted image 20221229172532.png|300]]

其实其中黄色的就是解集,我们单独把它标记出来

其实黄色的不是解集,只是表示此处准确率是1.0,但是实际上逻辑回归的损失函数并不是准确率,而是交叉熵函数,因此黄色区域中的部分点如果执行逻辑回归的梯度下降,数值仍然会变化(所以说这篇文章写的就不对)

plt.contourf(xx[:,0].reshape(x11.shape),xx[:,1].reshape(x11.shape),(xx[:,3] == 1).astype(float).reshape(x11.shape),cmap='binary')

![[附件/Pasted image 20221229172649.png|300]]

不会画三维图,所以只花了d=0的情况,实际上应该可以在三维空间中表示,大致图像应该是一个三棱柱,继续画d=0,2,4,6,8,10的截面图可以观察一下

x1 = np.linspace(-3,3,60)
x2 = np.linspace(-5,1,60)
x11,x22 = np.meshgrid(x1,x2)
fig,axes = plt.subplots(3,2)
axes = axes.flatten()
for i in range(0,6):
    xx = np.concatenate([x11.reshape(-1, 1),x22.reshape(-1, 1),np.full_like(x11.reshape(-1, 1),i*2)],1)
    xx_df = pd.DataFrame(xx)
    xx_df[3] = xx_df.apply(lambda x:logit_acc(features,[[x[0]],[x[1]],[x[2]]],labels),axis=1)
    xx = xx_df.values
    axes[i].contourf(xx[:,0].reshape(x11.shape),xx[:,1].reshape(x11.shape),(xx[:,3] == 1).astype(float).reshape(x11.shape),cmap='binary')

![[附件/Pasted image 20221229174909.png|400]]

pandas里面DataFrame有个apply方法,如果使用lambda函数,例如

np.random.seed(20)
df = pd.DataFrame(np.random.randint(0,10,(3,2)))
df
---
	0	1
0	3	9
1	4	6
2	7	2

df[2] = df.apply(lambda x:x[0] * x[1] -1,1)
# 这里第一个参数为lambda函数,第二个指示x代表什么,当x=1的时候x就是每行数据,因此一般用此方法根据每一行已知数据来添加新列
df
---
	0	1	2
0	3	9	26
1	4	6	23
2	7	2	13

def f1(x,y):
    return x+y

df[3] = df.apply(lambda x:f1(x[0],x[1]),1)
# lambda函数中的x可以当做参数传入自定义的函数中
df
---
	0	1	2	3
0	3	9	26	12
1	4	6	23	10
2	7	2	13	9

np.random.seed(20)
df = pd.DataFrame(np.random.randint(0,10,(3,2)))
df
---
	0	1
0	3	9
1	4	6
2	7	2

df.loc[3,:] = df.apply(lambda x:x[2] - x[1],0)
# 这里因为第二个参数指定为0,因此x就是df的每一列,其实[2]、[1],就是第二行、第一行,df.loc[3,:]表明增加第三行
df
---
	0	1
0	3.0	9.0
1	4.0	6.0
2	7.0	2.0
3	3.0	-4.0