近期准备开始做毕设,因为要用来实现一些算法的功能,因此在对比了各种框架后还是选择了pytorch。相较于其他的深度学习框架,pytorch的使用更加的简洁,也易于理解,并且,还有一个选择它的原因在于Github上有很多的开源代码都是使用PyTorch进行开发的。而且Pytorch也有着越来越完善的扩展库,可以说正处于当打之年。

Pytorch加载数据

​ 如何使用pytorch加载读取数据,主要涉及到两个类 DatasetDataloader

Dataset

​ 对数据进行加载时,例如对一堆数据,例如此时图中的”垃圾“,dataset主要是告诉我们如何获取数据,例如提取可回收数据,并对其进行一个编号。同时还会获取数据相应的label,因此dataset主要是提供一种方式来获取数据及其真实的label

image-20240409193934903

Dataloader

​ 可用来对dataset整理出来的数据进行打包,主要是为了为后面的网络提供不同的数据形式。

image-20240409220235552

对Dataset来说,如何获取每一个数据及其label、告诉我们总共有多少个数据,是它主要实现的功能。

以下是一个读取数据示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from torch.utils.data import Dataset
from PIL import Image
import os

class Mydata(Dataset):
def __init__(self, root_dir, label_dir):
self.root_dir = root_dir
self.labek_dir = label_dir
self.path = os.path.join(self.root_dir, self.labek_dir)
self.img_path = os.listdir(self.path)

def __getitem__(self, idx):
img_name = self.img_path[idx]
img_item_path = os.path.join(self.root_dir, self.labek_dir, img_name)
img = Image.open(img_item_path)
label = self.labek_dir
return img, label

def __len__(self):
return len(self.img_path)

root_dir = "..\data\\train"
ants_label_dir ="ants_image"
bees_label_dir ="bees_image"
ants_dataset =Mydata(root_dir, ants_label_dir)
bees_dataset =Mydata(root_dir, bees_label_dir)

# img_ant_zero, label_zero = ants_dataset[0]
# img_ant_zero.show()

# 整个数据集,可以直接相加是因为Dataset中已经写好了__add__方法,可以直接通过+来相加__len__方法中返回的值
train_dataset = ants_dataset + bees_dataset

TensorBoard的使用

TensorBoard是一个可视化工具,它可以用来展示网络图、张量的指标变化、张量的分布情况等。特别是在训练网络的时候,我们可以设置不同的参数(比如:权重W、偏置B、卷积层数、全连接层数等),使用TensorBoader可以很直观的帮我们进行参数的选择。它通过运行一个本地服务器,来监听6006端口。在浏览器发出请求时,分析训练时记录的数据,绘制训练过程中的图像。

通过SummaryWriter类,创建一个该类的对象,使用add_scalar方法即可绘制图像

1
2
3
4
5
6
7
8
9
10
11
from torch.utils.tensorboard import SummaryWriter

writer = SummaryWriter("logs") # 将内容存储在logs文件夹下

# writer.add_image()
for i in range(100):
writer.add_scalar("y=x", i, i)
# y=x(tag) : 数据标识符
# i(scalar_value) : 值(纵坐标)
# i(global_step) : 要记录的全局步值(横坐标)
writer.close()

image-20240418112852543

image-20240418113533396

再尝试:

1
2
3
4
5
6
7
8
9
from torch.utils.tensorboard import SummaryWriter

writer = SummaryWriter("logs") # 将内容存储在logs文件夹下

# writer.add_image()
for i in range(100):
writer.add_scalar("y=2x", 2*i, i)

writer.close()

image-20240418113938434

再每次想要得到一个新的图像时,需要对tag参数进行改变,pytorch会自动进行拟合。例如我在tag为y=2x的图像上绘制一个y=3x的数据:

1
writer.add_scalar("y=2x", 3*i, i)

image-20240418114536801

示例

以下是一个用于区分蚂蚁和蜜蜂进行二分类的例子,其中有训练数据集和验证数据集,对于训练数据集其中一种组织形式是会指定告诉我们每个数据集的label:

此时我们要通过add_image方法对一组图像进行研究,对于这个方法的参数,有如下必须的:

1
2
3
4
Args:
tag (str): Data identifier
img_tensor (torch.Tensor, numpy.ndarray, or string/blobname): Image data
global_step (int): Global step value to record

纵坐标相比add_scalar多了很多类型,此时我们如果使用Image库的open方法读取图片的话,返回的类型是不满足要求的,我们需要读取numpy类型的图像数据。

转换方式:img_array = np.array(img)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from torch.utils.tensorboard import SummaryWriter
import numpy as np
from PIL import Image

writer = SummaryWriter("logs") # 将内容存储在logs文件夹下
image_path = "../data/train/ants_image/0013035.jpg"
img_PIL = Image.open(image_path)
img_array = np.array(img_PIL)

writer.add_image('test', img_array, 1, dataformats='HWC')

# for i in range(100):
# writer.add_scalar("y=2x", 3*i, i)

writer.close()

不仅需要进行数据类型的转换,在add_image中还有一些shape的要求:
Shape:
img_tensor: Default is :math:(3, H, W). You can use torchvision.utils.make_grid() to
convert a batch of tensor into 3xHxW format or call add_images and let us do the job.
Tensor with :math:(1, H, W), :math:(H, W), :math:(H, W, 3) is also suitable as long as
corresponding dataformats argument is passed, e.g. CHW, HWC, HW.

当前我们数据的格式为(512, 768, 3) :print(img_array.shape),但是由于不是add_image方法默认的(3, H, W)的形式,因此需要使用dataformats进行定义:

1
writer.add_image('test', img_array, 1, dataformats='HWC')

然后在进行运行就可正常执行。

Tips:从PIL到numpy,需要在add_image()中指定shape中每一个数字/维度表示的含义。

我们再查看一下SummaryWriter类绘制出的图像结果:

image-20240418142610602

然后,再读取一张照片,将step参数改为2:

1
2
3
4
5
6
7
8
9
from torch.utils.tensorboard import SummaryWriter
import numpy as np
from PIL import Image
writer = SummaryWriter("logs") # 将内容存储在logs文件夹下
image_path = "../data/train/ants_image/5650366_e22b7e1065.jpg"
img_PIL = Image.open(image_path)
img_array = np.array(img_PIL)
writer.add_image('test', img_array, 2, dataformats='HWC')
writer.close()

image-20240418142925418

此时就可以拖拽切换step查看每次读取的图片。

Transforms的使用

transforms是 PyTorch 中提供的一个图像预处理模块,可以方便地对图像进行各种变换操作。

Transforms的结构与用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms

# python的用法 -> tensor数据类型
# 通过Transform.ToTensor去解决两个问题
# 1.transforms该如何使用(python)
# 2.为什么需要Tensor数据类型

img_path = "../data/train/ants_image/0013035.jpg"
img = Image.open(img_path)

writer = SummaryWriter('logs_tf')

# 1.transforms该如何使用(python)
tensor_trans = transforms.ToTensor() # 该类型返回Tensor类型的图片
tensor_img = tensor_trans(img) # 将img的图片转换为tensor类型的图片

# 2.为什么需要Tensor数据类型
# tensor数据类型包装了一些神经网络所需要的数据基数
writer.add_image("Tensor_img", tensor_img)

print(tensor_img)

image-20240418161839081

常见的Transforms

​ 主要就是使用transform类中的各种方法,包括各种输入、输出、作用。

image-20240418162003252

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms

writer = SummaryWriter("logs_trsf")
img = Image.open("../data/train/ants_image/0013035.jpg")
print(img)

# Totensor
trans_totensor = transforms.ToTensor()
img_tensor = trans_totensor(img)
writer.add_image("ToTensor", img_tensor)

# Normalize
# output[channel] = (input[channel] - mean[channel]) / std[channel]
# 需要输入均值(mean),标准差(std)
# 将输入的值归一化到(-1, 1)范围之间
print(img_tensor[0][0][0])
trans_norm = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
img_norm = trans_norm(img_tensor)
print(img_norm[0][0][0])
writer.add_image("Normalize", img_norm)

writer.close()

以上代码主要做了什么呢?首先将读入的数据转换为tensor格式,然后将其进行归一化,最后输出归一化结果:

1
2
3
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=768x512 at 0x27AB5995D60>
tensor(0.3137) # 归一化前原始数据
tensor(-0.3725) # 归一化结果数据

归一化后的图片结果:

image-20240418170550169

1
2
3
4
5
6
7
8
9
10
# Resize
# 调整传入的PIIL图像的尺寸
print(img.size)
trans_resize = transforms.Resize((512, 512))
# img PIL -> resize -> img_resize PIL
img_resize = trans_resize(img)
# img_resize PIL -> totensor -> img_resize tensor
img_resize = trans_totensor(img_resize)
writer.add_image("Resize", img_resize, 0)
print(img_resize)

image-20240418184956763

1
2
3
4
5
6
7
8
9
# Compose - resize - 2
# Resize如果只输入一个数的话,会按照比例进行缩放
trans_resize_2 = transforms.Resize(512)
# Compose()中的参数需要一个列表,compose会把前面的输出作为后面的输出,组合到一起
# 因此,需要注意前面的输出的数据类型是否可作为后面的输入类型,否则会报错
# PIL -> PIL -> tensor
trans_compose = transforms.Compose([trans_resize_2, trans_totensor])
img_resize_2 = trans_compose(img)
writer.add_image("Resize", img_resize_2, 1)

image-20240418204455715

1
2
3
4
5
6
7
# RandomCrop
trans_random = transforms.RandomCrop(512)
# 此处就是先对图像进行随机裁剪,然后转换为tensor格式
trans_compose_2 = transforms.Compose([trans_random, trans_totensor])
for i in range(10): # 随机裁剪10次
img_crop = trans_compose_2(img)
writer.add_image("RandomCrop", img_crop, i)

进行了十次随机裁剪:

image-20240418212725362

在类的使用中,一般主要注意以下几点:

  1. 关注输入输出类型
  2. 多看官方文档
  3. 关注方法需要什么参数
  4. 不知道返回值的时候,使用print()、print(type())、debug……

Torchvision中的数据集使用

​ 在pytorch中可以看到很多已有的可下载的数据集。通过TorchVision可以帮助我们快速的远程下载数据集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import torchvision
from torch.utils.tensorboard import SummaryWriter

# trochvision中的dataset与transform的联动,在CIFAR10中设置转换方法
dataset_transform = torchvision.transforms.Compose([
torchvision.transforms.ToTensor()
])

train_set = torchvision.datasets.CIFAR10(root="./dataset", train=True, download=True, transform=dataset_transform)
test_set = torchvision.datasets.CIFAR10(root="./dataset", train=False, download=True, transform=dataset_transform)

# print(test_set[0])
# print(test_set.classes)
#
# img, target = test_set[0]
# print(img)
# print(target)
# print(test_set.classes[target])

writer = SummaryWriter("log_dataset")
for i in range(10):
img, target = test_set[i]
writer.add_image("test_set", img, i)

writer.close()

image-20240501164541811

Dataloader的使用

​ Dataset是指用于存储和管理数据的类,而Dataloader用于从Dataset中按照指定方式读取数据。Dataloader的官方文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

# 准备的测试数据集
test_data = torchvision.datasets.CIFAR10(root="./dataset", train=False, transform=torchvision.transforms.ToTensor())

test_loader = DataLoader(dataset=test_data, batch_size=4, shuffle=True, num_workers=0, drop_last=False)
# batch_size : 每次从数据集中取4个数据进行打包
# drop_last : 如果为True,则会舍弃最后不足一组的数据
# shuffle : 打乱数据

# 测试数据集中第一张图片样本
img, target = test_data[0]
print(img.shape)
print(target)

writer = SummaryWriter("dataloader")
step = 0
for data in test_loader:
imgs, targets = data
# print(imgs.shape)
# print(targets)
writer.add_images("test_data", imgs, step)
step += 1

writer.close()

image-20240501185317124

神经网络的搭建

通过pytorch搭建神经网络主要用到的是torch.nn模块。所有定义的神经网络都应从torch.nn.Module类中继承。

以下是创建了一个最简单的神经网络,输入为一个数字,经过前进函数后,可以将输入加一后输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import torch
from torch import nn

class Yosheep(nn.Module):
def __init__(self):
super().__init__()

def forward(self, input):
output = input + 1
return output

yosheep = Yosheep()
x = torch.tensor(1.0)
output = yosheep(x)
print(output)

卷积操作

例子:

例如有一个5X5的输入图像,其中的每一块都表示这个位置的显色,并且还有一个卷积核。

image-20240501233749834

在进行卷积的过程中,就是将卷积核与输入图的前三行三列进行匹配,然后进行相乘相加,最终就输出一个10:

image-20240501234037279

当Stride为1时,卷积核在下一次会在图像中移动一步:

image-20240501234124527

然后到换行时就往下一格,依次进行计算即可,最终计算出结果:

image-20240501234437099

若Stride=2,与等于1不同的是,就是每次会走两步,移动路径如下:

image-20240501234552051image-20240501234659633image-20240501234715674

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import torch
import torch.nn.functional as F

input = torch.tensor([[1, 2, 0, 3, 1],
[0, 1, 2, 3, 1],
[1, 2, 1, 0, 0],
[5, 2, 3, 1, 1],
[2, 1, 0, 1, 1]])

kernel = torch.tensor([[1, 2, 1],
[0, 1, 0],
[2, 1, 0]])

# 第一个1是输入图片的数量,第二个1是通道数,由于这只是一个二维张量因此通道为1,第一个5是H为高,第二个5是W是宽
input = torch.reshape(input, (1, 1, 5, 5))
kernel = torch.reshape(kernel, (1, 1, 3, 3))

print(input.shape)
print(kernel.shape)

output = F.conv2d(input, kernel, stride=1)
print(output)
1
2
3
4
5
6
# 当Stride为1时
torch.Size([1, 1, 5, 5])
torch.Size([1, 1, 3, 3])
tensor([[[[10, 12, 12],
[18, 16, 16],
[13, 9, 3]]]])
1
2
3
4
5
# 当Stride为2时
torch.Size([1, 1, 5, 5])
torch.Size([1, 1, 3, 3])
tensor([[[[10, 12],
[13, 3]]]])

填充,会在图像的四周都填充指定数量的列,一般padding的值为0,当padding为1:

image-20240502000758746

1
2
3
4
5
6
# 当padding=1,Stride=1,此时再进行卷积的结果
tensor([[[[ 1, 3, 4, 10, 8],
[ 5, 10, 12, 12, 6],
[ 7, 18, 16, 16, 8],
[11, 13, 9, 3, 4],
[14, 13, 9, 7, 4]]]])

卷积层使用

再pytorch中,卷积操作有一维、二维、三维的操作方法,一般我们对图片使用最多的是二维,也就是其中的conv2d方法。

在conv2d的参数中,outchannel参数的设置,也就是输出通道数,当outchannel为2时,则会有两个卷积核,最终的输出是两个结果的叠加:

image-20240502153337184

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

# 加载数据
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
dataloader = DataLoader(dataset=dataset, batch_size=64)

# 搭建一个简单神经网络
class Yosheep(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = Conv2d(in_channels=3, out_channels=6, kernel_size=3, stride=1, padding=0)

# 此前进函数输入一个x后,对其进行卷积操作后输出
def forward(self, x):
x = self.conv1(x)
return x

yosheep = Yosheep()

writer = SummaryWriter("./nn_logs")

step = 0
for data in dataloader:
imgs, targets = data
output = yosheep(imgs)
# torch.Size([64, 3, 32, 32])
writer.add_images("input", imgs, step)

# 此时这个操作,如果是两个channel的图,经过叠加后,形成两个通道,但是若reshape将其变为一个通道,则会使其batch_size变大
# reshape中变化的参数,若第一个值不知道是多少,则写-1
output = torch.reshape(output, (-1, 3, 30, 30))
# torch.Size([64, 6, 30, 30]) -> torch.Size([64, 3, 30, 30])
writer.add_images("output", output, step)
step += 1

卷积后得到的输出:

image-20240502161704241

最大池化使用

最大池化使用的最多的方法还是maxpool2d。池化也是应用池化核,然后通过池化核去输入中进行匹配,不过此时的输出结果是最大的值(池化层默认步长是池化核的大小):

image-20240502162455295

如果匹配到了边缘,则就要看Ceil_module的设置,如果为true,则保留,否则不保留:

image-20240502162703002

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import torch
import torchvision.datasets
from torch import nn
from torch.nn import MaxPool2d
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

dataset = torchvision.datasets.CIFAR10("./dataset", train=False, download=True,
transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, batch_size=64)

# input = torch.tensor([[1, 2, 0, 3, 1],
# [0, 1, 2, 3, 1],
# [1, 2, 1, 0, 0],
# [5, 2, 3, 1, 1],
# [2, 1, 0, 1, 1]], dtype=torch.float32)
#
# input = torch.reshape(input, (-1, 1, 5, 5))
# print(input.shape)

class Yosheep(nn.Module):
def __init__(self):
super().__init__()
self.maxpool1 = MaxPool2d(kernel_size=3, ceil_mode=True)

def forward(self, input):
output = self.maxpool1(input)
return output

yosheep = Yosheep()
# output = yosheep(input)
# print(output)

writer = SummaryWriter("nn_maxpool_logs")
step = 0

for data in dataloader:
imgs, targets = data
writer.add_images("input", imgs, step)
output = yosheep(imgs)
writer.add_images("output", output, step)
step += 1

writer.close()

最大池化的应用可以想象,将1080p的视频转换为720p,会对画质减小,但是同时视频大小也会大大减小。

image-20240502165016418

非线性激活

引入非线性关系: 如果在神经网络中只使用线性操作(如线性加权和),整个网络就会变成一个大的线性函数,多个线性层的组合依然是一个线性变换。非线性激活函数(例如sigmoid、tanh、ReLU等)引入了非线性关系,允许网络学习和表示非线性的模式,这对于解决复杂任务非常关键。

以下使用ReLU演示:

输入经过ReLU处理后,会进行简单的改变,当输入为负数时,则会被变为0:

image-20240502174757238

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import torch
from torch import nn
from torch.nn import ReLU

input = torch.tensor([[1, -0.5],
[-1, 3]])

output = torch.reshape(input, (-1, 1, 2, 2))
print(output.shape)

class Yosheep(nn.Module):
def __init__(self):
super().__init__()
self.relu1 = ReLU()

def forward(self, input):
output = self.relu1(input)
return output

yosheep = Yosheep()
output = yosheep(input)
print(output)
1
2
3
torch.Size([1, 1, 2, 2])
tensor([[1., 0.],
[0., 3.]])

对图片进行操作,此处使用sigmoid:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import torch
import torchvision.datasets
from torch import nn
from torch.nn import ReLU, Sigmoid
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

input = torch.tensor([[1, -0.5],
[-1, 3]])

output = torch.reshape(input, (-1, 1, 2, 2))
print(output.shape)

dataset = torchvision.datasets.CIFAR10("./dataset", train=False, download=True,
transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, batch_size=64)
class Yosheep(nn.Module):
def __init__(self):
super().__init__()
self.relu1 = ReLU()
self.sigmoid1 = Sigmoid()

def forward(self, input):
output = self.sigmoid1(input)
return output

yosheep = Yosheep()

step = 0
writer = SummaryWriter("./nn_relu_logs")
for data in dataloader:
imgs, targets = data
writer.add_images("input", imgs, global_step=step)
output = yosheep(imgs)
writer.add_images("output", output, global_step=step)
step += 1
writer.close()

image-20240502175626811

正则化层

通过正则化,可以加快神经网络的速度,也可以解决过拟合的问题。(BatchNorm2d)

线性层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import torch
import torchvision
from torch import nn
from torch.nn import Linear
from torch.utils.data import DataLoader

dataset = torchvision.datasets.CIFAR10("./dataset", train=False, download=True,
transform=torchvision.transforms.ToTensor())

# 此处不加drop_last后会报错
dataloader = DataLoader(dataset, batch_size=64, drop_last=True)

class Yosheep(nn.Module):
def __init__(self):
super().__init__()
# in_features : 196609是拉长后的图片的长度
# out_features : 输出的特征长度
self.linear1 = Linear(196608, 10)

def forward(self, input):
output = self.linear1(input)
return output

yosheep = Yosheep()

for data in dataloader:
imgs, targets = data
print(imgs.shape)
# 要将图片拉长,最后一个参数是宽度,此时拉长后的宽度是未知的,因此要使用-1
# output = torch.reshape(imgs, (1, 1, 1, -1))
# 展平数据
output = torch.flatten(imgs)
print(output.shape)
output = yosheep(output)
print(output.shape)
1
2
3
4
5
6
# 原始图像数据
torch.Size([64, 3, 32, 32])
# 展开成一维后的结果
torch.Size([196608])
# Linear后的结果
torch.Size([10])

其余在神经网络中还有很多的层,可以在官方文档中查看:https://pytorch.org/docs/stable/nn.html

Sequential的使用与搭建一个小神经网络

image-20240502220559528

根据以上结构,构造对应的神经网络:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear


class Yosheep(nn.Module):
def __init__(self):
super().__init__()
# inchannel是3, outchannel是32, kernelsize为5,padding根据卷积公式计算
self.conv1 = Conv2d(3, 32, 5, padding=2)
# 此时maxpool核大小为2
self.maxpool1 = MaxPool2d(2)
# 输入为32,输出为32,padding根据卷积公式计算
self.conv2 = Conv2d(32, 32, 5, padding=2)
# 此时maxpool核大小为2
self.maxpool2 = MaxPool2d(2)
# 输入为32,输出为64,padding根据卷积公式计算
self.conv3 = Conv2d(32, 64, 5, padding=2)
# 此时maxpool核大小为2
self.maxpool3 = MaxPool2d(2)
# 进行展平
self.flatten = Flatten()
# 线性层
self.linear1 = Linear(1024, 64)
self.linear2 = Linear(64, 10)

def forward(self, x):
x = self.conv1(x)
x = self.maxpool1(x)
x = self.conv2(x)
x = self.maxpool2(x)
x = self.conv3(x)
x = self.maxpool3(x)
x = self.flatten(x)
x = self.linear1(x)
x = self.linear2(x)
return x

yosheep = Yosheep()
print(yosheep)
1
2
3
4
5
6
7
8
9
10
11
12
# 输出结构:
Yosheep(
(conv1): Conv2d(3, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(maxpool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(conv2): Conv2d(32, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(maxpool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(conv3): Conv2d(32, 64, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(maxpool3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear1): Linear(in_features=1024, out_features=64, bias=True)
(linear2): Linear(in_features=64, out_features=10, bias=True)
)

此时用一个全1的数据对建立好的模型进行一个测试:

1
2
3
4
5
# 新建一个全是1的数据
# batchsize为64,3通道,32*32
input = torch.ones((64, 3, 32, 32))
output = yosheep(input)
print(output.shape)
1
2
# 输出
torch.Size([64, 10])

此时对于以上的神经网络init函数中的内容,也可以使用Sequential方法来对其简化:

1
2
3
4
5
6
7
8
9
10
11
self.model1 = Sequential(
Conv2d(3, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 64, 5, padding=2),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10)
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import torch
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential


class Yosheep(nn.Module):
def __init__(self):
super().__init__()
self.model1 = Sequential(
Conv2d(3, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 64, 5, padding=2),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10)
)

def forward(self, x):
x = self.model1(x)
return x

yosheep = Yosheep()
print(yosheep)

也可以通过SummaryWriter来直接通过模型显示模型:

1
2
3
writer = SummaryWriter("logs_nn_sequential")
writer.add_graph(yosheep, input)
writer.close()

image-20240502231119409

通过双击即可详细查看内部的操作:

image-20240502231212830

损失函数与反向传播

损失函数的计算方法,可以看出loss对于模型来说是越小越好的:

image-20240503135959750

image-20240503140751617

loss的作用:

  1. 计算实际输出和目标之间的差距
  2. 为我们更新输出提供一定的依据(反向传播)
1
2
3
4
5
6
7
8
9
10
11
12
13
import torch
from torch.nn import L1Loss

inputs = torch.tensor([1, 2, 3], dtype=torch.float32)
targets = torch.tensor([1, 2, 5], dtype=torch.float32)

inputs = torch.reshape(inputs, (1, 1, 1, 3))
targets = torch.reshape(targets, (1, 1, 1, 3))

loss = L1Loss()
result = loss(inputs, targets)

print(result)
1
tensor(0.6667)

平方差MSELoss

1
2
loss_mse = nn.MSELoss()
result_mse = loss_mse(inputs, targets)
1
tensor(1.3333)

交叉熵

比如此时有一个三分类问题。此时有一个图片,一个神经网络,以及获取的一些数据:

image-20240503142944521

1
2
3
4
5
6
x = torch.tensor([0.1, 0.2, 0.3])
y = torch.tensor([1])
x = torch.reshape(x, (1, 3)) # batch_size = 1, 数据大小为3
loss_cross = nn.CrossEntropyLoss() # 计算交叉熵
result_cross = loss_cross(x, y)
print(result_cross)
1
tensor(1.1019)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import torch
import torchvision
from torch.nn import L1Loss, Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch import nn
from torch.utils.data import DataLoader

dataset = torchvision.datasets.CIFAR10("./dataset", train=False, download=True,
transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, batch_size=1)

class Yosheep(nn.Module):
def __init__(self):
super().__init__()
self.model1 = Sequential(
Conv2d(3, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 64, 5, padding=2),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10)
)

def forward(self, x):
x = self.model1(x)
return x

loss = nn.CrossEntropyLoss()

yosheep = Yosheep()
for data in dataloader:
imgs, target = data
outputs = yosheep(imgs)
result_loss = loss(outputs, target)
result_loss.backward()

以上就可以求出每个数据的loss值,并且可以得到损失函数的一个梯度,进而可以通过这个方向进行梯度下降。

优化器

通过反向传播,可以计算出需要调节的参数和其对应的梯度,进而可以用优化器根据梯度来进行调整。

优化器:optim,可以使用其中的算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import torch
import torchvision
from torch.nn import L1Loss, Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch import nn
from torch.utils.data import DataLoader

dataset = torchvision.datasets.CIFAR10("./dataset", train=False, download=True,
transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, batch_size=1)

class Yosheep(nn.Module):
def __init__(self):
super().__init__()
self.model1 = Sequential(
Conv2d(3, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 64, 5, padding=2),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10)
)

def forward(self, x):
x = self.model1(x)
return x

loss = nn.CrossEntropyLoss()

yosheep = Yosheep()
# 定义优化器,使用其中的SGD算法
optim = torch.optim.SGD(yosheep.parameters(), lr=0.01)
# 多次执行,看每轮的loss
for epoch in range(20):
running_loss = 0.0
for data in dataloader:
imgs, target = data
outputs = yosheep(imgs)
result_loss = loss(outputs, target)
# 把每个可调节参数的梯度调为0
optim.zero_grad()
# 反向传播,获取每个可调节参数的梯度
result_loss.backward()
optim.step() # 进行调优
running_loss = running_loss + result_loss
print(running_loss)
1
2
3
4
5
# loss下降的过程
tensor(18641.3711, grad_fn=<AddBackward0>)
tensor(16151.2676, grad_fn=<AddBackward0>)
tensor(15427.0596, grad_fn=<AddBackward0>)
......