深度学习 Pytorch 单层神经网络

news2025/1/27 3:45:59

神经网络是模仿人类大脑结构所构建的算法,在人脑里,我们有轴突连接神经元,在算法中,我们用圆表示神经元,用线表示神经元之间的连接,数据从神经网络的左侧输入,让神经元处理之后,从右侧输出结果。

在这里插入图片描述

下图是一个最简单的神经元的结构。从这里开始,我们正式开始认识神经网络。

在这里插入图片描述


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^i=b+w1xi1+w2xi2++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 y=ax+b 是同样的性质。其中 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^i=b+w1xi1+w2xi2++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^i=b+w1xi1+w2xi2++wnxin
其中 z ^ i \hat{z}_i z^i是包含了m个全部的样本的预测结果的列向量。注意,我们通常使用粗体的小写字母来表示列向量,粗体的大写字母表示矩阵或者行列式。 并且在机器学习中,我们默认所有的一维向量都是列向量。

我们可以使用矩阵来表示上面多个样本的回归结果的方程,其中 w w w可以被看做是一个结构为(n+1,1)的列矩阵(这里的n加上的1是我们的截距b), 是一个结构为(m,n+1)的特征矩阵(这里的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^3z^m = 1111x11x21x31xm1x12x22x32xm2x13x23x33xm3x1nx2nx3nxmn bw1w2wn

z ^ = X w \hat{z} = Xw z^=Xw

如果在我们的方程里没有常量b,我们则可以不写X中的第一列以及w中的第一行。

线性回归的任务,就是构造一个预测函数来映射输入的特征矩阵 和标签值 的线性关系。这个预测函数的图像是一条直线,所以线性回归的求解就是对直线的拟合过程。

预测函数的本质就是我们需要构建的模型,而构造预测函数的核心就是找出模型的权重向量 ,也就是求解线性方程组的参数(相当于求解 y = a x + b y=ax+b y=ax+b里的 a a a b b b)。

现在假设,我们的数据只有2个特征,则线性回归方程可以写作如下结构:
z ^ = b + x 1 w 1 + x 2 w 2 \hat{z}=b+x_1w_1+x_2w_2 z^=b+x1w1+x2w2
此时,我们只要对模型输入特征 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 b+x_1w_1+x_2w_2 b+x1w1+x2w2,这个值正是我们的预测值。可见,线性回归方程与上面的神经网络图达到的效果是一模一样的

在上述过程中,左侧的是神经网络的输入层input layer)。输入层由众多承载数据用的神经元组成,数据从这里输入,并流入处理数据的神经元中。在所有神经网络中,输入层永远只有一层,且每个神经元上只能承载一个特征(一个 x x x)或一个常量(通常都是1)。现在的二元线性回归只有两个特征,所以输入层上只需要三个神经元,包括两个特征和一个常量,其中这里的常量仅仅是被用来乘以偏差 b b b用的。对于没有偏差的线性回归来说,我们可以不设置常量1

右侧的是输出层output layer)。输出层由大于等于一个神经元组成,我们总是从这一层来获取预测结果。输出层的每个神经元上都承载着单个或多个功能,可以处理被输入神经元的数据。在线性回归中,这个功能就是“加和”,当我们把加和替换成其他的功能,就能够形成各种不同的神经网络。

在神经元之间相互连接的线表示了数据流动的方向,就像人脑神经细胞之间相互联系的“轴突”。在人脑神经细胞中,轴突控制电子信号流过的强度,在人工神经网络中,神经元之间的连接线上的权重也代表了信息可通过的强度。最简单的例子是,当 w w w0.5时,在特征 x 1 x_1 x1上的信息就只有0.5倍能够传递到下一层神经元中,因为被输入到下层神经元中去进行计算的实际值是 0.5 x 1 0.5x_1 0.5x1。相对的,如果 w 1 w_1 w12.5,则会传递2.5倍的 上的信息。因此,有的深度学习课程会将权重 w w w比喻成是电路中的”电压“,电压越大,则电信号越强烈,电压越小,信号也越弱,这都是在描述权重 w w w会如何影响传入下一层神经元的信息/数据量的大小。

到此,我们已经了解了线性回归的网络是怎么一回事,它是最简单的回归神经网络,同时也是最简单的神经网络。类似于线性回归这样的神经网络,被称为单层神经网络

单层神经网络
从直观来看,线性回归的网络结构明明有两层,为什么线性回归被叫做“单层神经网络”呢?业内通识是,在描述神经网络的层数的时候,我们不考虑输入层。输入层是每个神经网络都必须存在的一层,当使用相同的输入数据时,任意两个神经网络之间的不同之处就在输入层之后的所有层。所以,我们把输入层之后只有一层的神经网络称为单层神经网络。在非常非常少见的情况下,有的深度学习课程或教材中也会直接将所有层都算入其中,将上述网络称为“两层神经网络”,这种做法虽然不太规范,但也不能称之为“错误的”。因此,当出现“N层神经网络”的描述时,一定要注意原作者是否将输入层考虑进去了。

28.2 tensor实现单层神经网络的正向传播

让我们使用一组非常简单的代码来实现一下回归神经网络求解 z ^ \hat{z} z^的过程,在神经网络中,这个过程是从左向右进行的,被称为神经网络的正向传播(forward spread)。来看下面这组数据:

x0x1x2z
100-0.2
110-0.05
101-0.05
1110.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 zhat

zhat = 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 zhat
LnearR(X,w)
# output : 
RuntimeError : expected scalar type Long but found Float

ps:Long = 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_{i=1}^{m}(z_i - \hat{z}_i)^2 SSE=i=1m(ziz^i)2
如果预测值和真实值完全相等,那SSE的结果应该为0。在这里,SSE虽然非常接近0,但的确是不为0的。

SSE = sum((zhat - z) ** 2)
SSE
# output :
tensor(8.3267e-17)
#设置显示精度,再来看yhat与y_reg
torch.set_printoptions(precision=30) #看小数点后面30位的情况
zhat
# output :
tensor([-0.200000002980232238769531250000, -0.049999997019767761230468750000, -0.049999997019767761230468750000, 0.100000008940696716308593750000])
z
# output :
tensor([-0.200000002980232238769531250000, -0.050000000745058059692382812500, -0.050000000745058059692382812500,  0.100000001490116119384765625000])

zhatz的差异有两个原因:

  • 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, dtype=torch.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类表示了我们的输出层。现在我们就来看看它是如何使用的。

在这里插入图片描述

回顾一下我们的数据:

x0x1x2z
100-0.2
110-0.05
101-0.05
1110.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中输入的是(2,1)。
  • 上面只定义了X,没有定义wb。所有nn.Module的子类,形如nn.XXX的层,都会在实例化的同时随机生成wb的初始值。所以实例化之后,我们就可以调用以下属性来查看生成的 w w w b b b
# 查看生成的w
output.weight
# output :
Parameter containing:tensor([[ 0.683788955211639404296875000000, -0.588803172111511230468750000000]],requires_grad=True)
    
# 查看生成的b
output.bias
# output :
Parameter containing:tensor([0.426940977573394775390625000000], requires_grad=True)
  • 其中,w是必然会生成的,b是我们可以控制是否要生成的。
output = torch.nn.Linear(2, 1, bias = False)
  • 由于wb是随机生成的,所以同样的代码运行多次后的结果是不一致的。如果我们希望控制随机性,则可以使用torch中的random类。如下所示:
torch.random.manual_seed(420)	# 人为设置随机数种子
  • 由于不需要定义常量b,因此在特征张量中,也不需要留出与常数项相乘的x0那一列,在输入数据时,我们只输入了两个特征x1和x2
  • 输入层只有一层,且输入层的结构(神经元的个数)由输入的特征张量 X 决定,因此在pytorch中构筑神经网络时,不需要定义输入层。
  • 实例化之后,将特征张量输入到实例化后的类中,即可得到输出层的输出结果。

由于我们没有自己定义wb,所以无法让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*} yy=ax+blny=ln(ax+b)=ax+bey=eax+b

在探索的过程中,一种奇特的变化吸引了统计学家们的注意,这个变化就是sigmoid函数带来的变化。

Sigmoid函数的公式如下:
σ = S i g m o i d ( z ) = 1 1 + e − z \sigma = Sigmoid(z) = \frac{1}{1 + e^{-z}} σ=Sigmoid(z)=1+ez1
其中 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的导数在$ z=0 点时最大(这一点的斜率最大),所以它可以快速将数据从 点时最大(这一点的斜率最大),所以它可以快速将数据从 点时最大(这一点的斜率最大),所以它可以快速将数据从z=0$的附近排开,让数据点到远离自变量取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}} σ=1+ez1=1+eXw1
更神奇的是,当我们对线性回归的结果取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}{1+e^{-Xw}}}{1 - \frac{1}{1+e^{-Xw}}} \right) \\ &= \ln \left( \frac{\frac{1}{1+e^{-Xw}}}{\frac{e^{-Xw}}{1+e^{-Xw}}} \right) \\ &= \ln \left( \frac{1}{e^{-Xw}} \right) \\ &= \ln (e^{Xw}) \\ &= Xw \end{align*} ln1σσ=ln(11+eXw11+eXw1)=ln(1+eXweXw1+eXw1)=ln(eXw1)=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} 1pp,其中p是事件A发生的概率,而1-p是事件A不会发生的概率,并且p+(1-p)=1。因此,很多人在理解逻辑回归时,都对 σ \sigma σ做出如下的解释:我们让线性回归结果逼近01,此时 σ \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越接近10,我们就认为逻辑回归对这个样本的预测结果越肯定,样本被分类正确的可能性也越高。如果 σ i \sigma_i σi非常接近阈值(比如0.5),就说明逻辑回归其实对这个样本究竟应该是哪一类别,不是非常肯定。

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

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

x0x1x2andgate
1000
1100
1010
1111

拟合这组数据,只需要在刚才我们写好的代码后面加上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/(1+torch.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函数,让矩阵与向量相乘得到z
    zhat = torch.mv(X,w) 
    #执行sigmoid函数,你可以调用torch中的sigmoid函数,也可以自己用torch.exp来写
    sigma = torch.sigmoid(zhat) 
    #sigma = 1/(1+torch.exp(-zhat))
    #设置阈值为0.5, 使用列表推导式将值转化为0和1
    andhat = torch.tensor([int(x) for x in sigma >= 0.5], dtype = torch.float32) 
    return sigma, andhat
sigma, 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 符号函数sign,ReLU,Tanh

符号函数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= 101if z>0if z=0if z<0
由于函数的取值是间断的,符号函数也被称为“阶跃函数”,表示在0的两端,函数的结果y是从-1直接阶跃到了1。在这里,我们使用y而不是 σ \sigma σ来表示输出的结果,是因为输出结果直接是01-1这样的类别,就相当于标签了。对于sigmoid函数而言, 返回的是0~1之间的概率值,如果我们希望获取最终预测出的类别,还需要将概率转变成01这样的数字才可以。但符号函数可以直接返回类别,因此我们可以认为符号函数输出的结果就是最终的预测结果y。在二分类中,符号函数也可以忽略中间的时候,直接分为01两类,用如下式子表示:
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 z>0if z0
等号被并在上方或下方都可以。这个式子可以很容易被转化为下面的式子:
∵ 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} z=w1x1+w2x2+by={10if w1x1+w2x2+b>0if w1x1+w2x2+b0y={10if w1x1+w2x2>bif w1x1+w2x2b
此时, − b -b b就是一个阈值,我们可以使用任意字母来替代它,比较常见的是字母 θ \theta θ 。当然,不把它当做阈值,依然保留 w 1 x 1 + w 2 x 2 + b w_1x_1+w_2x_2+b w1x1+w2x2+b0进行比较的关系也没有任何问题。和sigmoid一样,我们也可以使用阶跃函数来处理”与门“的数据:

import torch
X = torch.tensor([[0,0],[1,0],[0,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

阶跃函数和sigmoid都可以完成二分类的任务。在神经网络的二分类中, 的默认取值一般都是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(z>0)(z0)
ReLU函数是一个非常简单的函数,本质就是max(0,z)max函数会从输入的数值中选择较大的那个值进行输出,以达到保留正数元素,将负元素清零的作用。ReLU的图像如下所示:

在这里插入图片描述

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

在这里插入图片描述

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


tanh

tanh(hyperbolic 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:σ=e2z+1e2z1
而双曲正切函数的图像如下:

在这里插入图片描述

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

tanh求导后可以得到如下公式和导数图像:
tanh ⁡ ′ ( z ) = 1 − tanh ⁡ 2 ( z ) \tanh'(z) = 1 - \tanh^2(z) tanh(z)=1tanh2(z)
在这里插入图片描述

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


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

之前我们使用torch.nn.Linear类实现了单层回归神经网络,现在我们试着来实现单层二分类神经网络,也就是逻辑回归。逻辑回归与线性回归的唯一区别,就是在线性回归的结果之后套上了sigmoid函数。

不难想象,只要让nn.Linear的输出结果再经过sigmoid函数,就可以实现逻辑回归的正向传播了。

PyTorch中,我们几乎总是从nn.functional中调用相关函数。

在这里插入图片描述

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

x0x1x2andgate
1000
1100
1010
1111

接下来,我们在之前线性回归代码的基础上,加上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更多时候只是被用作数学计算工具,而ReLUSigmoid却作为神经网络的组成部分被放在库functional中,这其实反映出实际使用时大部分人的选择。

ReLUSigmoid还是主流的、位于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} σk=Softmax(zk)=Kezezk
其中 e e e为自然常数(约为2.71828), 与sigmoid函数中的 z z z一样,表示回归类算法(如线性回归)的结果。 表示该数据的标签中总共有 K K K个标签类别,如三分类时 K = 3 K=3 K=3,四分类时 K = 4 K=4 K=4 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], dtype=torch.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], dtype=torch.float32)
torch.softmax(z,0)
#你也可以使用F.softmax, 它返回的结果与torch.softmax是完全一致的

#假设三个输出层神经元得到的z分别是10,9,5
z = torch.tensor([10,9,5], dtype=torch.float32)
torch.exp(z) / torch.sum(torch.exp(z)) # softmax函数的运算

z = torch.tensor([10,9,5], dtype=torch.float32)
torch.softmax(z,0)	# 第二个参数表示计算的维度索引
# output :
tensor([0.7275, 0.2676, 0.0049])

从上面的结果可以看出,softmax函数输出的是从01.0之间的实数,而且多个输出值的总和是1。因为有了这个性质,我们可以把softmax函数的输出解释为“概率”,这和我们使用sigmoid函数之后认为函数返回的结果是概率异曲同工。从结果来看,我们可以认为返回了我们设定的 ([10,9,5])的这个样本的结果应该是第一个类别(也就是z=10的类别),因为类别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个,因此应该是(2,3)
zhat = dense(X)
sigma = F.softmax(zhat,dim=1) #此时需要进行加和的维度是1

31 回归vs二分类vs多分类

到这里,我们已经见过了三个不同的神经网络:

在这里插入图片描述

注意到有什么相似和不同了吗?

首先可能会注意到的是,这三个神经网络都是单层神经网络,除了输入层,他们都有且只有一层网络。实际上,现实中使用的神经网络几乎99%都是多层的,但我们的网络也能够顺利进行预测,这说明单层神经网络其实已经能够实现基本的预测功能。同时,这也说明了一个问题,无论处理的是回归还是分类,神经网络的处理原理是一致的。实际上,就连算法的限制、优化方法和求解方法也都是一致的。回归和分类神经网络唯一的不同只有输出层上的 σ \sigma σ

虽然线性回归看起来并没有 σ \sigma σ的存在,但实际上我们可以认为线性回归中的 σ \sigma σ是一个恒等函数(identityfunction),即是说 σ ( z ) = z \sigma(z)=z σ(z)=z(相当于 y = x y=x y=x,或 f ( x ) = x f(x)=x f(x)=x)。而多分类的时候也可以不采用任何函数,只观察 z z z的大小,所以多分类也可以被认为是利用了恒等函数作为 σ \sigma σ。总结来说,回归和分类对应的 σ \sigma σ分别如下:

输出类型 σ \sigma σ
回归恒等函数
二分类sigmoid或任意可以实现二分类的函数(通常都是sigmoid
多分类softmax或恒等函数

第二个很容易发现的现象是,只有多分类的情况在输出层出现了超过一个神经元。实际上,当处理单标签问题时(即只有一个y的问题),回归神经网络和二分类神经网络的输出层永远只有一个神经元,而只有多分类的情况才会让输出层上超过一个神经元。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2283756.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

政安晨的AI大模型训练实践三:熟悉一下LF训练模型的WebUI

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 目录 启动WebUI 微调模型 LLaMA-Factory 支持通过 WebUI 零代码微调大语言模型。 启动Web…

Flink Gauss CDC:深度剖析存量与增量同步的创新设计

目录 设计思路 1.为什么不直接用FlinkCDC要重写Flink Gauss CDC 2.存量同步的逻辑是什么 2.1、单主键的切片策略是什么 2.2、​​​​​复合主键作切片,怎么保证扫描到所有的数据 3、增量同步的逻辑是什么 4、存量同步结束之后如何无缝衔接增量同步 5、下游数据如何落…

【深入理解SpringCloud微服务】Sentinel规则持久化实战

Sentinel规则持久化实战 Sentinel规则推送模式原始模式pull&#xff08;拉模式&#xff09;push&#xff08;推模式&#xff09; 实现拉模式实现推模式 Sentinel规则推送模式 原始模式 原始模式由Sentinel控制台直接推送规则到Sentinel客户端&#xff0c;Sentinel客户端将规则…

PCIE模式配置

对于VU系列FPGA&#xff0c;当DMA/Bridge Subsystem for PCI Express IP配置为Bridge模式时&#xff0c;等同于K7系列中的AXI Memory Mapped To PCI Express IP。

【由浅入深认识Maven】第2部分 maven依赖管理与仓库机制

文章目录 第二篇&#xff1a;Maven依赖管理与仓库机制一、前言二、依赖管理基础1.依赖声明2. 依赖范围&#xff08;Scope&#xff09;3. 依赖冲突与排除 三、Maven的仓库机制1. 本地仓库2. 中央仓库3. 远程仓库 四、 版本管理策略1. 固定版本2. 版本范围 五、 总结 第二篇&…

备赛蓝桥杯之第十五届职业院校组省赛第一题:智能停车系统

提示&#xff1a;本篇文章仅仅是作者自己目前在备赛蓝桥杯中&#xff0c;自己学习与刷题的学习笔记&#xff0c;写的不好&#xff0c;欢迎大家批评与建议 由于个别题目代码量与题目量偏大&#xff0c;请大家自己去蓝桥杯官网【连接高校和企业 - 蓝桥云课】去寻找原题&#xff0…

力扣 Hot 100 题解 (js版)更新ing

&#x1f6a9;哈希表 ✅ 1. 两数之和 Code&#xff1a; 暴力法 复杂度分析&#xff1a; 时间复杂度&#xff1a; ∗ O ( N 2 ) ∗ *O(N^2)* ∗O(N2)∗&#xff0c;其中 N 是数组中的元素数量。最坏情况下数组中任意两个数都要被匹配一次。空间复杂度&#xff1a;O(1)。 /…

DeepSeek-R1:性能对标 OpenAI,开源助力 AI 生态发展

DeepSeek-R1&#xff1a;性能对标 OpenAI&#xff0c;开源助力 AI 生态发展 在人工智能领域&#xff0c;大模型的竞争一直备受关注。最近&#xff0c;DeepSeek 团队发布了 DeepSeek-R1 模型&#xff0c;并开源了模型权重&#xff0c;这一举动无疑为 AI 领域带来了新的活力。今…

CY T 4 BB 5 CEB Q 1 A EE GS MCAL配置 - MCU组件

1、ResourceM 配置 选择芯片信号: 2、MCU 配置 2.1 General配置 1) McuDevErrorDetect: - 启用或禁用MCU驱动程序模块的开发错误通知功能。 - 注意:采用DET错误检测机制作为安全机制(故障检测)时,不能禁用开发错误检测。2) McuGetRamStateApi - enable/disable th…

校园商铺管理系统设计与实现(代码+数据库+LW)

摘 要 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上自…

【JavaWeb学习Day13】

Tlias智能学习系统 需求&#xff1a; 部门管理&#xff1a;查询、新增、修改、删除 员工管理&#xff1a;查询、新增、修改、删除和文件上传 报表统计 登录认证 日志管理 班级、学员管理&#xff08;实战内容&#xff09; 部门管理&#xff1a; 01准备工作 开发规范-…

springboot使用tomcat浅析

springboot使用tomcat浅析 关于外部tomcat maven pom配置 // 打包时jar包改为war包 <packaging>war</packaging>// 内嵌的tomcat的scope标签影响范围设置为provided&#xff0c;只在编译和测试时有效&#xff0c;打包时不带入 <dependency><groupId>…

如何使用CRM数据分析优化销售和客户关系?

嘿&#xff0c;大家好&#xff01;你有没有想过为什么有些公司在市场上如鱼得水&#xff0c;而另一些却在苦苦挣扎&#xff1f;答案可能就藏在他们的销售策略和客户关系管理&#xff08;CRM&#xff09;系统里。今天我们要聊的就是如何通过有效的 CRM 数据分析来提升你的销售额…

Qt 控件与布局管理

1. Qt 控件的父子继承关系 在 Qt 中&#xff0c;继承自 QWidget 的类&#xff0c;通常会在构造函数中接收一个 parent 参数。 这个参数用于指定当前空间的父控件&#xff0c;从而建立控件间的父子关系。 当一个控件被设置为另一控件的子控件时&#xff0c;它会自动成为该父控…

电力场效应晶体管(电力 MOSFET),全控型器件

电力场效应晶体管&#xff08;Power MOSFET&#xff09;属于全控型器件是一种电压触发的电力电子器件&#xff0c;一种载流子导电&#xff08;单极性器件&#xff09;一个器件是由一个个小的mosfet组成以下是相关介绍&#xff1a; 工作原理&#xff08;栅极电压控制漏极电流&a…

一文讲解Java中的重载、重写及里氏替换原则

提到重载和重写&#xff0c;Java小白应该都不陌生&#xff0c;接下来就通过这篇文章来一起回顾复习下吧&#xff01; 重载和重写有什么区别呢&#xff1f; 如果一个类有多个名字相同但参数不同的方法&#xff0c;我们通常称这些方法为方法重载Overload。如果方法的功能是一样…

Pandas基础02(DataFrame创建/索引/切片/属性/方法/层次化索引)

DataFrame数据结构 DataFrame 是一个二维表格的数据结构&#xff0c;类似于数据库中的表格或 Excel 工作表。它由多个 Series 组成&#xff0c;每个 Series 共享相同的索引。DataFrame 可以看作是具有列名和行索引的二维数组。设计初衷是将Series的使用场景从一维拓展到多维。…

Meta-CoT:通过元链式思考增强大型语言模型的推理能力

大型语言模型&#xff08;LLMs&#xff09;在处理复杂推理任务时面临挑战&#xff0c;这突显了其在模拟人类认知中的不足。尽管 LLMs 擅长生成连贯文本和解决简单问题&#xff0c;但在需要逻辑推理、迭代方法和结果验证的复杂任务&#xff08;如高级数学问题和抽象问题解决&…

【时时三省】(C语言基础)二进制输入输出

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 二进制输入 用fread可以读取fwrite输入的内容 字符串以文本的形式写进去的时候&#xff0c;和以二进制写进去的内容是一样的 整数和浮点型以二进制写进去是不一样的 二进制输出 fwrite 字…

【go语言】数组和切片

一、数组 1.1 什么是数组 数组是一组数&#xff1a;数组需要是相同类型的数据的集合&#xff1b;数组是需要定义大小的&#xff1b;数组一旦定义了大小是不可以改变的。 1.2 数组的声明 数组和其他变量定义没有什么区别&#xff0c;唯一的就是这个是一组数&#xff0c;需要给…