-
pytorch最基本的操作对象是Tensor(张量),它表示一个多维矩阵,类似于numpy的ndarrays,其可在GPU上使用可以加速计算,和numpy类型转换也非常方便
-
torch.nn为我们提供了成熟的层及一系列激活函数,可以像搭积木一样来非常快速的创建复杂的模型
-
在pytorch中搭建模型的流程:定义类(该类要继承nn.Module),在类中实现两个方法:1.初始化函数:实现在搭建网络过程中所需要实现的网络层结构。2.在forward函数中定义正向传播的过程。
-
创建一个层时,权重和偏置都会随机初始化
-
经卷积后的矩阵尺寸大小计算公式为:N=(W-F+2P)/S+1
-
torch种Tensor的通道顺序:[batch,channel,height,width]
-
复习一下CNN:1.卷积核的 层数 和 输入通道数 一致,卷积核的 个数 和 输出通道数 一致(卷积过程:每层卷积核分别和对应层的feature map进行卷积,同一通道相加)。2.池化层的目的:缩减参数以提高计算速度,提高feature map的鲁棒性(实验得知)
-
最大池化下采样后高度和宽度就变成了原来的一半(池化层只会改变高和宽,不会改变feature map的深度)
-
在模型测试的时候可以自定义一个随机的输入x作为输入图像,如:
> input = torch.rand([32,3,32,32]) //batch=32 channel=3````
> model = LeNet() //实例化模型
> output = model(input)
-
super函数是继承父类的某个函数(理解为先执行父类的某个函数,再执行下面的语句)
-
定义卷积层的函数为nn.Conv2d,参数顺序为:深度、卷积核的个数、卷积核的大小,步距默认为1
-
定义下采样层的函数为nn.MaxPool2d,参数顺序为:卷积核大小,步距,padding
-
定义全连接层的函数为nn.Linear,全连接层的输入是一个一维的向量,因此需要将得到的特征矩阵给展平成一维向量
-
最后一个全连接层的输出是需要根据自己的分类类别进行更改的
-
x = F.relu(self.conv1(x)) 的意思是输入经过第一个卷积层再经过relu激活函数
-
view函数起到的作用是reshape,view的参数的是改变后的shape,-1代表第一个维度(batch)是自动推理的,第二个参数就是展平
-
一个最简单的模型LeNet网络的model.py文件代码如下:
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv1 = nn.Conv2d(3, 16, 5)
self.pool1 = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(16, 32, 5)
self.pool2 = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(32*5*5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = F.relu(self.conv1(x))
x = self.pool1(x)
x = F.relu(self.conv2(x))
x = self.pool2(x)
x = x.view(-1, 32*5*5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
-
torchvision是pytorch的一个图形库,它服务于PyTorch深度学习框架的,主要用来构建计算机视觉模型。torchvision.transforms主要是用于常见的一些图形变换。以下是torchvision的构成:
> 1.torchvision.datasets: 一些加载数据的函数及常用的数据集接口;
> 2.torchvision.models: 包含常用的模型结构(含预训练模型),例如AlexNet、VGG、ResNet等;
> 3.torchvision.transforms: 常用的图片变换,例如裁剪、旋转等;
> 4.torchvision.utils: 其他的一些有用的方法。
> 其中的torchvision.transforms.Compose()类。这个类的主要作用是串联多个图片变换的操作,Compose里面的参数实际上就是个列表,而这个列表里面的元素就是你想要执行的transform操作
> transforms.Normalize:对数据按通道进行标准化,即先减均值,再除以标准差
> transforms.ToTensor:将PIL Image或者 ndarray 转换为tensor,并且归一化至[0-1]。注意:归一化至[0-1]是直接除以255,若自己的ndarray数据尺度有变化,则需要自行修改。
> transforms.Resize:缩放
> transforms.RandomResizedCrop:将给定图像随机裁剪为不同的大小和宽高比,然后缩放所裁剪得到的图像为制定的大小
> transforms.Grayscale:转为灰度图
> transforms.RandomHorizontalFlip:以给定的概率随机水平旋转给定的PIL的图像,默认为0.5;
> 关于:optimizer.zero_grad()---->将历史损失梯度清零 每计算一个batch就要调用一次optimizer.zero_grad(),如果不清除历史梯度,就会对计算的历史梯度进行累加通常,设置batchsize是根据硬件设备的条件,一般该值越大,训练的效果就会越好。但通常硬件设备由于内存等不足不可能用一个很大的batch去训练,就可以通过optimizer.zero_grad(),变相的实现一个很大的batch进行训练,也就是通过计算多个小的batch损失梯度,将其等效为一个大的batch的损失梯度进行反向传播
net = LeNet()
net.to(device)
loss_function = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)
for epoch in range(5):
running_loss = 0.0
for step, data in enumerate(train_loader):
inputs, labels = data
optimizer.zero_grad()
outputs = net(inputs)
loss = loss_function(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
if step % 500 == 499:
with torch.no_grad():
outputs = net(val_image)
predict_y = torch.max(outputs, dim=1)[1]
accuracy = (predict_y == val_label).sum().item() / val_label.size(0)
print('[%d, %5d] train_loss: %.3f test_accuracy: %.3f' %
(epoch + 1, step + 1, running_loss / 500, accuracy))
running_loss = 0.0
print('Finished Training')
save_path = './·····.pth'
torch.save(net.state_dict(), save_path)
transform = transforms.Compose(
[transforms.Resize((32, 32)),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
net = LeNet()
net.load_state_dict(torch.load('Lenet.pth'))
im = Image.open('1.jpg')
im = transform(im)
im = torch.unsqueeze(im, dim=0)
with torch.no_grad():
outputs = net(im)
predict = torch.max(outputs, dim=1)[1].data.numpy()
print(classes[int(predict)])
-
nn.Sequential函数能够将一系列的层结构进行打包,组合成一个新的层结构,常用在模型初始化定义层结构中,相对于使用 self.conv1 = nn.Conv2d(3, 16, 5)语句会非常的高效方便。
-
nn.ReLU(inplace=True)语句中,inplace参数可以理解为是pytorch通过增加计算量,降低内存使用的一种方法,通过这个方法,可以在内存中载入更大的一个模型。
-
nn.Sequential结构示例:
class AlexNet(nn.Module):
def __init__(self, num_classes=1000, init_weights=False):
super(AlexNet, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 48, kernel_size=11, stride=4, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(48, 128, kernel_size=5, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(128, 192, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(192, 192, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(192, 128, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
)
self.classifier = nn.Sequential(
nn.Dropout(p=0.5),
nn.Linear(128 * 6 * 6, 2048),
nn.ReLU(inplace=True),
nn.Dropout(p=0.5),
nn.Linear(2048, 2048),
nn.ReLU(inplace=True),
nn.Linear(2048, num_classes),
)
if init_weights:
self._initialize_weights()
def forward(self, x):
x = self.features(x)
x = torch.flatten(x, start_dim=1)
x = self.classifier(x)
return x
def _initialize_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
nn.init.normal_(m.weight, 0, 0.01)
nn.init.constant_(m.bias, 0)
-
self.modules()函数会返回网络中所有的层结构, isinstance()函数可以判断数据的类型
-
nn.init.kaiming_normal_和 nn.init.normal_都是初始化变量函数,后者是正态分布的方法给参数进行赋值,其实这些语句并不需要,写出来只是为了让大家学习,在torch中,会自动对卷积和全连接层是自动使用凯明方法初始化权重。
-
通过Dropout方法能够使全连接层的节点按一定比例进行失活,防止过拟合。其中的Dropout一般放在全连接层与全连接层之间,nn.Dropout(p=0.5)中的p代表随机失活的神经元的比例,默认是0.5。
-
nn.Linear(2048, 2048)定义全连接层的函数分别为输入神经元个数和输出神经元个数。
-
torch.flatten方法可以从自定义的维度开始展平变量
-
device = torch.device(“cuda:0” if torch.cuda.is_available() else “cpu”)语句: torch.device可以指定训练过程中所使用的设备,括号里面语法的意思是如果当前设备有可使用的GPU,那么就使用第一块GPU,如果没有则使用CPU。
-
解释一下os.path.abspath(os.path.join(os.getcwd(), “… /…”)) :os.getcwd()是获取当前文件所在的目录,…/…为返回上上层目录,os.path.join是拼接两个路径
-
torchvision.datasets.ImageFolder是pytorch加载数据集的函数,其默认你的数据集已经按类分好了文件夹,同一个文件夹下是同一类的照片。第二个参数是数据预处理,常用的语句如下:
data_transform = {
"train": transforms.Compose([transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),
"val": transforms.Compose([transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}
data_root = os.path.abspath(os.path.join(os.getcwd(), "../.."))
image_path = data_root + "/data_set/flower_data/"
train_dataset = datasets.ImageFolder(root=image_path + "/train",
transform=data_transform["train"])
train_num = len(train_dataset)
flower_list = train_dataset.class_to_idx
-
cla_dict = dict((val, key) for key, val in flower_list.items()) :该语句可以将上面的字典反转,方便后续操作
-
json_str = json.dumps(cla_dict, indent=4) :该语句可将cla_dict字典编码成json格式
-
torch.utils.data.DataLoader 可以将刚加载好的数据集以及所设定的batchsize和shuffle就可以随机的从样本中获取一批一批的数据,其参数num_workers(采用的线程个数)在window下只能设置为0,在linux下可设置为非零值(开启的线程数),以便加快数据的生成速度,从而提高训练的速度。注:shuffle=True是先打乱所有数据,再取batch。
-
net.train()和net.eval()会管理dropout和BN层,在训练过程中调用net.train()就会启用,在验证中调用net.eval()就会关闭dropout和BN
-
在一些Python的工程项目中,我们会看到函数参数中会有冒号,有的函数后面会跟着一个箭头,如下所示:def make_features(cfg: list):,值得注意的是,类型建议符并非强制规定和检查,也就是说即使传入的实际参数与建议参数不符,也不会报错,只是方便共同开发时程序员理解函数的输入与输出。
-
可变参数传入,在列表或者元组前面加一个*可以把list或tuple的元素变成可变参数传入函数中,如这样:nn.Sequential(*layers),当然函数的定义肯定是如下形式:def 函数名(*a):。
-
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple,而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。和可变参数类似,也可以先组装出一个dict,也可以调用简化的写法:**extra。**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的参数,函数参数将获得一个dict,如下所示:
net = vgg(model_name=model_name, num_classes=5, init_weights=True)
def vgg(model_name="vgg16", **kwargs):
model = VGG(make_features(cfg), **kwargs)
return model
-
torch.cat是将两个张量(tensor)按指定维度拼接在一起
-
nn.AdaptiveAvgPool2d是自适应平均池化函数,参数为输出特征矩阵的高和宽,无论输入特征矩阵的高和宽是什么样的大小,都能够指定的高和宽。