# 吴恩达机器学习ex2
逻辑回归:属于监督学习之一(预测有限值)
原有的线性函数,无法很好的预测有限值,可以使用Sigmoid函数。
part1:基础知识+代码
Sigmoid函数
g代表一个常用的逻辑函数,公式为:
g(z)=11+e−z
g\left(z \right)=\frac{1}{1+e^{-z}}
g(z)=1+e−z1
合起来我们得到逻辑回归模型的假设函数:
hθ(X)=11+e−θTX
h_\theta\left(X \right)=\frac{1}{1+e^{-\theta^TX}}
hθ(X)=1+e−θTX1
其中:
z=θTX
z=\theta^TX
z=θTX
表示了决策边界
对于决策边界函数,当z>0z>0z>0时,g(z)>0.5g(z)>0.5g(z)>0.5;反之。
一般以0.5为划分,即在预测时,当g(z)>0.5g(z)>0.5g(z)>0.5时,预测为1。
除此之外,逻辑回归的成本函数与梯度函数也与线性回归的有所不同。
成本函数
J(θ)=1m∑i=1m[−y(i)ln(hθ(x(i)))−(1−y(i))ln(1−hθ(x(i)))]
J\left(\theta \right)=\frac{1}{m}\sum_{i=1}^{m}[-y^{(i)}ln(h_\theta(x^{(i)}))-(1-y^{(i)})ln(1-h_\theta(x^{(i)}))]
J(θ)=m1i=1∑m[−y(i)ln(hθ(x(i)))−(1−y(i))ln(1−hθ(x(i)))]
这个函数是通过最大似然估计得到的(与概率论有关),是因为线性回归模型中的方法在这里不在适用,逻辑回归如果还用那个方法,那么这个方程将不是凸函数(不懂为什么,只知道结果)。
但我们可以对于这个方程进行一个定性分析,从而更好理解。
首先yyy只有两个值0或1,这意味着成本函数里的y(i)y^{(i)}y(i)和1−y(i)1-y^{(i)}1−y(i)一定会有一项是0。所以我们可以只分析其中一种情况。
不妨令y=1y=1y=1,我们研究这个式子:y(i)ln(hθ(x(i)))y^{(i)}ln(h_\theta(x^{(i)}))y(i)ln(hθ(x(i)))。
如果说hθ(x(i))h_\theta(x^{(i)})hθ(x(i))这一项趋近于1,也就意味着,我们预测其属于正例(1),又因为y=1y=1y=1,所以可以说我们预测正确了,此时成本函数y(i)ln(hθ(x(i)))y^{(i)}ln(h_\theta(x^{(i)}))y(i)ln(hθ(x(i)))的值很小,约为0。
如果说hθ(x(i))h_\theta(x^{(i)})hθ(x(i))这一项趋近于0,也就意味着,我们预测其属于反例(0),又因为y=1y=1y=1,所以可以说我们预测错误了,此时成本函数y(i)ln(hθ(x(i)))y^{(i)}ln(h_\theta(x^{(i)}))y(i)ln(hθ(x(i)))的值很大,约为无穷。
梯度函数
∂J(θ)∂θj=1m∑i=1m(hθ(x(i))−y(i))xj(i)
\frac{\partial J(\theta)}{\partial\theta_j}=\frac{1}{m}\sum_{i=1}^{m}(h_\theta(x^{(i)})-y^{(i)})x_j^{(i)}
∂θj∂J(θ)=m1i=1∑m(hθ(x(i))−y(i))xj(i)
这里的梯度函数与线性回归里的梯度函数形式一样,但要注意,假设函数有差异。
刚刚写的两个函数都是没添加正则项的形式,稍后还会有添加了正则项的。
代码部分
数据处理以及成本、梯度函数的代码形式几乎与之前的一样,就不再写了。
不同的点在于,我看其他人写的过程是,直接写好成本与梯度函数,然后使用SciPy’s truncated newton(TNC)实现寻找最优参数。这里我也沿用线性回归中的梯度下降法,自己试了一下。
代码如下:
def GradientDescent(inters,alpha,X,y,theta):
cost=np.zeros((inters))
for i in range(inters):
theta-=alpha*((sigmoid(X @ theta)-y) @ X)/m
cost[i]=ComputeCost(theta,X,y)
return theta,cost
inters=1000
alpha=0.001
theta,cost=GradientDescent(inters,alpha,X,y,theta)
同时,还要不断的绘成本值的图,来判断是否有问题。
nums=np.arange(inters)
plt.figure(figsize=(12,8))
plt.plot(nums,cost)
plt.show()
自己做的时候就发现,当我的学习率设置为0.01时就会震荡,所以只能往更小了设置,但这时速度就特别慢,inters=1000时远远不够的,至少要按这个次数运行十几次,最后结果才接近正确答案(也就是使用TNC法得到的参数)
下面是TNC法的代码:
import scipy.optimize as opt
# 需要编写一个梯度函数,只需要计算梯度!
# 目标函数与梯度函数里的参数顺序,第一个必须是“要优化的那个变量”,其余的书顺序要按args参数里的顺序来
def Gradient(theta,X,y):
return ((sigmoid(X @ theta)-y) @ X)/m
result=opt.fmin_tnc(func=ComputeCost,x0=theta,fprime=Gradient,args=(X,y))
需要注意的地方就是x0的shape,貌似必须要是一维的向量。
使用这个方法,得到结果特别的快,原因是两种方法是有差异的:
梯度下降法是一种一阶优化方法通过迭代更新参数,沿着目标函数梯度的反方向移动。在逻辑回归中,目标函数通常是对数似然损失函数。梯度下降法每一步的计算仅涉及一阶导数(梯度),计算量相对较小;TNC是一种二阶优化方法,属于拟牛顿法(Quasi-Newton Method)的变种,适用于有约束优化问题。它通过近似目标函数的Hessian矩阵(二阶导数)来加速收敛,通常比一阶方法更快达到局部最优解。
对于逻辑回归这类光滑凸优化问题,TNC往往能在较少的迭代次数内收敛,但每次迭代的计算成本较高。梯度下降法每次迭代成本低,但可能需要更多迭代才能达到相同精度。
结果可视化
此时参数只有两个,还是很容易画出决策边界的。
x2=(-result[0][0]-result[0][1]*X[:,1])/result[0][2]
plt.figure(figsize=(8,8))
positive=data[data['admitted'].isin([1])]
negative=data[data['admitted'].isin([0])]
plt.scatter(positive['exam1'],positive['exam2'],s=25,c='b',marker='o',label='1')
plt.scatter(negative['exam1'],negative['exam2'],s=25,c='r',marker='x',label='0')
plt.plot(X[:,1],x2,c='g')
plt.legend()
plt.xlabel('exam1 score')
plt.ylabel('exam2 score')
plt.show()
验证模型效果
def Predict(theta,X):
y=sigmoid(X@theta)
return [1 if x>=0.5 else 0 for x in y]
predictions = Predict(result[0], X)
correct = [1 if ((a == 1 and b == 1) or (a == 0 and b == 0)) else 0 for (a, b) in zip(predictions, y)]
accuracy = (sum(map(int, correct)) / len(correct))
print ('accuracy = {0}%'.format(accuracy))
实际上,仍使用训练集来验证是不合适的。
part2:L2正则化+多参数代码
正则化
在此之前先提出一个问题:为什么要设置多个参数?
刚刚只用了两个参数,是因为画出来散点图发现,决策边界完全可以用一条直线来划分,但此时绘制出散点图发现,起码要用一个圆才可以划分,所以才需要多个参数,但如果问题上升到一个更高的维度,我们无法靠想象力来想象出这个决策边界时,那时我们可能需要不断的尝试,用结果来说话(个人看法)。
增多参数是会增加模型的复杂度的,有时过分的增多参数是完全没必要的,浪费资源人力物力。而参数过少有时会无法满足需求。这又涉及到欠拟合与过拟合。
欠拟合是参数过少模型太简单,我们可以增加参数来解决。
过拟合是模型太复杂,我们可以减少参数或者增加数据量进行训练。
注意减少参数这四个字,我们可以做的是:要么让这一项参数为0,来彻底消除其影响,要么就是在目标函数,也就是成本函数中添加惩罚项;让计算机通过计算,决定这些参数的大小,这样做,结果往往是,那些多余的参数,最后大小总是趋近于0的,但毕竟不是0,所以该模型仍存留有那些参数的微小影响,这是有利的。第二种方法就是L2正则化。
让我们看一下加入正则化后的成本、梯度函数。
加入L2正则项的成本函数
J(θ)=1m∑i=1m[−y(i)ln(hθ(x(i)))−(1−y(i))ln(1−hθ(x(i)))]+λ2m∑j=1nθ2 J\left(\theta \right)=\frac{1}{m}\sum_{i=1}^{m}[-y^{(i)}ln(h_\theta(x^{(i)}))-(1-y^{(i)})ln(1-h_\theta(x^{(i)}))]+\frac{\lambda}{2m}\sum_{j=1}^{n}\theta^2 J(θ)=m1i=1∑m[−y(i)ln(hθ(x(i)))−(1−y(i))ln(1−hθ(x(i)))]+2mλj=1∑nθ2
加入L2正则项的梯度函数
∂J(θ)∂θj=1m∑i=1m(hθ(x(i))−y(i))xj(i)+λmθj
\frac{\partial J(\theta)}{\partial\theta_j}=\frac{1}{m}\sum_{i=1}^{m}(h_\theta(x^{(i)})-y^{(i)})x_j^{(i)}+\frac{\lambda}{m}\theta_j
∂θj∂J(θ)=m1i=1∑m(hθ(x(i))−y(i))xj(i)+mλθj
还有许多问题需要解答。
- 注意到参数λ\lambdaλ,它的大小也不能过大或过小。如果过大,那么成本函数可能更多的关注惩罚项,也就是关注于降低参数的大小,而不是其真正的成本;如果过小,那么惩罚项的影响太小,可能无法达到你的要求。
- 一般我们只需要对除常数项bbb以外的参数进行正则化,如果真的也要对bbb进行正则化,那也无伤大雅。原因也很好理解,常数项的大小,其实并不能决定模型的什么。
多参数代码
可以使用下面的代码思路来添加多个参数,只需要设置最高次数。
degree=3
for i in range(1,degree):
for j in range(0,i+1):
data['x(1)'+str(i-j)+',x(2)'+str(j)]=data['exam1']**(i-j) * data['exam1']**j
data.head()
最后可以看一下现在的数据长什么样子,并根据实际情况,选择合适的参数,从而建立自己的数据X和y。
模型部分代码
此外还需要重新写一下成本、梯度函数的代码,同样的方法,使用TNF法得到参数,我在编写的时候,为了方便,对常数项也进行了正则化,否则还需要在“成本、梯度函数的代码”中分两种情况。
最后,还可以使用高级Python库像scikit-learn来解决这个问题。
from sklearn import linear_model#调用sklearn的线性回归包
model = linear_model.LogisticRegression(penalty='l2', C=1.0)
model.fit(X, y)
model.score(X, y)
result=model.coef_
b=model.intercept_
print(result,b)
其中:model.score(X, y)得到的是模型的性能即准确率,默认也是在0.5处进行划分的。
model.coef_是除了常数项以外的参数,model.intercept_是常数项参数。需要注意的是,使用TNF方法得到的参数结果也是包含一个常数项的,这是因为我们手动的对原始数据添加了一列“1”。此时我们仍用这个数据得到了model,这个model给我们的这一列“1”也计算出了一个参数,除此之外还有model.intercept_,是需要考虑在内的。
决策函数图像
由于是多参数,此时有关zzz的方程大概率是隐性方程,图像不太好画,可以尝试使用二维画等高线的方法来绘图。
x1=np.linspace(-1,1,1000)
x2=np.linspace(-1,1,1000)
x1,x2=np.meshgrid(x1,x2)
def my_fun(result,x1,x2):
return result[0][0]*1+result[0][1]*x1+result[0][2]*x2+result[0][3]*x1**2+result[0][4]*x2**2+result[0][5]*x1*x2+model.intercept_
plt.figure(figsize=(8,8))
positive=data[data['admitted'].isin([1])]
negative=data[data['admitted'].isin([0])]
plt.scatter(positive['exam1'],positive['exam2'],s=25,c='b',marker='o',label='1')
plt.scatter(negative['exam1'],negative['exam2'],s=25,c='r',marker='x',label='0')
plt.legend()
plt.xlabel('exam1 score')
plt.ylabel('exam2 score')
plt.contour(x1,x2,my_fun(result,x1,x2),levels=[0])
plt.show()
其中x1,x2是横、纵轴。
x1,x2=np.meshgrid(x1,x2),这一步实际上是得到了一个网格,表示这片区域上的每个坐标。
my_fun(result,x1,x2)就是根据这个坐标,计算得到了一个同样维度的,对应于每个坐标点的函数值z。
plt.contour(x1,x2,my_fun(result,x1,x2),levels=[0]),其中levels=[0]表示这个等高线图,我们只要z=0这一条等高线。
这部分介绍的比较抽象,可以自己用一个简单的隐函数,按这个方法,尝试绘制一下等高线图。
530

被折叠的 条评论
为什么被折叠?



