卷积在pytorch中有两种方式,一种是torch.nn.Conv2d(),一种是torch.nn.functional.conv2d()。
这两种形式的卷积对于输入的要求是一样的,首先需要输入一个torch.autograd.Variable()的类型,大小是(batch,channel,H,W),其中batch是输入的一批数据的数目,第二个是通道数,一般彩色图是3,灰度图是1,而卷积网络过程中的通道数比较大,会出现几十到几百的通道数,H和w是输入图片的高度和宽度,比如一个batch是32的图片,每张图片是3通道,高和宽是50和100,那么输入的大小就是(32,3,50,100)。
PyTorch中有函数torch.nn.Conv2d来实现卷积。
torch.nn.Conv2d(
in_channels, #输入的通道数
out_channels, #输出的通道数
kernel_size, #卷积核大小
stride=1, #卷积核移动步长
padding=0, #补0的多少
dilation=1, #kernel间距
groups=1, #卷积核个数
bias=True
)
例如:
定义一个能够检测边缘的卷积核
#使用nn.Conv2d
conv1 = nn.Conv2d(1,1,3,bias=False) #定义卷积
sobel_kernel = np.array([-1,-1,-1],[-1,8,-1],[-1,-1,-1],dtype='float32') #定义轮廓检测算子
sobel_kernel = sobel_kernel.reshape((1,1,3,3)) #重新设置成卷积网络需要的格式,输入输出1,长宽3
conv1.weight.data = torch.from_numpy(sobel_kernel) #给卷积核赋值
#将检测边缘的kernel作用到图片上
edge = conv(Variable(img)) #注意图片转化Variabl
举例两种卷积方式:
import numpy as np
import torch
from torch import nn
from torch.autograd import Variable
import torch.nn.functional as F
from PIL import Image
import matplotlib.pyplot as plt
%matplotlib inline
im = Image.open('./cat.png').convert('L') #读入一张灰度图的图片
im = np.array(im,dtype='float32') #将其转换为一个矩阵
#可视化图片
plt.imshow(im.astype('uint8'),cmap='gray')
#将图片转化为pyorch tensor,并适配卷积输入的要求
im = torch.from_numpy(im.reshape((1,1,im.shape[0],im.shape[1])))
下面定义一个算子对其进行轮廓检测
#使用nn.Conv2d
conv1 = n.Conv2d(1,1,3,bias=False) #定义卷积
sobel_kernel = np.array([-1,-1,-1],[-1,8,-1],[-1,-1,-1],dtype='float32') #定义轮廓检测算子
#定义轮廓检测算子
sobel_kernel = sobel_kernel.reshape((1,1,3,3)) #重新设置成卷积网络需要的格式,输入输出1,长宽3
conv1.weight.data = torch.from_numpy(sobel_kernel) #给卷积核赋值
edge1 = conv1(Variable(im)) #作用在图片上
edge1 = edge1.data.squeeze().numpy() #将输出转换为图片的格式
#可视化
plt.imshow(edge1,cmap='gray')
#使用F.conv2d
sobel_kernel = np.array([-1,-1,-1],[-1,8,-1],[-1,-1,-1],dtype='float32') #定义轮廓检测算子
#定义轮廓检测算子
sobel_kernel = sobel_kernel.reshape((1,1,3,3)) #重新设置成卷积网络需要的格式,输入输出1,长宽3
weight = Variable(torch.from_numpy(sobel_kernel)) #给卷积核赋值
edge2 = F.conv2d(Variable(im),weight) #作用在图片上
edge2 = edge2.data.squeeze().numpy() #将输出转化为图片的格式
plt.imshow(edge2,cmap='gray')
可以看出两种形式能达到相同的效果,不同的就是,使用nn.Conv2d()相当于直接定义了一层卷积网络结构,而使用torch.nn.functional.conv2d()相当于定义了一个卷积的操作,所以使用后者需要再额外去定义一个weight,而且这个weight也必须是一个Variable,而使用nn.Conv2d()则会帮我们默认定义一个随机初始化的weight,如果需要修改,那么取出其中的值对其修改,如果不想修改,可以直接使用这个默认初始化的值,非常方便。
PyTorch中有函数torch.nn.MaxPool2d来实现池化。也可以用torch.nn.functional.max_pool2d().
torch.nn.max_pool(
kernel_size, #池化核大小
stride=None, #步长
padding=0, #补0
dialtion=1,
return_indices=False,
ceil_model=False
)
例如:
x相当于下采样。
#使用nn.MaxPool2d
pool = nn.MaxPool2d(2,2)
small_img = pool(Variable(img))
举例:
#使用nn.MaxPool2d
pool1 = nn.MaxPool2d(2,2)
print('before max pool,image shape:{} x {}'.format(im.shape[2],im.shape[3]))
small_im1 = pool1(Variable(im))
small_im1 = small_im1.data.squeeze().numpy()
print('after max pool,image shape:{} x {}'.format(small_im1.shape[0]),small_im1.shape[1]))
图片缩小一半。
plt.imshow(small_im1,cmap='gray')
池化只会减少图片的尺寸,并不影响图片的内容。
#F.max_pool2d
print('before max pool,image shape:{} x {}'.format(im.shape[2],im.shape[3]))
small_im2 = F.max_pool2d(Variable(im),,2,2)
small_im2 = small_im2.data.squeeze().numpy()
print('after max pool,image shape:{} x {}'.format(small_im1.shape[0]),small_im1.shape[1]))
plt.imshow(small_im2,cmap='gray')
数据处理的常见方法:中心化和标准化。
中心化相当于修正数据的中心位置,实现方法非常简单,就是在每个特征维度上减去对应的均值,最后得到0均值的特征。
标准化就是在数据变成0均值之后,为了使得不同的特征维有相同的规模,可以除以标准差近似为一个标准正态分布,也可以依据最大值和最小值将其转化为-1~1之间。
p批标准化,就是对于每一层网络的输出,对其做一个归一化,使其服从标准的正态分布,这样后一层网络的输入也是一个标准的正态分布,所以能够比较好的进行训练,加快收敛速度。
第一行和第二行计算出一个Batch中数据的均值和方差,接着使用第三个公式对Batch中的每个数据点做标准化,参数是为了计算稳定引入的一个小的常数,10-5,最后利用权重修正得到最后的输出结果。
常用方法:
from torchvision import transfors as tfs
new_im = tfs.Resize((100,200))(im) #图像缩放
random_im1 = tfs.RandomCrop(100)(im) #随机截取或者CentreCrop中心截取
h_filp = tfs.RandomHorizontalFlip()(im) #水平翻转,RandomVerticalFlip竖直翻转
rot_im = tfs.RandomRotation(45)(im) #旋转角度
contrast_im = tfs.ColorJitter(contrast=1)(im) #提高对比度0~2,1表示原图,brightness表示亮度 hue=0.5调整色彩
组合增强:
im_aug = tfs.Compose([
tfs.Resize(120),
tfs.RandomHorizontalFlip(),
tfs.RandomCrop(96),
tfs.ColorJitter(brightness=0.5,contrast=0.5,hue=0.5)
])
net = resnet(3,10)
optimizer = torch.optim.SGD(net.parameters(),lr=0.01,weight_decay=1e-4)
optimizer.param_groups[0]['lr'] = 1e-5
过拟合。
torch.nn.Dropout(p=0.5,inplace=False)
optimizer = torch.optim.SGD(net.parameters(),lr=0.01,weight_decay=1e-4) #加入正则项weight_decay
torchvision.datasets.ImageFolder()是torchvision中内置的一个模块,专门处理分类问题。
数据集存放格式:
几类就分成几个文件夹,同类别放同一个文件夹。
同时ImageFolder还支持传入数据预处理的方式:
from torchvision.datasets import ImageFolder
#三个文件夹,每个文件夹一共有3张图作为例子
folder_set = ImageFolder('./example_data/image/')
#查看名称和类别下标的对应
folder_set.class_to_idx
#得到所有的图片名字和标签
folder_set.imgs
#取出其中一个数据
im,label = folder_set[0]
im
label
from torchvision import transforms as tfs
#传入数据预处理方式
data_tfs = tfs.ToTensor()
folder_set = ImageFolder('./example_data/image/',transform=data_tfs)
im,label = folder_set[0]
im
如果数据是txt不是图片,上面的方法就处理不了了。就需要用到torch.utils.data.Dataset().
其实torchvision.datasets.ImageFolder()是torch.utils.data.Dataset()的一个子类。
如果我们希望定义自己的数据读入函数,只需要定义一个子类继承于Dataset,然后重新定义_getitem_()和_len_()这两个函数即可,_getitem_()表示按照下标取出其中一个数据,_len_()表示所有数据的总数。如:
from torch.utils.data import Dataset
#定义一个子类叫custom_dataset,继承Dataset
class custom_dataset(Dataset):
def _init_(self,txt_path,transform=None):
self.transform = transform #传入数据预处理
with open(txt_path,'r') as f:
lines = f.readlines()
self.img_list = [i.split()[0] for i in lines] #得到所有图片名字
self.label_list = [i.split()[1] for i in lines] #label
def _getitem_(self,idx): #根据idx取出其中一个
img = self.img_list[idx]
label = self.label_list[idx]
if self.transform is not None:
img = self.transform(img)
return img,label
def _len_(self):
return len(self.label_list)
tet_dataset = custom_dataset('./example_data/train.txt') #读入txt文件
#取其中一个数据
data,lebel = txt_dataset[0]
print(data)
print(label)
PyTorch提供了一个python的多线程迭代器,能够帮助我们一个batch的读入模型,同时使用多线程速度更快。
DataLoader使用最多的参数:
from torch.utils.data import DataLoader
train_data1 = DataLoader(folder_set,batch_size=2,shuffle=True)
for im,lebel in train_data1:#访问迭代器
print(label)
下面使用自定义的数据读入:
train_data2 = DataLoader(txt_dataset,8,True) #batch size设置为8
im,label = next(iter(train_data2)) #使用这种方式访问迭代器中第一个batch的数据
im
如果希望一个batch输出的label补成相同的长度,短的label用0补充,就需要使用collate_fn来自定义batch的处理方式:
def collate_fn(batch):
batch.sort(key=lambda x:len(x[1]),reverse=True) #将数据集按照label的长度从大到小排序
img,label = zip(*batch) #将数据和label配对取出
#填充
pad_label = []
lens = []
max_len = len(label[0])
for i in range(len(label)):
temp_label = label[i]
temp_label += '0' * (max_len - len(label[i]))
pad_label.append(temp_label)
lens.append(len(label[i]))
pad_label
return img,pad_label,lens #输出label的真实长度