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是自适应平均池化函数,参数为输出特征矩阵的高和宽,无论输入特征矩阵的高和宽是什么样的大小,都能够指定的高和宽。