吴恩达机器学习ex2学习总结(python实现)

# 吴恩达机器学习ex2

逻辑回归:属于监督学习之一(预测有限值)
原有的线性函数,无法很好的预测有限值,可以使用Sigmoid函数。

part1:基础知识+代码

Sigmoid函数

g代表一个常用的逻辑函数,公式为:
g(z)=11+e−z g\left(z \right)=\frac{1}{1+e^{-z}} g(z)=1+ez1
合起来我们得到逻辑回归模型的假设函数:
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=1m[y(i)ln(hθ(x(i)))(1y(i))ln(1hθ(x(i)))]
这个函数是通过最大似然估计得到的(与概率论有关),是因为线性回归模型中的方法在这里不在适用,逻辑回归如果还用那个方法,那么这个方程将不是凸函数(不懂为什么,只知道结果)。
但我们可以对于这个方程进行一个定性分析,从而更好理解。
首先yyy只有两个值0或1,这意味着成本函数里的y(i)y^{(i)}y(i)1−y(i)1-y^{(i)}1y(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)} θjJ(θ)=m1i=1m(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=1m[y(i)ln(hθ(x(i)))(1y(i))ln(1hθ(x(i)))]+2mλj=1nθ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 θjJ(θ)=m1i=1m(hθ(x(i))y(i))xj(i)+mλθj
还有许多问题需要解答。

  1. 注意到参数λ\lambdaλ,它的大小也不能过大或过小。如果过大,那么成本函数可能更多的关注惩罚项,也就是关注于降低参数的大小,而不是其真正的成本;如果过小,那么惩罚项的影响太小,可能无法达到你的要求。
  2. 一般我们只需要对除常数项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这一条等高线。
这部分介绍的比较抽象,可以自己用一个简单的隐函数,按这个方法,尝试绘制一下等高线图。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值