pytorch是一个灵活的深度学习开发平台,对于张量的操作与科学计算库numpy非常相似,使得这个库比较容易上手。
本文主要针对pytorch深度学习的初学者,作为笔记而非详细的教程或API,带有较强的个人化倾向和补充性质,对于一些较深的内容往往点到为止,在广度上也并不做特意追求。同时,与其它介绍pytorch的博客的不同之处在于,本文不仅介绍pytorch本身的用法,还结合了作者使用时的经验,增加了许多“周边”内容,比如python本身的一些功能,一些有用的库等等。
增加这些内容是因为对于经验不深、编程水平有限的读者来说,要想读懂github等平台上的现成项目代码,不仅需要了解与pytorch等深度学习库相关的代码,还要了解背景知识。额外介绍一些与深度学习无关的python附加内容,可以减少读者查阅资料的工作量。
在我自己学习tensorflow与pytorch的时候看了许多教程,有的非常详细。他们会告诉你,什么叫tensor,tensor的运算法则,然后给出一个简单的任务,从库里面调用数据集,等等等等。这样的讲法很细致,但是对于你手头的任务完全没有帮助。因为你所要用到的东西,教程里面可能几乎并没有涉及。这让人非常痛苦。所以首先的,当你使用pytorch完成一个任务的时候,你需要写的代码包括如下几个部分——
数据集文件:这是你的放在文件夹或各种表格里的数据和pytorch之间的桥梁,API接口配置文件:这里存放你的程序会用到的各种常量,比如训练的轮数、batch_size的大小、学习率的初值与衰减的比例,还有各种文件的路径等等训练脚本:这是用来训练的脚本,这里会定义训练函数train、评估函数eval,还会设置可视化用到的writer与保存模型的函数pickle库可以生成pkl文件,并以此作为python读取的数据文件。通过pickle.dump()可以存储,而通过先open再pickle.load()可以加载。dump可以存储多种数据,列表、字典、对象等等,使用时,第一个参数代表需要存储的内容,第二个参数代表pkl文件的路径+名称。读取时,一定按照二进制'rb'去open,之后再load.
import pickle with open(example.pkl, 'rb') as fpkl: info = pickle.load(fpkl)通常,训练好的模型参数使用pkl格式,pt格式或pth格式存储。这时候,使用torch.save()和torch.load()函数,在之后会介绍。
使用np.save()保存,第一个参数是路径+文件名,第二个参数是需要保存的内容。 提取使用np.load() 处理好的数据集往往使用这一格式进行存储。
在matlab中,使用load()函数打开mat文件,参数为文件的路径+名字,使用save()函数保存文件,第一个参数是文件的路径+名字,第二个参数是需要保存的内容。 在python中,使用scipy.io来进行操作。通过loadmat()和savemat()两个函数对需要保存的内容进行操作,通常会采用字典的形式进行存取。
数据的存储有txt,csv,xlsx的格式,此外还有图片,对于图片的处理,本节暂不讨论。
对于处在一个文件夹下,没有被包含在子文件夹当中的脚本,可以像库一样直接import。
此处的mod1代表当前脚本的目录的上级目录之前的脚本,需要在sys.path中增加上级目录,然后导入。
对于同级文件夹之下的脚本,也就是“隔壁”的脚本,需要在隔壁文件夹当中增加__init__.py,这个init文件当中不需要写任何内容。但如果在导入这个文件夹之下的脚本时有什么特殊的操作,也可以写在init当中。
数据的量级是不同的,量纲也是不同的。一个100量级的数据和一个0.01量级的数据,如果不加处理地输入神经网络,会对网络的表现有所影响。为了避免这种影响,通过归一化消除量级、量纲的影响。
标准化更进一步,将数据映射到标准正态分布上,用原始数据减去平均数然后除以标准差。 对于一般的数据,使用numpy自己定义标准化函数,或者像下面的代码一样,使用sklearn库进行计算,此处展示了两种scaler方法: 注意:代码来源https://blog.csdn.net/Yellow_python/article/details/81051144
from sklearn.preprocessing import MinMaxScaler, StandardScaler X = [[0.0, 1], [0.4, 1], [0.6, 9], [0.8, 9]] print(MinMaxScaler().fit(X).transform(X)) print(StandardScaler().fit(X).transform(X))如果是想要对于神经网络中的torch tensor进行标准化,需要用到BatchNorm相关的函数,会在之后进行介绍。
这里暂时不探讨为什么要使用parser以及使用parser的好处,只单纯列举一些用法,如果在阅读代码时遇到,便不至于感到困惑,也可以有样学样用在自己的代码中。
以下代码初始化了一个parser,向其中写入了一个参数,并导出了这个参数。
parser = argparse.ArgumentParser(description='parser的描述') parser.add_argument( '--参数(参数的名字,写在两个短横线后,名称内部不区分下划线与短横线)', default='此处填写参数的默认值', help='此处填写参数的介绍') arg = parser.parse_args() # 使用arg.参数名 的形式调用你预设的参数即可在命令行中,用这种命令更改你预设的参数:
python example.py --参数 想要改成的值特别的,对于布尔型参数,可以使用store_true和store_false的default值,在命令行中,如果用上面的双横线形式“提到”了这个参数,那么store_true将把它设为True,store_false将把它设为False。就像下面的代码一样:
parser = argparse.ArgumentParser(description='parser的描述') parser.add_argument( '--参数(参数的名字,写在两个短横线后,名称内部不区分下划线与短横线)', default='store_true', help='此处填写参数的介绍') arg = parser.parse_args() # 使用arg.参数名 的形式调用你预设的参数即可在命令行中:
python example.py --参数 (对于store_true,只要“提到”,就是True)在上一节中提到了parser,parser的使用将函数所使用的参数集中到了一起,既可以在脚本中直接修改默认值,也可以使用命令行进行覆盖。但是这样仍然不够直观与集中,yaml文件为我们提供了新的可能性,它的特性决定了其非常适合作为一种配置文件。关于yaml的具体写法在这里不再详细介绍,如有需要可以参考https://www.cnblogs.com/wxmdevelop/p/7341292.html yaml文件中,一个无缩进的条目对应parser中的一个arg,或者说是args这个字典中的一个key,而缩进的条目则作为其上边第一个缩进少一级的条目的一个key,也就是这个key对应的value也变成一个字典,作为小字典中的一个key。 以下的代码展示了如何使用文件中的配置覆盖掉parser中的default配置,并指出parser中忘记添加的arg:
with open(yaml文件的路径, 'r') as f: default_arg = yaml.load(f, Loader=yaml.FullLoader) key = vars(p).keys() for k in default_arg.keys(): if k not in key: print('WRONG ARG: {}'.format(k)) assert (k in key) parser.set_defaults(**default_arg)注意:此部分参考https://www.jianshu.com/p/2d9927a70594
其中的MyDataset是自定义的数据集的名字,可以根据需要自己定义。
在初始化函数中,会将数据集的路径等信息定义为对象的属性,同时也会定义其他之后将要用到的各种属性。需要注意的是,训练集和验证集都会从Dataset类实例化,而其名字的不同是在实例化的时候决定的,而其内容的不同是在init的时候决定的,因此,是通过init时,使用的目录不同或使用的索引文件不同等方式,达到使用同一个类构建训练集和测试集这两个内容不同的数据集的。
这个函数是最好写的一个函数,返回一个整数,是数据集的长度。数据集中有多少条数据,就返回几。
这个函数接收一个index参数,表示取出数据集中的某个序号对应的那一条数据。不同的数据有不同的组织形式,因此在这个函数中需要体现如何拿出一个数据并整理成特定的形式,通常是某个特定长度的numpy array。
实例化Dataset与实例化一个类没有区别,填写好__init__()中需要传进去的参数就可以。
example_dataset = MyDataset(各种参数)pytorch提供了transforms库对图片进行简单的批处理。此处可以参考https://www.jianshu.com/p/a6996ed4eb37。 而Compose方法可以将分步的操作整合到一起。将每一步操作的方法写成列表,传入Compose当中,就可以得到一个总的Compose,在数据集实例化的时候,作为一个参数init数据集。
from torchvision.transforms import transforms example_dataset = MyDataset(各种参数, transforms=transforms.Compose([操作1, 操作2]))一个网络由许多网络层组成,每一个网络层对象都需要各自的参数初始化。 在class的init函数中,完成初始化的工作。另一方面,在使用网络层的时候,有输入和输出,在forward函数中,规定数据的流向。 因此,对于一个网络层,我们要知道两方面的内容:
它的初始化参数有哪些?值是什么?它的输入数据是什么格式,输出数据又是什么格式?先看一段代码,这段代码定义了一个pytorch网络,我们会在之后逐渐介绍如何定义一个网络。 注意:代码参考https://www.jianshu.com/p/f0a78e983b9a
from torch import nn import torch.nn.functional as F class Net(nn.Module): # 继承父类 def __init__(self): super(Net, self).__init__() # 首先init self.fc1 = nn.Linear(20, 120) # 接着定义网络层 self.fc2 = nn.Linear(120, 64) self.fc3 = nn.Linear(64, 1) self.drop = nn.Dropout(0.3) def forward(self, x): # forward中定义batch数据的流向 x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.drop(x) x = self.fc3(x) return x if __name__ == '__main__': net = Net() print(net)注意,此处参考https://www.cnblogs.com/yjphhw/p/9773333.html