赞
踩
1.将繁琐且重复的事情做成高内聚的黑箱
2.将问题模块化:处理数据,构建网络,损失函数,优化函数,模型保存。五大模块
自己从0构建
1).文件夹model或者net:CIMIC-GAN.py(整体网络结构), common.py(部分小网络结构),还有一些和网络相关的配置文件yaml文件)。
2).文件夹data:存放数据集或者和数据集信息有关的配置文件(yaml文件),或者有数据预处理的内容。
3).文件夹utils:一些需要调用的方法,各种小工具。
4).文件夹script:写一下必要的小脚本,外部调用的小脚本。
5).文件夹runs:训练得到的结果,可以有权重文件夹weight。
6).文件夹methods:loss.py,evaluate.py数据后处理,画个图啥的。
7)train.py(训练入口)
8)test.py或者detect.py(测试入口)
3.把常用的东西定义出来,方便求解简单问题。
首先千万不要一上来就学习一大堆的理论知识,因为会忘记,而且没实际用过的话记忆也不会深刻,只有自己亲自动手debug了之后才会非常深刻。举个例子比如添加参数组,尤其在一些工程类代码当中数量太多了,不同的参数组合会组合成不同的字典,yolo就会根据不同的参数组设置不同的优化策略。
吃透源码,和学会吃透任何源码的方法,主要指debug调试。
前提已经吃透代码
在原先的基础上修改成自己的东西:重新构建属于自己的项目结构,例如把功能性强的函数封装到一起。模仿开源代码的格式进行自己代码的构建。
Dataset是pytorch官方提供的类,我们只有先继承这个类才能官方的基础上重写。
class 某某某Dataset(Dataset):
非常常见的for i,data in enumerate(list_train):
enumerate函数的功能就是如果传进来的对象是一个列表,那么它就会对列表内的每一个元素进行一个标序号,从0开始标序号,本质上相当于一个双层循环。
训练集验证集测试集三种数据集的区别,首先train,test,valid内容上不能有完全的重合,也就是说必须是不一样的。验证集出现的目的,就是如果没有验证集的话,测试集只能等待训练集训练完了才能发挥作用,所以验证集的作用就是和训练集一样切割开来,每训练一个epoch就把验证集对应的部分传入网络参数当中测试一下,其实目的就是为了边训练就能便看到结果。所以再训练过程中是同时使用了训练集和验证集。
只要出现继承就一定会出现重写。
一般情况下如果碰到了一个函数不认识,千万不要百度,通过debug可以假如说可以知道输入内容,和等号左边输出得出来的是个什么东西,就能猜出官方定义的函数是个什么作用。如果想要深入了解官方函数的话,就只能用一个操作比较困难的remote debug了。
因为是大多数官方定义的类实例化出来的对象大多数都比较复杂,一旦实例化出一个对象,那么一定会执行被实例化类中的__int__函数。所以这个被实例化出来的对象在debug窗口中一定有几个实例化属性对应被实例化类的实例化方法__int__所初始化的实例化属性。
@staticmethod,类方法,不会出现self,也叫做静态方法。
对于代码中经常出现的for循环来讲,重要的不是in后面的函数,而是for后面的变量究竟是什么,因为for后面的变量一定是in后面的函数的返回值,debug的精髓就是通过返回值猜出函数的意思。
debug变量模块中只要出现了单引号就以一定是字符串,不管是在哪里出现。
当debug步过一句实例化对象时,一定会执行被实例化类里的方法,所以可以提前在那个类里的某个地方打好断点方便我们分析。
图片的大小和维度可能不是一样的,但是传到数据网络的tensor矩阵的shape必须是一样的。
循环的嵌套,比如说一条语句本身就在循环里,而这条语句每次执行一次都会调用其他类中的__xx__方法好几十次。
断点的作用甚至还可以判断验证有没有一个函数相互调,跳出去又跳回来的时候有没有经过其他地方,如果真的经过断点处的话会直接跳到断点处。断点加上条件也可以设置成条件断点。
长摁住ctrl把鼠标放在函数上就会显示一些需要传入的参数,还可以点进去。
pytorch提供了三种容器:
nn.Sequential相当于把初始化网络和前向传播合在一起了,顺序简化网络,相当于不用写forward函数了。但是为什么还是会经常看到forward函数呢?那是因为x.view这种类型的操作不在nn模块下,也不在F模块下。但是这种情况下在层的命名上会出现很多问题,所以要用字典的方式,在nn.Sequential里面加一个OrderDict,在每一层前面加上有序的命名字典,其实就是一个能起名字一个不能起名字,易于修改网络参数。此容器用于线性网络比较好。
ModuleList:不是顺序的,所以前向传播得自己写,也就是相当于通过循环构建网络,用于大量重复网络的构建。
ModuleDict:也就是通过参数定义的方式来使用不同的网络层,常用于可选择的网络层。
torch.nn:
nn.Parameter 表示可学习的参数如weight,bias
nn.Modeule:所有网络层的基类,管理网络属性
nn,functional:函数具体实现,如卷积池化激活函数等
nn.init;参数 初始化方法。
损失函数的重写可以有但不多见,优化函数的重写有但是更少。
一段代码打断点的顺序就是前面的数据和网络的定义先不打断点,直接到最后打断点,然后数据肯定会传到前面再再前面打断点。
程序中有时候会出现一些一个下划线这种返回值,_这种返回值一般代表没有什么乱用的返回值。
还有一些东西是通过debug没法看到的,比如有一些代码会是这种情况:
变量=X.方法1()方法2()方法3()方法4()方法5()
这种就有点恶心了,套了十万个方法,控制台中有一个python小按钮:
然后用print语句从内到外一句一句打印,一层一层往外扩,打印到方法5的时候必然也会出现方法1234.打印出来的结果再结合Debug模块中的变量列表就能知道是什么意思了。假如这一句报错了的话,这种方法还能一层一层排查是哪里出现了错误。
还有时候debug看不出来是什么,比如for循环中in后面一大坨不知道是什么玩意,就先先把那一坨单独拿出来做一行代码,这样就能看出来了。
不管是什么可执行脚本肯定都有超参数的构建和解析:这些超参数肯定最后被存到了opt了,所以调用这些超参数的时候基本上都是opt.超参数。
自己写代码的时候也是需要不停的debug的。
尽量不要早类的外面定义变量。
高维表示的意思是变得更小了,信息更浓缩了的意思。比如[256,1984]会变成[256,128],这样的变化叫做高维表示。
1.如果网络在gpu上,那么模型和数据也得放在gpu上。如果把网络和数据同时放在gpu上,那么输出的结果仍然在gpu上。
但为什么有时候label也要放在gpu上呢,因为有的代码会把inputs和label做一个比较判断,一个放在gpu上了,那么另外一个一必须要放在 gpu上。
查看动手学深度学习的模型和参数的保存和加载。
首先万变不离其宗,处理数据,构建网络,损失函数,优化函数,模型保存这五大模块是根本。因为这种代码一般是一个团队在维护,但凭个人就想把整个团队维护的东西都吃透显然不现实,就算可以做到也没有什么意义效率也不是特别高。所以学会忽略。
忽略的意思就是学会抓主干,像什么tensorborad这种东西能跑通能用就可以了,不必深究,但是主干必须得好好debug一下。pycharm就提供了很好的把代码折起来的功能。有些函数具体是怎么实现的的咱们也不用管,只要debug前后输出和输入知道是干什么的就可以了。但是有一些函数关乎完成自己的课题就必须得改。
debug途中发现有些语句就没有执行,直接忽略,节省时间。
在开源代码把这五个模块都找到
第一步:重写__init__和__getitem__这两个函数。
第二步:把dataset传到dataloader里面。
这里针对pytorch主要有两个API
处理多个样本而言,先通过dataset得到单个训练样本之后,再通过dataloader将数据变成mini-bartch的形式,将多个样本组合成一个mini-batch,还能当训练一定epoch之后,将数据打乱,甚至还能将数据固定的保存再GPU中。
处理单个训练样本,从磁盘读取数据集,包括标签,变形,预处理,最终把数据集映射变成x和y。
torch内置的一些数据集可以直接通过torchvision导入,源码见官方文档Tutoriais。
详细见官方文档Docs
map-style datasets就是指从磁盘里获取的数据集类型
还有一种实时流式的,iterable-style datasets.
详情见pytorch官方源码
大多数情况数据集并没有内置,我们需要自己构建一个Dataset类,基于自己构建的dataset放入dataloader中,参与神经网络的训练。
我们自定义的 Dataset class类必须要继承一个类Dataset,且必须要实现三个函数,s: init, len, and getitem。
init函数:传入一些磁盘路径啥的
len函数:直接返回数据集大小
getitem函数:核心,通过idx返回样本,主要是将数据加载到内存中,[idx, 0],通过第idx行,第0列就可以得到每一个数据样本文件的名称,再通过一些函数将数据加载到内存中。加载到内存后,会进行一些操作,可能对数据会进行一些处理比如说归一化,预处理,比如替换过滤文本字符,图形处理。就通过transform函数就可以进行一些预处理。[idx, 1],通过第idx行,第1列存储的是label,也能处理label。最后这个函数会返回一个元组或者字典。总之这个模块就是定义路径,数据预处理,外面自己肯定也要定义一个transform()函数和一个target_transform()函数,通过这两个函数分别对特征和标签进行一个预处理。
由于数据存在噪声,为了使抗噪性增强使用mini-batch,每一个训练周期完成以后还能对数据打乱reshuffle也可以防止过拟合,还能调用多进程multiprocessing使数据的读取不会影响GPU模型训练的延迟,用多进程加载数据。
from torch.utils.data import DataLoader
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
更多源码见官方文档也啥也没有,直接看dataloader源码把,得先传入dataset实例化对象,同时还需要传入一些其他参数:
sampler,batch_sampler:如何采样,可以默认也可以自己实现,也可以自定义sampler方法
num_works:cpu核,多进程
pin_memory:不需要重复保存,可以将tensor保存在gpu中,其实用不用都无所谓。
drop_last:当总样本数量不是batch_size的整数倍的时候,丢掉最后一个小批次。
collate_fn:输入是一个batch,输出也是一个batch,就是对每一个batch进行一些操作例如pad,甚至更加复杂的操作。
把dataset塞到dataloader之后就可以进行遍历,每一次遍历都会得到一个batch_size的特征和标签,当设置shuffle等于true之后,遍历完所有batch,从头开始就会不一样了。
详细见官方文档
next(iter(train_dataloader)),先将train_dataloader变成一个迭代器,再用next()函数依次生成就可以了。
1.有时候代码文件名不够直观。代码冗余重复严重,需要大大的修改。优化代码内容。
2.非python文件千万不要加注释,尤其是一些配置文件
3.tqdm仅仅是训练时一个进度条的效果。
4.batch_size=16,并不是说拿出来一个列表,列表里面有16个元素,最后的输出是一个东西,这16个还要进行一个合并的操作。
5.tqdm函数左右是相等的,tqdm就是有一个进度条的效果。
6. [index][0]是用来去中括号的。
7. 在终端打出python就相当于控制台,import XXX as XXX,就可以开始测试了。
8. range(闭区间,开区间),一般后面得加1。
9. 卷积神经网络最重要的两个参数就是输入通道数和输出通道数。因为池化层对通道数是没有变化的,但是卷积层一定是有变化的。当网络结构出现分支的时候,就需要把网络分支的前几层的输出通道数都存储起来,把输出通道数都存在一个列表里面,每当往一个新的卷积层传参数的时候,输入通道数就是这个列表[-1],这个列表的最后一个元素。
10.别一看到list就以为是列表,也有可能是range类型,从几到几。
11.见到负数就要下意识的想到是从后往前的索引。
12.列表推导式:
if后面的是判断条件,如果满足,就执行前面的语句,如果不满足就执行else后面的语句。
/ 用来换行
不管遇到什么问题到要去官网的api搜一下
使用函数.masked_fill填充的时候为了不出现NAN,用-le9替换掉-np.inf
打印出来的tensor中有几个中括号就是几维的
去pytorch的官方文档中看一下雅可比矩阵:torch.autograd.functional.jacobian()
torch.bmm()矩阵的乘法
由列表推导式生成的张量列表一般要配合torch.cat()函数使用,torch.cat对列表使用,作用就是根据内层的中括号合并tensor,并将多个tensor合并成一个tensor。
torch.unsqueeze()扩维就是用来为每一个tensor增加中括号的作用
torch.ones()还有torch.zeros()括号里面都跟的是形状
看一个tensor有几维,看中括号的层数就知道,几层中括号就是几维,从0开始数,转置的时候就知道了
在实例化对象后面直接加括号,就相当于调用该实例的forward方法。
几维矩阵是指,这个矩阵的形状有几个数字组成,而不是数字的值是多少。
构建出来的mask矩阵中的元素要么为一要么为负无穷,且维度和原矩阵相同,因为与1相乘维度不变,与-inf相乘经过softmax会变成0。
dilation空洞
卷积虽然是下采样,但是通道数会越来越多。
一定要好好理解kernel_size的含义,卷积和的通道数x卷积核长x卷积核宽。
complex指的是torch当中复数的数据类型,
一般多继承会使用super这个函数。
列表推导式转字典推导式,推导式内部还能再加判断。
torch.set_default_tensor_type(torch.DoubleTensor)
torch.zeros(2,3)
torch.ones(5)
torch.zeros_like(input)
torch.ones_like(input)
torch.arange(1, 4) (左闭右开)用于遍历生成连续索引,如果之传入一个参数默认传入参数end
torch.range(1, 4, 0.5) (左闭右闭)这两组api常用于for循环的in后面
torch.linspace(3, 10, steps=5) torch.logspace(start=0.1, end=1.0, steps=1) 创建线性的和创建对数的
torch.eye(3) 二维对角矩阵
torch.full((2, 3), 3.141592) 传入一个已有张量并用数据填充
torch.cat(tensors, dim=0, *, out=None) → Tensor 非常常用是需要传入张量组成的列表,除了要cat的张量其他维度必须一样
torch.stack() cat是续接,而stack是叠加,相当于使用cat维度不变但是对应维度长度会变,但是使用stack会直接多出1维
torch.chunk() 均匀分割
torch.split() 指定大小分割
dsplit hsplit dstack hstack 纵向分割,横向分割 深度堆叠,广度堆叠
torch.gather(A张量,dim=?,B张量) 看了老半天,就是将B的索引对应dim维度上用B该索引对应的位置的元素 替换掉,生成一套和B形状一模一样的索引,对应这些索引再用A的元素填充。
torch.reshape((之前的形状),(之后的形状)) 反正不管怎么reshape,乘起来肯定都是一样的。
torch.Tensor.scatter_ 有autoplace版本和inplace版inplace在pytorch中是加下划线和gather很像
torch.squeeze() 删除掉长度为一维度
torch.unsqueeze() 新增维度,就是一个加中括号的
torch.take(张量A,张量B) 将张量B的元素本身视为索引,通过索引取到张量A对应的元素并生成新的张量,并碾成一维
torch.tile(张量A,元组或列表(,)) 很重要,复制拷贝,传入元组中数字的个数一定要和张量A的维数是一致的,数字是几,就相当于于该数字对应的维度复制即份,数字是1不做改变,例:[[2,2]],(1,2)—>复制出来新张量对应维度的数字翻倍[[2,4]]
torch.transpose() 常用了都,老生长谈了
torch.unbind() 返回给定维度降维后组成的元组,指降给定维度这一维
torch.where(条件,x,y) 很常用,首先前提是是x张量和y张量的形状要相同,满足条件的位置用x的元素,否则用y的元素
torch.manuml_seed(seed) 为生成随机数固定一个种子,只要种子固定,每次运行代码都会从同样的分布中采样,主要是为了模型或者超参数的可复现,主要是为了模型参数的随机初始化。
torch.bernoulli(概率) 返回伯努利采样的结果,只返回0或者1
torch.normal(mean.std) 高斯分布
torch.unsqueeze()
方法一 torch.Tensor(列表).to(torch.int32)
方法二 torch.cat(列表)
[torch.arange(max(tgt_len)) for _ in src_len]
torch.randint(2,5,(batch_size,))
torch.randn(5)
.to(torch.bool)
torch.rand() 均匀分布,0为闭区间,1为开区间
torch.randn()标准正态分布
torch.randint(最小,最大,(形状)) 从任意整数区间随机得到整数
torch.randperm(n)构建随机组合,只需要传入一个上界n,就会返回0到n-1这些数随机的一个shuffle,然后返回一个随机shuffle的列表。
to()括号里写要转换的类型
to(torch.int32)
1.数组切片df[:,0::2]从0开始步长为2的偶数列,数组切片df[:,1::2]从0开始步长为2的奇数列,这里的数组也可以是tensor里有双层中括号
2.arange函数有步长
3.for循环也有步长
1.生成特定形状的且经过padding的一个mini-batch的随机向量:
定好的序列长度=torch.randint(2,5,(mini-batch的形状))
或者直接把形状不随机生成了,直接把实际序列形状定死
定好的序列长度=torch.Tensor([2,4]).to(torch.int32)
torch.cat([torch.unsqueeze(F.pad(torch.randint(1,词表长度,(L,)),(0,定好的序列长度-L)),0) for L in src_len])
2.平均生成序列
torch.arange(填空一个数两个数三个数都可以).reshape((-1,1))转换成一列
.reshape还有加一层中括号的作用,之前讲到的torch.unsqueeze()也有增加中括号的作用
.reshape((-1,1))转换成一列,这里几层括号没有任何区别
.reshape((1,-1))转换成一行
3.掩码矩阵的构造方式
1)先确定掩码矩阵的形状和维度
2)通过列表推导式求出有效矩阵,
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。