【AI】Pytorch_模型构建

建议点赞收藏关注!持续更新至pytorch大部分内容更完。
本文已达到10w字,故按模块拆开,详见目录导航。

整体框架如下
数据及预处理
模型及其构建
损失函数及优化器


本节目录

  • 模型
    • 线性回归
    • 逻辑回归
    • LeNet
    • AlexNet
  • 构建模块
    • 组织复杂网络
    • 初始化网络参数
      • 梯度消失 or 梯度爆炸
    • 定义网络层
      • 卷积层
      • 池化层
      • 线性层(全连接层)
      • 激活函数层

模型

模型训练基本步骤:数据->模型->损失函数->优化器->迭代训练

线性回归

1.确定模型:y=wx+b 关系是线性的,需要求解w,b
2.选择损失函数 MSE均方差
在这里插入图片描述
3.求解梯度并更新w,b
w=w-LRw.grad 加上梯度的负方向 即减梯度正方向
b=b-LR
w.grad

# -*- coding:utf-8 -*-
"""
@brief      : 一元线性回归模型
"""
import torch
import matplotlib.pyplot as plt
torch.manual_seed(10)lr = 0.05  # 学习率  # 创建训练数据
x = torch.rand(20, 1) * 10  # x data (tensor), shape=(20, 1)
y = 2*x + (5 + torch.randn(20, 1))  # y data (tensor), shape=(20, 1)
#torch.randn(20, 1)是一些噪声# 构建初始化线性回归参数
w = torch.randn((1), requires_grad=True)
b = torch.zeros((1), requires_grad=True)for iteration in range(1000):# 前向传播wx = torch.mul(w, x)#w*xy_pred = torch.add(wx, b)#y_pred是预测值=w*x+b# 计算 MSE lossloss = (0.5 * (y - y_pred) ** 2).mean()#0.5这里是为了消掉求导中的系数2# 反向传播 得到梯度loss.backward()# 更新参数 即减掉lr * grad梯度b.data.sub_(lr * b.grad) w.data.sub_(lr * w.grad)# 清零张量的梯度   20191015增加w.grad.zero_()b.grad.zero_()# 绘图if iteration % 20 == 0:plt.cla()   # 防止社区版可视化时模型重叠plt.scatter(x.data.numpy(), y.data.numpy())plt.plot(x.data.numpy(), y_pred.data.numpy(), 'r-', lw=5)plt.text(2, 20, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color':  'red'})plt.xlim(1.5, 10)plt.ylim(8, 28)plt.title("Iteration: {}\nw: {} b: {}".format(iteration, w.data.numpy(), b.data.numpy()))plt.pause(0.5)if loss.data.numpy() < 1:#loss小于1时停止迭代breakplt.show()

逻辑回归

注意:是线性的二分类模型
模型表达式:y=f(wx+b), f(x)=1/(1+e^-x),f(x)成为sigmoid函数 又叫logistic函数,它会将输入的数据映射到0~1之间(概率)
在这里插入图片描述
如何二分类?通常将阈值设置为0.5,超过0.5为1,不足0.5为0
在这里插入图片描述

逻辑回归又名 对数几率回归,这里的几率对应y/(1-y)
ln[y/(1-y)]=wx+b 公式与逻辑回归表达式可以互相得到

对数回归:表达式lny=wx+b


import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import numpy as np
torch.manual_seed(10)# ============================ step 1/5 生成数据 ============================
sample_nums = 100
mean_value = 1.7
bias = 1
n_data = torch.ones(sample_nums, 2)
x0 = torch.normal(mean_value * n_data, 1) + bias      # 类别0 数据 shape=(100, 2) 
#normal(mean,std)
y0 = torch.zeros(sample_nums)                         # 类别0 标签 shape=(100)
x1 = torch.normal(-mean_value * n_data, 1) + bias     # 类别1 数据 shape=(100, 2)
y1 = torch.ones(sample_nums)                          # 类别1 标签 shape=(100)
train_x = torch.cat((x0, x1), 0)
train_y = torch.cat((y0, y1), 0)# ============================ step 2/5 选择模型 ============================
class LR(nn.Module):def __init__(self):super(LR, self).__init__()self.features = nn.Linear(2, 1)self.sigmoid = nn.Sigmoid()def forward(self, x):#前向传播x = self.features(x)x = self.sigmoid(x)return xlr_net = LR()   # 实例化逻辑回归模型# ============================ step 3/5 选择损失函数 ============================
loss_fn = nn.BCELoss()#二分类交叉熵# ============================ step 4/5 选择优化器   ============================
lr = 0.01  # 学习率
optimizer = torch.optim.SGD(lr_net.parameters(), lr=lr, momentum=0.9)
#SGD随机梯度下降法
'''
关于momentum
SGD(Stochastic Gradient Descent,随机梯度下降)是一种常用的优化算法,用于训练机器学习模型。 Momentum 是SGD的一种变体,它引入了一个动量项(momentum term),目的是为了加速收敛并减少震荡。简单来说,Momentum会在每一次更新方向上考虑历史的梯度信息,而不是仅仅基于当前的梯度。
这个动量项会让模型偏向于沿之前梯度的方向移动,如果之前的方向是有利的,那么在后续迭代中会保持这种趋势,有助于避免陷入局部最优。
'''
# ============================ step 5/5 模型训练 ============================
for iteration in range(1000):# 前向传播:将数据输入给模型y_pred = lr_net(train_x)# 计算 lossloss = loss_fn(y_pred.squeeze(), train_y)# 反向传播loss.backward()# 更新参数optimizer.step()# 清空梯度optimizer.zero_grad()# 绘图if iteration % 20 == 0:mask = y_pred.ge(0.5).float().squeeze()  # 以0.5为阈值进行分类correct = (mask == train_y).sum()  # 计算正确预测的样本个数acc = correct.item() / train_y.size(0)  # 计算分类准确率plt.scatter(x0.data.numpy()[:, 0], x0.data.numpy()[:, 1], c='r', label='class 0')plt.scatter(x1.data.numpy()[:, 0], x1.data.numpy()[:, 1], c='b', label='class 1')w0, w1 = lr_net.features.weight[0]w0, w1 = float(w0.item()), float(w1.item())plot_b = float(lr_net.features.bias[0].item())plot_x = np.arange(-6, 6, 0.1)plot_y = (-w0 * plot_x - plot_b) / w1plt.xlim(-5, 7)plt.ylim(-7, 7)plt.plot(plot_x, plot_y)plt.text(-5, 5, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color': 'red'})plt.title("Iteration: {}\nw0:{:.2f} w1:{:.2f} b: {:.2f} accuracy:{:.2%}".format(iteration, w0, w1, plot_b, acc))plt.legend()plt.show()plt.pause(0.5)if acc > 0.99:break

LeNet

conv1->pool1->conv2->pool2->fc1->fc2->fc3
在这里插入图片描述
forward:从左往右的顺序,前向传播;反之为backward

AlexNet

在这里插入图片描述
在这里插入图片描述
features部分是卷积池化
classfier部分是全联接

Alexnet是分组卷积,原因是受制于硬件,因为它用了两个GPU进行训练。如上图结构所示,一张图片分成上下两组卷积进行,直到最后全连接时上下两组才连接起来。

class AlexNet(nn.Module):def __init__(self, num_classes: int = 1000, dropout: float = 0.5) -> None:super().__init__()_log_api_usage_once(self)self.features = nn.Sequential(nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=3, stride=2),nn.Conv2d(64, 192, kernel_size=5, padding=2),nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=3, stride=2),nn.Conv2d(192, 384, kernel_size=3, padding=1),nn.ReLU(inplace=True),nn.Conv2d(384, 256, kernel_size=3, padding=1),nn.ReLU(inplace=True),nn.Conv2d(256, 256, kernel_size=3, padding=1),nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=3, stride=2),)self.avgpool = nn.AdaptiveAvgPool2d((6, 6))self.classifier = nn.Sequential(nn.Dropout(p=dropout),nn.Linear(256 * 6 * 6, 4096),#全连接nn.ReLU(inplace=True),nn.Dropout(p=dropout),nn.Linear(4096, 4096),nn.ReLU(inplace=True),nn.Linear(4096, num_classes),)def forward(self, x: torch.Tensor) -> torch.Tensor:x = self.features(x)x = self.avgpool(x)x = torch.flatten(x, 1)x = self.classifier(x)return x# 4 AlexNet
alexnet = torchvision.models.AlexNet()

构建模块

在这里插入图片描述
构建模块需要构建子模块(在__init__()),拼接子模块(在forward())。

所有的模型、网络层都是继承nn.Module
在这里插入图片描述
nn.Module总结:

  1. 一个module可以包含多个子module,比如说convNd,pool都是一个module,区别在于这种子module的module属性为空,即它们没有子模块了
  2. 一个module相当于一个运算,必须实现forward()
  3. 每个module都有8个字典管理它的属性;例如,执行module的init创建self.conv2时就会进入__setattr__判断传参类型,如果是parameter则加入到parameters字典中,其key==[该参数],如果是module则加入到modules字典中,其key==[conv2];(细节可以通过源码debug来查看)

nn.Module的属性有
在这里插入图片描述
以下是与hooks有关的字典:
在这里插入图片描述

class LeNet(nn.Module):def __init__(self, classes):super(LeNet, self).__init__()#这里实现了调用父类的init()self.conv1 = nn.Conv2d(3, 6, 5)#这些都可以debug之后step in 看一下它继续往哪个代码底层执行,了解代码原理self.conv2 = nn.Conv2d(6, 16, 5)self.fc1 = nn.Linear(16*5*5, 120)self.fc2 = nn.Linear(120, 84)self.fc3 = nn.Linear(84, classes)def forward(self, x):out = F.relu(self.conv1(x))out = F.max_pool2d(out, 2)out = F.relu(self.conv2(out))out = F.max_pool2d(out, 2)out = out.view(out.size(0), -1)out = F.relu(self.fc1(out))out = F.relu(self.fc2(out))out = self.fc3(out)return outdef initialize_weights(self):for m in self.modules():if isinstance(m, nn.Conv2d):nn.init.xavier_normal_(m.weight.data)if m.bias is not None:m.bias.data.zero_()elif isinstance(m, nn.BatchNorm2d):m.weight.data.fill_(1)m.bias.data.zero_()elif isinstance(m, nn.Linear):nn.init.normal_(m.weight.data, 0, 0.1)m.bias.data.zero_()

# -*- coding: utf-8 -*-
"""
# @file name  : create_module.py
# @brief      : 学习模型创建学习
"""
import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
import torchvision.transforms as transforms
import torch.optim as optim
from matplotlib import pyplot as plt
path_lenet = os.path.abspath(os.path.join(BASE_DIR, "..", "..", "model", "lenet.py"))
path_tools = os.path.abspath(os.path.join(BASE_DIR, "..", "..", "tools", "common_tools.py"))
assert os.path.exists(path_lenet), "{}不存在,请将lenet.py文件放到 {}".format(path_lenet, os.path.dirname(path_lenet))
assert os.path.exists(path_tools), "{}不存在,请将common_tools.py文件放到 {}".format(path_tools, os.path.dirname(path_tools))import sys
hello_pytorch_DIR = os.path.abspath(os.path.dirname(__file__)+os.path.sep+".."+os.path.sep+"..")
sys.path.append(hello_pytorch_DIR)from model.lenet import LeNet
from tools.my_dataset import RMBDataset
from tools.common_tools import set_seedset_seed()  # 设置随机种子
rmb_label = {"1": 0, "100": 1}# 参数设置
MAX_EPOCH = 10
BATCH_SIZE = 16
LR = 0.01
log_interval = 10
val_interval = 1# ============================ step 1/5 数据 ============================
split_dir = os.path.abspath(os.path.join(BASE_DIR, "..", "..", "data", "rmb_split"))
if not os.path.exists(split_dir):raise Exception(r"数据 {} 不存在, 回到lesson-06\1_split_dataset.py生成数据".format(split_dir))
train_dir = os.path.join(split_dir, "train")
valid_dir = os.path.join(split_dir, "valid")norm_mean = [0.485, 0.456, 0.406]
norm_std = [0.229, 0.224, 0.225]train_transform = transforms.Compose([transforms.Resize((32, 32)),transforms.RandomCrop(32, padding=4),transforms.ToTensor(),transforms.Normalize(norm_mean, norm_std),
])valid_transform = transforms.Compose([transforms.Resize((32, 32)),transforms.ToTensor(),transforms.Normalize(norm_mean, norm_std),
])# 构建MyDataset实例
train_data = RMBDataset(data_dir=train_dir, transform=train_transform)
valid_data = RMBDataset(data_dir=valid_dir, transform=valid_transform)# 构建DataLoder
train_loader = DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
valid_loader = DataLoader(dataset=valid_data, batch_size=BATCH_SIZE)# ============================ step 2/5 模型 ============================
net = LeNet(classes=2)
net.initialize_weights()# ============================ step 3/5 损失函数 ============================
criterion = nn.CrossEntropyLoss()                                                   # 选择损失函数# ============================ step 4/5 优化器 ============================
optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9)                        # 选择优化器
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)     # 设置学习率下降策略# ============================ step 5/5 训练 ============================
train_curve = list()
valid_curve = list()for epoch in range(MAX_EPOCH):loss_mean = 0.correct = 0.total = 0.net.train()for i, data in enumerate(train_loader):# forwardinputs, labels = dataoutputs = net(inputs)# backwardoptimizer.zero_grad()loss = criterion(outputs, labels)loss.backward()# update weightsoptimizer.step()# 统计分类情况_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).squeeze().sum().numpy()# 打印训练信息loss_mean += loss.item()train_curve.append(loss.item())if (i+1) % log_interval == 0:loss_mean = loss_mean / log_intervalprint("Training:Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(epoch, MAX_EPOCH, i+1, len(train_loader), loss_mean, correct / total))loss_mean = 0.scheduler.step()  # 更新学习率# validate the modelif (epoch+1) % val_interval == 0:correct_val = 0.total_val = 0.loss_val = 0.net.eval()with torch.no_grad():for j, data in enumerate(valid_loader):inputs, labels = dataoutputs = net(inputs)loss = criterion(outputs, labels)_, predicted = torch.max(outputs.data, 1)total_val += labels.size(0)correct_val += (predicted == labels).squeeze().sum().numpy()loss_val += loss.item()valid_curve.append(loss_val)print("Valid:\t Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(epoch, MAX_EPOCH, j+1, len(valid_loader), loss_val, correct / total))train_x = range(len(train_curve))
train_y = train_curvetrain_iters = len(train_loader)
valid_x = np.arange(1, len(valid_curve)+1) * train_iters*val_interval # 由于valid中记录的是epochloss,需要对记录点进行转换到iterations
valid_y = valid_curveplt.plot(train_x, train_y, label='Train')
plt.plot(valid_x, valid_y, label='Valid')plt.legend(loc='upper right')
plt.ylabel('loss value')
plt.xlabel('Iteration')
plt.show()# ============================ inference ============================BASE_DIR = os.path.dirname(os.path.abspath(__file__))
test_dir = os.path.join(BASE_DIR, "test_data")test_data = RMBDataset(data_dir=test_dir, transform=valid_transform)
valid_loader = DataLoader(dataset=test_data, batch_size=1)for i, data in enumerate(valid_loader):# forwardinputs, labels = dataoutputs = net(inputs)_, predicted = torch.max(outputs.data, 1)rmb = 1 if predicted.numpy()[0] == 0 else 100print("模型获得{}元".format(rmb))

组织复杂网络

介绍容器containers,包装起来作为一个整体
在这里插入图片描述
可以利用sequential将LeNet划分成features,classifier两部分
在这里插入图片描述
可以发现sequential定义后的打印出来模型结构,里面不再是有name的,而是拿序号区分不同层的。所以要用字典形式构建,见下面代码中class LeNetSequential 和class LeNetSequentialOrderDict的区别。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

# -*- coding: utf-8 -*-
"""
# @file name  : module_containers.py
# @brief      : 模型容器——Sequential, ModuleList, ModuleDict
"""
import torch
import torchvision
import torch.nn as nn
from collections import OrderedDict# ============================ Sequential
class LeNetSequential(nn.Module):def __init__(self, classes):super(LeNetSequential, self).__init__()self.features = nn.Sequential(nn.Conv2d(3, 6, 5),nn.ReLU(),nn.MaxPool2d(kernel_size=2, stride=2),nn.Conv2d(6, 16, 5),nn.ReLU(),nn.MaxPool2d(kernel_size=2, stride=2),)self.classifier = nn.Sequential(nn.Linear(16*5*5, 120),nn.ReLU(),nn.Linear(120, 84),nn.ReLU(),nn.Linear(84, classes),)def forward(self, x):x = self.features(x)#自己定义的sequentialx = x.view(x.size()[0], -1)x = self.classifier(x)#自己定义的sequentialreturn xclass LeNetSequentialOrderDict(nn.Module):def __init__(self, classes):super(LeNetSequentialOrderDict, self).__init__()self.features = nn.Sequential(OrderedDict({'conv1': nn.Conv2d(3, 6, 5),'relu1': nn.ReLU(inplace=True),'pool1': nn.MaxPool2d(kernel_size=2, stride=2),'conv2': nn.Conv2d(6, 16, 5),'relu2': nn.ReLU(inplace=True),'pool2': nn.MaxPool2d(kernel_size=2, stride=2),}))self.classifier = nn.Sequential(OrderedDict({'fc1': nn.Linear(16*5*5, 120),'relu3': nn.ReLU(),'fc2': nn.Linear(120, 84),'relu4': nn.ReLU(inplace=True),'fc3': nn.Linear(84, classes),}))def forward(self, x):x = self.features(x)x = x.view(x.size()[0], -1)x = self.classifier(x)return x# net = LeNetSequential(classes=2)
# net = LeNetSequentialOrderDict(classes=2)
#
# fake_img = torch.randn((4, 3, 32, 32), dtype=torch.float32)
#
# output = net(fake_img)
#
# print(net)
# print(output)# ============================ ModuleListclass ModuleList(nn.Module):def __init__(self):super(ModuleList, self).__init__()self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(20)])# # 输入特征数为10,输出特征数为10def forward(self, x):for i, linear in enumerate(self.linears):x = linear(x)return x# net = ModuleList()
#
# print(net)
#
# fake_data = torch.ones((10, 10))
#
# output = net(fake_data)
#
# print(output)# ============================ ModuleDictclass ModuleDict(nn.Module):def __init__(self):super(ModuleDict, self).__init__()self.choices = nn.ModuleDict({'conv': nn.Conv2d(10, 10, 3),'pool': nn.MaxPool2d(3)})self.activations = nn.ModuleDict({'relu': nn.ReLU(),'prelu': nn.PReLU()})def forward(self, x, choice, act):x = self.choices[choice](x)x = self.activations[act](x)return xnet = ModuleDict()fake_img = torch.randn((4, 10, 32, 32))output = net(fake_img, 'conv', 'relu')print(output)# 4 AlexNet
alexnet = torchvision.models.AlexNet()

初始化网络参数

梯度消失 or 梯度爆炸

在这里插入图片描述
也就是说要控制网络层每一层输出不能太大,也不能太小。也就是说数据的方差不能过大过小,维持在1即可。
在这里插入图片描述
证明如下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
红色圈起来的部分就是 想要让每次标准差std都稳定在1(左右),已知初始化D(xi)=1,则需要D(w)=1/n即可

通常Xavier 泽威尔采用均匀分布,如下图右边所示。
在这里插入图片描述
n_i是输入层的神经元个数,n_(i+1)是输出层的神经元个数。
当需要保持方差一致性(始终在1左右),则有上图公式推导。

在这里插入图片描述
这里的a是负半轴的斜率,第二行是变种relu


"""
总结 这里首先初始化标准差为1,后面在31层就会变成nan
# @file name  : grad_vanish_explod.py
# @brief      : 梯度消失与爆炸实验
"""
import os
import torch
import random
import numpy as np
import torch.nn as nn
from tools.common_tools import set_seedset_seed(1)  # 设置随机种子class MLP(nn.Module):def __init__(self, neural_num, layers):super(MLP, self).__init__()#构建100层线性层,每层有256个神经元self.linears = nn.ModuleList([nn.Linear(neural_num, neural_num, bias=False) for i in range(layers)]) self.neural_num = neural_numdef forward(self, x):for (i, linear) in enumerate(self.linears):x = linear(x)#xavier x=torch.tanh(x)#后面relu非饱和激活函数替换掉了饱和函数tanh等,用kaiming初始化替换掉对应的xavier初始化x = torch.relu(x)print("layer:{}, std:{}".format(i, x.std()))#判断是否为nanif torch.isnan(x.std()):print("output is nan in {} layers".format(i))breakreturn xdef initialize(self):#遍历每一个模块modulesfor m in self.modules():if isinstance(m, nn.Linear):#判断模块是否为线性层##初始值为标准正态分布,均值为0,标准差为1,但是每层的标准差会越来越大,发生std爆炸nn.init.normal_(m.weight.data) #标准正态分布进行初始化#均值为0,标准差为1,标准差会发生爆炸#此时的初始化权值可以使每层的均值为0,std为1.nn.init.normal_(m.weight.data, std=np.sqrt(1/self.neural_num))   #手动计算# a = np.sqrt(6 / (self.neural_num + self.neural_num))#计算激活函数的增益,即数据输入到激活函数后标准差的变化的上下限的绝对值# tanh_gain = nn.init.calculate_gain('tanh')# a *= tanh_gain#设置均匀分布来初始化权值# nn.init.uniform_(m.weight.data, -a, a)#自动计算nn.init.xavier_uniform_(m.weight.data, gain=nn.init.calculate_gain('tanh'))#手动计算# nn.init.normal_(m.weight.data, std=np.sqrt(2 / self.neural_num))#自动计算nn.init.kaiming_normal_(m.weight.data)flag = 0
# flag = 1if flag:layer_nums = 100 #100层神经网络neural_nums = 256 #每一层神经元个数为256batch_size = 16 net = MLP(neural_nums, layer_nums)net.initialize()inputs = torch.randn((batch_size, neural_nums))  # 标准正态分布normal: mean=0, std=1output = net(inputs)print(output)# ======================================= calculate gain =======================================# flag = 0
flag = 1if flag:x = torch.randn(10000) #标准正态分布创建10000个数据点out = torch.tanh(x)gain = x.std() / out.std()print('gain:{}'.format(gain))tanh_gain = nn.init.calculate_gain('tanh')print('tanh_gain in PyTorch:', tanh_gain)

在这里插入图片描述

nn.init.calculate_gain(nonlinearity,param=None)
'''
计算激活函数方差变化尺度 =输入数据的方差/经过激活函数后输出数据的方差
nonlinearity:激活函数名称
param:激活函数参数 如Leaky ReLU的Negative_slop
'''

定义网络层

卷积层

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下图是一个三维卷积:利用二维卷积在一张图片的3个通道上进行,最后叠加起来。
在这里插入图片描述

padding:可以利用它保持特征图shape不变在这里插入图片描述
dilation:空洞卷积在这里插入图片描述
尺寸计算:
在这里插入图片描述
在这里插入图片描述

torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
'''
(1)功能:对多个二维信号进行二维卷积;
(2)参数:
in_channels: 输入通道数;
out_channels: 输出通道数,等于卷积核的个数;
kernel_size: 卷积核的尺寸;
stride: 步长,stride步长是滑动时两个维度都一同滑动几个像素; 
padding: 填充个数,保证输入和输出图像在尺寸上是匹配的;
dilation: 空洞卷积的大小;
groups: 分组卷积设置;
bias: 偏置;
'''set_seed(3)  # 设置随机种子 随机种子不一样,之后nn.init.xavier_normal_初始化也不一样,导致图片卷积后的效果不同。# ================================= load img ==================================
path_img = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lena.png")
img = Image.open(path_img).convert('RGB')  # 0~255 RGB# convert to tensor 张量
img_transform = transforms.Compose([transforms.ToTensor()])
img_tensor = img_transform(img)
# C*H*W to B*C*H*W 扩展成四维,增加batchsize维度
img_tensor.unsqueeze_(dim=0)    # ================================= create convolution layer ==================================# ================ 2d
flag = 1
# flag = 0
if flag:#图像是RGB具有三个通道,因此in_channels=3,设置一个卷积核来观察out_channels=1,卷积核为3*3conv_layer = nn.Conv2d(3, 1, 3)   # (in_channels, out_channels, 卷积核size) #打印可以发现conv2d的_parameters字典中的weight.shape为[1,3,3,3]#=(out_channels, in_channels , h, w) h,w是二维卷积核的尺寸nn.init.xavier_normal_(conv_layer.weight.data)#初始化# calculationimg_conv = conv_layer(img_tensor)# ================================= visualization ==================================
print("卷积前尺寸:{}\n卷积后尺寸:{}".format(img_tensor.shape, img_conv.shape))
img_conv = transform_invert(img_conv[0, 0:1, ...], img_transform)
img_raw = transform_invert(img_tensor.squeeze(), img_transform)
plt.subplot(122).imshow(img_conv, cmap='gray')
plt.subplot(121).imshow(img_raw)
plt.show()

转置卷积(常用于图像分割)
但最好不要称其为反卷积,因为卷积与其是不可逆的运算,虽然前后图像相转置的,但是权值不可逆。
在这里插入图片描述
假设图像shape 44, kernel 33, padding=0,stride=1

正常卷积:首先,将输入图像44拉成161的向量,其中16是44所有的像素;1是1张图片。
然后,卷积核3
3会变成416的矩阵,其中16是33=9个权值通过补0得到16个数;,4是根据图像尺寸、padding、stride、dilation等计算得到的输出图像中像素的总个数。
最后,卷积核矩阵乘以图像矩阵得到41,然后再reshape为22的输出特征图。

转置卷积:就是在正常卷积之后22 再转置回去44
首先,将输入图像22拉成41的向量,其中4是22=4得到的输入图像中所有像素的个数,1是1张图片。
然后,卷积核3
3会变成164的矩阵,其中4是33=9中剔除多余的剩下的4个,16是根据图像尺寸、padding、stride、dilation等计算得到的输出图像中像素的总个数。
最后,卷积核矩阵乘以图像矩阵得到161,然后再reshape为44的输出特征图。
在这里插入图片描述
在这里插入图片描述
尺寸计算公式刚好和卷积是相逆的。
反卷积过后会有棋盘效应,可以看到处理后图像像棋盘一样。
在这里插入图片描述

nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride=1, padding=0, output_padding=0, groups=1, bias=True, dilation=1, padding_mode='zeros', device=None, dtype=None)'''
参数同卷积参数
'''# -*- coding: utf-8 -*-
"""
# @file name  : nn_layers_convolution.py
# @brief      : 学习卷积层
"""
import os
import torch.nn as nn
from PIL import Image
from torchvision import transforms
from matplotlib import pyplot as plt
from tools.common_tools import transform_invert, set_seedset_seed(3)  # 设置随机种子# ================================= load img ==================================
path_img = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lena.png")
img = Image.open(path_img).convert('RGB')  # 0~255 RGB# convert to tensor 张量
img_transform = transforms.Compose([transforms.ToTensor()])
img_tensor = img_transform(img)
# C*H*W to B*C*H*W 扩展成四维,增加batchsize维度
img_tensor.unsqueeze_(dim=0)    # ================================= create convolution layer ==================================# ================ 2d
flag = 1
# flag = 0
if flag:#图像是RGB具有三个通道,因此in_channels=3,设置一个卷积核来观察out_channels=1,卷积核为3*3conv_layer = nn.Conv2d(3, 1, 3)   # (in_channels, out_channels, 卷积核size) #初始化#weights:[1,3,3,3]=(out_channels, in_channels , h, w) h,w是二维卷积核的尺寸nn.init.xavier_normal_(conv_layer.weight.data)# calculationimg_conv = conv_layer(img_tensor)# ================ transposed
# flag = 1
flag = 0
if flag:conv_layer = nn.ConvTranspose2d(3, 1, 3, stride=2)   #(in_channels, out_channels, 卷积核size) nn.init.xavier_normal_(conv_layer.weight.data)# calculationimg_conv = conv_layer(img_tensor)# ================================= visualization ==================================
print("卷积前尺寸:{}\n卷积后尺寸:{}".format(img_tensor.shape, img_conv.shape))
img_conv = transform_invert(img_conv[0, 0:1, ...], img_transform)
img_raw = transform_invert(img_tensor.squeeze(), img_transform)
plt.subplot(122).imshow(img_conv, cmap='gray')
plt.subplot(121).imshow(img_raw)
plt.show()

2d卷积一般用于图片,3d卷积一般用于视频,当做3d卷积时,需要在原先shape中间插入一个D-deep,作为图片帧数

img_tensor.unsqueeze_(dim=2)    # B*C*H*W to B*C*D*H*Wconv_layer = nn.Conv3d(3, 1, (3, 3, 3), padding=(1, 0, 0), bias=False)#(3,3,3)分别表示deep方向的kernel大小,shape
'''
如果padding[0]不为1的话,
则deep方向上只有原图片,但是原图片的shape转变成
1*3*1*512*512,batchsize*channel*deep*height*width
显然 图片shape的deep方向1<kernel deep方向的3 程序会报错
当padding[0]为1的话,相当于在原图像前后各添加一层0,这样就3 >=kernel的deep方向3,就不会报错了
'''

3d卷积是在三个方向上卷积
在这里插入图片描述

池化层

对信号收集总结,使数量多到少,有maxpool/avgpool

nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
'''
kernel_size: 池化核的尺寸;
stride: 步长,stride步长是滑动时滑动几个像素;通常与kernel_size一致,不会去重叠。
padding: 填充个数,保证输入和输出图像在尺寸上是匹配的;
dilation: 空洞卷积的大小;
ceil_mode:尺寸取整,ceil_mode设置为True则为尺寸向上取整,默认为False即尺寸向下取整;
return_indices:记录最大值像素所在的位置的索引,通常用于最大值反池化上采样时使用;
'''set_seed(1)  # 设置随机种子# ================================= load img ==================================
path_img = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lena.png")
img = Image.open(path_img).convert('RGB')  # 0~255# convert to tensor
#c:通道,h:高,w:宽
img_transform = transforms.Compose([transforms.ToTensor()])
img_tensor = img_transform(img)
img_tensor.unsqueeze_(dim=0)    # C*H*W to B*C*H*W# ================================= create convolution layer ==================================# ================ maxpool
flag = 1
if flag:#stride步长通常与池化窗口大小2*2一致,保证池化时不会发生重叠maxpool_layer = nn.MaxPool2d((2, 2), stride=(2, 2))  img_pool = maxpool_layer(img_tensor)# ================================= visualization ==================================
print("池化前尺寸:{}\n池化后尺寸:{}".format(img_tensor.shape, img_pool.shape))
img_pool = transform_invert(img_pool[0, 0:3, ...], img_transform)
img_raw = transform_invert(img_tensor.squeeze(), img_transform)
plt.subplot(122).imshow(img_pool)
plt.subplot(121).imshow(img_raw)
plt.show()

在这里插入图片描述
池化后照片没有啥区别,所以 池化常用于冗余信息的剔除并减少计算量。

最大值反池化就是利用return_indices将原先池化后的元素放到新区域的对应索引上,其他区域为0
在这里插入图片描述

nn.MaxUnpool2d(kernel_size, stride=None, padding=0)nn.AvgPool2d(kernel_size, stride=None, padding=0, ceil_mode=False, count_include_pad=True, divisor_override=None)
'''
ceil_mode:尺寸取整,ceil_mode设置为True则为尺寸向上取整,默认为False即尺寸向下取整;
count_include_pad:填充值是否用于计算;
divisor_override:除法因子,此时计算平均值时分母为divisor_override,本来是像素值的个数。平均值池化的图像相较于最大值池化的图像较暗淡,
(像素越大越亮 偏白)
因为像素值较小,最大值池化图像取得是最大值,
平均值池化图像取得是平均值。
'''set_seed(1)  # 设置随机种子# ================================= load img ==================================
path_img = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lena.png")
img = Image.open(path_img).convert('RGB')  # 0~255# convert to tensor
#c:通道,h:高,w:宽
img_transform = transforms.Compose([transforms.ToTensor()])
img_tensor = img_transform(img)
img_tensor.unsqueeze_(dim=0)    # C*H*W to B*C*H*W# ================================= create convolution layer ==================================# ================ max unpool
flag = 1
if flag:# poolingimg_tensor = torch.randint(high=5, size=(1, 1, 4, 4), dtype=torch.float) #生成尺寸为1*1*4*4的整数[0, 5)均匀分布maxpool_layer = nn.MaxPool2d((2, 2), stride=(2, 2), return_indices=True)img_pool, indices = maxpool_layer(img_tensor) #记录最大像素值的索引# unpooling#创建反池化层的输入,输入的尺寸与图片池化后的尺寸一致。img_reconstruct = torch.randn_like(img_pool, dtype=torch.float) #标准正态分布#构建反池化层(窗口、步长参数与池化层参数一一对应相等)maxunpool_layer = nn.MaxUnpool2d((2, 2), stride=(2, 2))#生成反池化后的图像img_unpool = maxunpool_layer(img_reconstruct, indices)print("raw_img:\n{}\nimg_pool:\n{}".format(img_tensor, img_pool))print("img_reconstruct:\n{}\nimg_unpool:\n{}".format(img_reconstruct, img_unpool))

AdaptiveMaxPool2d和maxpooling层有什么区别呢?
Maxpool.
已知:input size, kernel, stride.
未知:Output size

AdaptiveMaxpool
已知 :input size, Output size
未知:kernel, stride

线性层(全连接层)

在这里插入图片描述
w_0第一行是input层第一个神经元传递给hidden层每个神经元的权值weight,同理…
如果有bias 还要加上bias。 y=wx+b

如果没有非线性激活函数,那么n层线性层==1层线性层

nn.Linear(in_features, out_features, bias=True, device=None, dtype=None)
'''
对一维信号(向量)进行线性组合;
参数:
in_features: 输入节点数;
in_features: 输出节点数;
bias: 是否需要偏置,默认为True;
'''# ================ linear
flag = 1
if flag:inputs = torch.tensor([[1., 2, 3]]) #1*3linear_layer = nn.Linear(3, 4) #输入节点3个,输出节点4个#w权值矩阵为4*3,每一行代表一个神经元与上一层神经元连接的权值。linear_layer.weight.data = torch.tensor([[1., 1., 1.],[2., 2., 2.],[3., 3., 3.],[4., 4., 4.]])linear_layer.bias.data.fill_(0.5)output = linear_layer(inputs)print(inputs, inputs.shape)print(linear_layer.weight.data, linear_layer.weight.data.shape)#output=inputs*w^T+biasprint(output, output.shape) #输出的尺寸为1*4

激活函数层

  1. nn.Sigmoid
    这个在逻辑回归里见过。
    在这里插入图片描述
    橙色曲线是其导数图像。
    (1)多个网络层叠加后,导数范围会变得更小,甚至消失。
    (2)输出不是0均值分布,破坏了数据分布。
  2. nn.Tanh在这里插入图片描述
    这个公式是怎么推导的?
    在这里插入图片描述
    导数<0时,网络非常深时,同样会梯度消失。
  3. nn.ReLu()修正线性单元
    在这里插入图片描述
    当多层网络不断使梯度叠加,会导致梯度爆炸;当累乘时可以缓解梯度消失。
    在这里插入图片描述
    nn.LeakyReLU(negative_slope=0.01, inplace=False)
    LeakyReLU的slope参数0.01表示:负半轴0.01的斜率.
    在这里插入图片描述
    nn.PReLU(num_parameters=1, init=0.25, device=None, dtype=None)
    在这里插入图片描述
num_parameters(整数):可学习参数 a 的数量,只有两种选择,要么定义成1,表示在所有通道上应用相同的 a进行激活,要么定义成输入数据的通道数,表示在所有通道上应用不同的 a进行激活,默认1。init(float):a的初始值输入数据的第二维度表示为通道维度,当输入维度小于2时,不存在通道维度,此时默认通道数为1通过调用.weight方法来取出参数a 
即使有多个 a,init也还是只能输入一个float类型的数

nn.RReLU(lower=0.125, upper=0.3333333333333333, inplace=False)
RReLU:斜率是随机的,符合均匀分布.在这里插入图片描述

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

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

相关文章

中国各地级市的海拔标准差

海拔标准差是衡量地理测量准确性的重要指标&#xff0c;它通过计算特定地点的海拔测量值与平均海拔之间的偏差来评估数据的可靠性。较小的标准差意味着测量结果较为一致&#xff0c;而较大的标准差则可能指出数据的波动性或测量误差。 计算方法 海拔标准差的计算遵循以下公式…

【pycharm-乱码】简单记录一下都有哪些涉及编码

控制台 路径&#xff1a;setting-》general-》console setting-》editor-》file encodings 路径&#xff1a;setting-》editor->file and code templates #!/user/bin/env python3 # -*- coding: utf-8 -*-setting->tools->ssh terminal

新款14 英寸和16英寸MacBook Pro开始组装生产

据 DigiTimes 报道&#xff0c;苹果的供应链已于 8 月开始批量生产搭载 M4 Pro 和 M4 Max 芯片的下一代 14 英寸和 16 英寸 MacBook Pro 机型。这意味着笔记本电脑的组装工作很可能已经开始。 此信息与显示行业分析师 Ross Young 的说法相符&#xff0c;他声称下一代 14 英寸和…

Edge资源占用优化:调整浏览器设置与关闭自动更新检查

最近&#xff0c;作者在学习过程中&#xff0c;打开任务管理器的时候注意到&#xff0c;即使没有打开浏览器&#xff0c;edge依然有着内存占用较高的情况&#xff1a; 于是就在网上收集了一些后台调优的方法&#xff0c;如果各位朋友有更多优化浏览器资源占用的方法&#xff0c…

智能优化特征选择|基于鲸鱼WOA优化算法实现的特征选择研究Matlab程序(KNN分类器)

智能优化特征选择|基于鲸鱼WOA优化算法实现的特征选择研究Matlab程序&#xff08;KNN分类器&#xff09; 文章目录 一、基本原理原理流程举个例子总结 二、实验结果三、核心代码四、代码获取五、总结 智能优化特征选择|基于鲸鱼WOA优化算法实现的特征选择研究Matlab程序&#x…

【Linux系统】线程的同步 生产消费模型

同步 同步概念 同步&#xff1a;在保证数据安全的前提下&#xff0c;让线程能够按照某种特定的顺序访问临界资源&#xff0c;从而有效避免饥饿问题&#xff0c;叫做同步 竞态条件&#xff1a;因为时序问题&#xff0c;而导致程序异常&#xff0c;我们称之为竞态条件。在线程场…

[OpenCV] 数字图像处理 C++ 学习——13Canny边缘检测 附完整代码

文章目录 前言1.理论基础(1)高斯模糊平滑图像(GaussianBlur)(2)计算图像梯度(Sobel/Scharr)(3)非极大值抑制 (Non-maximum Suppression)(4)双阈值检测 (Double Threshold)(5)边缘跟踪&#xff08;通过滞后处理&#xff09; 2.代码实现3.完整代码 前言 Canny 边缘检测(高斯滤波…

django学习入门系列之第十点《django中数据库操作--创建与删除表》

文章目录 django创建与删除表开始创建表创建指令新增表删除表删除列新增列修改报错提示语言总结 往期回顾 django创建与删除表 删除表 创建表 修改表 操作目录 开始创建表 class text_into(models.Model):name models.CharField(max_length32)password models.CharField…

Electron快速上手

什么是Electron 一款应用广泛的跨平台的桌面应用开发框架。Electron的本质是结合了 Chromium 与Node.js。使用HTML、CSS、JS 等Web技术构建桌面应用程序。 .vue,.tsx,.less,.ts也可以使用 Electron 流程模型 主进程是纯node环境&#xff0c;可以访问__dirname,fs模块等&#…

DDD设计方法-3-仓储,封装持久化数据

前情提要&#xff1a;一共包含 如下六篇文章&#xff08;篇幅精简&#xff0c;快速入门&#xff09; 1、初识DDD 2、聚合、实体、值对象 3、仓储&#xff0c;封装持久化数据 4、端口和适配器 5、领域事件 6、领域服务&#xff0c;实现约定 DDD设计方法-3-仓储&#xff0c;封装…

绿联充电宝怎么样?深度测评西圣、绿联、倍思磁吸充电宝!

在如今这个电子设备不离手的时代&#xff0c;充电宝成为了我们生活中不可或缺的伙伴。而磁吸充电宝以其便捷的使用方式和时尚的外观&#xff0c;更是受到了众多消费者的青睐&#xff0c;今天&#xff0c;我们将对西圣、绿联、倍思这三个品牌的磁吸充电宝进行深度测评&#xff0…

Three之材质Material

本文目录 前言一、基础材质1.1 特点及属性1.2 代码1.3 效果 二、标准材质2.1 特点及属性2.2 代码及效果 三、深度材质四、法向材质五、朗伯材质六、Phong式材质七、粒子材质八、 着色器材质九、其他材质 前言 Three.js中的材质&#xff08;Material&#xff09;是独立于物体顶点…

Anthropic Claude Artifacts,克劳德聊天机器人如何简化代码编程

最近有一位8岁的小男孩&#xff0c;没有任何编程经验&#xff0c;却成功创建了一个网页游戏。他利用了Claude AI和Cursor来生成代码&#xff0c;这充分展示了人工智能在简化编程和创作过程中的巨大潜力。前几天还能看见一个8岁的女孩用生成式人工智能Cursor ai工具可以搭建出一…

一款基于Vue的低代码可视化表单设计器工具,6K star的可视化表单设计器工具,轻松搞定表单,支持多端适配(附源码)

前言 随着Web应用的日益复杂化&#xff0c;表单的设计与开发成为了许多项目中不可或缺的一环。然而&#xff0c;在实际cao作过程中&#xff0c;表单设计往往面临着重复工作量大、效率低下等问题。那么&#xff0c;是否有一款工具能够简化这一过程&#xff0c;提高开发者的效率…

【MySQL07】【锁】

文章目录 一、前言二、事务的读写情况1. 写-写情况2. 读-写情况3. 一致性读4. 锁定读2.1 共享锁和独占锁2.2 锁定读的语句 5. 写操作 三、多粒度锁四、表锁和行锁1. 表级锁1.1 表级别的 S锁 和 X锁1.2 表级别的 IS 锁和 IX锁1.3 表级别的 AUTO-INC 锁 2. 行级锁2.1 行级锁的分类…

【流式输出】LangChain流式输出的概念

&#x1f601; 作者简介&#xff1a;一名大四的学生&#xff0c;致力学习前端开发技术 ⭐️个人主页&#xff1a;夜宵饽饽的主页 ❔ 系列专栏&#xff1a;JavaScript小贴士 &#x1f450;学习格言&#xff1a;成功不是终点&#xff0c;失败也并非末日&#xff0c;最重要的是继续…

记一次头疼事故:springSecurity无法重定向到登录页/springSecurity整合layui后,会话丢失,点击选项卡无法定位到登录页。

1、问题概述? 1、springboot工程引入了springSecurity权限框架实现用户登录功能,当刷新浏览器地址栏的时候能够自动的重定向到登录页实现登录。 2、但是项目中使用了layui的选项卡,当会话丢失的时候(或者重启工程后直接访问),选项卡无法回到登录页,而是选项卡中的数据表…

yolo数据集钢材表面缺陷v8下载适用yolov5等全版本已标注txt格式

钢材表面缺陷检测数据集介绍 数据集概述 本数据集专为钢材表面缺陷检测任务而设计&#xff0c;包含了大量的钢材表面图像&#xff0c;每张图像均带有详细的缺陷标注信息。数据集旨在帮助研究人员和开发人员训练高精度的目标检测模型&#xff0c;以应用于钢材制造和质量控制等多…

建网站公司策划书

建网站公司策划书&#xff1a;建设创新型网站服务企业 一、背景介绍 随着信息时代的来临&#xff0c;互联网已经成为人们生活和工作的重要组成部分。在这个数字化的时代&#xff0c;企业需要强大的在线存在来吸引客户、展示产品和提供服务。为满足市场需求&#xff0c;我们公…

图形语言传输格式glTF和三维瓦片数据3Dtiles(b3dm、pnts)学习

文章目录 3DTilesb3dm一、glTF1.glTF 3D模型格式有两种2.glTF 场景描述结构3.glTF的JSON结构 二、 3DTiles 原文 工具资料 格式详解 格式详解&#xff01; 3D Tiles 是一种开源的、优化的文件格式&#xff0c;支持逐级细节&#xff08;LOD&#xff09;和空间索引&#xff0c;使…