常用Tensor操作:
通过tensor.view
方法可以调整tensor的形状,但必须保证调整前后元素总数一致。view
不会修改自身的数据,返回的新tensor与源tensor共享内存,也即更改其中的一个,另外一个也会跟着改变。在实际应用中可能经常需要添加或减少某一维度,这时候squeeze
和unsqueeze
两个函数就派上用场了。
resize
是另一种可用来调整size
的方法,但与view
不同,它可以修改tensor的大小。如果新大小超过了原大小,会自动分配新的内存空间,而如果新大小小于原大小,则之前的数据依旧会被保存。
# None类似于np.newaxis, 为a新增了一个轴
# 等价于a.view(1, a.shape[0], a.shape[1])
a[None].shape
与gather
相对应的逆操作是scatter_
,gather
把数据从input中按index取出,而scatter_
是把取出的数据再放回去。注意scatter_
函数是inplace操作。
假设输入的形状是(m, n, k)
- 如果指定dim=0,输出的形状就是(1, n, k)或者(n, k)
- 如果指定dim=1,输出的形状就是(m, 1, k)或者(m, k)
- 如果指定dim=2,输出的形状就是(m, n, 1)或者(m, n)
如按某个维度sum,则该维度为一个数。
dot/cross | 内积/外积 |
Tensor和Numpy数组之间具有很高的相似性,彼此之间的互操作也非常简单高效。需要注意的是,Numpy和Tensor共享内存。由于Numpy历史悠久,支持丰富的操作,所以当遇到Tensor不支持的操作时,可先转成Numpy数组,处理后再转回tensor,其转换开销很小。当numpy的数据类型和Tensor的类型不一样的时候,数据会被复制,不会共享内存。如float32和float64数据类型不一样。
Module
中的可学习参数可以通过named_parameters()
或者parameters()
返回迭代器,前者会给每个parameter都附上名字,使其更具有辨识度。打印参数:layer为实例化的一个网络。
for name, parameter in layer.named_parameters():
print(name, parameter) # w and b
为方便用户使用,PyTorch实现了神经网络中绝大多数的layer,这些layer都继承于nn.Module,封装了可学习参数parameter
,并实现了forward函数,且很多都专门针对GPU运算进行了CuDNN优化,其速度和性能都十分优异。本书不准备对nn.Module中的所有层进行详细介绍,具体内容读者可参照官方文档1或在IPython/Jupyter中使用nn.layer?来查看。阅读文档时应主要关注以下几点:
- 构造函数的参数,如nn.Linear(in_features, out_features, bias),需关注这三个参数的作用。
- 属性、可学习参数和子module。如nn.Linear中有
weight
和bias
两个可学习参数,不包含子module。 - 输入输出的形状,如nn.linear的输入形状是(N, input_features),输出为(N,output_features),N是batch_size。
为方便用户使用,PyTorch实现了神经网络中绝大多数的layer,这些layer都继承于nn.Module,封装了可学习参数parameter
,并实现了forward函数,且很多都专门针对GPU运算进行了CuDNN优化,其速度和性能都十分优异。本书不准备对nn.Module中的所有层进行详细介绍,具体内容读者可参照官方文档1或在IPython/Jupyter中使用nn.layer?来查看。阅读文档时应主要关注以下几点:
- 构造函数的参数,如nn.Linear(in_features, out_features, bias),需关注这三个参数的作用。
- 属性、可学习参数和子module。如nn.Linear中有
weight
和bias
两个可学习参数,不包含子module。 - 输入输出的形状,如nn.linear的输入形状是(N, input_features),输出为(N,output_features),N是batch_size。
这些自定义layer对输入形状都有假设:输入的不是单个数据,而是一个batch。输入只有一个数据,则必须调用tensor.unsqueeze(0)
或 tensor[None]
将数据伪装成batch_size=1的batch。
优化器torch.optim需重点掌握:
- 优化方法的基本使用方法
- 如何对模型的不同部分设置不同的学习率
- 如何调整学习率
应该什么时候使用nn.Module,什么时候使用nn.functional呢?答案很简单,如果模型有可学习的参数,最好用nn.Module,否则既可以使用nn.functional也可以使用nn.Module,二者在性能上没有太大差异,具体的使用取决于个人的喜好。如激活函数(ReLU、sigmoid、tanh),池化(MaxPool)等层由于没有可学习参数,则可以使用对应的functional函数代替,而对于卷积、全连接等具有可学习参数的网络建议使用nn.Module。
数据处理:数据加载可通过自定义的数据集对象。数据集对象被抽象为Dataset
类,实现自定义的数据集需要继承Dataset,并实现两个Python魔法方法:
__getitem__
:返回一条数据,或一个样本。obj[index]
等价于obj.__getitem__(index)
__len__
:返回样本的数量。len(obj)
等价于obj.__len__()
torchvision主要包含三部分:
- models:提供深度学习中各种经典网络的网络结构以及预训练好的模型,包括
AlexNet
、VGG系列、ResNet系列、Inception系列等。 - datasets: 提供常用的数据集加载,设计上都是继承
torhc.utils.data.Dataset
,主要包括MNIST
、CIFAR10/100
、ImageNet
、COCO
等。 - transforms:提供常用的数据预处理操作,主要包括对Tensor以及PIL Image对象的操作。
CUDA:除了调用对象的.cuda
方法之外,还可以使用torch.cuda.device
,来指定默认使用哪一块GPU,或使用torch.set_default_tensor_type
使程序默认使用GPU,不需要手动调用cuda。
要实现模型单机多卡十分容易,直接使用 new_module = nn.DataParallel(module, device_ids)
, 默认会把模型分布到所有的卡上。多卡并行的机制如下:
- 将模型(module)复制到每一张卡上
- 将形状为(N,C,H,W)的输入均等分为 n份(假设有n张卡),每一份形状是(N/n, C,H,W),然后在每张卡前向传播,反向传播,梯度求平均。要求batch-size 大于等于卡的个数(N>=n)
在绝大多数情况下,new_module的用法和module一致,除了极其特殊的情况下(RNN中的PackedSequence)。另外想要获取原始的单卡模型,需要通过new_module.module
访问。
本质上上述这些信息最终都是保存成Tensor。Tensor的保存和加载十分的简单,使用t.save和t.load即可完成相应的功能。在save/load时可指定使用的pickle模块,在load时还可将GPU tensor映射到CPU或其它GPU上。
我们可以通过t.save(obj, file_name)
等方法保存任意可序列化的对象,然后通过obj = t.load(file_name)
方法加载保存的数据。对于Module和Optimizer对象,这里建议保存对应的state_dict
,而不是直接保存整个Module/Optimizer对象。Optimizer对象保存的主要是参数,以及动量信息,通过加载之前的动量信息,能够有效地减少模型震荡。