1-逻辑回归

逻辑回归

逻辑回归是一个二元分类算法。

数学术语

以一个二分类问题为例子:输入一张图片,识别该图片是否为猫,输出0:不是猫;输出1,是猫。

x表示输入,y表示输出。

一张图片的每个像素包含RGB三原色,因此一张图片可以看成三个不同的矩阵,三个矩阵分别代表图片像素的R,G,B值。

如果一张图片的大小为64x64。即共有64x64个像素值。那么可以用3个64x64的实数矩阵来表示该图片。

R=[323556157]G=[589621842]B=[1481759106]R= \left[\begin{matrix} 3 & 2 & 3\\ 5 & 5 & 6\\ 1 & 5 & 7\\ \end{matrix}\right] G= \left[\begin{matrix} 5 & 8 & 9\\ 6 & 2 & 1\\ 8 & 4 & 2\\ \end{matrix}\right] B= \left[\begin{matrix} 1 & 4 & 8\\ 1 & 7 & 5\\ 9 & 10 & 6\\ \end{matrix}\right]

如上述矩阵,以3x3的矩阵为例子。

要将这些矩阵中的像素值展开为一个向量x作为算法的输入。可将R,G,B三个矩阵,按行展开。变成下列向量。

x=[3235569106]Tx = \begin{bmatrix} 3& 2& 3& 5& 5& 6& \cdots& 9& 10& 6& \end{bmatrix}^{T}

如果图像大小为64x64,那么该向量的元素个数为 3x64x64=12288。

使用nx=12288n_x=12288来表示输入特征向量x的维度,也可简写为n

每个图像样本由一对(x, y)表示。其中x表示一个n维向量,y是一个标签,取值为0或1, 即y0,1y\in{0, 1}

因此,训练集样本可写为:(x(1),y(1))(x^{(1)}, y^{(1)})(x(2),y(2))(x^{(2)}, y^{(2)}),…。

其中,上标1表示第一个样本的输入和输出,以此类推。

使用小写的m来代表样本总数,为了区分训练集和测试集,也可进一步写为mtrainm_{train}mtestm_{test}

将所有训练样本写成更为紧凑的形式。

可定义矩阵X,将每个样本的输入特征向量x作为矩阵X的一列,构成一个n * m的矩阵。

X=[x(1)x(2)x(m)]X = \begin{bmatrix} x^{(1)} & x^{(2)} & \cdots & x^{(m)} \end{bmatrix}

在其他地方,有可能看到X矩阵的定义是将输入向量作为矩阵的一行,但是上述方式在运算时较为简单,因此推荐使用上述定义方式。

同样的,将输出y写为更紧凑的形式,与矩阵X的定义类似,将每个输出y作为矩阵Y的一列,如下。

Y=[y1y2ym]Y = \begin{bmatrix} y^{1} & y^{2} & \cdots & y^{m} \end{bmatrix}

Y是一个1 * m的矩阵。

逻辑回归算法

逻辑回归是一个用于二分类(binary clallification)的算法。

对于二分类问题来说,给定一个输入特征向量X,可能对应一张图片,要识别这张图片是否是一只猫,定义y^\widehat{y}表示y等于1的概率,即:

y^=P(y=1x)\widehat{y} = P(y = 1|x)

X是一个nxn_x维的向量,使用ww来表示逻辑回归的参数,这也是一个nxn_x维向量(ww是特征权重,维度与特征向量相同),还有一个参数bb,这是一个实数,表示偏差。

那么有公式:

y^=wTx+b\widehat{y} = w^Tx+b

得到一个关于x的线性函数,在线性回归中经常用到。但是在这里并不适用。因为y^\widehat{y}应该在0和1之间,而这个函数无法控制取值范围。

所以将y^\widehat{y}作为自变量,通过sigmoid函数,并将输出作为预测概率。

sigmoid函数的图像为:

image-20220906184818163

sigmoid函数表达式为:

σ(z)=11+ez\sigma(z) = \frac{1}{1+e^{-z}}

如果z非常大,那么sigmoid函数接近1,反之,如果z是一个很小的负数,那么sigmoid函数接近0。

代价函数

通过代价函数来训练参数wwbb

对于给定的数据集,我们希望预测出来的y^\widehat{y}更接近于训练集中的yy值。

需要说明,上面的定义是对于一个训练样本来说的。这种形式也适用于每个样本。使用带圆括号的上标来区分索引和样本。

训练样本i所对应的预测值是y(i)y^{(i)},使用训练样本的wTx(i)+bw^Tx^{(i)+b}然后通过sigmoid函数得到的。

因此上标(i)就代表第i个训练样本。

损失函数

损失函数又叫做误差函数,用来衡量算法运行情况。

Loss function:L(y^,y)L(\widehat{y}, y)

通过这个损失函数,来衡量预测输出值和实际值有多接近。一般使用预测值和实际值的平方差。但是在逻辑回归中通常不使用这种方法。因为当使用这种方式时,在后续的优化中是非凸的,只能找到多个局部最优值,梯度下降法很可能找不到全局最优值。因此虽然平方差是一个很好的损失函数,但是在逻辑回归模型中会定义另外一个损失函数。

L(y^,y)=[(ylog(y^)+(1y)log(1y^)]L(\widehat{y}, y) = -[(ylog(\widehat{y}) + (1-y)log(1-\widehat{y})]

对于损失函数,总是想让它尽可能的小。为了更好理解这个损失函数怎么起作用,举两个例子。

  • y=1y=1时,损失函数L=log(j^)L=-log(\widehat{j}),如果要使损失函数尽可能小,那么y^\widehat{y}就要尽可能大(有个负号),而y^\widehat{y}的取值范围为[0, 1],因此y^\widehat{y}会无限接近1。
  • y=0y=0时,损失函数L=log(1y^)L=-log(1-\widehat{y}),如果要使损失函数尽可能小,那么y^\widehat{y}就要尽可能小,而y^\widehat{y}的取值范围为[0, 1],因此y^\widehat{y}会无限接近0。

在这门课中,有很多函数效果与这个类似,就是当y=1y=1,那么就尽可能让y^\widehat{y}大,反之亦然。

损失函数是在单个训练样本中定义的,它衡量的是算法在单个训练样本中的表现如何,为了衡量算法在全部训练样本上的表现,需要定义算法的总代价函数。

算法的代价函数就是对m个样本的损失函数求和然后除以m:

J(w,b)=1mi=1mL(j^(i),y(i))=1mi=1mL(j^(i)log(j^(i))(1y(i))log(1j^(i)))\begin{aligned} J(w, b) & = \frac{1}{m}\sum_{i=1}^mL(\widehat{j}^{(i)},y^{(i)}) \\ & = \frac{1}{m}\sum_{i=1}^mL(-\widehat{j}^{(i)}log(\widehat{j}^{(i)}) - (1-y^{(i)})log(1-\widehat{j}^{(i)})) \end{aligned}

损失函数只适用于单个训练样本,而代价函数是参数 的总代价。

所以在训练逻辑回归模型时,需要找到合适的w,bw,b,来让代价函数的总代价降到最低。

根据对逻辑回归算法的推导以及对单个样本的损失函数的推导和针对算法所选用参数的总代价函数的推导。结果表明逻辑回归可以看做是一个非常小的神经网络。

梯度下降法

这部分在2-线性回归中有详细的说明,这里不再赘述。

给出参数的变化公式:

w:=wαJ(w,b)wb:=bαJ(w,b)bw:= w- \alpha \frac{\partial J(w, b)}{\partial w} \\ b:= b- \alpha \frac{\partial J(w, b)}{\partial b}

计算图求导数

image-20220907084907681

这是一个计算的流程图。

可以把最后的J理解为代价函数。当从左向右以此计算得到代价函数值时,可以理解为正向传播。

而如果从右向左,反向计算J对各个中间变量的导数,称为反向传播。

例如计算dJdv\frac{dJ}{dv}的导数,因为J=3vJ=3v,因此dJdv=3\frac{dJ}{dv} = 3,这就完成了一次反向传播。

依次类推,可以计算J对于变量a,u,b,c的导数。

实际上,这就是微积分里的链式法则。

逻辑回归中的梯度下降

首先回顾逻辑回归中的公式:

z=wTx+by^=a=σ(z)L(a,y)=ylog(a)(1y)log(1a)z = w^Tx + b \\ \widehat{y} = a = \sigma(z) \\ L(a, y) = -ylog(a) - (1-y)log(1-a)

其中a是逻辑回归中的输出。

因此计算的流程为(以两个参数为例):

image-20220907085822814

反向求导:

dLda=ya+1y1adLdz=dLdadadz\frac{dL}{da} = -\frac{y}{a} + \frac{1-y}{1-a} \\ \frac{dL}{dz} = \frac{dL}{da}\frac{da}{dz}

a=σ(z)a = \sigma(z)的表达式和导数为:

a=σ(z)=11+ezdadz=ez(1+ez)2=a(1a)a= \sigma(z) = \frac{1}{1+e^{-z}} \\ \begin{aligned} \frac{da}{dz} & = \frac{e^{-z}}{(1+e^{-z})^2} \\ & = a(1-a) \end{aligned}

带入上述导数公式中:

dz=dLdz=(ya+1y1a)(a(1a))=ay\begin{aligned} dz = \frac{dL}{dz} & = (-\frac{y}{a} + \frac{1-y}{1-a})(a(1-a)) \\ & = a-y \end{aligned}

(上面公式中的dz就表示损失函数对参数z的简写)

现在进行最后一步反向推导,就是计算参数w1,w2,b对损失函数的影响。

可以使用之前计算梯度下降导数的公式直接写出损失函数对参数的导数。

之前有:

dw1=1mimx1(i)(a(i)y(i))dw_1 = \frac{1}{m}\sum_{i}^mx^{(i)}_1(a^{(i)} - y^{(i)})

其中dw1=Lw1=x1dz=(ay)x1dw_1 = \frac{\partial L}{\partial w_1} = x_1\cdot dz = (a-y)x_1db=dz=aydb = dz = a-y

因此在使用单个样本的梯度下降算法中,需要做的事情为:

  1. 使用公式dz=aydz = a-y计算dzdz
  2. 使用dw1=x1dz,dw2=x2dz,db=dzdw_1 = x_1dz,dw_2 = x_2dz, db = dz公式计算参数变化量
  3. 使用公式w1=w1αdw1,w2=w2αdw2,b=bαdbw_1 = w_1 - \alpha dw_1,w_2 = w_2 - \alpha{dw_2}, b = b - \alpha{db}更新参数

这就是关于单个样本的梯度下降算法更新一次参数的步骤。

m个样本的梯度下降

上述已将说明了如何在单个样本中应用梯度下降。

现在将它应用到m个训练样本上。

首先需要记住代价函数的定义:

J(w,b)=1mimL(a(i),y(i))a(i)=σ(z(i))=σ(wTx(i)+b)J(w,b) = \frac{1}{m}\sum_i^mL(a^{(i)}, y^{(i)}) \\ a^{(i)} = \sigma(z^{(i)}) = \sigma(w^Tx^{(i)} +b)

a(i)a^{(i)}是训练样本的预测值。

之前只使用了一个训练样本(x(i),y(i))(x^{(i)}, y^{(i)}),现在使用带有求和的全局代价函数,实际就是m个样本损失值的平均。

所以全局代价函数对参数w1w_1的微分也就是各项损失对w1w_1微分的平均值。

给出梯度下降算法应用到m个样本的伪代码流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
J = 0, dw1 = 0, dw2 = 0, db = 0
for i = 1 to m:
z(i) = wx(i) + b;
a(i) = sigmoid(z(i))
J += -[y(i)log(a(i)) + (1- y(i))log(1- a(i))]; // +=符号是求和
//这里计算所有样本的代价总和
dz(i) = a(i) - y(i);
dw1 += x1(i)dz(i);
dw2 += x2(i)dz(i);
db += dz(i); //是所有样本的各项参数求和
J /= m;
dw1 /= m;
dw2 /= m;
db /= m; //取平均值
w1 = w1 - alpha*dw1;
w2 = w2 - alpha*dw2;
b = b - alpha*db; //更新所有参数

简单来说就是:

  1. 设置参数初始值,遍历训练样本,对于每个样本,求出预测值a(i)a^{(i)}和代价函数值,然后求出dw1,dw2,dbdw_1, dw_2, db
  2. 将代价函数值和各参数导数值求和,取平均值
  3. 更新参数

这只是一步梯度下降,上述的步骤需要不停地重复很多次。

这种计算中有两个缺点,应用这种方法在逻辑回归上需要编写两个for循环,第一个for循环是遍历m个训练样本,第二个for循环是遍历所有特征。这个例子中只有两个特征,因此遍历较简单,但是当特征很多的时候,需要写一个显式的for循环来遍历n个特征。

因为在深度学习领域有很大的数据集,因此使用for循环会使算法效率非常低。

可以使用向量化技术,这项技术可以摆脱显式的for循环。

向量化(Vectorization)

向量化其实就是使用矩阵乘法来提高运算速度。

例如在计算z=wTx+bz = w^Tx +b时,如果采用非向量化的方法计算:

z=w1x1+w2x2+...+wnxn+bz = w_1x_1+w_2x_2 + ... + w_nx_n + b

而如果采用向量化计算,则应该是:

z=[w1w2wn][x1x2xn]+bz = \begin{bmatrix} w_1 & w_2 & \cdots & w_n \end{bmatrix} \begin{bmatrix} x_1 \\ x_2 \\ \vdots \\ x_n \end{bmatrix} + b

这种向量化的计算方式会极大的提升运算的速度。

一个测试例子,分别使用循环计算和向量化计算,测试两者的计算时间。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import numpy as np
import time #导入时间库
a = np.random.rand(1000000)
b = np.random.rand(1000000) #通过round随机得到两个一百万维度的数组
tic = time.time() #现在测量一下当前时间
#向量化的版本
c = np.dot(a,b)
toc = time.time()
print(c)
print('Vectorized version:' + str(1000*(toc-tic)) +'ms') #打印一下向量化的版本的时间
#继续增加非向量化的版本
c = 0
tic = time.time()
for i in range(1000000):
c += a[i]*b[i]
toc = time.time()
print(c)
print('For loop:' + str(1000*(toc-tic)) + 'ms')#打印for循环的版本的时间

输出结果为
249695.7026965451
Vectorized version:1.5761852264404297ms
249695.70269653096
For loop:598.6647605895996ms

可以看到,循环计算的时间是向量化计算时间的400倍左右。

因此在计算m个变量的梯度下降时,不会再将dw1,dw2dw_1, dw_2设为0,而是设置dwdw为一个n维的向量,从而可以使用向量化技术计算。

dw=dw+x(i)dz(i)dw =dw + x^{(i)}dz^{(i)} \\

向量化逻辑回归

在之前的正向传播中,需要分别计算每一个样本。

image-20220907103446153

也就是,首先计算样本1的预测值,计算出偏导值,然后计算样本2,以此类推。

使用向量化技术可以一次性计算所有样本,不需要写for循环。

先将输入特征向量X写为矩阵形式:

X=[x(1)x(2)x(m)]X = \begin{bmatrix} \vdots & \vdots & \cdots &\vdots \\ x^{(1)} & x^{(2)} & \cdots & x^{(m)} \\ \vdots & \vdots & \cdots &\vdots \\ \end{bmatrix}

那么由公式z(i)=wTx(i)+bz^{(i)} = w^Tx^{(i)} + b,得出向量化的公式为:

Z=[z(1)z(1)z(m)]=wTX+b=[w1w2wn][x(1)x(2)x(m)]+[bbb]m\begin{aligned} Z = \begin{bmatrix} z^{(1)} & z^{(1)} & \cdots & z^{(m)} \end{bmatrix} & = w^TX + b \\ & = \begin{bmatrix} w_1 & w_2 & \cdots & w_n \end{bmatrix} \begin{bmatrix} \vdots & \vdots & \cdots &\vdots \\ x^{(1)} & x^{(2)} & \cdots & x^{(m)} \\ \vdots & \vdots & \cdots &\vdots \\ \end{bmatrix} + \overbrace{ \begin{bmatrix} b & b & \cdots & b \end{bmatrix}}^{m个} \end{aligned}

使用大写的Z代表上述的z矩阵。

得到矩阵Z后,需要使用sigmoid函数输出预测值a(i)a^{(i)}

A=[a(1)a(m)a(m)]=σ(Z)A = \begin{bmatrix} a^{(1)} & a^{(m)} & \cdots & a^{(m)} \end{bmatrix} = \sigma(Z)

通过向量化技术,可以一次性计算所有的a

总结:刚刚说明了如何使用项量化技术来计算正向传播中的各步骤。

同样,也可以利用向量化技术高效计算反向传播并以此计算梯度。

向量化逻辑回归中的梯度输出

注:大写字母代表向量或矩阵,小写字母代表元素

在上节中,已经通过向量化技术计算出矩阵A,也就是所有的预测值。

接下来继续使用向量化技术计算梯度。

将训练样本中的输出y值和导数dZ也写为一个向量:

Y=[y(1)y(2)y(m)]dZ=[dz(1)dz(2)dz(m)]Y = \begin{bmatrix} y^{(1)} & y^{(2)} & \cdots & y^{(m)} \end{bmatrix} \\ dZ = \begin{bmatrix} dz^{(1)} & dz^{(2)} & \cdots & dz^{(m)} \end{bmatrix} \\

那么就可以一次性计算出所有训练样本的导数值。

dZ=AYdZ = A - Y

然后就可以分别求参数的梯度。

db=db+dz(i)db=db/mdb = db + dz^{(i)} \\ db = db / m

因此参数b的梯度可以由下面公式求出:

db=1mnp.sum(dZ)db = \frac{1}{m}np.sum(dZ)

其中np就是指Python中的numpy,调用numpysum函数就可以对向量dZ求和。

同样,参数w的计算使用向量化技术:

dW=1mX(dZ)T=1m[x(1)x(2)x(m)][dz(1)dz(2)dz(m)]\begin{aligned} dW & = \frac{1}{m} X(dZ)^T \\ & = \frac{1}{m} \begin{bmatrix} \vdots & \vdots & \cdots &\vdots \\ x^{(1)} & x^{(2)} & \cdots & x^{(m)} \\ \vdots & \vdots & \cdots &\vdots \\ \end{bmatrix} \begin{bmatrix} dz^{(1)} \\ dz^{(2)} \\ \vdots \\ dz^{(m)} \\ \end{bmatrix} \end{aligned}

求得的向量dW的每一个分量分别对应于各参数的梯度。

那么,完整的给出从给定参数到计算出各参数的梯度步骤:

  1. Z=wTX+b=np.dot(wT,X)+bZ = w^TX + b = np.dot(w^T, X) + bnp.dot()numpy包的矩阵相乘函数,b是一个数字,但Python会自动将它拓展成一个m维向量(称为广播broadcasting)。
  2. A=σ(Z)A = \sigma(Z):求出各个样本的预测值
  3. dZ=AYdZ = A -Y:求出
  4. dW=1mX(dZ)TdW = \frac{1}{m}X(dZ)^T
  5. db=1mnp.sum(dZ)db = \frac{1}{m}np.sum(dZ)

然后使用dWdb更新参数:

W=Wα(dW)b=bα(db)W = W- \alpha(dW) \\ b = b - \alpha(db)

至此,使用上述公式完成了前向和后向传播,实现了对所有训练样本进行预测和求导。

这实现了一次迭代,也就是一次梯度下降,如果要迭代多次进行提地下降,那么仍然需要使用for循环。

代码实现


1-逻辑回归
https://zhaoquaner.github.io/2022/09/10/DeepLearning/CourseNote/1-逻辑回归/
更新于
2022年9月19日
许可协议