这是我参考视频教程copy的代码,教程里没有说明每行语句的具体用途,为了理解这段代码,我查找了一些使我困惑的函数和语句的具体用途,用注释和引用块的方式给出,充当备忘。
import torch
# torch.nn用于构建神经网络的基本结构
import torch.nn as nn
# torch.nn.functional,一个神经网络基础函数包。
# 包含卷积,池化,非线性激活函数,归一化函数,线性函数,Dropout函数等
import torch.nn.functional as F
# torch.optim是一个实现了各种优化算法的库。
# 支持大部分常用的方法,并且接口具备足够的通用性,使得未来能够集成更加复杂的方法。
import torch.optim as potim
# torchvision包含了目前流行的数据集,模型结构和常用的图片转换工具。
# torchvision.datasets为数据集
# torchvision.transforms可以对图像进行变换
from torchvision import datasets, transforms
import numpy as np
print("PyTorch Version: ", torch.__version__)
# 上面代码块的输出
PyTorch Version: 1.0.0
# datasets
mnist_data = datasets.MNIST("./mnist_data",
train=True,
download=True,
transform=transforms.Compose(
[transforms.ToTensor()]))
mnist_data[0][0].shape
# 上面代码块的输出
torch.Size([1, 28, 28])
torchvision.datasets.MNIST(root, train=True, transform=None, target_transform=None, download=False)
加载MNIST数据集并进行基本操作
root:数据集在本地的保存位置
train(bool, optional):True则该数据集为训练集,False则是测试集。默认为True
transform(callable, optional):一个转换函数,将PIL图像转换为别的形式输出
target_transform(callable, optional):一个函数,输入为target,输出对其的转换。例子,输入的是图片标注的string,输出为word的索引。
download(bool, optional):True = 从互联网上下载数据集,并把数据集放在root目录下。如果数据集之前下载过,将处理过的数据(mnist.py中有相关函数)放在processed文件夹下
torchvision.transforms.Compose(transforms)
对数据集进行一系列转换的函数
transforms:由多个transform组成的列表,依次使用这些transform进行转换
torchvision.transforms.ToTensor()
把一个取值范围是[0,255]的PIL.Image或者shape为(H,W,C)的numpy.ndarray,转换成形状为[C,H,W],取值范围是[0,1.0]的torch.FloadTensor
为什么ndarray是(H, W, C)而Tensor是[C, H, W]:C-通道数,H-高度,W-宽度。ndarray和Tensor表示的是同一张图片,但是二者默认的表示顺序不同。ndarray侧重真实矩阵形式表示,而Tensor面向神经网络训练时的需要(分通道)。
# torch.nn.Module是所有网络的基类
# pytorch下写的任何神经网络都应该继承这个基类
class Net(nn.Module):
# 初始化一些需要用到的层
def __init__(self):
# 在__init__方法中首先执行父类的__init__():super(Net, self).__init__()
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 20, 5, 1)
self.conv2 = nn.Conv2d(20, 50, 5, 1)
self.fc1 = nn.Linear(4 * 4 * 50, 500)
self.fc2 = nn.Linear(500, 10)
# torch.nn.Module的一个抽象方法,所有继承自torch.nn.Module的子类都应该实现该方法
# 用该方法定义网络具体结构
# 网络的具体结构为(共6层):
# 输入 -> 卷积层(relu)-> 最大值池化 -> 卷积层(relu)-> 最大值池化 -> 全连接层(relu)-> 全连接层(log_softmax)-> 输出
# 该方法隐式执行。如下两行语句将会执行该方法,并将forward(input_tensor)的输出赋值给result
# net = Net()
# result = net(input_tensor)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.max_pool2d(x, 2, 2)
x = F.relu(self.conv2(x))
x = F.max_pool2d(x, 2, 2)
x = x.view(-1, 4 * 4 * 50)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return F.log_softmax(x, dim=1)
torch.nn.Conv2d(in_channels,out_channels,kernel_size,stride=1,padding=0,dilation=1,groups=1,bias=True)
二维卷积层
in_channels(int):输入通道数。手写数字图像是黑白图像,只有一个通道,所以第一个卷积层的输入通道数为1
out_channels(int):输出通道数。这个算法预先设定好了第一个卷积层输出通道为20个,第二个输出通道为50个
kernel_size(int or tuple):卷积核的尺寸。输入为int时卷积核为方形,输入为tuple时可以任意指定形状
stride(int or tuple, optional):卷积的步长。输入为int时任何方向步长相等,输入为tuple时,第一维表示行步长,第二维表示列步长。默认值是1。教程在这里输入了1,可能是习惯吧,习惯了设定不同的步长
padding(int or tuple, optional):为输入图像的每一维的两侧填充0的行数(简而言之,对图像进行零填充以控制图像大小)。tuple与stride的效果同
dilation(int or tuple, optional):卷积核元素之间的间距。比如一个3X3卷积核:在dilation=1时,卷积核在原始图像的3X3的小区域内卷积;在dilation=2时,卷积核的9个元素平均分布在原始图像的5X5的区域内做一次卷积
groups(int, optional):in_channels和out_channels都需要能被groups整除。默认值为1,此时代表每一个输入通道都通过卷积连接到每一个输出通道。当groups=2时,代表输入通道和输出通道平均分成两组,分别进行卷积,然后连接到一起
bias(bool, optional):为True时,在输出时添加一个可学习的偏置
torch.nn.Linear(in_features, out_features, bias=True)
对输入做一个线性变换,也就是全连接层
in_features:每个样本输入的大小
out_features:每个样本输出的大小,即该层神经元个数
torch.nn.functional.relu(input, inplace=False)
relu函数,即小于零时置零,大于零时不变
input(Tensor):输入
inplace(bool, optional):选择是否就地操作,即是分配一个新的内存空间还是直接在input上操作。默认为False。
torch.nn.functional.max_pool2d(input, kernel_size, stride=None, padding=0, dilation=1, ceil_mode=False, return_indices=False)
最大值池化
input(Tensor):输入
kernel_size(int or tuple):最大值池化的窗口大小
stride(int or tuple):移动的步长
padding(int or tuple, optional):为输入图像的每一维的两侧填充0的行数
dilation(int or tuple, optional):窗口元素之间的间距。
ceil_mode(bool, optional):计算shape时是否使用向上取整,False则使用向下取整。默认为False
return_indices(bool, optional):是否输出最大值索引(做上池化时需要这个索引),默认为False
torch.nn.functional.log_softmax(input, dim=None, _stacklevel=3, dtype=None)
等效于log(softmax(input))
input(Tensor):输入
dim(int):用来计算log_softmax的那个维度。dim=0时对每一列操作,dim=1时对每一行操作
_stacklevel(int, optional):出现warnings时的stack追踪深度,默认为3
dtype(torch.dtype, optional):如果指定了,就在操作之前将input中元素强转为dtype类型,常用于防止溢出
torch.nn.functional.nll_loss(input, target, weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')
input:输入
target:正确分类标签
reduction(string, optional):参数为'none' | 'mean' | 'sum','none'时不做任何处理,'mean'时输出平均数,'sum'时输出总和
def train(model, device, train_loader, optimizer, epoch):
model.train() # 将模块设置为训练模式,默认参数为True即训练模式,False则为evaluation模式(测试模式)
for idx, (data, target) in enumerate(train_loader):
# torch.Tensor.to()将Tensor转换为对应dtype或device版本
data, target = data.to(device), target.to(device)
# 将数据传入模型中进行一次训练
pred = model(data)
# 计算训练结果与真实值之间的负对数似然损失函数(The negative log likelihood loss)
loss = F.nll_loss(pred, target)
# SGD
# 将梯度设置为0
optimizer.zero_grad()
# 反向传播
loss.backward()
# 进行一次优化
optimizer.step()
if idx % 100 == 0:
print("Train Epoch: {}, iteration: {} Loss: {}".format(
epoch, idx, loss.item()))
def test(model, device, test_loader):
model.eval() # 将模型设置为测试模式
total_loss = 0.0
correct = 0.0
# 在不需计算grad的情况下运行
# 因为在计算过程中可能会出现require_grad = True的情形,此时会给grad分配内存空间
with torch.no_grad():
for idx, (data, target) in enumerate(test_loader):
data, target = data.to(device), target.to(device)
output = model(data) # 数据经过训练后的模型的结果
total_loss += F.nll_loss(output, target, reduction="sum").item() # 计算loss
pred = output.argmax(dim=1) # 返回output中所有元素最大值的索引,也就是最终被分到第几类
correct += pred.eq(target.view_as(pred)).sum().item() # 计算正确的个数
total_loss /= len(test_loader.dataset)
acc = correct / len(test_loader.dataset) * 100.
print("Test loss: {}, Accuracy: {}".format(total_loss, acc))
torch.optim.Optimizer.zero_grad(set_to_none: bool = False)
将梯度初始化为0
set_to_none(bool, optional):将梯度设置为None,节约空间。但是需要访问grad时会出错,建议保持False
optimizer,zero_grad(),backward(),step()各方法的意义
这部分还有很多可以写,但是个人对这部分的理解有限,所以用的是别人的解释。建议如果有能力的话,自己查找相关资料
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
batch_size = 32
train_dataloader = torch.utils.data.DataLoader(datasets.MNIST(
"./mnist_data",
train=True,
download=True,
# 此处的0.1307是样本均值,0.3081是样本方差。Normalize进行均值归一化操作
transform=transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.1307, ), (0.3081, ))])),
batch_size=batch_size,
shuffle=True,
num_workers=1,
pin_memory=True)
test_dataloader = torch.utils.data.DataLoader(datasets.MNIST(
"./mnist_data",
train=False,
download=True,
transform=transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.1307, ), (0.3081, ))])),
batch_size=batch_size,
shuffle=False,
num_workers=1,
pin_memory=True)
lr = 0.01 # 学习率
momentum = 0.5 # 动量
model = Net().to(device) # 模型
optimizer = torch.optim.SGD(model.parameters(), lr=lr,
momentum=momentum) # 使用随机梯度下降
num_epochs = 2 # 训练轮数
for epoch in range(num_epochs):
train(model, device, train_dataloader, optimizer, epoch)
test(model, device, test_dataloader)
# 保存
torch.save(model.state_dict(), "mnist_cnn.pt")
# 上面代码块的输出
Train Epoch: 0, iteration: 0 Loss: 2.31730055809021
Train Epoch: 0, iteration: 100 Loss: 0.47498029470443726
Train Epoch: 0, iteration: 200 Loss: 0.21783362329006195
Train Epoch: 0, iteration: 300 Loss: 0.14298346638679504
Train Epoch: 0, iteration: 400 Loss: 0.12032605707645416
Train Epoch: 0, iteration: 500 Loss: 0.2093348652124405
Train Epoch: 0, iteration: 600 Loss: 0.07504405081272125
Train Epoch: 0, iteration: 700 Loss: 0.09818609058856964
Train Epoch: 0, iteration: 800 Loss: 0.0559423565864563
Train Epoch: 0, iteration: 900 Loss: 0.01989009976387024
Train Epoch: 0, iteration: 1000 Loss: 0.03885611891746521
Train Epoch: 0, iteration: 1100 Loss: 0.1089647114276886
Train Epoch: 0, iteration: 1200 Loss: 0.01422014832496643
Train Epoch: 0, iteration: 1300 Loss: 0.0961468443274498
Train Epoch: 0, iteration: 1400 Loss: 0.05515772104263306
Train Epoch: 0, iteration: 1500 Loss: 0.16151171922683716
Train Epoch: 0, iteration: 1600 Loss: 0.08142450451850891
Train Epoch: 0, iteration: 1700 Loss: 0.12128034234046936
Train Epoch: 0, iteration: 1800 Loss: 0.02070571482181549
Test loss: 0.07619362027645112, Accuracy: 97.55
Train Epoch: 1, iteration: 0 Loss: 0.01667921245098114
Train Epoch: 1, iteration: 100 Loss: 0.051861390471458435
Train Epoch: 1, iteration: 200 Loss: 0.0019468367099761963
Train Epoch: 1, iteration: 300 Loss: 0.004166126251220703
Train Epoch: 1, iteration: 400 Loss: 0.05624380707740784
Train Epoch: 1, iteration: 500 Loss: 0.05534198880195618
Train Epoch: 1, iteration: 600 Loss: 0.01171034574508667
Train Epoch: 1, iteration: 700 Loss: 0.01633147895336151
Train Epoch: 1, iteration: 800 Loss: 0.06951265037059784
Train Epoch: 1, iteration: 900 Loss: 0.055513106286525726
Train Epoch: 1, iteration: 1000 Loss: 0.03756368160247803
Train Epoch: 1, iteration: 1100 Loss: 0.05086361616849899
Train Epoch: 1, iteration: 1200 Loss: 0.02783718705177307
Train Epoch: 1, iteration: 1300 Loss: 0.056053027510643005
Train Epoch: 1, iteration: 1400 Loss: 0.11548101156949997
Train Epoch: 1, iteration: 1500 Loss: 0.011726364493370056
Train Epoch: 1, iteration: 1600 Loss: 0.1298655867576599
Train Epoch: 1, iteration: 1700 Loss: 0.07312324643135071
Train Epoch: 1, iteration: 1800 Loss: 0.0066553205251693726
Test loss: 0.04361509647369385, Accuracy: 98.67
torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, num_workers=0, collate_fn=
, pin_memory=False, drop_last=False)
dataset(Dataset):加载数据的数据集。
batch_size(int, optional):每个batch加载多少个样本(默认: 1)。
shuffle(bool, optional):设置为True时会在每个epoch重新打乱数据(默认: False).
sampler(Sampler, optional):定义从数据集中提取样本的策略。如果指定,则忽略shuffle参数。
num_workers(int, optional):用多少个子进程加载数据。0表示数据将在主进程中加载(默认: 0)
collate_fn(callable, optional):没看懂,默认就行,不影响
pin_memory(bool, optional):如果为True,则DataLoader在将tensor返回之前将其复制到CUDA固定的内存中。
drop_last(bool, optional):如果数据集大小不能被batch size整除,则设置为True后可删除最后一个不完整的batch。如果设为False并且数据集的大小不能被batch size整除,则最后一个batch将更小。(默认: False)
torch.optim.SGD(params, lr=
, momentum=0, dampening=0, weight_decay=0, nesterov=False)
params(iterable):待优化参数的iterable或者是定义了参数组的dict
lr(float):学习率
momentum(float, optional):动量因子(默认:0)
weight_decay(float, optional):权重衰减(L2惩罚)(默认:0)
dampening(float, optional):动量的抑制因子(默认:0)
nesterov(bool, optional):使用Nesterov动量(默认:False)
关于动量方面,查看Adam算法
torch.save(obj, f, pickle_module=
, pickle_protocol=2)
将模型保存为硬盘文件
obj:想要保存的对象。此处若是module则保存整个网络状态,加载时使用model=torch.load(mymodel.pt)
此处若是module.state_dict()则只保存参数,不保存网络结构,加载时需先定义好网络结构,然后再load,也即
model = My_model(*args, **kwargs)
model.load_state_dict(torch.load(mymodel.pt))
f:类文件对象 (返回文件描述符)或一个保存文件名的字符串
pickle_module:用于pickling元数据和对象的模块
pickle_protocol:指定pickle protocal 可以覆盖默认参数
保存的文件有三种文件名:.pth,.pt,.pkl,三者除了文件名后缀之外无任何区别。pytorch约定使用.pt文件