二、二分类神经网络:逻辑回归

1 二分类神经网络的理论基础

线性回归是统计学经典算法,它能够拟合出一条直线来描述变量之间的线性关系。但在实际中,变量之间的关系通常都不是一条直线,而是呈现出某种曲线关系。在统计学的历史中,为了让统计学模型能够更好地拟合曲线,统计学家们在线性回归的方程两边引入了联系函数(link function),对线性回归的方程做出了各种样的变化,并将这些变化后的方程称为“广义线性回归”。其中比较著名的有等式两边同时取对数的对数函数回归、同时取指数的S形函数回归等。
y=ax+b→ln⁡y=ln⁡(ax+b)y=ax+b→ey=eax+b\begin{array}{l} y=a x+b \rightarrow \ln y=\ln (a x+b) \\ y=a x+b \rightarrow e^{y}=e^{a x+b} \end{array} y=ax+b→lny=ln(ax+b)y=ax+b→ey=eax+b​
在探索的过程中,一种奇特的变化吸引了统计学家们的注意,这个变化就是sigmoid函数带来的变化。Sigmoid函数的公式如下:
σ=Sigmoid⁡(z)=11+e−z\sigma=\operatorname{Sigmoid}(z)=\frac{1}{1+e^{-z}} σ=Sigmoid(z)=1+e−z1​
其中eee为自然常数(约为2.71828),其中zzz是它的自变量,σ\sigmaσ是因变量,zzz的值常常是线性模型的取值(比如,线性回归的结果zzz)。 Sigmoid函数是一个S型的函数,它的图像如下:

从图像上就可以看出,这个函数的性质相当特别。当自变量zzz趋近正无穷时,因变量σ\sigmaσ趋近于1,而当zzz趋近负无穷时,σ\sigmaσ趋近于0,这使得sigmoid函数能够将任何实数映射到(0,1)区间。同时,Sigmoid的导数在zzz=0点时最大(这一点的斜率最大),所以它可以快速将数据从zzz=0的附近排开,让数据点到远离自变量取0的地方去。这样的性质,让sigmoid函数拥有将连续性变量zzz=0转化为离散型变量σ\sigmaσ的力量,这也就是化回归算法为分类算法的力量。

具体怎么操作呢?只要将线性回归方程的结果作为自变量带入sigmoid函数,得出的数据就一定是(0,1) 之间的值。此时,只要我们设定一个阈值(比如0.5),规定σ\sigmaσ大于0.5时,预测结果为1类,σ\sigmaσ小于0.5时,预测结果为0类,则可以顺利将回归算法转化为分类算法。此时,我们的标签就是类别0和1了。这个阈值可以自己调整,在没有调整之前,一般默认0.5。
σ=11+e−z=11+e−Xw\sigma=\frac{1}{1+e^{-z}}=\frac{1}{1+e^{-\boldsymbol{X} \boldsymbol{w}}} σ=1+e−z1​=1+e−Xw1​
更神奇的是,当我们对线性回归的结果取sigmoid函数之后,只要再进行以下操作:
1)将结果以几率(σ1−σ)\left(\frac{\sigma}{1-\sigma}\right)(1−σσ​)的形式展现
2)在几率上求以e为底的对数
ln⁡σ1−σ=ln⁡(11+e−Xw1−11+e−Xw)=ln⁡(11+e−Xwe−Xw1+e−Xw)=ln⁡(1e−Xw)=ln⁡(eXw)=Xw\begin{aligned} \ln \frac{\sigma}{1-\sigma} &=\ln \left(\frac{\frac{1}{1+e^{-X w}}}{1-\frac{1}{1+e^{-X w}}}\right) \\ &=\ln \left(\frac{\frac{1}{1+e^{-X w}}}{\frac{e^{-X w}}{1+e^{-X w}}}\right) \\ &=\ln \left(\frac{1}{e^{-X \boldsymbol{w}}}\right) \\ &=\ln \left(e^{\boldsymbol{X} \boldsymbol{w}}\right) \\ &=\boldsymbol{X} \boldsymbol{w} \end{aligned} ln1−σσ​​=ln(1−1+e−Xw1​1+e−Xw1​​)=ln(1+e−Xwe−Xw​1+e−Xw1​​)=ln(e−Xw1​)=ln(eXw)=Xw​

不难发现,让取对数几率后所得到的值就是我们线性回归的!因为这个性质,在等号两边加sigmoid的算法被称为“对数几率回归”,在英文中就是Logistic Regression,就是逻辑回归。逻辑回归可能是广义线性回归中最广为人知的算法,它是一个叫做“回归“实际上却总是被用来做分类的算法,对机器学习和深度学习都有重大的意义。在面试中,如果我们希望了解一个人对机器学习的理解程度,第一个问题可能就会从sigmoid函数以及逻辑回归是如何来的开始。

2 tensor实现二分类神经网络的正向传播

我们可以在PyTorch中非常简单地实现逻辑回归的预测过程,让我们来看下面这一组数据。很容易注意到,这组数据和上面的回归数据的特征( x1x_{1}x1​,x2x_{2}x2​,是完全一致的,只不过标签y由连续型结果转变为了分类型的0和1。这一组分类的规律是这样的:当两个特征都为1的时候标签就为1,否则标签就为0。这一组特殊的数据被我们称之为“与门” (AND GATE),这里的“与”正是表示“特征一与特征二都是1”的含义。

要拟合这组数据,只需要在刚才我们写好的代码后面加上sigmoid函数以及阈值处理后的变化。

import torch
X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype = torch.float32)
andgate = torch.tensor([[0],[0],[0],[1]], dtype = torch.float32)
'''保险起见,生成二维的、 float32类型的标签'''
w = torch.tensor([-0.2,0.15,0.15], dtype = torch.float32)def LogisticR(X,w):zhat = torch.mv(X,w)sigma = torch.sigmoid(zhat)#sigma = 1/(1+torch.exp(-zhat))andhat = torch.tensor([int(x) for x in sigma >= 0.5], dtype = torch.float32)'''int(true)为1   int(False)为0'''return sigma, andhatsigma, andhat = LogisticR(X,w)
sigma
#tensor([0.4502, 0.4875, 0.4875, 0.5250])
andhat
#tensor([0., 0., 0., 1.])

接下来,我们对这段代码进行详细的说明:

'''导入torch库'''
import torch'''特征张量,养成良好习惯,上来就定义数据类型'''
X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype = torch.float32)
'''标签,分类问题的标签是整型'''
andgate = torch.tensor([0,0,0,1], dtype = torch.float32)
'''定义w,注意这一组w与之前在回归中使用的完全一样'''
w = torch.tensor([-0.2,0.15,0.15], dtype = torch.float32)def LogisticR(X,w):zhat = torch.mv(X,w) '''首先执行线性回归的过程,依然是mv函数,让矩阵与向量相乘得到z'''sigma = torch.sigmoid(zhat) '''执行sigmoid函数,你可以调用torch中的sigmoid函数,也可 以自己用torch.exp来写'''#sigma = 1/(1+torch.exp(-zhat))andhat = torch.tensor([int(x) for x   in sigma >= 0.5], dtype =  torch.float32)
'''设置阈值为0.5, 使用列表推导式将值转化为0和1'''return sigma, andhat
sigma
#tensor([0.4502, 0.4875, 0.4875, 0.5250])
andhat
#tensor([0., 0., 0., 1.])andgate == andhat
'''最后得到的都是0和1,虽然是andhat的数据格式是float32,但本质上数还是整数,不存在精度问题'''
#tensor([True, True, True, True])

可见,这里得到了与我们期待的结果一致的结果,这就将回归算法转变为了二分类。这个过程在神经网络中的表示图如下:

可以看出,这个结构与线性回归的神经网络唯一不同的就是输出层中多出了一个Sigmoid(zzz)。当有了Sigmoid函数的结果之后,只要了解阈值是0.5(或者任意我们自己设定的数值),就可以轻松地判断任意样本的预测标签y^\hat{y}y^​。在二分类神经网络中,Sigmoid实现了将连续型数值转换为分类型数值的作用, 在现代神经网络架构中,除了Sigmoid函数之外,还有许多其他的函数可以被用来将连续型数据分割为离散型数据,接下来,我们就介绍一下这些函数。

3 符号函数sign,ReLU,Tanh

  • 符号函数sign

符号函数是图像如下所示的函数。

我们可以使用以下表达式来表示它:
y={1if z>00if z=0−1if z<0y=\left\{\begin{aligned} 1 & \text { if } z>0 \\ 0 & \text { if } z=0 \\ -1 & \text { if } z<0 \end{aligned}\right. y=⎩⎪⎨⎪⎧​10−1​ if z>0 if z=0 if z<0​
由于函数的取值是间断的,符号函数也被称为“阶跃函数”,表示在0的两端,函数的结果y是从-1直接阶跃到了1。在这里,我们使用y而不是来表示输出的结果,是因为输出结果直接是0、1、-1这样的类别,就相当于标签了。对于sigmoid函数而言,返回的是0~1之间的概率值,如果我们希望获取最终预测出的类别,还需要将概率转变成0或1这样的数字才可以。但符号函数可以直接返回类别,因此我们可以认为符号函数输出的结果就是最终的预测结果y。在二分类中,符号函数也可以忽略中间的时候,直接分为0和1两类,用如下式子表示:
y={1if z>00if z≤0y=\left\{\begin{array}{ll} 1 & \text { if } z>0 \\ 0 & \text { if } z \leq 0 \end{array}\right. y={10​ if z>0 if z≤0​
等号被并在上方或下方都可以。这个式子可以很容易被转化为下面的式子:
∵z=w1x1+w2x2+b∴y={1if w1x1+w2x2+b>00if w1x1+w2x2+b≤0∴y={1if w1x1+w2x2>−b0if w1x1+w2x2≤−b\begin{array}{l} \because z=w_{1} x_{1}+w_{2} x_{2}+b \\ \therefore y=\left\{\begin{array}{ll} 1 & \text { if } w_{1} x_{1}+w_{2} x_{2}+b>0 \\ 0 & \text { if } w_{1} x_{1}+w_{2} x_{2}+b \leq 0 \end{array}\right. \\ \therefore y=\left\{\begin{array}{ll} 1 & \text { if } w_{1} x_{1}+w_{2} x_{2}>-b \\ 0 & \text { if } w_{1} x_{1}+w_{2} x_{2} \leq-b \end{array}\right. \end{array} ∵z=w1​x1​+w2​x2​+b∴y={10​ if w1​x1​+w2​x2​+b>0 if w1​x1​+w2​x2​+b≤0​∴y={10​ if w1​x1​+w2​x2​>−b if w1​x1​+w2​x2​≤−b​​
此时,−b-b−b就是一个阈值,我们可以使用任意字母来替代它,比较常见的是字母θ\thetaθ。当然,不把它当做阈值,依然保留w1x1+w2x2+bw_{1} x_{1}+w_{2} x_{2}+bw1​x1​+w2​x2​+b与0进行比较的关系也没有任何问题。和sigmoid一样,我们也可以使用阶跃函数来处理”与门“的数据:

import torch
X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype = torch.float32)
andgate = torch.tensor([[0],[0],[0],[1]], dtype = torch.float32)
w = torch.tensor([-0.2,0.15, 0.15], dtype = torch.float32)def LinearRwithsign(X,w):zhat = torch.mv(X,w)andhat = torch.tensor([int(x) for x in zhat >= 0], dtype = torch.float32)return zhat, andhat
zhat, andhat = LinearRwithsign(X,w)
zhat
#tensor([-0.2000, -0.0500, -0.0500,  0.1000])
andhat
#tensor([0., 0., 0., 1.])

阶跃函数和sigmoid都可以完成二分类的任务。在神经网络的二分类中,σ\sigmaσ的默认取值一般都是sigmoid函数,少用阶跃函数,这是由神经网络的解法决定的,我们将在第三部分来详细讲解。

  • ReLU

ReLU(Rectified Linear Unit)函数又名整流线型单元函数,英文发音为/rel-you/,是现在神经网络领域中的宠儿,应用甚至比sigmoid更广泛。ReLU提供了一个很简单的非线性变换:当输入的自变量大于0时,直接输出该值,当输入的自变量小于等于0时,输出0。这个过程可以用以下公式表示出来:
Re⁡LU:σ={z(z>0)0(z≤0)\operatorname{Re} L U: \sigma=\left\{\begin{array}{ll} z & (z>0) \\ 0 & (z \leq 0) \end{array}\right. ReLU:σ={z0​(z>0)(z≤0)​
ReLU函数是一个非常简单的函数,本质就是max(0,z)。max函数会从输入的数值中选择较大的那个值进行输出,以达到保留正数元素,将负元素清零的作用。ReLU的图像如下所示:

相对的,ReLU函数导数的图像如下:

当输入zzz为正数时,ReLU函数的导数为1,当zzz为负数时,ReLU函数的导数为0,当输入为0时,ReLU函数不可导。因此,ReLU函数的导数图像看起来就是阶跃函数,这是一个美好的巧合。

  • tanh

tanh(hyperbolic tangent)是双曲正切函数,英文发音/tæntʃ/或者/θæn/ (两者均来源于柯林斯简明词典,p1520)。双曲正切函数的性质与sigmoid相似,它能够将数值压缩到(-1,1)区间内。
tanh⁡:σ=e2z−1e2z+1\tanh : \sigma=\frac{e^{2 z}-1}{e^{2 z}+1} tanh:σ=e2z+1e2z−1​
而双曲正切函数的图像如下:

可以看出,tanh的图像和sigmoid函数很像,不过sigmoid函数的范围是在(0,1)之间,tanh却是在坐标系的原点(0,0)点上中心对称。

对tanh求导后可以得到如下公式和导数图像:
tanh⁡′(z)=1−tanh⁡2(z)\tanh ^{\prime}(z)=1-\tanh ^{2}(z) tanh′(z)=1−tanh2(z)

可以看出,当输入的zzz约接近于0,tanh函数导数也越接近最大值1,当输入越偏离0时,tanh函数的导数越接近于0。这些函数是最常见的二分类转化函数,他们在神经网络的结构中有着不可替代的作用。在单层神经网络中,这种作用是无法被体现的,因此关于这一点,我们可以之后再进行说明。到这里,我们\只需要知道这些函数都可以将连续型数据转化为二分类就足够了。

4 torch.functional实现单层二分类神经网络的正向传播

之前我们使用torch.nn.Linear类实现了单层回归神经网络,现在我们试着来实现单层二分类神经网络,也就是逻辑回归。逻辑回归与线性回归的唯一区别,就是在线性回归的结果之后套上了sigmoid函数。不难想象,只要让nn.Linear的输出结果再经过sigmoid函数,就可以实现逻辑回归的正向传播了。
在PyTorch中,我们几乎总是从nn.functional中调用相关函数。

回顾一下我们的数据和网络架构:

接下来,我们在之前线性回归代码的基础上,加上nn.functional来实现单层二分类神经网络:

import torch
from torch.nn import functional as F
X = torch.tensor([[0,0],[1,0],[0,1],[1,1]], dtype = torch.float32)
torch.random.manual_seed(420) #人为设置随机数种子
dense = torch.nn.Linear(2,1)
zhat = dense(X)
sigma = F.sigmoid(zhat)
y = [int(x) for x in sigma > 0.5]
y
#[1, 1, 1, 1]

在这里,nn.Linear虽然依然是输出层,但却没有担任最终输出值的角色,因此这里我们使用dense作为变量名。 dense表示紧密链接的层,即上一层的大部分神经元都与这一层的大部分神经元相连,在许多神经网络中我们都会用到密集链接的层,因此dense是我们经常会用到的一个变量名。我们将数据从nn.Linear传入,得到zhat,然后再将zhat的结果传入sigmoid函数,得到sigma,之后在设置阈值为0.5,得到最后的y。
在PyTorch中,我们可以从functional模块里找出大部分之前我们提到的函数,来看看ReLU、 Tanh以及Sign函数都是如何使用PyTorch实现的吧:

torch.random.manual_seed(420) #人为设置随机数种子
dense = torch.nn.Linear(2,1)
zhat = dense(X) #符号函数sign
print(zhat)
torch.sign(zhat)
#tensor([[0.6730],
#        [1.1048],
#        [0.2473],
#        [0.6792]], grad_fn=<AddmmBackward0>)
#tensor([[1.],
#        [1.],
#        [1.],
#        [1.]], grad_fn=<SignBackward0>)#ReLU
F.relu(zhat)
#tensor([[0.6730],
#        [1.1048],
#        [0.2473],
#        [0.6792]], grad_fn=<ReluBackward0>)#tanh
torch.tanh(zhat)
#tensor([[0.5869],
#        [0.8022],
#        [0.2424],
#        [0.5910]], grad_fn=<TanhBackward0>)

在PyTorch的安排中,符号函数sign与双曲正切函数tanh更多时候只是被用作数学计算工具,而ReLU和Sigmoid却作为神经网络的组成部分被放在库functional中,这其实反映出实际使用时大部分人的选择。ReLU与Sigmoid还是主流的、位于nn.Linear后的函数。

Lesson 8.38.4 二分类神经网络torch.nn.functional实现单层二分类网络的正向传播相关推荐

  1. Lesson 8.18.2 单层回归神经网络torch.nn.Linear实现单层回归神经网络的正向传播

    ​​​​​​​​在之前的介绍中,我们已经了解了神经网络是模仿人类大脑结构所构建的算法,在人脑里,我们有轴突连接神经元,在算法中,我们用圆表示神经元,用线表示神经元之间的连接,数据从神经网络的左侧输入, ...

  2. KLD Loss( tf.nn.softmax, torch.nn.functional.softmax, log_softmax, kl_div) 计算技巧(一)

    最近在比较不同模型的性能,发现虽然文献中使用的相同的指标,比如KLD.但是数据的处理方式却存在着差异,这会导致最后的数据并不具有直接可比性. 这里记录下,其中的一些值得记住的细节.主要涉及的API包括 ...

  3. 基于torch.nn.functional.conv2d实现CNN

    在我们之前的实验中,我们一直用torch.nn.Conv2D来实现卷积神经网络,但是torch.nn.Conv2D在实现中是以torch.nn.functional.conv2d为基础的,这两者的区别 ...

  4. 剖析 | torch.nn.functional.softmax维度详解

    写代码,看代码都要心中有数,输入是什么,输出是什么,结果是如何计算出来的. 一维数据: # -*- coding: utf-8 -*- import torch import numpy as np ...

  5. torch.sigmoid、torch.nn.Sigmoid和torch.nn.functional.sigmoid的区别

      review代码的时候发现我使用的是torch.sigmoid,pycharm标黄cannot find reference 'sigmoid' in '__init__.py'于是产生了这样的疑 ...

  6. torch.nn.functional.interpolate函数

    torch.nn.functional.interpolate实现插值和上采样 torch.nn.functional.interpolate(input, size=None, scale_fact ...

  7. Pytorch之torch.nn.functional.pad函数详解

    torch.nn.functional.pad是PyTorch内置的矩阵填充函数 (1).torch.nn.functional.pad函数详细描述如下: torch.nn.functional.pa ...

  8. Pytorch的第二步:(1) torch.nn.functional.conv卷积模块详解模块

    torch.nn.functional 涉及了所有 torch.nn 需要 类 和 方法 ,torch.nn 构建的模块通常就是调用 torch.nn.functional 里的方法实现的,通过学习 ...

  9. torch.nn.functional.cross_entropy.ignore_index

    ignore_index表示计算交叉熵时,自动忽略的标签值,example: import torch import torch.nn.functional as F pred = [] pred.a ...

最新文章

  1. python学生管理系统-python实现学生管理系统
  2. php 浏览商品记录,php浏览历史记录
  3. 链表——实现单链表的反转
  4. oracle 11g 下载地址
  5. cpp怎么转成html,如何编辑HTML(标签),通过CppWebBrowser
  6. SkyEye仿真ZYNQ芯片,轻松运行国产操作系统ReWorks
  7. sql server 2008 数据结构及数据内容一起导出的方法(导出脚本形式)
  8. C++递归或非递归实现求斐波拉契数列第n项
  9. 微软签署最大规模风电购买协议 打造“无碳”数据中心
  10. 抽象工厂模式 Abstract Factory Pattern
  11. c语言加粗字体怎么弄,excel表格如何批量加粗文字
  12. 安装程序工具 (Installutil.exe)22
  13. VLC支持的视频和音频文件扩展名
  14. 封装el-select(全球国家名字及国家区号),select 输入框回显
  15. Axure RP Extension for Chrome最新版查看RP原型
  16. 点亮LED灯——arduino 学习第一天
  17. 虚拟现实的伦理问题----陈教授讲座听后感
  18. win10无法复制文件到system32,提示需要权限操作
  19. java stringbuilder 构造函数_java---StringBuilder类的用法(转载)
  20. C#类库推荐 拼多多.Net SDK,开源免费!

热门文章

  1. 服务器共享文件和电脑同步,云服务器共享文件夹同步
  2. python123第九周测验答案2020_运用python123平台助力编程课教学
  3. php listview,ListView Item多布局的实现
  4. php allowoverride,Apache之AllowOverride参数详解
  5. mysql数据库访问300ms以上_[Java教程]一张900w的数据表,16s执行的SQL优化到300ms?...
  6. html怎么做到滚动鼠标转换,横向的网页如何实现鼠标滑轮横向移动?_html/css_WEB-ITnose...
  7. java web基本流程
  8. java trackid_Java Preference.getContext方法代码示例
  9. php什么框架性能高,主流PHP框架性能比较
  10. 4che3 scu发送超时设置_Redis实现订阅发布与批量发送短信