淘客手机网站建设,在线制作电子公章生成器,东莞横沥邮编,西安网站建设培训班神经网络是模仿人类大脑结构所构建的算法#xff0c;在人脑里#xff0c;我们有轴突连接神经元#xff0c;在算法中#xff0c;我们用圆表示神经元#xff0c;用线表示神经元之间的连接#xff0c;数据从神经网络的左侧输入#xff0c;让神经元处理之后#xff0c;从右…神经网络是模仿人类大脑结构所构建的算法在人脑里我们有轴突连接神经元在算法中我们用圆表示神经元用线表示神经元之间的连接数据从神经网络的左侧输入让神经元处理之后从右侧输出结果。 下图是一个最简单的神经元的结构。从这里开始我们正式开始认识神经网络。 28 单层回归网络线性回归
28.1 单层回归网络的理论基础
深度学习中的计算是“简单大量”而不是“复杂的单一问题”。神经网络的原理很多时候都比经典机器学习算法简单。了解神经网络可以从 线性回归 算法开始。
线性回归算法是机器学习中最简单的回归类算法多元线性回归指的就是一个样本对应多个特征的线性回归问题。假设我们的数据现在就是二维表对于一个有 n n n个特征的样本而言它的预测结果可以写作一个几乎人人熟悉的方程 z ^ i b w 1 x i 1 w 2 x i 2 … w n x i n \hat{z}_i b w_1 x_{i1} w_2 x_{i2} \ldots w_n x_{in} z^ibw1xi1w2xi2…wnxin w w w和 b b b被统称为模型的权重其中 b b b被称为截距(intercept)也叫做偏差(bias) w 1 w_1 w1~ w n w_n wn被称为回归系数(regression coefficient)也叫作权重(weights) x i 1 x_{i1} xi1~ x i n x_{in} xin是样本 i i i上的不同特征。这个表达式其实就和我们小学时就无比熟悉的 y a x b y ax b yaxb 是同样的性质。其中 y y y被我们称为因变量在线性回归中表示为 z z z在机器学习中也就表现为我们的标签。如果写作 z z z则代表真实标签。如果写作 z ^ \hat{z} z^读作z帽或者zhat则代表预测出的标签。模型得出的结果一定是预测的标签。
符号规范在我们学习autograd的时候我们说线性回归的方程是 y ^ i b w 1 x i 1 w 2 x i 2 … w n x i n \hat{y}_i b w_1 x_{i1} w_2 x_{i2} \ldots w_n x_{in} y^ibw1xi1w2xi2…wnxin。但在这里为什么写做 z z z呢首先无论是回归问题还是分类问题y永远表示标签labels。在回归问题中y是连续型数字在分类问题中y是离散型的整数。对于线性回归来说线性方程的输出结果就是最终的标签。但对于整个深度学习体系而言复杂神经网络的输出才是最后的标签。在我们单独对线性回归进行说明的时候行业惯例就是使用 z z z来表示线性回归的结果。
如果考虑我们有m个样本则回归结果可以被写作: z ^ i b w 1 x i 1 w 2 x i 2 … w n x i n \hat{z}_i b w_1 x_{i1} w_2 x_{i2} \ldots w_n x_{in} z^ibw1xi1w2xi2…wnxin 其中 z ^ i \hat{z}_i z^i是包含了m个全部的样本的预测结果的列向量。注意我们通常使用粗体的小写字母来表示列向量粗体的大写字母表示矩阵或者行列式。 并且在机器学习中我们默认所有的一维向量都是列向量。
我们可以使用矩阵来表示上面多个样本的回归结果的方程其中 w w w可以被看做是一个结构为(n1,1)的列矩阵这里的n加上的1是我们的截距b 是一个结构为(m,n1)的特征矩阵这里的n加上的1是为了与截距b相乘而留下的一列1这列1有时也被称作 x 0 x_0 x0则有 [ z ^ 1 z ^ 2 z ^ 3 … z ^ m ] [ 1 x 11 x 12 x 13 … x 1 n 1 x 21 x 22 x 23 … x 2 n 1 x 31 x 32 x 33 … x 3 n … … … … … 1 x m 1 x m 2 x m 3 … x m n ] ∗ [ b w 1 w 2 … w n ] \begin{bmatrix}\hat{z}_1 \\\hat{z}_2 \\\hat{z}_3 \\\ldots \\\hat{z}_m\end{bmatrix} \begin{bmatrix} 1 x_{11} x_{12} x_{13} \ldots x_{1n} \\ 1 x_{21} x_{22} x_{23} \ldots x_{2n} \\ 1 x_{31} x_{32} x_{33} \ldots x_{3n} \\ \ldots \ldots \ldots \ldots \ldots \\ 1 x_{m1} x_{m2} x_{m3} \ldots x_{mn} \end{bmatrix} * \begin{bmatrix} b \\ w_1 \\ w_2 \\ \ldots \\ w_n \end{bmatrix} z^1z^2z^3…z^m 111…1x11x21x31…xm1x12x22x32…xm2x13x23x33…xm3…………x1nx2nx3n…xmn ∗ bw1w2…wn z ^ X w \hat{z} Xw z^Xw
如果在我们的方程里没有常量b我们则可以不写X中的第一列以及w中的第一行。
线性回归的任务就是构造一个预测函数来映射输入的特征矩阵 和标签值 的线性关系。这个预测函数的图像是一条直线所以线性回归的求解就是对直线的拟合过程。
预测函数的本质就是我们需要构建的模型而构造预测函数的核心就是找出模型的权重向量 也就是求解线性方程组的参数相当于求解 y a x b yaxb yaxb里的 a a a与 b b b。
现在假设我们的数据只有2个特征则线性回归方程可以写作如下结构 z ^ b x 1 w 1 x 2 w 2 \hat{z}bx_1w_1x_2w_2 z^bx1w1x2w2 此时我们只要对模型输入特征 x 1 x_1 x1 x 2 x_2 x2的取值就可以得出对应的预测值 z ^ \hat{z} z^。神经网络的预测过程是从神经元左侧输入特征让神经元处理数据并从右侧输出预测结果。这个过程和我们刚才说到的线性回归输出预测值的过程是一致的。如果我们使用一个神经网络来表达线性回归上的过程则可以有 这就是一个最简单的单层回归神经网络的表示图。
在神经网络中竖着排列在一起的一组神经元叫做“一层网络”所以线性回归的网络直观看起来有两层两层神经网络通过写有参数的线条相连。我们从左侧输入常数1和特征取值 x 1 x_1 x1 x 2 x_2 x2再让它们与相对应的参数相乘就可以得到 b b b x 1 w 1 x_1w_1 x1w1 x 2 w 2 x_2w_2 x2w2三个结果。这三个结果通过连接到下一层神经元的直线被输入下一层神经元。我们在第二层的神经元中将三个乘积进行加和使用符号 ∑ \sum ∑表示就可以得到加和结果 z ^ \hat{z} z^即 b x 1 w 1 x 2 w 2 bx_1w_1x_2w_2 bx1w1x2w2这个值正是我们的预测值。可见线性回归方程与上面的神经网络图达到的效果是一模一样的。
在上述过程中左侧的是神经网络的输入层input layer。输入层由众多承载数据用的神经元组成数据从这里输入并流入处理数据的神经元中。在所有神经网络中输入层永远只有一层且每个神经元上只能承载一个特征(一个 x x x)或一个常量通常都是1。现在的二元线性回归只有两个特征所以输入层上只需要三个神经元包括两个特征和一个常量其中这里的常量仅仅是被用来乘以偏差 b b b用的。对于没有偏差的线性回归来说我们可以不设置常量1。
右侧的是输出层output layer。输出层由大于等于一个神经元组成我们总是从这一层来获取预测结果。输出层的每个神经元上都承载着单个或多个功能可以处理被输入神经元的数据。在线性回归中这个功能就是“加和”当我们把加和替换成其他的功能就能够形成各种不同的神经网络。
在神经元之间相互连接的线表示了数据流动的方向就像人脑神经细胞之间相互联系的“轴突”。在人脑神经细胞中轴突控制电子信号流过的强度在人工神经网络中神经元之间的连接线上的权重也代表了信息可通过的强度。最简单的例子是当 w w w为0.5时在特征 x 1 x_1 x1上的信息就只有0.5倍能够传递到下一层神经元中因为被输入到下层神经元中去进行计算的实际值是 0.5 x 1 0.5x_1 0.5x1。相对的如果 w 1 w_1 w1是2.5则会传递2.5倍的 上的信息。因此有的深度学习课程会将权重 w w w比喻成是电路中的”电压“电压越大则电信号越强烈电压越小信号也越弱这都是在描述权重 w w w会如何影响传入下一层神经元的信息/数据量的大小。
到此我们已经了解了线性回归的网络是怎么一回事它是最简单的回归神经网络同时也是最简单的神经网络。类似于线性回归这样的神经网络被称为单层神经网络。
单层神经网络从直观来看线性回归的网络结构明明有两层为什么线性回归被叫做“单层神经网络”呢业内通识是在描述神经网络的层数的时候我们不考虑输入层。输入层是每个神经网络都必须存在的一层当使用相同的输入数据时任意两个神经网络之间的不同之处就在输入层之后的所有层。所以我们把输入层之后只有一层的神经网络称为单层神经网络。在非常非常少见的情况下有的深度学习课程或教材中也会直接将所有层都算入其中将上述网络称为“两层神经网络”这种做法虽然不太规范但也不能称之为“错误的”。因此当出现“N层神经网络”的描述时一定要注意原作者是否将输入层考虑进去了。 28.2 tensor实现单层神经网络的正向传播
让我们使用一组非常简单的代码来实现一下回归神经网络求解 z ^ \hat{z} z^的过程在神经网络中这个过程是从左向右进行的被称为神经网络的正向传播forward spread。来看下面这组数据
x0x1x2z100-0.2110-0.05101-0.051110.1
我们将构造能够拟合出以上数据的单层回归神经网络
import torch
X torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype torch.float32)
z torch.tensor([[-0.2],[-0.05], [-0.05],[0.1]])
w torch.tensor([-0.2,0.15,0.15])def LinearR(X,w):zhat torch.mv(X,w)return zhatzhat LinearR(X,w)28.3 tensor计算中的新手陷阱
接下来我们对这段代码进行详细的说明
# 导入库
import torch# 首先生成特征张量
X torch.tensor([[1, 0, 0], [1, 1, 0], [1, 0, 1], [1, 1, 1]])
# 我们输入的是整数默认生成的是int64的类型# 生成标签
z torch.tensor([-0.2, -0.05, -0.05, 0.1])
# 我们输入的是浮点数默认生成的是float32的类型# 定义常量b和权重w
w torch.tensor([-0.2, 0.15, 0.15])
# 注意常量b所在的位置必须与特征张量中X中全为1的那一列所在位置相对应tensor计算中的第一大坑PyTorch的静态性 在前几节有提到过静态性指的是对输入的张量类型有明确的要求例如部分函数只能输入浮点型张量而不能输入整型张量。 # 定义线性回归计算的函数
def LinearR(X, w):# 矩阵与向量相乘时向量必须作为mv的第二个参数zhat torch.mv(X, w)return zhatLnearR(X,w)
# output :
RuntimeError : expected scalar type Long but found FloatpsLong int64 PyTorch中的许多函数都不接受浮点型的分类标签但也有许多函数要求真实标签的类型必须与预测值的类型一致因此标签的类型定义总是一个容易踩坑的地方。通常来说我们还是回将标签定义为float32如果在函数运行时报错要求整形我们再使用.long()方法将其转换为整型。
另一个非常容易踩坑的地方是PyTorch中许多函数不接受一维张量但同时也有许多函数不接受二维标签(_|||)。因此我们在生成标签时可以默认生成二维标签若函数报错说不能接受二维标签我们再使用view()函数将其调整为一维。
# 因此之后需要改成
def LinearR(X, w):zhat torch.mv(X.float(), w)return zhat# 还可以使用大写的Tensor来解决这个问题但这个方法并不推荐
X torch.Tensor([[1, 0, 0], [1, 1, 0], [1, 0, 1], [1, 1, 1]])# 或者直接养成好习惯
X torch.Tensor([[1, 0, 0], [1, 1, 0], [1, 0, 1], [1, 1, 1]], dtype torch.float32)LinearR(X,w)
# output :
tensor([-0.2000, -0.0500, -0.0500, 0.1000])torch.tensor——判断你的输入类型是什么类型然后根据你输入的数据类型来确定结果的数据类型 torch.Tensor——无论你输入什么数据都无脑使用float32 tensor计算中的第二大坑精度问题
# 预测值
zhat LinearR(X, w)
# 真实值
z torch.tensor([[-0.2],[-0.05], [-0.05],[0.1]], dtype torch.float32)zhat z
# output :
tensor([ True, False, False, False])False一定不是因为数据类型发生错误得出的因为我们已经把z的数据类型改为了浮点型。
在多元线性回归中我们使用SSE误差平方和来衡量回归的结果优劣 S S E ∑ i 1 m ( z i − z ^ i ) 2 SSE \sum_{i1}^{m}(z_i - \hat{z}_i)^2 SSEi1∑m(zi−z^i)2 如果预测值和真实值完全相等那SSE的结果应该为0。在这里SSE虽然非常接近0但的确是不为0的。
SSE sum((zhat - z) ** 2)
SSE
# output :
tensor(8.3267e-17)#设置显示精度再来看yhat与y_reg
torch.set_printoptions(precision30) #看小数点后面30位的情况zhat
# output :
tensor([-0.200000002980232238769531250000, -0.049999997019767761230468750000, -0.049999997019767761230468750000, 0.100000008940696716308593750000])z
# output :
tensor([-0.200000002980232238769531250000, -0.050000000745058059692382812500, -0.050000000745058059692382812500, 0.100000001490116119384765625000])zhat和z的差异有两个原因
float32由于只保留32位所以精确性会有一些问题。torch.mv这个函数在进行计算时内部计算时会出现一些很微小的精度问题。
精度问题在tensor维度很高数字很大时也会变得更大
preds torch.ones(300,68,64,64) * 0.1
preds.sum() * 10
# output :
tensor(83558352.)preds torch.ones(300,68,64,64)
preds.sum()
# output :
tensor(83558400.)怎么解决这个问题呢
与python中存在decimal库不同pytorch设置了64位浮点数来 尽量 减轻精度问题
preds torch.ones(300,68,64,64,dtype torch.float64) *0.1
preds.sum() * 10
# output :
tensor(83558400.000000059604644775390625000000, dtypetorch.float64)但即便如此也不能完全消除精度问题带来的区别
如果你希望能够无视掉非常小的区别而让两个张量的比较结果展示为True可以使用下面的代码
torch.allclose(zhat, z)28.4 torch.nn.Linear实现单层回归神经网络的正向传播 上面为pytorch的架构图从图中我们可以看到torch.nn是包含了构筑神经网络结构基本元素的包在这个包中可以找到任意的神经网络层。这些神经网络层都是nn.Module这个大类的子类。
我们的torch.nn.Linear就是神经网络中的“线性层”它可以实现形如 z ^ X w \hat{z}Xw z^Xw的加和功能。
在单层回归神经网络结构图中torch.nn.Linear类表示了我们的输出层。现在我们就来看看它是如何使用的。 回顾一下我们的数据
x0x1x2z100-0.2110-0.05101-0.051110.1
接下来使用nn.Linear来实现单层回归神经网络
import torch
X torch.tensor([[0,0],[1,0],[0,1],[1,1]],dtype torch.float32)
output torch.nn.Linear(2, 1)
zhat output(X)nn.Linear是一个类在这里代表了输出层所以使用了output作为变量名output 这一行相当于是类的实例化过程。实例化的时候nn.Linear需要输入两个参数分别是上一层的神经元个数这一层的神经元个数。上一层是输出层因此神经元个数由特征的个数决定2个。这一层是输出层作为回归神经网络输出层只有一个神经元。因此nn.Linear中输入的是21。上面只定义了X没有定义w和b。所有nn.Module的子类形如nn.XXX的层都会在实例化的同时随机生成w和b的初始值。所以实例化之后我们就可以调用以下属性来查看生成的 w w w和 b b b
# 查看生成的w
output.weight
# output :
Parameter containing:tensor([[ 0.683788955211639404296875000000, -0.588803172111511230468750000000]],requires_gradTrue)# 查看生成的b
output.bias
# output :
Parameter containing:tensor([0.426940977573394775390625000000], requires_gradTrue)其中w是必然会生成的b是我们可以控制是否要生成的。
output torch.nn.Linear(2, 1, bias False)由于w和b是随机生成的所以同样的代码运行多次后的结果是不一致的。如果我们希望控制随机性则可以使用torch中的random类。如下所示
torch.random.manual_seed(420) # 人为设置随机数种子由于不需要定义常量b因此在特征张量中也不需要留出与常数项相乘的x0那一列在输入数据时我们只输入了两个特征x1和x2。输入层只有一层且输入层的结构神经元的个数由输入的特征张量 X 决定因此在pytorch中构筑神经网络时不需要定义输入层。实例化之后将特征张量输入到实例化后的类中即可得到输出层的输出结果。
由于我们没有自己定义w和b所以无法让nn.Linear输出的zhat与我们真实的z接近——让真实值与预测值差异更小的部分我们会在之后进行讲解。 29 二分类神经网络逻辑回归
29.1 二分类神经网络的理论基础
线性回归是统计学经典算法它能够拟合出一条直线来描述变量之间的 线性关系 。但 在实际中变量之间的关系通常都不是一条直线而是呈现出某种曲线关系 。在统计学的历史中为了让统计学模型能够更好地拟合曲线统计学家们在线性回归的方程两边引入了联系函数link function对线性回归的方程做出了各种各样的变化并将这些变化后的方程称为“广义线性回归”。其中比较著名的有等式两边同时取对数的对数函数回归、同时取指数的S形函数回归等。 y a x b → ln y ln ( a x b ) y a x b → e y e a x b \begin{align*} y ax b \quad \rightarrow \quad \ln y \ln(ax b) \\ y ax b \quad \rightarrow \quad e^y e^{ax b} \end{align*} yyaxb→lnyln(axb)axb→eyeaxb
在探索的过程中一种奇特的变化吸引了统计学家们的注意这个变化就是sigmoid函数带来的变化。
Sigmoid函数的公式如下 σ S i g m o i d ( z ) 1 1 e − z \sigma Sigmoid(z) \frac{1}{1 e^{-z}} σSigmoid(z)1e−z1 其中 e e e为自然常数约为2.71828其中 z z z是它的自变量 σ \sigma σ是因变量 z z z的值常常是线性模型的取值比如线性回归的结果 z z z。Sigmoid函数是一个S型的函数它的图像如下 从图像上就可以看出这个函数的性质相当特别。当自变量 z z z趋近正无穷时因变量 σ \sigma σ趋近于1而当 z z z趋近负无穷时 σ \sigma σ趋近于0这使得sigmoid函数能够将任何实数映射到(0,1)区间。同时Sigmoid的导数在$ z0 点时最大这一点的斜率最大所以它可以快速将数据从 点时最大这一点的斜率最大所以它可以快速将数据从 点时最大这一点的斜率最大所以它可以快速将数据从z0$的附近排开让数据点到远离自变量取0的地方去。这样的性质让sigmoid函数拥有将连续性变量 转化为离散型变量 的力量这也就是化回归算法为分类算法的力量。
具体怎么操作呢只要将线性回归方程的结果作为自变量带入sigmoid函数得出的数据就一定是(0,1)之间的值。此时只要我们设定一个阈值比如0.5规定 大于0.5时预测结果为1类 小于0.5时预测结果为0类则可以顺利将回归算法转化为分类算法。此时我们的标签就是类别0和1了。这个阈值可以自己调整在没有调整之前一般默认0.5。 σ 1 1 e − z 1 1 e − X w \sigma \frac{1}{1 e^{-z}} \frac{1}{1 e^{-Xw}} σ1e−z11e−Xw1 更神奇的是当我们对线性回归的结果取sigmoid函数之后只要再进行以下操作
1将结果 σ \sigma σ以几率 ( σ 1 − σ ) \left(\frac{\sigma}{1-\sigma}\right) (1−σσ)的形式展现
2在几率上求以e为底的对数
就很容易得到 ln σ 1 − σ ln ( 1 1 e − X w 1 − 1 1 e − X w ) ln ( 1 1 e − X w e − X w 1 e − X w ) ln ( 1 e − X w ) ln ( e X w ) X w \begin{align*} \ln \frac{\sigma}{1-\sigma} \ln \left( \frac{\frac{1}{1e^{-Xw}}}{1 - \frac{1}{1e^{-Xw}}} \right) \\ \ln \left( \frac{\frac{1}{1e^{-Xw}}}{\frac{e^{-Xw}}{1e^{-Xw}}} \right) \\ \ln \left( \frac{1}{e^{-Xw}} \right) \\ \ln (e^{Xw}) \\ Xw \end{align*} ln1−σσln(1−1e−Xw11e−Xw1)ln(1e−Xwe−Xw1e−Xw1)ln(e−Xw1)ln(eXw)Xw 不难发现让 σ \sigma σ取对数几率后所得到的值就是我们线性回归的 z z z因为这个性质在等号两边加sigmoid的算法被称为“对数几率回归”在英文中就是Logistic Regression就是逻辑回归。逻辑回归可能是广义线性回归中最广为人知的算法它是一个叫做“回归“实际上却总是被用来做分类的算法对机器学习和深度学习都有重大的意义。在面试中如果我们希望了解一个人对机器学习的理解程度第一个问题可能就会从sigmoid函数以及逻辑回归是如何来的开始。 σ \sigma σ值代表了样本为某一类标签的概率 ln σ 1 − σ \ln \frac{\sigma}{1 - \sigma} ln1−σσ是形似对数几率的一种变化。而几率odds的本质其实是 p 1 − p \frac{p}{1-p} 1−pp其中p是事件A发生的概率而1-p是事件A不会发生的概率并且p(1-p)1。因此很多人在理解逻辑回归时都对 σ \sigma σ做出如下的解释我们让线性回归结果逼近0和1此时 σ \sigma σ和 1 − σ 1-\sigma 1−σ之和为1因此它们可以被我们看作是一对正反例发生的概率即 σ \sigma σ是某样本i的标签被预测为1的概率而 1 − σ 1-\sigma 1−σ是i的标签被预测为0的概率 σ 1 − σ \frac{\sigma}{1-\sigma} 1−σσ就是样本i的标签被预测为1的相对概率。基于这种理解逻辑回归、即单层二分类神经网络返回的结果被当成是概率来看待和使用如果直接说它就是概率或许不太严谨。每当我们希望求解“样本i的标签是1或是0的概率”时我们就使用逻辑回归。因此当一个样本对应的 σ i \sigma_i σi越接近1或0我们就认为逻辑回归对这个样本的预测结果越肯定样本被分类正确的可能性也越高。如果 σ i \sigma_i σi非常接近阈值比如0.5就说明逻辑回归其实对这个样本究竟应该是哪一类别不是非常肯定。 29.2 tensor实现二分类神经网络的正向传播
我们可以在PyTorch中非常简单地实现逻辑回归的预测过程让我们来看下面这一组数据。很容易注意到这组数据和上面的回归数据的特征 x 1 , x 2 x_1,x_2 x1,x2是完全一致的只不过标签y由连续型结果转变为了分类型的0和1。这一组分类的规律是这样的当两个特征都为1的时候标签就为1否则标签就为0。这一组特殊的数据被我们称之为 “与门”AND GATE 这里的“与”正是表示“特征一与特征二都是1”的含义。
x0x1x2andgate1000110010101111
要拟合这组数据只需要在刚才我们写好的代码后面加上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.2, 0.15, 0.15], 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 1/(1torch.exp(-zhat))#sigma torch.sigmoid(zhat)andhat torch.tensor([int(x) for x in sigma 0.5], dtype torch.float32)return sigma, andhat接下来我们对这段代码进行详细的说明
# 导入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):#首先执行线性回归的过程依然是mv函数让矩阵与向量相乘得到zzhat torch.mv(X,w) #执行sigmoid函数你可以调用torch中的sigmoid函数也可以自己用torch.exp来写sigma torch.sigmoid(zhat) #sigma 1/(1torch.exp(-zhat))#设置阈值为0.5, 使用列表推导式将值转化为0和1andhat torch.tensor([int(x) for x in sigma 0.5], dtype torch.float32) return sigma, andhatsigma, andhat LogisticR(X,w)sigma
# output :
tensor([0.450166016817092895507812500000, 0.487502634525299072265625000000,0.487502634525299072265625000000, 0.524979174137115478515625000000])andhat
# output :
tensor([0., 0., 0., 1.])andgate andhat
#最后得到的都是0和1虽然andhat数据格式是float32但本质上数还是整数不存在精度问题可见这里得到了与我们期待的结果一致的结果这就将回归算法转变为了二分类。这个过程在神经网络中的表示图如下 可以看出这个结构与线性回归的神经网络唯一不同的就是输出层中多出了一个Sigmoid(z) 。当有了Sigmoid函数的结果 σ \sigma σ之后只要了解阈值是0.5或者任意我们自己设定的数值就可以轻松地判断任意样本的预测标签 y ^ \hat{y} y^。在二分类神经网络中Sigmoid实现了将连续型数值转换为分类型数值的作用在现代神经网络架构中除了Sigmoid函数之外还有许多其他的函数可以被用来将连续型数据分割为离散型数据接下来我们就介绍一下这些函数。 29.3 符号函数signReLUTanh
符号函数sign 我们可以使用以下表达式来表示它 y { 1 if z 0 0 if z 0 − 1 if z 0 y \begin{cases} 1 \text{if } z 0 \\ 0 \text{if } z 0 \\ -1 \text{if } z 0 \end{cases} y⎩ ⎨ ⎧10−1if z0if z0if z0 由于函数的取值是间断的符号函数也被称为“阶跃函数”表示在0的两端函数的结果y是从-1直接阶跃到了1。在这里我们使用y而不是 σ \sigma σ来表示输出的结果是因为输出结果直接是0、1、-1这样的类别就相当于标签了。对于sigmoid函数而言 返回的是0~1之间的概率值如果我们希望获取最终预测出的类别还需要将概率转变成0或1这样的数字才可以。但符号函数可以直接返回类别因此我们可以认为符号函数输出的结果就是最终的预测结果y。在二分类中符号函数也可以忽略中间的时候直接分为0和1两类用如下式子表示 y { 1 if z 0 0 if z ≤ 0 y \begin{cases} 1 \text{if } z 0 \\ 0 \text{if } z \leq 0 \end{cases} y{10if z0if z≤0 等号被并在上方或下方都可以。这个式子可以很容易被转化为下面的式子 ∵ z w 1 x 1 w 2 x 2 b ∴ y { 1 if w 1 x 1 w 2 x 2 b 0 0 if w 1 x 1 w 2 x 2 b ≤ 0 ∴ y { 1 if w 1 x 1 w 2 x 2 − b 0 if w 1 x 1 w 2 x 2 ≤ − b \because z w_1 x_1 w_2 x_2 b \\ \therefore y \begin{cases} 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{cases} \\ \therefore y \begin{cases} 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{cases} ∵zw1x1w2x2b∴y{10if w1x1w2x2b0if w1x1w2x2b≤0∴y{10if w1x1w2x2−bif w1x1w2x2≤−b 此时 − b -b −b就是一个阈值我们可以使用任意字母来替代它比较常见的是字母 θ \theta θ 。当然不把它当做阈值依然保留 w 1 x 1 w 2 x 2 b w_1x_1w_2x_2b w1x1w2x2b与0进行比较的关系也没有任何问题。和sigmoid一样我们也可以使用阶跃函数来处理”与门“的数据
import torch
X torch.tensor([[0,0],[1,0],[0,1],[1,1]],dtypetorch.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阶跃函数和sigmoid都可以完成二分类的任务。在神经网络的二分类中 σ \sigma σ的默认取值一般都是sigmoid函数少用阶跃函数这是由神经网络的解法决定的。 ReLU
ReLU(Rectified Linear Unit)函数又名整流线型单元函数应用甚至比sigmoid更广泛。ReLU提供了一个很简单的非线性变换当输入的自变量大于0时直接输出该值当输入的自变量小于等于0时输出0。这个过程可以用以下公式表示出来 R e L U : σ { z ( z 0 ) 0 ( z ≤ 0 ) ReLU: \sigma \begin{cases} z (z 0) \\ 0 (z \leq 0) \end{cases} ReLU:σ{z0(z0)(z≤0) ReLU函数是一个非常简单的函数本质就是max(0,z)。max函数会从输入的数值中选择较大的那个值进行输出以达到保留正数元素将负元素清零的作用。ReLU的图像如下所示 相对的ReLU函数导数的图像如下 当输入 z z z为正数时ReLU函数的导数为1当 z z z为负数时ReLU函数的导数为0当输入为0时ReLU函数不可导。因此ReLU函数的导数图像看起来就是阶跃函数这是一个美好的巧合。 tanh
tanhhyperbolic tangent是双曲正切函数双曲正切函数的性质与sigmoid相似它能够将数值压缩到(-1,1)区间内。 t a n h : σ e 2 z − 1 e 2 z 1 tanh: \sigma \frac{e^{2z} - 1}{e^{2z} 1} tanh:σe2z1e2z−1 而双曲正切函数的图像如下 可以看出tanh的图像和sigmoid函数很像不过sigmoid函数的范围是在(0,1)之间tanh却是在坐标系的原点(0,0)点上中心对称。
对tanh求导后可以得到如下公式和导数图像 tanh ′ ( z ) 1 − tanh 2 ( z ) \tanh(z) 1 - \tanh^2(z) tanh′(z)1−tanh2(z)
可以看出当输入的 约接近于0tanh函数导数也越接近最大值1当输入越偏离0时tanh函数的导数越接近于0。**这些函数是最常见的二分类转化函数他们在神经网络的结构中有着不可替代的作用。**在单层神经网络中这种作用是无法被体现的因此关于这一点我们可以之后再进行说明。到这里我们只需要知道这些函数都可以将连续型数据转化为二分类就足够了。 29.4 torch.functional实现二分类神经网络的正向传播
之前我们使用torch.nn.Linear类实现了单层回归神经网络现在我们试着来实现单层二分类神经网络也就是逻辑回归。逻辑回归与线性回归的唯一区别就是在线性回归的结果之后套上了sigmoid函数。
不难想象只要让nn.Linear的输出结果再经过sigmoid函数就可以实现逻辑回归的正向传播了。
在PyTorch中我们几乎总是从nn.functional中调用相关函数。 回顾一下我们的数据和网络架构
x0x1x2andgate1000110010101111
接下来我们在之前线性回归代码的基础上加上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]在这里nn.Linear虽然依然是输出层但却没有担任最终输出值的角色因此这里我们使用dense作为变量名。dense表示紧密链接的层即上一层的大部分神经元都与这一层的大部分神经元相连在许多神经网络中我们都会用到密集链接的层因此dense是我们经常会用到的一个变量名。我们将数据从nn.Linear传入得到zhat然后再将zhat的结果传入sigmoid函数得到sigma之后再设置阈值为0.5得到最后的y。
在PyTorch中我们可以从functional模块里找出大部分之前我们提到的函数
#符号函数sign
torch.sign(zhat)
#ReLU
F.relu(zhat)
#tanh
torch.tanh(zhat)在PyTorch的安排中符号函数sign与双曲正切函数tanh更多时候只是被用作数学计算工具而ReLU和Sigmoid却作为神经网络的组成部分被放在库functional中这其实反映出实际使用时大部分人的选择。
ReLU与Sigmoid还是主流的、位于nn.Linear后的函数。 30 多分类神经网络Softmax回归
30.1 认识softmax函数
之前介绍分类神经网络时我们只说明了二分类问题即标签只有两种类别的问题0和1猫和狗。虽然在实际应用中许多分类问题都可以用二分类的思维解决但依然存在很多多分类的情况最典型的就是手写数字的识别问题。计算机在识别手写数字时需要对每一位数字进行判断而个位数字总共有10个0~9所以手写数字的分类是十分类问题一般分别用0~9表示。 Softmax函数是深度学习基础中的基础它是神经网络进行多分类时默认放在输出层中处理数据的函数。假设现在神经网络是用于三分类数据且三个分类分别是苹果柠檬和百香果序号则分别是分类1、分类2和分类3。则使用softmax函数的神经网络的模型会如下所示 与二分类一样我们从网络左侧输入特征从右侧输出概率且概率是通过线性回归的结果 z z z外嵌套softmax函数来进行计算。在二分类时输出层只有一个神经元只输出样本对于正类别的概率通常是标签为1的概率而softmax的输出层有三个神经元分别输出该样本的真实标签是苹果、柠檬或百香果的概率 σ 1 , σ 2 , σ 3 \sigma_1,\sigma_2,\sigma_3 σ1,σ2,σ3。在多分类中神经元的个数与标签类别的个数是一致的如果是十分类在输出层上就会存在十个神经元分别输出十个不同的概率。此时样本的预测标签就是所有输出的概率 σ 1 , σ 2 , σ 3 \sigma_1,\sigma_2,\sigma_3 σ1,σ2,σ3中最大的概率对应的标签类别。
那每个概率是如何计算出来的呢来看Softmax函数的公式 σ k Softmax ( z k ) e z k ∑ K e z \sigma_k \text{Softmax}(z_k) \frac{e^{z_k}}{\sum^{K} e^z} σkSoftmax(zk)∑Kezezk 其中 e e e为自然常数约为2.71828 与sigmoid函数中的 z z z一样表示回归类算法如线性回归的结果。 表示该数据的标签中总共有 K K K个标签类别如三分类时 K 3 K3 K3四分类时 K 4 K4 K4。 k k k表示标签类别 k k k类。很容易可以看出Softmax函数的分子是多分类状况下某一个标签类别的回归结果的指数函数分母是多分类状况下所有标签类别的回归结果的指数函数之和因此Softmax****函数的结果代表了样本的结果为类别 k k k的概率**。 30.2 Pytorch中的softmax函数
我们曾经提到过神经网络是模型效果很好但运算速度非常缓慢的算法。softmax函数也存在相同的问题——它可以将多分类的结果转变为概率这是一个极大的优势但它需要的计算量非常巨大。由于softmax的分子和分母中都带有 e e e为底的指数函数所以在计算中非常容易出现极大的数值。 如上图所示 e10就已经等于20000了而回归结果 z z z完全可能是成千上万的数字。事实上e100会变成一个后面有40多个0的超大值e1000则会直接返回无限大inf这意味着这些数字已经超出了计算机处理数时要求的有限数据宽度超大数值无法被计算机运算和表示。这种现象叫做“溢出“当计算机返回”内存不足”或Python服务器直接断开连接的时候可能就是发生了这个问题。来看看这个问题实际发生时的状况
#对于单一样本假定一组巨大的z
z torch.tensor([1010,1000,990], dtypetorch.float32)
torch.exp(z) / torch.sum(torch.exp(z)) # softmax函数的运算
# output :
tensor([nan, nan, nan])因此我们一般不会亲自使用tensor来手写softmax函数。在PyTorch中我们往往使用内置好的softmax函数来计算softmax的结果我们可以使用torch.softmax来轻松的调用它具体代码如下
z torch.tensor([1010,1000,990], dtypetorch.float32)
torch.softmax(z,0)
#你也可以使用F.softmax, 它返回的结果与torch.softmax是完全一致的#假设三个输出层神经元得到的z分别是1095
z torch.tensor([10,9,5], dtypetorch.float32)
torch.exp(z) / torch.sum(torch.exp(z)) # softmax函数的运算z torch.tensor([10,9,5], dtypetorch.float32)
torch.softmax(z,0) # 第二个参数表示计算的维度索引
# output :
tensor([0.7275, 0.2676, 0.0049])从上面的结果可以看出softmax函数输出的是从0到1.0之间的实数而且多个输出值的总和是1。因为有了这个性质我们可以把softmax函数的输出解释为“概率”这和我们使用sigmoid函数之后认为函数返回的结果是概率异曲同工。从结果来看我们可以认为返回了我们设定的 [10,9,5]的这个样本的结果应该是第一个类别也就是z10的类别因为类别1的概率是最大的
需要注意的是使用了softmax函数之后各个 之间的大小关系并不会随之改变这是因为指数函数ez是单调递增函数也就是说使用softmax之前的 如果比较大那使用softmax之后返回的概率也依然比较大。这是说无论是否使用softmax我们都可以判断出样本被预测为哪一类我们只需要看最大的那一类就可以了。所以在神经网络进行分类的时候如果不需要了解具体分类问题每一类的概率是多少而只需要知道最终的分类结果我们可以省略输出层上的softmax函数。 30.3 使用nn.Linear与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,3) #此时输出层上的神经元个数是3个因此应该是23
zhat dense(X)
sigma F.softmax(zhat,dim1) #此时需要进行加和的维度是131 回归vs二分类vs多分类
到这里我们已经见过了三个不同的神经网络 注意到有什么相似和不同了吗
首先可能会注意到的是这三个神经网络都是单层神经网络除了输入层他们都有且只有一层网络。实际上现实中使用的神经网络几乎99%都是多层的但我们的网络也能够顺利进行预测这说明单层神经网络其实已经能够实现基本的预测功能。同时这也说明了一个问题无论处理的是回归还是分类神经网络的处理原理是一致的。实际上就连算法的限制、优化方法和求解方法也都是一致的。回归和分类神经网络唯一的不同只有输出层上的 σ \sigma σ。
虽然线性回归看起来并没有 σ \sigma σ的存在但实际上我们可以认为线性回归中的 σ \sigma σ是一个恒等函数identityfunction即是说 σ ( z ) z \sigma(z)z σ(z)z相当于 y x yx yx或 f ( x ) x f(x)x f(x)x。而多分类的时候也可以不采用任何函数只观察 z z z的大小所以多分类也可以被认为是利用了恒等函数作为 σ \sigma σ。总结来说回归和分类对应的 σ \sigma σ分别如下
输出类型 σ \sigma σ回归恒等函数二分类sigmoid或任意可以实现二分类的函数通常都是sigmoid多分类softmax或恒等函数
第二个很容易发现的现象是只有多分类的情况在输出层出现了超过一个神经元。实际上当处理单标签问题时即只有一个y的问题回归神经网络和二分类神经网络的输出层永远只有一个神经元而只有多分类的情况才会让输出层上超过一个神经元。