线性模型——Logistic回归
逻辑回归是一种常用的处理二分类问题的线性模型,可以理解为线性回归与sigmoid函数的结合,需要注意的是,逻辑回归只能处理线性可分的数据集分类问题。
在逻辑回归中,目的是训练出一个模型,使得该模型能够将现有数据集进行分类。既然要实现分类,首先要有类比,以二分类问题为例,也就是将数据集中的样本分为两个类别。对于一个线性可分的数据集
D
\mathcal{D}
D,其标签用{0,1}表示,也就是说数据集中的样本分为两类,一类是0,一类是1,而我们的目标是找到一个模型,使得计算机能够将这两类分开,并且在给定一个不在数据集中的数据后,能够准确预测出该数据属于哪一类。
首先回顾一下线性回归,我们先以单个样本为例来看看逻辑回归是如何运作的。假设数据集
D
D
D中一个样本
x
=
{
x
1
,
x
2
,
.
.
.
x
M
}
T
{\bf x} = \{x_1,x_2,...x_M\}^\text T
x={x1,x2,...xM}T,其中
M
M
M是特征数量,存在权重向量
w
=
{
w
1
,
w
2
,
.
.
.
w
D
}
T
{\bf w} = \{w_1,w_2,...w_D\}^\text T
w={w1,w2,...wD}T以及偏置
b
b
b,线性回归的目的是通过训练出合适的权重向量与偏置,使得其能够很好的拟合特征与标签的映射关系,其预测值为:
y
^
=
w
T
x
=
w
1
x
1
+
w
2
x
2
+
.
.
.
+
w
D
x
D
+
b
\hat {y} = {\bf w}^\text T {\bf x} = w_1x_1+w_2x_2+...+w_Dx_D+b
y^=wTx=w1x1+w2x2+...+wDxD+b
为了表述简单,将权重向量和特征向量扩充为增广权重向量与增广特征向量 ${\bf w} ={w_1,w_2,…w_D,b}^\text T $ 、
x
=
{
x
1
,
x
2
,
.
.
.
x
M
,
1
}
T
{\bf x} = \{x_1,x_2,...x_M,1\}^\text T
x={x1,x2,...xM,1}T,因此线性输出为
y
^
=
w
T
x
\hat y = {\bf w}^\text T {\bf x}
y^=wTx,大小为1行1列。
线性回归的输出实际上是找到了一条直线去拟合特征与标签的关系,其标签的取值范围是负无穷到正无穷,那么它与逻辑回归这一分类模型有何关系呢?首先,前面提到,逻辑回归的目标是找到一个模型使得计算机能够将数据集中的数据进行归类,对于一个线性可分的数据集,我们可否利用一条直线将数据集分为两类呢?当然是可以的。如下图,直线将数据集一分为二,直线上方的数据为类别1,下方的为类别0。
确定了可以利用一条直线来将线性可分的数据集一分为二,这不就成了线性回归问题吗?只需要找到一条直线就行。当然不对,这里又涉及到另一个问题。在线性回归当中,预测值是一个连续的,负无穷到正无穷的值,然而在分类问题中,预测值是一个离散的值(预测是哪一个类别,在逻辑回归中,是0和1),因此直接使用线性回归来处理分类问题是不行的。因此需要一个函数,将连续值转换为离散值,这个函数被称为激活函数。对于二分类问题,我们通常使用sigmoid函数来充当激活函数,表达式和图像如下:
sigmoid
(
x
)
=
1
1
+
e
−
x
\text {sigmoid}(x) = \frac{1}{1 + e^{-x}}
sigmoid(x)=1+e−x1
从图形可以看出,sigmoid函数的输入是负无穷到正无穷,输出属于[0,1],并且当输入为0是,输出为0.5。当我们把线性回归的输出当作sigmoid的输入时,便可以将线性回归的输出转换到[0,1],我们可以认为当预测值大于0.5时,当前样本属于类别1,反之为0。因此,我们可以将逻辑回归的预测表达式表示为,其大小为 1x1:
y
^
l
=
sigmoid
(
−
w
T
x
)
\hat y_{l} = \text{sigmoid}(-{\bf w}^\text T{\bf x})
y^l=sigmoid(−wTx)
为了训练模型,我们需要定义损失函数,在逻辑回归中,我们将损失函数(交叉熵损失函数)定义为(*表示矩阵的对应元素相乘):
L
=
−
1
N
∑
i
=
1
N
(
y
∗
log
y
^
l
,
i
+
(
1
−
y
i
)
∗
log
(
1
−
y
^
l
,
i
)
)
\mathcal{L} =-\frac{1}{N}\sum_{i = 1}^N( y *\log \hat y_{l,i}+(1-y_i) * \log (1-\hat y_{l,i}))
L=−N1i=1∑N(y∗logy^l,i+(1−yi)∗log(1−y^l,i))
其中
y
y
y表示标签值,
y
^
l
,
i
∈
[
0
,
1
]
\hat y_{l,i} \in [0,1]
y^l,i∈[0,1]表示预测值。当标签为1时,损失函数为
L
=
−
1
N
∑
i
=
1
N
(
log
y
^
l
,
i
)
\mathcal{L} = -\frac{1}{N}\sum_{i = 1}^N(\log \hat y_{l,i})
L=−N1∑i=1N(logy^l,i),考虑函数
f
=
−
log
y
i
′
f = -\log y_i'
f=−logyi′,函数图像如下,可以看出,此时预测值越接近1(真实值),损失函数越小。
当标签为0时,损失函数为
L
=
−
1
N
∑
i
=
1
N
log
(
1
−
y
^
l
,
i
)
\mathcal{L} = -\frac{1}{N}\sum_{i = 1}^N\log (1-\hat y_{l,i})
L=−N1∑i=1Nlog(1−y^l,i),考虑函数
f
=
−
log
(
1
−
y
i
′
)
f = -\log (1-y_i')
f=−log(1−yi′),函数图像如下,可以看出,此时预测值越接近0(真实值),损失函数越小。
综上所述,通过交叉熵损失函数,能够有效的解决分类问题。为了求得最佳的模型,需要对权重系数进行更新,在这里我们采用梯度下降(还可以用牛顿法)算法更新参数,该损失函数的梯度表示为:(详细推导见书籍)
L
o
s
s
=
−
1
N
∑
n
=
1
N
[
y
(
n
)
∗
log
(
y
^
l
,
i
(
n
)
)
+
(
1
−
y
(
n
)
)
∗
log
(
(
1
−
y
^
l
,
i
(
n
)
)
)
]
Loss = \frac{-1}{N}\sum_{n = 1}^N [y^{(n)} * \log (\hat y_{l,i}^{(n)}) + (1-y^{(n)}) * \log ((1-\hat y_{l,i}^{(n)}))]
Loss=N−1n=1∑N[y(n)∗log(y^l,i(n))+(1−y(n))∗log((1−y^l,i(n)))]
因此,得到逻辑回归算法中的参数更新公式为:
w
t
+
1
=
w
t
+
α
1
N
∑
n
=
1
N
x
(
n
)
[
y
(
n
)
−
y
^
l
,
i
(
n
)
]
{\bf w}_{t+1} = {\bf w}_t + \alpha\frac{1}{N} \sum_{n = 1}^N x^{(n)}[y^{(n)} - \hat y_{l,i}^{(n)}]
wt+1=wt+αN1n=1∑Nx(n)[y(n)−y^l,i(n)]
为了便于代码实现逻辑回归,我们将上述表达式改为矩阵形式。假设线性可分数据集的样本数量为
N
N
N,特征的数量为
M
M
M。初始化的增广权重向量为
W
=
[
w
1
,
w
2
,
.
.
.
w
M
,
b
]
M
+
1
,
1
T
{\bf W} = [w_1,w_2,...w_M,b]^{\text T}_{M+1, 1}
W=[w1,w2,...wM,b]M+1,1T,增广特征向量构成的矩阵为
X
=
[
L
1
,
L
2
,
.
.
.
L
N
]
(
M
+
1
)
,
N
X = [L_1, L_2,...L_N]_{(M+1),N}
X=[L1,L2,...LN](M+1),N,其中
L
i
=
[
x
1
i
,
x
2
i
,
.
.
.
,
x
M
i
,
1
]
M
+
1
,
1
T
L_i = [x_1^i,x_2^i,...,x_M^i,1]^{\text T}_{M+1,1}
Li=[x1i,x2i,...,xMi,1]M+1,1T,其中
x
j
i
x_j^i
xji表示第
i
i
i个样本的第
j
j
j个特征。因此将其通过线性回归后,得到的结果为(矩阵乘法):
Y
^
1
,
N
=
W
T
X
\hat Y_{1,N} = W^\text T X
Y^1,N=WTX
将线性回归的结果输入到sigmoid函数,便可以得到预测值:
Y
^
1
,
N
l
=
sigmoid
(
−
Y
^
)
\hat Y^l_{1,N} = \text{sigmoid}(-\hat Y)
Y^1,Nl=sigmoid(−Y^)
得到损失函数为(点乘,对应元素相乘):
L
1
,
N
=
−
1
N
Y
1
,
N
∗
log
Y
^
1
,
N
+
(
1
−
Y
1
,
N
)
∗
log
(
1
−
Y
^
1
,
N
)
\mathcal{L}_{1,N} = \frac{-1}{N}Y_{1,N}* \log\hat Y_{1,N} + (1-Y_{1,N})*\log(1-\hat Y_{1,N})
L1,N=N−1Y1,N∗logY^1,N+(1−Y1,N)∗log(1−Y^1,N)
参数更新表达式(矩阵乘法):
W
D
+
1
,
1
t
+
1
=
W
D
+
1
,
1
t
+
α
X
D
+
1
,
N
(
Y
N
,
1
T
−
Y
^
N
,
1
T
)
W_{D+1,1}^{t+1} = W_{D+1,1}^{t} + \alpha X_{D+1,N}(Y_{N,1}^\text T - \hat Y_{N,1}^{\text T})
WD+1,1t+1=WD+1,1t+αXD+1,N(YN,1T−Y^N,1T)
分类之后需要画出决策边界的话,对于仅有两个特征的分类,可以让线性回归表达式为0,然后解出两个特征的函数关系即可。
逻辑回归的流程可以归纳为以下几步:
(1)准备数据集
(2)获取增广特征向量,标签,生成增广权重向量
(3)获取线性回归输出
(4)将线性回归结果输入sigmoid函数,得到预测结果
(5)计算损失函数
(6)更新参数
实现代码
"""
@File : LogisticRegression.py
@Author : CheckOneA
@Contact : 932261247@qq.com
@License : (C)Copyright 2018-2021
@Version : V1.0
@Date : 2022/9/7
@Encoding : UTF-8
@Des : The class of the logistic regression
"""
import numpy as np
import torch
class LogisticRegression:
def __init__(self, data, n_feature, model_gradient_descent, learning_rate, bath_size, interation_max):
self.data = data
self.n_feature = n_feature
self.num_data = self.data.shape[0]
self.model_gradient_descent = model_gradient_descent
self.bath_size = bath_size
self.interation_max = interation_max
self.lr = learning_rate
def train(self):
loss_dis = []
data_train = None
# 初始化参数值 w = [w1, w2, ... ,wD, b] 其中的为特征数量 b为偏置 大小为 (D+1)* 1
w = np.zeros((self.n_feature + 1, 1)).reshape(-1, 1)
if self.model_gradient_descent == 1:
data_train = self.data
# 开始迭代
for interation_index in range(self.interation_max):
if self.model_gradient_descent == 2:
data_train = self.data[np.random.randint(0, self.num_data), :]
elif self.model_gradient_descent == 3:
data_index = np.random.randint(low=0, high=self.num_data, size=self.bath_size)
data_train = self.data[data_index, :]
# 获取标签 结果为[y1,y2,y3,...yN] 其中N为样本数量
label = (data_train[:, 2]).reshape(1, -1)
# 获取特征 大小为 D+1 * N
temp = data_train[:, 0:2]
# 在特征矩阵上最后一列补1
feature = (np.insert(temp, self.n_feature, np.ones(data_train.shape[0]), axis=1)).T
# 线性模型输出
line_output = w.T.dot(feature)
# 将线性模型输出输入到sigmoid函数 得到标签预测值
label_prodict = self.sigmoid(-line_output)
# 得到损失值
loss = self.loss_function(label, label_prodict)
loss_dis.append(loss)
# 计算参数更新的表达式
parameter_update = self.lr * (np.dot(feature, (label - label_prodict).T))
# 更新参数
w = w + parameter_update
return w, loss_dis
@staticmethod
def sigmoid(input_data):
res = 1 / (1 + np.exp(input_data))
return res
@ staticmethod
def loss_function(y, y_p):
loss = -np.sum((y * np.log10(y_p) + (1 - y) * np.log10(1 - y_p))) / len(y)
return loss
测试代码
"""
@File : Test.py
@Author : CheckOneA
@Contact : 932261247@qq.com
@License : (C)Copyright 2018-2021
@Version : V1.0
@Date : 2022/9/7
@Encoding : UTF-8
@Des : None
"""
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from LogisticRegression import LogisticRegression
COLOR = ['r', 'b']
MARKER = ['s', 'o']
NUM_FEATURE = 2
data_df = pd.read_csv("Data/data.csv")
data_np = data_df.to_numpy()
num_data = data_np.shape[0]
logistic_regression = LogisticRegression(data=data_np,
n_feature=NUM_FEATURE,
model_gradient_descent=1,
learning_rate=0.00001,
bath_size=64,
interation_max=2000000)
w, loss = logistic_regression.train()
print(w)
plt.figure()
'''for data_index in range(num_data):
plt.scatter(data_np[data_index][0], data_np[data_index][1], color=COLOR[int(data_np[data_index][2])],
marker=MARKER[int(data_np[data_index][2])])
plt.title("The classification that don't train")'''
x = np.linspace(1, len(loss), len(loss))
print(loss)
plt.plot(x, loss)
plt.show()
plt.figure()
for data_index in range(num_data):
plt.scatter(data_np[data_index][0], data_np[data_index][1], color=COLOR[int(data_np[data_index][2])],
marker=MARKER[int(data_np[data_index][2])])
x_0 = np.linspace(30, 100)
x_1 = (- w[0] / w[1]) * x_0 - (w[2] / w[1])
plt.plot(x_0, x_1)
plt.title("The classification that don't train")
plt.show()
训练结果
数据集下载