参考资料:
1.黄海广老师:吴恩达机器学习笔记github
本文是吴恩达机器学习课程中的第二个编程训练。关于逻辑回归的详细介绍可以参考吴恩达机器学习课程
编程任务:
已知一些学生的两次考试成绩和录取情况,训练一个分类器,可以根据两次考试成绩判断学生是否被录取
逻辑回归算法的实现流程基本上同线性回归的实现流程是一样的。
逻辑回归算法与线性回归算法的不同主要体现在特征与标签之间的函数关系上:
线性回归算法: h θ ( x ) = θ 0 x 0 + θ 1 x 1 + θ 2 x 2 + θ 3 x 3 + . . . h_θ(x) = θ_0x_0 + θ_1x_1+θ_2x_2+θ_3x_3+... hθ(x)=θ0x0+θ1x1+θ2x2+θ3x3+...
逻辑回归算法: h θ ( x ) = g ( θ 0 x 0 + θ 1 x 1 + θ 2 x 2 + θ 3 x 3 + . . . ) h_θ(x) =g(θ_0x_0 + θ_1x_1+θ_2x_2+θ_3x_3+...) hθ(x)=g(θ0x0+θ1x1+θ2x2+θ3x3+...)
这里的函数 g ( z ) g(z) g(z)是sigmoid函数:
g ( z ) = 1 1 + e − z g(z) = \frac{1}{1+e^{-z}} g(z)=1+e−z1
上图是sigmoid函数的图像,其值在0,1之间
这里补充说明一下逻辑回归算法的假设函数为什么要这样假设。
由于逻辑回归算法准确来说是一个分类问题,标签值只有0,1两种可能。我们采用线性回归的假设是很难进行数据的分类的。而逻辑回归算法则采用概率的形式来进行分类。由于sigmoid函数的取值为(0,1),我们可以假设当sigmoid函数的值大于等于0.5时,将其分为1类,小于0.5时,将其分为0类。这样就很好的解决了一个分类问题。
由于我们采用的假设函数已经与线性回归时采用的假设函数不同,因此,代价函数也会有相应的变化。
J ( θ ) = 1 m ∑ m ( y ( i ) ∗ log ( h θ ( x ( i ) ) ) + ( 1 − y ( i ) ) log ( 1 − h θ ( x ( i ) ) ) ) J(θ) = \frac{1}{m}\sum^m(y^{(i)}*\log(h_θ(x^{(i)})) +(1-y^{(i)})\log(1-h_θ(x^{(i)}))) J(θ)=m1∑m(y(i)∗log(hθ(x(i)))+(1−y(i))log(1−hθ(x(i))))
这里的上标 ( i ) (i) (i)表示的是第几个样本中的特征和标签
对代价函数求取偏导:
∂ J ( θ ) ∂ θ = 1 m ∑ m ( h θ ( x ( i ) ) − y ( i ) ) x ( i ) \frac{∂J(θ)}{∂θ} =\frac{1}{m}\sum^m(h_θ(x^{(i)}) -y^{(i)})x^{(i)} ∂θ∂J(θ)=m1∑m(hθ(x(i))−y(i))x(i)
在线性回归过程中使用的方法为:批量梯度下降法。批量梯度下降法在逻辑回归中也同样适用,但是在本例中,笔者使用批量梯度下降法并没有能够(完全不能)解决分类问题。
在本例中使用到的是另外一种优化算法:Newton-CG,此外还有共轭梯度法(CG),变尺度法(BFGS),限制变尺度法(L-BFGS)
关于这些优化算法的具体数学原理,这里不做介绍(因为笔者也不懂)
Newton-CG的数学原理可以参考论文基于Newton法改进的BFGS迭代法与Newton-CG算法
但这都不是重点,重点是应用,如何应用这些算法来最小化代价函数
这里参考scipy模块函数使用介绍
scipy.optimize.minimize(fun, x0, args=(), method=None, jac=None, hess=None, hessp=None, bounds=None, constraints=(), tol=None, callback=None, options=None)
'''
fun:需要最小化的目标函数
x0:迭代的初始值,要求x0.shape为(n,)
args:传递给目标函数的额外参数
method:要采用的最小化方法
jac:计算梯度向量(即偏导数),可调用函数
'''
在本例中,决策边界即: X × θ = 0 X × θ=0 X×θ=0即: θ 0 x 0 + θ 1 x 1 + θ 2 x 2 = 0 θ_0x_0 + θ_1x_1+θ_2x_2=0 θ0x0+θ1x1+θ2x2=0
我们已知: θ 0 , x 0 = 1 , θ 1 , x 1 , θ 2 θ_0,x_0=1,θ_1,x_1,θ_2 θ0,x0=1,θ1,x1,θ2,因此我们可以根据上式求得 x 2 x_2 x2
于是我们就得到了一条直线,这个直线就是决策边界: x 2 = − ( θ 0 θ 2 + θ 1 θ 0 x 1 ) x_2 = -(\frac{θ_0}{θ_2} + \frac{θ_1}{θ_0}x_1) x2=−(θ2θ0+θ0θ1x1)
数据集:自黄海广老师的GitHub仓库,具体链接
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import scipy.optimize as opt
from sklearn.metrics import classification_report # 这个包是评价报告
def get_X(df): # 获取特征
ones = pd.DataFrame({'ones': np.ones(len(df))})
data = pd.concat([ones, df], axis=1)
return data.iloc[:, :-1].values
def get_y(df): # 获取标签
return np.array(df.iloc[:, -1])
def normalize_feature(df): # 特征缩放,本例中并没有使用
return df.apply(lambda column: (column - column.mean()) / column.std())
def sigmoid(z): # 定义sigmoid函数
return 1 / (1 + np.exp(-z))
def cost(theta, X, y): # 定义代价函数,这里需要注意X,theta,y的维度
return np.mean(-y * np.log(sigmoid(X @ theta)) - (1 - y) * np.log(1 - sigmoid(X @ theta)))
# (100,1)*[(100,3)(3,1)] 这里并不影响计算计算结果
# 值得一提的是,在numpy中,+,-,*,/都只是相对应元素的运算,不遵循矩阵计算规则
# numpy中的矩阵计算需要使用@或者.dot()函数
# 同样也可以将ndarray类型数据通过np.matrix()转换成矩阵,使用*进行计算
def gradient(theta, X, y): # 求偏导
return (1 / len(X)) * X.T @ (sigmoid(X @ theta) - y) # (3,100)[(100,3)(3,1)]====>(3,1)
def predict(x, theta): # 预测函数,用来进行验证
prob = sigmoid(x @ theta)
return (prob >= 0.5).astype(int)
# 读取数据,获取特征和标签,并初始化theta
data = pd.read_csv("ex2data1.txt", names=['exam1', 'exam2', 'admitted'])
X = get_X(data) # (100, 3)
y = get_y(data) # (100,)
theta = np.zeros(3) # (3,)
# 使用优化算法Newton-CG来最小化代价函数
result = opt.minimize(fun=cost, x0=theta, args=(X, y), method='Newton-CG', jac=gradient)
final_theta = result.x # 代价函数最小化后的得到的theta
y_pred = predict(X, final_theta) # 对原有的数据特征进行预测
print(classification_report(y, y_pred)) # 真实值,预测值,进行比较,输出评价报告
# 绘制散点图
sns.set(context="notebook", style="ticks", font_scale=1.5)
sns.lmplot('exam1', 'exam2', hue='admitted', data=data,
height=6,
fit_reg=False,
scatter_kws={"s": 25}
)
# 绘制决策边界
coefficient = -(result.x / result.x[2]) # 已知权重,x_0 = 1,则可以将x_1视为x,将x_2视为y,根据θx = 0 来求解y
x = np.arange(130, step=0.1) # 这里的x就是x_1
y = coefficient[0] + coefficient[1] * x # 这里的y就是用x_1 表示的x_2
plt.plot(x, y, 'grey')
plt.xlim(0, 130)
plt.ylim(0, 130)
plt.title('Decision Boundary')
plt.show()
precision recall f1-score support
0 0.87 0.85 0.86 40
1 0.90 0.92 0.91 60
accuracy 0.89 100
macro avg 0.89 0.88 0.88 100
weighted avg 0.89 0.89 0.89 100