(1)单层正向回归网络
b | x1 | x2 | z |
1 | 0 | 0 | -0.2 |
1 | 1 | 0 | -0.05 |
1 | 0 | 1 | -0.05 |
1 | 1 | 1 | 0.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)
zhat
下面对这段代码做一个详细说明:
(1)tensor的定义
这里我们需要定义成浮点数的原因,是因为torch中有很多函数不接受两个类型不同的参数传入,例如这里的mv函数,如果我们省略浮点的定义,就会出现报错如下:
这种静态的编程可以说既是pytorch的优点也是缺点,优点在于如果省略了对传入参数的检查处理,就会在运行速度上快很多,尤其是传入数据量大的时候,对每一个数据进行检查是一个很慢的过程。缺点在于你需要人为记住哪些函数是可以不同类型哪些不可以,pytorch没有一个统一的标准,唯一的办法就只有自己多试几次,所以建议大家在定义tensor的时候就默认定义成为float32
另外由于很多pytorch函数不接受一维张量,所以这里我们在定义的时候也养成两个[[的二维张量的习惯
(2)精度问题
如果我们想要查看运行结果是否和答案一致的时候,我们运行下面代码会发现奇怪的现象:
那就是为什么后面三个会是false,这其实就是精度的问题了。
所以当我们人为把精度调高到30位的时候就可以发现问题了:
当数据量大的时候,会因为精度问题产生较大的误差,如果想要控制误差大小,可以使用64位浮点数,唯一的缺点就是会比较占用内存。如果我们想要忽略较小的误差可以使用下面这个函数:
通过shift+tab快捷键来查看函数参数使用,我们也可以通过调整rtol和atol控制误差大小,可以看到这里的默认参数已经挺小的了
(2)torch.nn.Linear
在使用之前,我们先给出pytorch的架构图,包含了常用的类和库
我们先给出代码再进行解释:
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)
zhat
首先nn.Linear是一个类,在这里代表了输出层,所以使用output作为变量名,output的这一行相当于是类的实例化过程。里面输入的参数2和1分别指上一层的神经元个数和这一层的神经元个数。
由于nn.Module的子类会在实例化的同时自动生成随机的w和b,所以这里我们上一层就只有两个x,又由于这是回归类模型所以输出层只有一个。我们也可以调用查看随机生成的数:
如果我们不想要bias可以设置成false:output = torch.nn.Linear(2,1,bias = False)
如果我们不想都每次都随机生成不同的权重,可以设置随机数种子:torch.random.manual_seed(250)
(3)逻辑回归
我们学习过线性回归之后,都知道线性回归能够拟合直线的线性关系,但是现实生活中的数据关系往往不是线性的,所以为了更好的拟合曲线关系,统计学家就在线性回归的方程两边加上了联系函数,这种回归也被叫做广义线性回归。而在探索联系函数的过程中,就发现了sigmoid函数。
我们可以运行下面代码来画出sigmoid图像:
import numpy as np
import matplotlib.pyplot as plt# 定义Sigmoid函数
def sigmoid(x):return 1 / (1 + np.exp(-x))# 生成x轴数据
x = np.linspace(-10, 10, 400)# 计算y轴数据
y = sigmoid(x)# 绘图
plt.figure(figsize=(8, 6))
plt.plot(x, y, label="Sigmoid Function", color='b')
plt.title("Sigmoid Function")
plt.xlabel("x")
plt.ylabel("sigmoid(x)")
plt.grid(True)
plt.axhline(0, color='black',linewidth=0.5)
plt.axvline(0, color='black',linewidth=0.5)
plt.legend()
plt.show()
首先,我们可以观察函数图像,这是一个将所有数映射到(0,1)之间的函数,x趋于负无穷时,函数值趋近于0,x趋近于正无穷时,函数值趋近于1。
运行下面代码,我们可以画出sigmoid的导数图像:
import numpy as np
import matplotlib.pyplot as plt# 定义Sigmoid函数
def sigmoid(x):return 1 / (1 + np.exp(-x))# 定义Sigmoid函数的导数
def sigmoid_derivative(x):s = sigmoid(x)return s * (1 - s)# 生成x轴数据
x = np.linspace(-10, 10, 400)# 计算Sigmoid函数的导数
y_derivative = sigmoid_derivative(x)# 绘图
plt.figure(figsize=(8, 6))
plt.plot(x, y_derivative, label="Sigmoid Derivative", color='r')
plt.title("Derivative of Sigmoid Function")
plt.xlabel("x")
plt.ylabel("sigmoid'(x)")
plt.grid(True)
plt.axhline(0, color='black',linewidth=0.5)
plt.axvline(0, color='black',linewidth=0.5)
plt.legend()
plt.show()
可以发现在0处取到的导数值最大,往边上走迅速减小,所以它可以快速将数据从0的附近排开。这样的性质,让sigmoid函数拥有将连续型变量转化为离散型变量的力量,这也让它拥有了化回归类问题为分类问题的能力。
当我们将该函数以对数几率的形式表达出来的时候,会发现另一个有趣的地方。
我们神奇地发现,让取对数几率后所得到的值就是我们线性回归的z,因为这个性质,在等号两边加sigmoid 的算法被称为“对数几率回归”,在英文中就是Logistic Regression,就是逻辑回归。
此时 和 之和为1,因此它们可以被我们看作是一对正反例发生的概率,即是某样本i的标签被预测为1的概率,而是i的标签被预测为0的概率,相比的结果就是样本i的标签被预测为1的相对概率。基于这种理解,虽然可能不严谨,逻辑回归、即单层二分类神经网络返回的结果一般被当成是概率来看待和使用。
(1)与门的实现:
x0 | x1 | x2 | andgate |
1 | 0 | 0 | 0 |
1 | 1 | 0 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 1 | 1 |
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 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)return sigma, andhat
sigma, andhat = LogisticR(X,w)
andhat
这里我们求sigma的时候用的是库里面的函数,注释的是手动写的。
唯一需要额外解释的是andhat的列表推导式:
sigma >= 0.5
会对 sigma
中的每个元素进行比较,返回一个布尔型张量,并且形状与 sigma
相同举个例子,假设 sigma = torch.tensor([0.2, 0.7, 0.5, 0.3])
,那么 sigma >= 0.5
的结果会是:tensor([False, True, True, False])
接下来是列表推导式部分,它会遍历 sigma >= 0.5
中的每个布尔值,并将每个布尔值转化为整数(True
转化为 1
,False
转化为 0
)。也就是说,这个列表推导式的功能是将布尔值转换为二分类的预测值(0 或 1)。
上面的例子通过列表推导式 [int(x) for x in sigma >= 0.5]
,会得到:[0, 1, 1, 0]
当然除了sigmoid还有很多经典的函数,例如sign,ReLU,Tanh等函数,这里我们就不再一一列出来了,用到的时候再说。
(2)torch版本实现
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(250) #人为设置随机数种子
dense = torch.nn.Linear(2,1)
zhat = dense(X)
sigma = F.sigmoid(zhat)
y = [int(x) for x in sigma > 0.5]
y
(4)softmax
当我们遇到多分类问题的时候,往往使用softmax来解决
Softmax 是一种常用于多分类问题的激活函数,特别是在神经网络的输出层,它将一组实数转换为一个概率分布。Softmax 是“归一化指数函数”的一种形式,其主要功能是将任意实数向量转换成一个概率分布,使得每个元素的值介于0和1之间,且所有元素的和为1。这对于多分类问题来说非常重要,因为它可以帮助我们得到不同类别的概率,从而进行分类决策。
可是由于指数的缘故,经常会发生溢出的情况,如下:
所以一般我们不手动自己写softmax,一般会调用pytorch内置的函数
你可能会疑惑为什么这个函数不会出现溢出的问题,这是因为使用了一点小技巧。
为了避免溢出,我们可以将每个输入值减去输入向量的最大值,这样可以确保计算中的指数值不会太大,仍然可以保留同样结果的相对比例。通过以下变换,我们可以得到数值稳定的 Softmax:
另外还需要解释一点的是这里的0是什么,我们可以通过快捷键查看函数的参数发现,第二个参数是维度,也就是说该函数需要你指定沿着哪一个维度进行softmax操作。
这里由于是只有一行,所以就是沿着这一行做softmax,所以维度的索引值为0,下面举几个例子:
如果是二维张量dim=1就是对两个小的张量进行softmax
这意味着 Softmax 没有对每个样本独立处理,而是计算了每个类别在所有样本中的相对比例。
我们再举一个大一点的三维张量的例子:
import torch
import torch.nn.functional as F# 输入一个 3D 张量 (batch_size, channels, height)
z = torch.tensor([[[1.0, 2.0, 3.0, 4.0], # 第一个样本的 4 个特征值[1.0, 2.0, 3.0, 4.0], # 第二个样本的 4 个特征值[1.0, 2.0, 3.0, 4.0]], # 第三个样本的 4 个特征值[[5.0, 6.0, 7.0, 8.0], # 第一个样本的 4 个特征值[5.0, 6.0, 7.0, 8.0], # 第二个样本的 4 个特征值[5.0, 6.0, 7.0, 8.0]]] # 第三个样本的 4 个特征值# 打印输入张量
print("输入张量:")
print(z)
print("\n")# 沿着 dim=2 计算 Softmax(计算每个特征维度的概率)
softmax_dim2 = F.softmax(z, dim=2)
print("沿着 dim=2 计算 Softmax:")
print(softmax_dim2)
print("\n")# 沿着 dim=1 计算 Softmax(计算每个类别的概率)
softmax_dim1 = F.softmax(z, dim=1)
print("沿着 dim=1 计算 Softmax:")
print(softmax_dim1)
print("\n")# 沿着 dim=0 计算 Softmax(计算每个样本的概率)
softmax_dim0 = F.softmax(z, dim=0)
print("沿着 dim=0 计算 Softmax:")
print(softmax_dim0)
由于这是一个shape为(2,3,4)的张量,所以dim=2也就是最后一个维度4的求和,所以是四个元素加起来等于1;dim=1也就是中间维度3,按照列加起来等于1;dim=0第一个维度2,所以是第一个小张量里面的加第二个小张量里面的等于1.