1、问题背景
在训练的初始阶段,我们将要构建一个逻辑回归模型来预测,某个学生是否被大学录取。设想你是大学相关部分的管理者,想通过申请学生两次测试的评分,来决定他们是否被录取。现在你拥有之前申请学生的可以用于训练逻辑回归的训练样本集。对于每一个训练样本,你有他们两次测试的评分和最后是被录取的结果。为了完成这个预测任务,我们准备构建一个可以基于两次测试评分来评估录取可能性的分类模型。
2、使用的数据是ex2data1.txt
3、不带正则化项
1 数据读取与可视化
- 读取数据
- 分别使用散点图可视化被录取和未被录取的学生,得到图1所示的结果,似乎具有一条较为清晰的决策边界
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
path = 'ex2data1.txt'
data = pd.read_csv(path, header=None, names=['Exam 1', 'Exam 2', 'Admitted'])
data.head()
## 数据可视化
positive = data[data['Admitted'].isin([1])] # 正样本表示被录取
negative = data[data['Admitted'].isin([0])] # 负样本表示未被录取
fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(positive['Exam 1'], positive['Exam 2'], s=50, c='b', marker='o', label='Admitted')
ax.scatter(negative['Exam 1'], negative['Exam 2'], s=50, c='r', marker='x', label='Not Admitted')
ax.legend()
ax.set_xlabel('Exam 1 Score')
ax.set_ylabel('Exam 2 Score')
plt.show()
2 获取数据特征及标签
- 与线性回归类似,需要分别获取数据的特征及标签
## 定义获取特征及标签的函数
def get_X(df): # 读取特征
# """
# use concat to add intersect feature to avoid side effect
# not efficient for big dataset though
# """
ones = pd.DataFrame({
'ones': np.ones(len(df))}) # ones是m行1列的dataframe
data = pd.concat([ones, df], axis=1) # 合并数据,根据列合并
return np.array(data.iloc[:, :-1]) # 这个操作返回 ndarray,不是矩阵
def get_y(df): # 读取标签
# '''assume the last column is the target'''
return np.array(df.iloc[:, -1]) # df.iloc[:, -1]是指df的最后一列
def normalize_feature(df):
# """Applies function along input axis(default 0) of DataFrame."""
return df.apply(lambda column: (column - column.mean()) / column.std()) # 特征缩放
- 使用上述函数对实验数据进行处理,同时初始化参数向量
## 获取数据及其标签
X = get_X(data)
print(X.shape)
print(type(X))
y = get_y(data)
print(y.shape)
print(type(y))
theta = theta=np.zeros(3) # X(m*n) so theta is n*1
print(theta.shape)
print(type(theta))
## 输出如下:
(100, 3)
<class 'numpy.ndarray'>
(100,)
<class 'numpy.ndarray'>
(3,)
<class 'numpy.ndarray'>
3 sigmoid函数
- 常用的sigmoid函数如式(1)所示。
g ( z ) = 1 1 + e − z (1) g\left( z \right)=\frac{1}{1+{ {e}^{-z}}} \tag{1} g(z)=1+e−z1(1)
- 而逻辑回归的假设函数如式(2)所示。
h θ ( x ) = 1 1 + e − θ T X (2) { {h}_{\theta }}\left( x \right)=\frac{1}{1+{ {e}^{-{ {\theta }^{T}}X}}} \tag{2} hθ(x)=1+e−θTX1(2)
- 定义sigmoid函数,并检查有效性。由图2可知,sigmoid函数有效。
## 定义sigmoid函数
def sigmoid(z):
# 可以作用于向量或矩阵的每一个元素
return 1 / (1 + np.exp(-z))
## 检查sigmoid函数是否有效
nums = np.arange(-10, 10, step=1)
fig, ax = plt.subplots(figsize=(12,8))
ax.plot(nums, sigmoid(nums), 'r')
plt.show()
4 代价函数
- 逻辑回归的代价函数如式(3)所示:
J ( θ ) = 1 m ∑ i = 1 m [ − y ( i ) log ( h θ ( x ( i ) ) ) − ( 1 − y ( i ) ) log ( 1 − h θ ( x ( i ) ) ) ] (3) J\left( \theta \right)=\frac{1}{m}\sum\limits_{i=1}^{m}{[-{ {y}^{(i)}}\log \left( { {h}_{\theta }}\left( { {x}^{(i)}} \right) \right)-\left( 1-{ {y}^{(i)}} \right)\log \left( 1-{ {h}_{\theta }}\left( { {x}^{(i)}} \right) \right)]} \tag{3} J(θ)=m1i=1∑m[−y(i)log(hθ(x(i)))−(1−y(i))log(1−hθ(x(i)))](3)
- 由此,定义代价函数:
## 定义代价函数
def cost(theta, X, y):
''' cost fn is -l(theta) for you to minimize'''
return np.mean(-y * np.log(sigmoid(X @ theta)) - (1 - y) * np.log(1 - sigmoid(X @ theta)))
# X @ theta与X.dot(theta)等价
## 使用代价函数计算初始参数对应的代价
cost(theta, X, y) # 计算初始参数对应的代价
## 结果如下:
0.6931471805599453
- 代价函数计算过程中的维度变化如(4)(5)所示:
( − y ) ( 100 , 1 ) − ( s i g m o i d ( ( X ( 100 , 3 ) θ ( 3 , 1 ) ) ( 100 , 1 ) ) ) ( 100 , 1 ) = ( . . . ) ( 100 , 1 ) (4) (-y)_{(100,1)}-(sigmoid((X_{(100,3)}\theta_{(3,1)})_{(100,1)}))_{(100,1)}=(...)_{(100,1)} \tag{4} (−y)(100,1)−(sigmoid((X(100,3)θ(3,1))(100,1)))(100,1)=(...)(100,1)(4)
n p . m e a n ( ( . . . ) ( 100 , 1 ) ) = 实数 (5) np.mean((...)_{(100,1)})=实数 \tag{5} np.mean((...)(100,1))=实数(5)
5 梯度下降
- 逻辑回归的梯度下降更新公式如式(6)所示。与线性回归类似,但是假设函数不同。
∂ J ( θ ) ∂ θ j = 1 m ∑ i = 1 m ( h θ ( x ( i ) ) − y ( i ) ) x j ( i ) (6) \frac{\partial J\left( \theta \right)}{\partial { {\theta }_{j}}}=\frac{1}{m}\sum\limits_{i=1}^{m}{({ {h}_{\theta }}\left( { {x}^{(i)}} \right)-{ {y}^{(i)}})x_{_{j}}^{(i)}} \tag{6} ∂θj∂J(θ)=m1i=1∑m(hθ(x(i))−y(i))xj(i)(6)
- 定义梯度下降函数
def gradient(theta, X, y):
# '''just 1 batch gradient'''
return (1 / len(X)) * X.T @ (sigmoid(X @ theta) - y)
- 梯度下降函数的维度变化如(7)(8)所示
( ( s i g m o i d ( X ( 100 , 3 ) θ ( 3 , 1 ) ) ) ( 100 , 1 ) − y ( 100 , 1 ) ) ( 100 , 1 ) (7) ((sigmoid(X_{(100,3)}\theta_{(3,1)}))_{(100,1)}-y_{(100,1)})_{(100,1)} \tag{7} ((sigmoid(X(100,3)θ(3,1)))(100,1)−y(100,1))(100,1)(7)
( X T ) ( 3 , 100 ) ( . . . ) ( 100 , 1 ) = ( . . . ) ( 3 , 1 ) (8) (X^{T})_{(3,100)}(...)_{(100,1)}=(...)_{(3,1)} \tag{8} (XT)(3,100)(...)(100,1)=(...)(3,1)(8)
6 拟合参数
- 使用
scipy.optimize.optimize
方法来拟合参数:
import scipy.optimize as opt
res = opt.minimize(fun=cost, x0=theta, args=(X, y), method='Newton-CG', jac=gradient)
## 用到的函数形参解释:
'''
:param fun:需要最小化的目标函数,这里就是代价函数
:param x0:需要优化的参数,这里就是向量\theta
:param args:其他需要输入到目标函数中的量,这里就是数据特征和标签
:param method:使用的搜寻算法
:param jac:用于计算梯度向量的函数
:return:优化结果
'''
## 获得拟合结果
final_theta = res.x
print(final_theta) # [-25.15594466 0.20618886 0.20142773]
7 预测
- 定义预测函数:
def predict(x, theta):
prob = sigmoid(x @ theta)
return (prob >= 0.5).astype(int)
- 使用训练集预测(由于前面并未划分训练集和测试集):
y_pred = predict(X, final_theta)
- 预测结果如图3所示:
8 决策边界
- 逻辑回归的决策边界是概率值等于0.5,因此:
h θ ( x ) = 1 1 + e − θ T x = 0.5 (9) h_{\theta}(x)=\frac{1}{1+e^{-\theta^{T}x}}=0.5 \tag{9} hθ(x)=1+e−θTx1=0.5(9)
- 基于(9)式,得到决策边界为:
θ T x = 0 \theta^{T}x=0 θTx=0
- 决策边界上的点对应于sigmoid函数输出为0.5的点,对于每一个这样的点,(1)式可改写成:
θ 0 + θ 1 x 1 + θ 2 x 2 = 0 x 2 = − ( θ 0 + θ 1 x 1 ) θ 2 x 2 = − θ 0 θ 2 − θ 1 θ 2 x 1 \theta_{0}+\theta_{1}x_{1}+\theta_{2}x_{2}=0\\ x_{2}=\frac{-(\theta_{0}+\theta_{1}x_{1})}{\theta_{2}}\\ x_{2}=-\frac{\theta_{0}}{\theta_{2}}-\frac{\theta_{1}}{\theta_{2}}x_{1} θ0+θ1x1+θ2x2=0x2=θ2−(θ0+θ1x1)x2=−θ2θ0−θ2θ1x1
- 具体实现,可视化结果如图4所示。
## 计算决策边界
coef = -(final_theta/ final_theta[2]) # find the equation
print(coef)
x = np.arange(130, step=0.1)
y = coef[0] + coef[1]*x
## 可视化决策边界
## 数据可视化
positive = data[data['Admitted'].isin([1])] # 正样本表示被录取
negative = data[data['Admitted'].isin([0])] # 负样本表示未被录取
fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(positive['Exam 1'], positive['Exam 2'], s=50, c='b', marker='o', label='Admitted')
ax.scatter(negative['Exam 1'], negative['Exam 2'], s=50, c='r', marker='x', label='Not Admitted')
ax.legend()
ax.set_xlabel('Exam 1 Score')
ax.set_ylabel('Exam 2 Score')
plt.plot(x, y, 'grey')
plt.title('Decision Boundary')
plt.show()