Tensors 类似于 NumPy 的 ndarrays ,同时 Tensors 可以使用 GPU 进行计算。 任何使张量会发生变化的操作都有一个前缀 ‘_’。
x = torch.rand(5, 3) y = torch.rand(5, 3) print(torch.add(x, y)) y.add_(x) print(y)输出:
tensor([[-0.1859, 1.3970, 0.5236], [ 2.3854, 0.0707, 2.1970], [-0.3587, 1.2359, 1.8951], [-0.1189, -0.1376, 0.4647], [-1.8968, 2.0164, 0.1092]]) tensor([[-0.1859, 1.3970, 0.5236], [ 2.3854, 0.0707, 2.1970], [-0.3587, 1.2359, 1.8951], [-0.1189, -0.1376, 0.4647], [-1.8968, 2.0164, 0.1092]])可以看到,在加上“_"后,张量y发生改变。
************ 1.将张量属性 .requires_grad 设置为 True,则会开始跟踪针对 tensor 的所有操作。 2.调用 Tensor.backward()进行反向传播求导。 3.反向传播前,需要将梯度清零。(pytorch会进行梯度累加) 4.pytorch是使用动态图,tensorflow是静态图。 5.计算一次梯度,手动更新一次网络. ************ 如果将张量属性 .requires_grad 设置为 True,则会开始跟踪针对 tensor 的所有操作。完成计算后,您可以调用 .backward() 来自动计算所有梯度。该张量的梯度将累积到 .grad 属性中。 一个类对于 autograd 实现非常重要那就是 Function。Tensor 和 Function 互相连接并构建一个非循环图,它保存整个完整的计算过程的历史信息。每个张量都有一个 .grad_fn 属性保存着创建了张量的 Function 的引用(如果用户自己创建张量,则g rad_fn 是 None )。 创建一个张量,设置 requires_grad=True 来跟踪与它相关的计算。
为什么设置require_grad=true? 用于说明当前量是否需要在计算中保留对应的梯度信息.Tensor变量的requires_grad的属性默认为False,若一个节点requires_grad被设置为True,那么所有依赖它的节点的requires_grad都为True。 以线性回归为例,容易知道权重w和偏差b为需要训练的对象,为了得到最合适的参数值,我们需要设置一个相关的损失函数,根据梯度回传的思路进行训练。 Tensor变量的requires_grad的属性默认为False,若一个节点requires_grad被设置为True,那么所有依赖它的节点的requires_grad都为True。
x = torch.ones(2, 2, requires_grad=True) print(x) y = x + 2 print(y) z = y * y * 3 out = z.mean() print(y.grad_fn) print(z, out) out.backward() print(x.grad) tensor([[1., 1.], [1., 1.]], requires_grad=True) tensor([[3., 3.], [3., 3.]], grad_fn=<AddBackward0>) <AddBackward0 object at 0x7fe1db427470> tensor([[27., 27.], [27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>) tensor([[4.5000, 4.5000], [4.5000, 4.5000]])如果你想计算导数,你可以调用 Tensor.backward()。如果 Tensor 是标量(即它包含一个元素数据),则不需要指定任何参数backward(),但是如果它有更多元素,则需要指定一个gradient 参数来指定张量的形状。 通过将代码包裹在 with torch.no_grad(),来停止对从跟踪历史中 的 .requires_grad=True 的张量自动求导。
x = torch.randn(3, requires_grad=True) y = x * 2 while y.data.norm() < 1000: y = y * 2 print(y) v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float) y.backward(v) print(x.grad) print(x.requires_grad) print((x ** 2).requires_grad) with torch.no_grad(): print((x ** 2).requires_grad)仿真如下:
tensor([ -444.6791, 762.9810, -1690.0941], grad_fn=<MulBackward0>) tensor([1.0240e+02, 1.0240e+03, 1.0240e-01]) True True False原因在于在PyTorch中,计算得到的梯度值会进行累加。 传统的训练函数,一个batch是这么训练的:
for i,(images,target) in enumerate(train_loader): # 1. input output images = images.cuda(non_blocking=True) target = torch.from_numpy(np.array(target)).float().cuda(non_blocking=True) outputs = model(images) loss = criterion(outputs,target) # 2. backward optimizer.zero_grad() # reset gradient loss.backward() optimizer.step()1.获取loss:输入图像和标签,通过infer计算得到预测值,计算损失函数; 2.optimizer.zero_grad() 清空过往梯度;loss.backward() 反向传播,计算当前梯度; 3.optimizer.step() 根据梯度更新网络参数 简单的说就是进来一个batch的数据,计算一次梯度,更新一次网络.
for i,(images,target) in enumerate(train_loader): # 1. input output images = images.cuda(non_blocking=True) target = torch.from_numpy(np.array(target)).float().cuda(non_blocking=True) outputs = model(images) loss = criterion(outputs,target) # 2.1 loss regularization loss = loss/accumulation_steps # 2.2 back propagation loss.backward() # 3. update parameters of net if((i+1)%accumulation_steps)==0: # optimizer the net optimizer.step() # update parameters of net optimizer.zero_grad() # reset gradient1.获取loss:输入图像和标签,通过infer计算得到预测值,计算损失函数; 2.loss.backward() 反向传播,计算当前梯度; 3.多次循环步骤1-2,不清空梯度,使梯度累加在已有梯度上; 4.梯度累加了一定次数后,先optimizer.step() 根据累计的梯度更新网络参数,然后optimizer.zero_grad() 清空过往梯度,为下一波梯度累加做准备;
总结来说:梯度累加就是,每次获取1个batch的数据,计算1次梯度,梯度不清空,不断累加,累加一定次数后,根据累加的梯度更新网络参数,然后清空梯度,进行下一次循环。
一定条件下,batchsize越大训练效果越好,梯度累加则实现了batchsize的变相扩大,如果accumulation_steps为8,则batchsize ‘变相’ 扩大了8倍,是解决显存受限的一个不错的trick,使用时需要注意,学习率也要适当放大。
参考链接:https://www.zhihu.com/question/303070254/answer/573037166 https://blog.csdn.net/byron123456sfsfsfa/article/details/92210253 http://www.pytorch123.com/SecondSection/neural_networks/
魔术方法(magic method)是特殊方法的昵称,在Python中的特殊方法,一般都是使用诸如__xxx__(前后两个下划线,中间是方法名)的命名方式.
在Python中,要拿到一个集合的某个元素,可以使用对应的引索进行取值,比如list[key],这背后利用的是__getitem__方法,为了拿到my_list[key]的值,解释器实际上会调用my_list.getitem(key)。
Python也是面向对象编程语言,对于求一个集合的长度使用len(list)而不是list.len()会感觉有点奇怪,背后就是特殊方法的作用,调用了list.len()方法,和面向对象完全符合,而且还起到简化的作用,变得更加通俗易懂。
call_: 使得类对象具有类似函数的功能。 例如:
class A(): def __call__(self, param): print('i can called like a function') print('掺入参数的类型是:', type(param)) a = A() a('i')仿真
i can called like a function 掺入参数的类型是: <class ‘str’>此时该类对象表现的像一个函数,可以进行对它传参数。此时类对象调用call方法。 那当然也可以在__call__里调用其他的函数啊,在__call__函数中调用forward函数,并且返回调用的结果
class A(): def __init__(self, init_age): super().__init__() print('我年龄是:',init_age) self.age = init_age def __call__(self, added_age): res = self.forward(added_age) return res def forward(self, input_): print('forward 函数被调用了') return input_ + self.age print('对象初始化。。。。') a = A(10) input_param = a(2) print("我现在的年龄是:", input_param)输出:
对象初始化。。。。 我年龄是: 10 forward 函数被调用了 我现在的年龄是: 12pytorch主要也是按照__call__()和__init__(),forward()三个函数实现网络层之间的架构的 参考链接:https://blog.csdn.net/dss_dssssd/article/details/83750838
__init__方法负责对象的初始化,系统执行该方法前,其实该对象已经存在了,要不然初始化什么东西呢?先看例子:
# class A(object): python2 必须显示地继承object class A: def __init__(self): print("__init__ ") super(A, self).__init__() def __new__(cls): print("__new__ ") return super(A, cls).__new__(cls) #返回对象实例 def __call__(self): # 可以定义任意参数 print('__call__ ') A()输出:
__new__ __init__从输出结果来看, __new__方法先被调用,返回一个实例对象,接着 init 被调用。 new 方法的返回值就是类的实例对象,这个实例对象会传递给 init 方法中定义的 self 参数,以便实例对象可以被正确地初始化。
如果 new 方法不返回值(或者说返回 None)那么 init 将不会得到调用,这个也说得通,因为实例对象都没创建出来,调用 init 也没什么意义,此外,Python 还规定,init 只能返回 None 值,否则报错,这个留给大家去试。 参考链接:https://foofish.net/magic-method.html
Python的特殊方法__getitem_() 主要作用是可以让对象实现迭代功能。 定义一个Sentence类,通过索引提取单词。
import re RE_WORD = re.compile(r'\w+') class Sentence: def __init__(self, text): self.text = text self.words = RE_WORD.findall(text) # re.findall函数返回一个字符串列表,里面的元素是正则表达式的全部非重叠匹配 def __getitem__(self, index): return self.words[index]输出:
>>> s = Sentence('The time has come') >>> for word in s: print(word) The time has come >>> s[0] 'The' >>> s[1] 'time'通过测试发现,示例 s 可以正常迭代。但是没有定义 getitem() 测试则会报错, TypeError: ‘***’ object is not iterable 。 参考链接:https://blog.csdn.net/ZT7524/article/details/96479481?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param
1 __call__使得类对象具有类似函数的功能。 2 _new_方法先被调用,创建并返回一个实例对象。 3 _init_方法负责对象的初始化,系统执行该方法前,其实该对象已经存在了。 4 Python的特殊方法__getitem() 主要作用是可以让对象实现迭代功能。 5 _len_方法用于返回列表长度。
这里我们在_init_()函数中定义了网络层,在forward()层中将这些层连接起来。在调用时候,首先net=Net()生成类对象,由于这个类继承了nn.module这个类,也就继承了_call_()函数,这样对象就有了类似函数的功能。当out = net(input)时候,自动调用_call_()函数,而这个函数调用了forward()函数。所以调用out = net(input)就实现了网络输入功能。 然后反向传播函数被自动通过 autograd 定义了。你可以使用任何张量操作在前馈函数上。反向传播具体如下:
net.zero_grad() # 对梯度进行清零 out.backward() #反向传播通过设置requires_grad=true来说明当前输入变量需要保留梯度信息。若一个节点requires_grad被设置为True,那么所有依赖它的节点的requires_grad都为True。由于保留了梯度信息,所以pytorch会进行梯度累加,每次计算时候需要进行清零。 pytorch使用动态图,所谓动态图,就是每次当我们搭建完一个计算图,然后在反向传播结束之后,整个计算图就在内存中被释放了。如果想再次使用的话,必须从头再搭一遍,参见下边这个例子。
# 这是一个关于 PyTorch 是动态图的例子: a = torch.tensor([3.0, 1.0], requires_grad=True) b = a * a loss = b.mean() loss.backward() # 正常 loss.backward() # RuntimeError # 第二次:从头再来一遍 a = torch.tensor([3.0, 1.0], requires_grad=True) b = a * a loss = b.mean() loss.backward() # 正常每个张量都有一个 .grad_fn 属性保存着创建了张量的 Function 的引用,(如果用户自己创建张量,则grad_fn 是 None )。关于function和反向传播的作用参考:https://www.jianshu.com/p/33753873911a
接下来定义损失:
output = net(input) #输出的标签 target = torch.randn(10) # a dummy target, for example target = target.view(1, -1) # 样本标签 criterion = nn.MSELoss() loss = criterion(output, target) #损失函数,看输出与样本标签的差异 net.zero_grad() # zeroes the gradient buffers of all parameters loss.backward()这里criterion = nn.MSELoss()生成类对象,由于该类中实现了_call_()函数,所以可以像函数一样传参使用,而该函数会输出nn.Module类对象,所以可以使用backward方法。
接下来就定义优化器,这里说一下随机梯度下降、小批量梯度下降和批量梯度下降 批量梯度下降法是最原始的形式,它是指在每一次迭代时使用所有样本来进行梯度的更新。 随机梯度下降法不同于批量梯度下降,随机梯度下降是每次迭代使用一个样本来对参数进行更新。 小批量梯度下降,是对批量梯度下降以及随机梯度下降的一个折中办法。其思想是:每次迭代 使用 ** batch_size** 个样本来对参数进行更新。 更新参数代码如下:
import torch.optim as optim # create your optimizer optimizer = optim.SGD(net.parameters(), lr=0.01) #定义优化器,传入网络参数和学习率 # in your training loop: optimizer.zero_grad() # 清空梯度信息 output = net(input) loss = criterion(output, target) loss.backward() optimizer.step() # 更新参数信息优化器输入需要优化的参数和学习率。optimizer.zero_grad()会使清空这些参数的梯度信息,然后optimizer.step() 更新参数信息。
参考链接: http://www.pytorch123.com/SecondSection/neural_networks/ 本博客借用了大量别的博客的原话,属于个人总结内容,仅供学习使用。
