作业题目
设想你是某门课程的教师,想通过学生平时和期末两次的评定成绩,来决定他们是否通过这门课程。现在你拥有之前学生的可以用于训练逻辑回归的训练样本集logistic_regression_data-1.txt。对于每一个训练样本,你有他们平时和期末评分及最后是通过课程的结果(1为通过,0为不通过)。由此建立逻辑回归分类器。
问题概述
逻辑回归(Logistic Regression)是一种用于解决二分类(0 or 1)问题的机器学习方法,用于估计某种事物的可能性。本次作业需要通过编写python程序实现一个逻辑回归分类器,通过一个学生两次考试的成绩,判断他们是否可以通过此次课程。
代价函数
构造代价函数如下图所示:
L
o
s
s
(
h
θ
(
x
)
,
y
)
=
∑
−
y
∗
l
o
g
(
h
θ
(
x
)
)
−
(
1
−
y
)
∗
l
o
g
(
1
−
h
θ
(
x
)
)
Loss(h_\theta(x),y)=\sum -y*log(h_\theta(x))-(1-y)*log(1-h_\theta(x))
Loss(hθ(x),y)=∑−y∗log(hθ(x))−(1−y)∗log(1−hθ(x))
在上式中
h
θ
(
x
)
=
1
1
+
e
(
−
θ
T
x
)
h_\theta(x)=\frac{1}{1+e^(-\theta^Tx)}
hθ(x)=1+e(−θTx)1
Python代码为:
def Loss(W)
loss = 0.0
for i in range(N):
loss += -Y[i]*np.log(sig(np.inner(W,X[i]))) - (1 - Y[i])*np.log(1 - sig(np.inner(W,X[i])))
return loss
求解最优化问题
梯度下降法
梯度下降法的下降方向在每次迭代时都指向梯度下降最快的方向。这种方法在远离最优解时下降速度很快,但越靠近最优解收敛速度越慢,所以很难收敛。
同时,梯度下降法的学习率参数不能指定过大,否则可能出现np.log函数无法正常计算的问题。在循环中还需要加上最大迭代次数,避免因为不收敛而无法跳出循环。
牛顿法
牛顿法可以直接以全局的最优解为方向优化问题,而不依靠局部的梯度方向,所以牛顿法更容易收敛。牛顿法需要计算出代价函数的Hessian矩阵,前提是该矩阵必须正定,如果矩阵非正定则需要使用拟牛顿法,构造校正矩阵代替Hessian阵。
牛顿法可以很快收敛得到优化问题的最优解。通过以上两种方法可以求得决策边界的系数。
结果验证
通过运算得到了决策边界的系数(分别对应w0+w1x1+w2w2=0的三个系数),以及运算结果与最优值的误差值,以及迭代次数。可以发现梯度下降法迭代到了设置的最大次数(10000次),而牛顿法迭代两次就得到了最优解。
上图中,绿色边界为牛顿法得到的边界,而蓝色边界为梯度下降法得到的结果,可见最速下降法还远远没有迭代结束。
由上图可以看出牛顿法的决策边界基本正确,有个别散点落在了其他区域,但整体来说,边界是正确的。对于训练数据来说,该模型的准确率可以达到:90/100=90%。
作业代码
import numpy as np
import matplotlib.pyplot as plt
def readData():
X = []
Y = []
with open ('logistic_regression_data-1.txt','r') as file:
for line in file.readlines():
cur_line = line.strip().split(',')
X.append([1.0, float(cur_line[0]),float(cur_line[1])])
Y.append(float(cur_line[-1]))
X = np.asarray(X, dtype=np.double)
N = X.shape[0]
Y = np.asarray(Y, dtype=np.double)
return N,X,Y
def sig(x):
return 1/(1 + np.exp(-x))
def Loss(W):
loss = 0.0
for i in range(N):
loss += -Y[i]*np.log(sig(np.inner(W,X[i]))) - (1 - Y[i])*np.log(1 - sig(np.inner(W,X[i])))
return loss
def GD(X,Y,eta,tol):
W = np.array([0.0,0.0,0.0])
error = 10
steps = 0
stepL = []
errorL = []
max_cycle = 10000
while((error > tol)&(steps<max_cycle)):
l1 = Loss(W)
steps += 1
deltaW = 0
deltaW += gradL(X,Y,W)
W = W - eta*deltaW
l2 = Loss(W)
error = np.abs(l2 - l1)
stepL.append(steps)
errorL.append(error)
plt.scatter(stepL,errorL)
plt.show()
return W,error,steps
def gradL(X,Y,W):
Y = np.squeeze(Y)
D = sig(np.inner(W,X)) - Y
G = np.inner(X.T,D)
return G
def hessian(X,N,W):
h_w = []
for i in range(N):
h_w += [sig(np.inner(W,X[i]))*(1 - sig(np.inner(W,X[i])))]
h_w = np.asarray(h_w)
B = np.diag(h_w)
H = np.dot(X.T,np.dot(B,X))
return H
def Newton(X,Y,tol):
W = np.array([0.0,0.0,0.0])
error = 10
steps = 0
stepL = []
errorL = []
while(error > tol):
l1 = Loss(W)
steps += 1
for i in range(N):
H = hessian(X,N,W)
G = gradL(X,Y,W)
W = W - np.dot(np.linalg.pinv(H),G)
l2 = Loss(W)
error = np.abs(l2 - l1)
stepL.append(steps)
errorL.append(error)
plt.scatter(stepL,errorL)
plt.show()
return W,error,steps
if __name__ == '__main__':
N,X,Y = readData()
W, error,steps = GD(X,Y,0.00001,10**(-4))
print("\nGradient Descent\n", W, "\nError: ", error, " Steps: ", steps)
W2, error,steps = Newton(X,Y,10**(-5))
print("\nNewton's\n", W2, "\nError: ", error, " Steps: ", steps)
X1_0 = []
X2_0 = []
X1_1 = []
X2_1 = []
for i,t in enumerate(Y):
if(t == 0):
X1_0 += [X[:,1][i]]
X2_0 += [X[:,2][i]]
else:
X1_1 += [X[:,1][i]]
X2_1 += [X[:,2][i]]
plt.scatter(X1_0,X2_0, s=30, c='red', marker='s')
plt.scatter(X1_1,X2_1, s=30, c='green')
X1 = X[:,1]
#T1 = - W[1]/W[2]*X1
#T2 = [-W[0]/W[2] for i in range(X.shape[0])]
#Y1 = T2 + T1
T1 = - W2[1]/W2[2]*X1
T2 = [-W2[0]/W2[2] for i in range(X.shape[0])]
Y2 = T2 + T1
#plt.plot(X1,Y1,'-')
plt.plot(X1,Y2,'-.')
plt.xlabel('1st Exam Score')
plt.ylabel('2nd Enam Score')
plt.show()