网站开发的概念,有哪些可以免费推广的网站,外包服务网站排名,网站认证是什么意思建议点赞收藏关注#xff01;持续更新至pytorch大部分内容更完。 本文已达到10w字#xff0c;故按模块拆开#xff0c;详见目录导航。
整体框架如下 数据及预处理 模型及其构建 损失函数及优化器 本节目录 模型线性回归逻辑回归LeNetAlexNet 构建模块组织复杂网络初始化网络…建议点赞收藏关注持续更新至pytorch大部分内容更完。 本文已达到10w字故按模块拆开详见目录导航。
整体框架如下 数据及预处理 模型及其构建 损失函数及优化器 本节目录 模型线性回归逻辑回归LeNetAlexNet 构建模块组织复杂网络初始化网络参数梯度消失 or 梯度爆炸 定义网络层卷积层池化层线性层(全连接层)激活函数层 模型
模型训练基本步骤数据-模型-损失函数-优化器-迭代训练
线性回归
1.确定模型ywxb 关系是线性的需要求解w,b 2.选择损失函数 MSE均方差 3.求解梯度并更新w,b ww-LRw.grad 加上梯度的负方向 即减梯度正方向 bb-LRw.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_gradTrue)
b torch.zeros((1), requires_gradTrue)for iteration in range(1000):# 前向传播wx torch.mul(w, x)#w*xy_pred torch.add(wx, b)#y_pred是预测值w*xb# 计算 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-, lw5)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()
逻辑回归
注意是线性的二分类模型 模型表达式yf(wxb), f(x)1/(1e^-x),f(x)成为sigmoid函数 又叫logistic函数它会将输入的数据映射到01之间概率 如何二分类通常将阈值设置为0.5超过0.5为1不足0.5为0
逻辑回归又名 对数几率回归这里的几率对应y/(1-y) ln[y/(1-y)]wxb 公式与逻辑回归表达式可以互相得到
对数回归表达式lnywxb 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(), lrlr, momentum0.9)
#SGD随机梯度下降法关于momentum
SGDStochastic 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], cr, labelclass 0)plt.scatter(x1.data.numpy()[:, 0], x1.data.numpy()[:, 1], cb, labelclass 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:breakLeNet
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_size11, stride4, padding2),nn.ReLU(inplaceTrue),nn.MaxPool2d(kernel_size3, stride2),nn.Conv2d(64, 192, kernel_size5, padding2),nn.ReLU(inplaceTrue),nn.MaxPool2d(kernel_size3, stride2),nn.Conv2d(192, 384, kernel_size3, padding1),nn.ReLU(inplaceTrue),nn.Conv2d(384, 256, kernel_size3, padding1),nn.ReLU(inplaceTrue),nn.Conv2d(256, 256, kernel_size3, padding1),nn.ReLU(inplaceTrue),nn.MaxPool2d(kernel_size3, stride2),)self.avgpool nn.AdaptiveAvgPool2d((6, 6))self.classifier nn.Sequential(nn.Dropout(pdropout),nn.Linear(256 * 6 * 6, 4096),#全连接nn.ReLU(inplaceTrue),nn.Dropout(pdropout),nn.Linear(4096, 4096),nn.ReLU(inplaceTrue),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总结
一个module可以包含多个子module比如说convNd,pool都是一个module区别在于这种子module的module属性为空即它们没有子模块了一个module相当于一个运算必须实现forward()每个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, padding4),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_dirtrain_dir, transformtrain_transform)
valid_data RMBDataset(data_dirvalid_dir, transformvalid_transform)# 构建DataLoder
train_loader DataLoader(datasettrain_data, batch_sizeBATCH_SIZE, shuffleTrue)
valid_loader DataLoader(datasetvalid_data, batch_sizeBATCH_SIZE)# step 2/5 模型
net LeNet(classes2)
net.initialize_weights()# step 3/5 损失函数
criterion nn.CrossEntropyLoss() # 选择损失函数# step 4/5 优化器
optimizer optim.SGD(net.parameters(), lrLR, momentum0.9) # 选择优化器
scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.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 (i1) % log_interval 0:loss_mean loss_mean / log_intervalprint(Training:Epoch[{:03}/{:03}] Iteration[{:03}/{:03}] Loss: {:.4f} Acc:{:.2%}.format(epoch, MAX_EPOCH, i1, len(train_loader), loss_mean, correct / total))loss_mean 0.scheduler.step() # 更新学习率# validate the modelif (epoch1) % 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[{:03}/{:03}] Iteration[{:03}/{:03}] Loss: {:.4f} Acc:{:.2%}.format(epoch, MAX_EPOCH, j1, 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, labelTrain)
plt.plot(valid_x, valid_y, labelValid)plt.legend(locupper 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_dirtest_dir, transformvalid_transform)
valid_loader DataLoader(datasettest_data, batch_size1)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, ModuleDictimport 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_size2, stride2),nn.Conv2d(6, 16, 5),nn.ReLU(),nn.MaxPool2d(kernel_size2, stride2),)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(inplaceTrue),pool1: nn.MaxPool2d(kernel_size2, stride2),conv2: nn.Conv2d(6, 16, 5),relu2: nn.ReLU(inplaceTrue),pool2: nn.MaxPool2d(kernel_size2, stride2),}))self.classifier nn.Sequential(OrderedDict({fc1: nn.Linear(16*5*5, 120),relu3: nn.ReLU(),fc2: nn.Linear(120, 84),relu4: nn.ReLU(inplaceTrue),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(classes2)
# net LeNetSequentialOrderDict(classes2)
#
# fake_img torch.randn((4, 3, 32, 32), dtypetorch.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 梯度爆炸
消失原因深层网络不合适的损失函数 爆炸原因权值初始化过大 解决方法梯度剪切、权重正则化、激活函数改进、使用batchnorm、ResNet 也就是说要控制网络层每一层输出不能太大也不能太小。也就是说数据的方差不能过大过小维持在1即可。 证明如下 红色圈起来的部分就是 想要让每次标准差std都稳定在1左右已知初始化D(xi)1则需要D(w)1/n即可
通常Xavier 泽威尔采用均匀分布如下图右边所示。 n_i是输入层的神经元个数n_(i1)是输出层的神经元个数。 当需要保持方差一致性始终在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, biasFalse) for i in range(layers)]) self.neural_num neural_numdef forward(self, x):for (i, linear) in enumerate(self.linears):x linear(x)#xavier xtorch.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标准差会发生爆炸#此时的初始化权值可以使每层的均值为0std为1.nn.init.normal_(m.weight.data, stdnp.sqrt(1/self.neural_num)) #手动计算 Xavier# a np.sqrt(6 / (self.neural_num self.neural_num))计算激活函数的增益
即增益值是指张量的数据输入到激活函数之后标准差的变化上下限的绝对值。
例如对于0均值1标准差的数据而言经过激活函数tanh之后标准差会减少5/3倍左右如果对与经过激活函数后的数据进行增益变换(一般来说是乘上增益系数)可以使当前模块和激活函数连续作用后的输出张量元素分布服从一个比较合理的值。
# 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, gainnn.init.calculate_gain(tanh))#手动计算 这个是kaiming初始化此时a0# nn.init.normal_(m.weight.data, stdnp.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() #初始化weight.datainputs torch.randn((batch_size, neural_nums)) # 输入数据设置为标准正态分布normal: mean0, std1output 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() #gain是一个变化比例 输入数据的标准差/输出数据的标准差print(gain:{}.format(gain))tanh_gain nn.init.calculate_gain(tanh)print(tanh_gain in PyTorch:, tanh_gain) nn.init.calculate_gain(nonlinearity,paramNone)计算激活函数方差变化尺度 输入数据的方差/经过激活函数后输出数据的方差
nonlinearity:激活函数名称
param激活函数参数 如Leaky ReLU的Negative_slop定义网络层
卷积层 下图是一个三维卷积利用二维卷积在一张图片的3个通道上进行最后叠加起来。
padding:可以利用它保持特征图shape不变 dilation:空洞卷积 尺寸计算
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride1, padding0, dilation1, groups1, biasTrue, padding_modezeros, deviceNone, dtypeNone)(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_(dim0) # create convolution layer # 2d
flag 1
# flag 0
if flag:#图像是RGB具有三个通道因此in_channels3,设置一个卷积核来观察out_channels1,卷积核为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, cmapgray)
plt.subplot(121).imshow(img_raw)
plt.show()
转置卷积常用于图像分割 但最好不要称其为反卷积因为卷积与其是不可逆的运算虽然前后图像相转置的但是权值不可逆。 假设图像shape 44, kernel 33, padding0,stride1
正常卷积首先将输入图像44拉成161的向量其中16是44所有的像素1是1张图片。 然后卷积核33会变成416的矩阵其中16是339个权值通过补0得到16个数4是根据图像尺寸、padding、stride、dilation等计算得到的输出图像中像素的总个数。 最后卷积核矩阵乘以图像矩阵得到41然后再reshape为22的输出特征图。
转置卷积就是在正常卷积之后22 再转置回去44 首先将输入图像22拉成41的向量其中4是224得到的输入图像中所有像素的个数1是1张图片。 然后卷积核33会变成164的矩阵其中4是339中剔除多余的剩下的4个16是根据图像尺寸、padding、stride、dilation等计算得到的输出图像中像素的总个数。 最后卷积核矩阵乘以图像矩阵得到161然后再reshape为44的输出特征图。 尺寸计算公式刚好和卷积是相逆的。 反卷积过后会有棋盘效应可以看到处理后图像像棋盘一样。
nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride1, padding0, output_padding0, groups1, biasTrue, dilation1, padding_modezeros, deviceNone, dtypeNone)
参数同卷积参数
# -*- 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_(dim0) # create convolution layer # 2d
flag 1
# flag 0
if flag:#图像是RGB具有三个通道因此in_channels3,设置一个卷积核来观察out_channels1,卷积核为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, stride2) #(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, cmapgray)
plt.subplot(121).imshow(img_raw)
plt.show()
2d卷积一般用于图片3d卷积一般用于视频当做3d卷积时需要在原先shape中间插入一个D-deep作为图片帧数
img_tensor.unsqueeze_(dim2) # B*C*H*W to B*C*D*H*Wconv_layer nn.Conv3d(3, 1, (3, 3, 3), padding(1, 0, 0), biasFalse)#(3,3,3)分别表示deep方向的kernel大小,shape如果padding[0]不为1的话
则deep方向上只有原图片但是原图片的shape转变成
1*3*1*512*512,batchsize*channel*deep*height*width
显然 图片shape的deep方向1kernel deep方向的3 程序会报错
当padding[0]为1的话相当于在原图像前后各添加一层0这样就3 kernel的deep方向3就不会报错了3d卷积是在三个方向上卷积
池化层
对信号收集总结使数量多到少有maxpool/avgpool
nn.MaxPool2d(kernel_size, strideNone, padding0, dilation1, return_indicesFalse, ceil_modeFalse)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_(dim0) # 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, strideNone, padding0)nn.AvgPool2d(kernel_size, strideNone, padding0, ceil_modeFalse, count_include_padTrue, divisor_overrideNone)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_(dim0) # C*H*W to B*C*H*W# create convolution layer # max unpool
flag 1
if flag:# poolingimg_tensor torch.randint(high5, size(1, 1, 4, 4), dtypetorch.float) #生成尺寸为1*1*4*4的整数[0, 5)均匀分布maxpool_layer nn.MaxPool2d((2, 2), stride(2, 2), return_indicesTrue)img_pool, indices maxpool_layer(img_tensor) #记录最大像素值的索引# unpooling#创建反池化层的输入输入的尺寸与图片池化后的尺寸一致。img_reconstruct torch.randn_like(img_pool, dtypetorch.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。 ywxb
如果没有非线性激活函数那么n层线性层1层线性层
nn.Linear(in_features, out_features, biasTrue, deviceNone, dtypeNone)对一维信号向量进行线性组合
参数
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)#outputinputs*w^Tbiasprint(output, output.shape) #输出的尺寸为1*4
激活函数层
nn.Sigmoid 这个在逻辑回归里见过。 橙色曲线是其导数图像。 1多个网络层叠加后导数范围会变得更小甚至消失。 2输出不是0均值分布破坏了数据分布。nn.Tanh 这个公式是怎么推导的 导数0时网络非常深时同样会梯度消失。nn.ReLu()修正线性单元 当多层网络不断使梯度叠加会导致梯度爆炸当累乘时可以缓解梯度消失。 nn.LeakyReLU(negative_slope0.01, inplaceFalse) LeakyReLU的slope参数0.01表示负半轴0.01的斜率. nn.PReLU(num_parameters1, init0.25, deviceNone, dtypeNone)
num_parameters(整数)可学习参数 a 的数量只有两种选择要么定义成1表示在所有通道上应用相同的 a进行激活要么定义成输入数据的通道数表示在所有通道上应用不同的 a进行激活默认1。init(float)a的初始值输入数据的第二维度表示为通道维度当输入维度小于2时不存在通道维度此时默认通道数为1通过调用.weight方法来取出参数a
即使有多个 ainit也还是只能输入一个float类型的数nn.RReLU(lower0.125, upper0.3333333333333333, inplaceFalse) RReLU斜率是随机的符合均匀分布.