赞
踩
目录
本文章记录自己在刚入门pytorch的学习中的一些感悟和经验分享,还有一些自己在学习代码中的一些见解,我都将记录在博客上面,自己总结的可能有些还不够妥当,不过如果能帮助大家的话也是很不错的。
一:张量介绍
首先我们为什么需要张量(tensor)这个东西呢?因为在早期的计算机科学和机器学习中,也常用一维数组、二维数组等数据结构来处理数据。但这些概念通常只限于低维度的数据,对于高维度的数据处理较为麻烦。但是后来我们发现,计算机科学和机器学习中我们需要处理往往是高维度的数据,例如图像、音频、视频、文本等等。所以引入了张量变量,它是一种更加通用的多维数组,可以表示任意维度的数组,因此在处理高维度数据时非常方便。
使用过Python中NumPy计算包的读者会对本部分很熟悉。没使用过也没关系,后面我会一步一步详细教会大家如何去使用。 无论使用哪个深度学习框架,它的张量类与Numpy的ndarray类似。 但深度学习框架又比Numpy的ndarray多一些重要功能: 首先,GPU很好地支持加速计算(在深度学习中我们要处理的数据是非常庞大的),而NumPy仅支持CPU计算; 其次,张量类支持自动微分。 这些功能使得张量类更适合深度学习。
二:下面举例子来进一步介绍tensor
首先,我们可以使用 arange 创建一个行向量 x。这个行向量包含以0开始的前12个整数,它们默认创建为整数。也可指定创建类型为浮点数。张量中的每个值都称为张量的 元素(element)。例如,张量 x 中有 12 个元素。除非额外指定,新的张量将存储在内存中,并采用基于CPU的计算。x.numel()返回张量x中元素的数量。通过张量的shape属性来访问张量的形状 。
-
- import torch
- x = torch.arange(12)
- x,x.numel(),x.shape
- (tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]),
- 12,
- torch.Size([12]))
要想改变一个张量的形状而不改变元素数量和元素值,可以调用reshape函数。但前提是元素的个数不能发生改变。
-
- X = x.reshape(3, 4)
- X
- tensor([[ 0, 1, 2, 3],
- [ 4, 5, 6, 7],
- [ 8, 9, 10, 11]])
有时,我们希望使用全0、全1、其他常量,或者从特定分布中随机采样的数字来初始化矩阵。可以采用zeros方法来生成
-
- torch.zeros((2, 3, 4))
- tensor([[[0., 0., 0., 0.],
- [0., 0., 0., 0.],
- [0., 0., 0., 0.]],
-
- [[0., 0., 0., 0.],
- [0., 0., 0., 0.],
- [0., 0., 0., 0.]]])
有时我们想通过从某个特定的概率分布中随机采样来得到张量中每个元素的值。 例如,当我们构造数组来作为神经网络中的参数时,我们通常会随机初始化参数的值。 以下代码创建一个形状为(3,4)的张量。 其中的每个元素都从均值为0、标准差为1的标准高斯分布(正态分布)中随机采样。
-
- torch.randn(3, 4)
- tensor([[ 0.7277, -1.3848, -0.2607, 0.9701],
- [-2.3290, -0.3754, 0.2457, 0.0760],
- [-1.2832, -0.3600, -0.3321, 0.8184]])
我们还可以通过提供包含数值的Python列表(或嵌套列表),来为所需张量中的每个元素赋予确定值。 在这里,最外层的列表对应于轴0,内层的列表对应于轴1。
-
- torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
- tensor([[2, 1, 4, 3],
- [1, 2, 3, 4],
- [4, 3, 2, 1]])
除此之外,我们还有很多操作,如运算符等等,这里就不一一举例了,#exp是计算e的几次方
-
- a = torch.tensor([1,2,3])
- b = torch.tensor([1,2,3])
- a*b
- #tensor([1, 4, 9])
- #exp是计算e的几次方
- torch.exp(torch.tensor([0., 1., 2.,3.]))
- #tensor([ 1.0000, 2.7183, 7.3891, 20.0855])
如果需要指定类型的话,可以在定义tensor变量那,定义dtype指定为哪种类型,如果想生成多维的话,可以在reshape那写成reshape((1,1,3,4)),里面的数字个数就代表维度
-
- X = torch.arange(12, dtype=torch.float32).reshape((3,4))
torch.cat((X, Y), dim=x)是PyTorch中用来拼接张量的函数。其中,X和Y是待拼接的两个张量,dim=x参数指定了拼接的维度。例如数据是二维的,dim=1表示在张量的第二个维度上进行拼接。
是哪个维度的判断方法是:从最外层开始是第0维度。这里又篇专门介绍这个dim的,供大家参考
对 torch 中 dim 的总结和理解_torch dim_Linky1990的博客-CSDN博客
-
- X = torch.arange(12, dtype=torch.float32).reshape((3,4))
- Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
- torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1)
-
- (tensor([[ 0., 1., 2., 3.],
- [ 4., 5., 6., 7.],
- [ 8., 9., 10., 11.],
- [ 2., 1., 4., 3.],
- [ 1., 2., 3., 4.],
- [ 4., 3., 2., 1.]]),
- tensor([[ 0., 1., 2., 3., 2., 1., 4., 3.],
- [ 4., 5., 6., 7., 1., 2., 3., 4.],
- [ 8., 9., 10., 11., 4., 3., 2., 1.]]))
有时,我们想通过逻辑运算符构建二元张量。 以X == Y为例: 对于每个位置,如果X和Y在该位置相等,则新张量中相应项的值为1。 这意味着逻辑语句X == Y在该位置处为真,否则该位置为0。
-
- X == Y
-
- tensor([[False, True, False, True],
- [False, False, False, False],
- [False, False, False, False]])
在上面的部分中,我们看到了如何在相同形状的两个张量上执行按元素操作。 在某些情况下,即使形状不同,我们仍然可以通过调用 广播机制(broadcasting mechanism)来执行按元素操作。 这种机制的工作方式如下:
通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状;
对生成的数组执行按元素操作。
-
- a = torch.arange(3).reshape((3, 1))
- b = torch.arange(2).reshape((1, 2))
- #a+b
- #tensor([[0, 1],
- [1, 2],
- [2, 3]])
上面的操作就会转化成上面这种操作形式:就是上面说的:通过适当复制元素来扩展一个或两个数组
如下所示:矩阵a将复制列, 矩阵b将复制行,然后再按元素相加。
-
- a = torch.tensor([[0,0],
- [1,1],
- [2,2]])
- b = torch.tensor([[0,1],
- [0,1],
- [0,1]])
- #a+b
- #tensor([[0, 1],
- [1, 2],
- [2, 3]])
也可以进行切片操纵后进行赋值:
-
- X = torch.arange(12).reshape((3,4))
- X[0:2, :] = 12
- X
- tensor([[12, 12, 12, 12],
- [12, 12, 12, 12],
- [ 8, 9, 10, 11]])
将深度学习框架定义的张量转换为NumPy张量(ndarray)很容易,反之也同样容易。 torch张量和numpy数组将共享它们的底层内存,就地操作更改一个张量也会同时更改另一个张量。
-
- A = X.numpy()
- B = torch.tensor(A)
- type(A), type(B)
- #(numpy.ndarray, torch.Tensor)
-
- a = torch.tensor([3.5])
- a, type(a.item()),a.item() ,float(a), int(a)
- #(tensor([3.5000]), float, 3.5, 3.5, 3)
三:tensor是如何自动微分(求导)的?
举个例子:
首先需要requires_grad这个参数设置为true才能计算梯度,x.grad :默认值是None,存放y关于x的导数,通过调用反向传播函数,来自动计算y关于x每个分量的梯度。当有了y关于x的函数后。就可以利用y.backward()去计算x的梯度了,如何x.grad查看各个变量的梯度
2 * x是x的平方的导数,
-
- import torch
- x = torch.arange(4.0, requires_grad=True)
- x.grad
- y = torch.dot(x, x) #0*0+1*1+2*2+3*3=14
- print(y)
- y.backward()
- print(x.grad)
- # 验证
- print(x.grad == 2 * x)
-
- tensor(14., grad_fn=<DotBackward0>)
- tensor([0., 2., 4., 6.])
- tensor([True, True, True, True])
在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
-
- x.grad.zero_()
- y = torch.dot(x, x)#写不写这个,答案都一样,因为y=x.sum(),重新赋值了
- y = x.sum()
- print(y)#如果对y求导的话,x的梯度就是x1,x2...前面的系数,都是1
- y.backward()
- print(x.grad)
-
- tensor(6., grad_fn=<SumBackward0>)
- tensor([1., 1., 1., 1.])##如果对y求导的话,x的梯度就是x1,x2...前面的系数,都是1
-
- #如果不清零,下一次的结果是:
- tensor(6., grad_fn=<SumBackward0>)
- tensor([2., 2., 2., 2.])
非标量变量的反向传播
-
- x.grad.zero_()
- # #哈达玛积,对应元素相乘
- x = torch.ones(4,requires_grad=True)
- y = x*x#这个才是类似于把上面代码中的torch.dot(x,x)给y
- #而是计算批量中每个样本单独计算的偏导数之和
- y.sum().backward()
- print(x.grad)
- print(x.grad.sum())
- tensor([2., 2., 2., 2.])
- tensor(8.)
#把y当作常数,detach后,u不提供梯度计算,而且u只是一个常数
-
- x.grad.zero_()
- y = x*x
- u=y.detach()
- z=u*x
- z.sum().backward()
- print(x.grad == u)#,这里的x.grad是z关于x的梯度,自变量和隐变量的关系
- tensor([1., 1., 1., 1.], grad_fn=<MulBackward0>)
- tensor([True, True, True, True])
下面这段代码跟上面类似
-
- x.grad.zero_()
- y.sum().backward()
- print(x.grad == 2*x)
- tensor([True, True, True, True])
四:实现简单的线性回归
分为五个部分:
1:定义数据
这里的数据是人工数据的生成函数,生成y=Xw+b+噪声,num_examples相当于batchsize,y的输出个数。生成均值为0,方差为1,数据维度是(num_examples, len(w))的随机数据作为训练样本。注意:matmul中禁止矩阵与标量的乘法。
这里介绍torch.matmul函数:
(如果input和other都是二维矩阵,则torch.matmul等价于矩阵乘法运算;如果input和other都是一维张量,则torch.matmul等价于向量内积运算;如果input和other都是高维张量,则torch.matmul会自动进行广播。
此外,@也是PyTorch中进行矩阵乘法的一种简洁的写法,与torch.matmul等价。)
detach的原因是为了迎合某些pytorch版本需要detach后才能转换为numpy,而且他也会从梯度的计算图拉出来,不再参与梯度计算, 因为y是关于x的函数,所以肯定是一个有关系的图片,
-
- import torch
- from d2l import torch as d2l
- from torch.utils import data
- import random
-
-
- def synthetic_data(w, b, num_examples):
- X = torch.normal(0, 1, (num_examples, len(w)))
- y = torch.matmul(X, w) + b # 生成X对应的预测值y,(1000,2)*(2,1)=(1000,1)类
- y += torch.normal(0, 0.01, y.shape) # 加入噪音是使模型复杂化
- return X, y.reshape((-1, 1))
-
- #初始化w和,b
- ture_w = torch.tensor([2., -3.4 ])
- #ture_w = torch.tensor([[2.],[ -3.4 ]])写成这个结果也一样
- print(ture_w.shape)
- ture_b = 4.2
- features, labels = synthetic_data(ture_w, ture_b, 1000)
- print('features:', features[0], '\nlabels:', labels[0])
- d2l.plt.scatter(features[:, 1].numpy(),
- labels.numpy())
- d2l.plt.show()
这是人工数据生成的图片:
转换torch类型:
yield语法是只输出一次就退出for循环了
-
-
- def data_iter(batch_size, features, labels):
- num_exampe = len(features)
- index = list(range(num_exampe))
- random.shuffle(index) # 打乱下标
- for i in range(0, num_exampe, batch_size): # 每次挑batchsize步去取数据
- batch_index = torch.tensor(index[i: min(i + batch_size, num_exampe)]) #只输出0-10内的数据
- yield features[batch_index], labels[batch_index]#直接yield弹出
'运行
2:定义模型,先随便画一条线,再优化拟合
-
- w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
- print(w.shape)
- b = torch.zeros(1, requires_grad=True)
- def linerg(X, w, b):
- return torch.matmul(X, w) + b
3:定义优化算法
with torch.no_grad():#不需要梯度,因为更新的时候不参与梯度计算,除以 batch_size是归一化处理,好处是梯度不会太大也不会太小,这样就比较好去调参数。因为 在损失函数里没有除均值,所以在这里除,乘法对梯度来说是线性的,所以除哪里确实都一样,最后,因为pytorch不会自动把梯度清零所以需要手动清零
-
- def sgd(params,lr,batch_size):
- #小批量随机梯度下降
- with torch.no_grad():
- for param in params:
- param -= lr*param.grad / batch_size
- param.grad.zero_()
'运行
# 4:定义损失函数
-
- def squared_loss(y_hat, y):
- """均方损失"""
- return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
'运行
6:训练,得出loss也就是损失,来判断模型的好坏:
一般训练流程是这样的:
每一轮的训练都是是用上一轮更新的w和b去训练。这样效果才会越来越好。或者有时候效果不好也可能是lr的等其他问题。 l = loss(net(X, w, b), y) 这个y也就是上面的labels, X和y的小批量损失, X:把随机采到的数据放到定义好的模型去,再用模型预测的值与采到的数据的y进行计算loss。如何再计算l关于[w,b]的梯度,这里使用梯度下降法来更新梯度。然后 每更新一次batchsize后更新的w和b。再去与labels计算损失。拿出来最后一个损失。
这是最简单做法也是早期实现预测线性模型的做法。后期我会出更多在这后来出现的一些更复杂的模型。
-
- net = linerg
- lr = 0.03
- num_epochs = 10
- loss = squared_loss
- for epoch in range(num_epochs):
- for X, y in data_iter(batch_size, features, labels):
- l = loss(net(X, w, b), y)
- l.sum().backward()
- sgd([w, b], lr, batch_size)
- with torch.no_grad():
- train_l = loss(net(features, w, b), labels)
- print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f},w:{w},b:{b}')
这是训练的出来的损失,从中我们可以看到,随着训练次数的增加,我们的loss会越来越低,这是好事,但不代表你的模型就很好,因为这只是训练的loss小,我们要做的是拿这个模型去预测那些测试的数据,或者是未知的数据,如果这时候我们的结果好不错的话,就说明我们这个实验是好的。也就是常说的泛化能力好。
回归可以用于预测多少的问题。 比如预测房屋被售出价格,或者棒球队可能获得的胜场数,又或者患者住院的天数。事实上,我们也对分类问题感兴趣:不是问“多少”,而是问“哪一个”:这将在我下一篇文章详细介绍他们的区别和完整代码的实现。
总结:深度学习存储和操作数据的主要接口是张量(n维数组)。它提供了各种功能,包括基本数学运算、广播、索引、切片、内存节省和转换其他Python对象。学习了深度网络是如何实现和优化的。在这一过程中只使用张量和自动微分,不需要定义层或复杂的优化器。
视频,笔记和代码,以及注释都已经上传网盘,放在主页置顶文章
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。