PyTorch基础
安装配置相关内容
组成部分
PyTorch由四个主要的包组成:
- torch: 类似于Numpy的通用数组库,可将张量类型转换为torch.cuda.TensorFloat,病并在GPU上训练。
- torch.autograd: 用于构建计算图形并自动获取梯度的包。
- torch.nn: 具有共享层和损失函数的神经网络库。
- torch.optim: 具有优化算法(SGD、Adam等)的优化包。
验证
import torch
if __name__ == '__main__':
#测试CUDA
print("Support CUDA?:",torch.cuda.is_available())
x = torch.tensor([10.0])
x = x.cuda()
print(x)
y = torch.randn(2,3)
y = y.cuda()
print(y)
z = x + y
print(z)
#测试 CUDNN
from torch.backends import cudnn
print("Support cudnn?",cudnn.is_available())
成功安装PyTorch结果:
Support CUDA?: True
tensor([10.], device='cuda:0')
tensor([[-0.5466, -0.1426, 0.7953],
[-0.3563, -0.8996, 0.5566]], device='cuda:0')
tensor([[ 9.4534, 9.8574, 10.7953],
[ 9.6437, 9.1004, 10.5566]], device='cuda:0')
Support cudnn? True
Numpy与Tensor
Numpy: 会把ndarray放在CPU中进行加速计算。
Tensor: 由Torch产生的Tensor会放在GPU中加速计算(假设当前环境有GPU)
Tensor基础
从接口的角度来划分Tensor的操作,分为两类:
- torch.function,如torch.sum,torch.add等
- tensor.function,如tensor.view,tensor.add等
从修改的角度划分为:
- 不修改自身数据,如x.add(y), x 的数据不变,返回一个新的Tensor。
- 修改自身数据,如x.add_(y)(运算符带下划线后缀),结果保存在x中,x被修改。
import torch
x = torch.tensor([1,2])
y = torch.tensor([3,4])
z = x.add(y)
print(z)
print(x)
x.add_(y)
print(x)
结果:
tensor([4, 6])
tensor([1, 2])
tensor([4, 6])
创建Tensor
import torch
#根据List生成Tensor
t1 = torch.Tensor([1,2,3,4,5,6])
print(t1)
#根据指定形状生Tensor
t2 = torch.Tensor(2,3)
print(t2)
#根据给定的Tensor的形状
t3 = torch.Tensor([[1,2,3],[4,5,6]])
print(t3)
#查看Tensor的形状
print(t2.size())
#Shape与size()等价方式
print(t2.shape)
#根据已有的形状创建Tensor
t4 = torch.Tensor(t.size())
print(t4)
结果:
tensor([1., 2., 3., 4., 5., 6.])
tensor([[1., 2., 3.],
[4., 5., 6.]])
tensor([[1., 2., 3.],
[4., 5., 6.]])
torch.Size([2, 3])
torch.Size([2, 3])
tensor([[1., 2., 3.],
[4., 5., 6.]])
torch.Tensor与torch.tensor的区别
- torch.Tensor是torch.empty与torch.tensor之间的一种混合。当传入数据时,torch.Tensor使用全局默认dtype(FloatTensor), 而torch.tensor是从数据中推断数据类型。
- torch.tensor(1)返回一个固定值1,而torch.Tensor(1)返回一个大小为1的张量,是随机初始化的值。
import torch
t1 = torch.Tensor(1)
t2 = torch.tensor(1)
print("t1的值{},t1数据类型{}".format(t1,t1.type()))
print("t2的值{},t2数据类型{}".format(t2,t2.type()))
结果:
t1的值tensor([1.4013e-45]),t1数据类型torch.FloatTensor
t2的值1,t2数据类型torch.LongTensor
自动生成Tensor实例:
import torch
#生成一个单位矩阵
t1 = torch.eye(2,2)
print(t1)
#自动生成全为0的矩阵
t2 = torch.zeros(2,3)
print(t2)
#根据规则生成数据
t3 = torch.linspace(1,10,4)
print(t3)
#生成满足均匀分布随机数
t4 = torch.rand(2,3)
print(t4)
#生成满足标准分布随机数
t5 = torch.randn(2,3)
print(t5)
#返回所给数据相同,值全为0的张量
t6 = torch.zeros_like(torch.rand(2,3))
print(t6)
结果;
tensor([[1., 0.],
[0., 1.]])
tensor([[0., 0., 0.],
[0., 0., 0.]])
tensor([ 1., 4., 7., 10.])
tensor([[0.2249, 0.0111, 0.2639],
[0.9047, 0.5943, 0.0370]])
tensor([[ 0.1190, 1.1709, 1.2999],
[ 0.1154, -0.1287, -1.4796]])
tensor([[0., 0., 0.],
[0., 0., 0.]])
修改Tensor形状
import torch
#生成一个2*3的矩阵
x = torch.randn(2,3)
print(x)
#查看矩阵的形状
print(x.size())
#查看x的维度
print(x.dim())
#把x变为3*2的矩阵
print(x.view(3,2))
#把x展平为1维向量
y = x.view(-1)
print(y.shape)
#添加一个维度
z = torch.unsqueeze(y,0)
#查看z的形状
print(z.size())
#计算z的元素个数
print(z.numel())
结果:
tensor([[ 0.5907, -0.2381, 0.6862],
[ 0.4232, -0.7212, -0.9779]])
torch.Size([2, 3])
2
tensor([[ 0.5907, -0.2381],
[ 0.6862, 0.4232],
[-0.7212, -0.9779]])
torch.Size([6])
torch.Size([1, 6])
6
若只想重塑张量,使用torch.reshape。如果希望确保两个张量共享相同的数据,使用torch.view。
索引操作
import torch
#设置一个随机种子
torch.manual_seed(100)
#生成一个形状为2*3的矩阵
X = torch.randn(2,3)
print(X)
#根据索引获取第一行所有数据
print(X[0,:])
#获取最后一列数据
print(X[:,-1])
#生成是否大于0的Byter张量
mask=x>0
#获取大于0的值
print(torch.masked_select(x,mask))
#获取非0下标,即行,列索引
print(torch.nonzero(mask))
#获取指定索引对应的值,输出根据以下规则得到
#out[i][j] = input[index[i][j]][j] # if dim == 0
#out[i][j] = input[i][index[i][j]] # if dim == 1
index=torch.LongTensor([[0,1,1]])
torch.gather(x,0,index)
index=torch.LongTensor([[0,1,1],[1,1,1]])
a=torch.gather(x,1,index)
#把a的值返回到一个2x3的0矩阵中
z=torch.zeros(2,3)
z.scatter_(1,index,a)
结果:
tensor([[ 0.3607, -0.2859, -0.3938],
[ 0.2429, -1.3833, -2.3134]])
tensor([ 0.3607, -0.2859, -0.3938])
tensor([-0.3938, -2.3134])
tensor([0.5907, 0.6862, 0.4232])
tensor([[0, 0],
[0, 2],
[1, 0]])
tensor([[ 0.5907, -0.2381, 0.0000],
[ 0.0000, -0.7212, 0.0000]])
广播机制
import torch
import numpy as np
A = np.arange(0, 40,10).reshape(4, 1)
print(A)
B = np.arange(0, 3)
print(B)
#把ndarray转换为Tensor
A1=torch.from_numpy(A) #形状为4x1
print(A1)
B1=torch.from_numpy(B) #形状为3
print(B1)
#Tensor自动实现广播
C=A1+B1
print(C)
#我们可以根据广播机制,手工进行配置
#根据规则1,B1需要向A1看齐,把B变为(1,3)
B2=B1.unsqueeze(0) #B2的形状为1x3
print(B2)
#使用expand函数重复数组,分别的4x3的矩阵
A2=A1.expand(4,3)
print(A2)
B3=B2.expand(4,3)
print(B3)
#然后进行相加,C1与C结果一致
C1=A2+B3
print(C1)
结果:
[[ 0]
[10]
[20]
[30]]
[0 1 2]
tensor([[ 0],
[10],
[20],
[30]], dtype=torch.int32)
tensor([0, 1, 2], dtype=torch.int32)
tensor([[ 0, 1, 2],
[10, 11, 12],
[20, 21, 22],
[30, 31, 32]], dtype=torch.int32)
tensor([[0, 1, 2]], dtype=torch.int32)
tensor([[ 0, 0, 0],
[10, 10, 10],
[20, 20, 20],
[30, 30, 30]], dtype=torch.int32)
tensor([[0, 1, 2],
[0, 1, 2],
[0, 1, 2],
[0, 1, 2]], dtype=torch.int32)
tensor([[ 0, 1, 2],
[10, 11, 12],
[20, 21, 22],
[30, 31, 32]], dtype=torch.int32)
逐元素操作
示例:
import torch
t = torch.randn(1, 3)
t1 = torch.randn(3, 1)
t2 = torch.randn(1, 3)
print(t)
print(t1)
print(t2)
#t+0.1*(t1/t2)
print(torch.addcdiv(t, 0.1, t1, t2))
#计算sigmoid
print(torch.sigmoid(t))
#将t限制在[0,1]之间
print(torch.clamp(t,0,1))
#t+2进行就地运算
print(t.add_(2))
结果:
tensor([[-0.2586, -0.2510, 0.4770]])
tensor([[-0.5883],
[-0.6131],
[ 0.4322]])
tensor([[ 0.4612, -0.9014, -0.2675]])
tensor([[-0.3861, -0.1857, 0.6969],
[-0.3915, -0.1830, 0.7061],
[-0.1649, -0.2989, 0.3154]])
tensor([[0.4357, 0.4376, 0.6170]])
tensor([[0.0000, 0.0000, 0.4770]])
tensor([[1.7414, 1.7490, 2.4770]])
归并操作
对输入进行归并或合并操作
归并操作一般涉及一个dim参数,指定沿哪个维进行归并。另一个参数是keepdim,说明输出结果中是否保留维度1,缺省情况是False,即不保留。
import torch
#生成一个含6个数的向量
a=torch.linspace(0,10,6)
print(a)
#使用view方法,把a变为2x3矩阵
a=a.view((2,3))
print(a)
#沿y轴方向累加,即dim=0
b=a.sum(dim=0)
print(b)
#沿y轴方向累加,即dim=0,并保留含1的维度
b=a.sum(dim=0,keepdim=True)
print(b)
结果
tensor([ 0., 2., 4., 6., 8., 10.])
tensor([[ 0., 2., 4.],
[ 6., 8., 10.]])
tensor([ 6., 10., 14.])
tensor([[ 6., 10., 14.]])
比较操作
import torch
x=torch.linspace(0,10,6).view(2,3)
print(x)
#求所有元素的最大值
print(torch.max(x))
#求y轴方向的最大值
print(torch.max(x,dim=0))
#求最大的2个元素
print(torch.topk(x,1,dim=0))
结果:
tensor([[ 0., 2., 4.],
[ 6., 8., 10.]])
tensor(10.)
torch.return_types.max(
values=tensor([ 6., 8., 10.]),
indices=tensor([1, 1, 1]))
torch.return_types.topk(
values=tensor([[ 6., 8., 10.]]),
indices=tensor([[1, 1, 1]]))
矩阵操作
示例:
import torch
a=torch.tensor([2, 3])
b=torch.tensor([3, 4])
print(torch.dot(a,b))
x=torch.randint(10,(2,3))
y=torch.randint(6,(3,4))
print(x)
print(y)
print(torch.mm(x,y))
x=torch.randint(10,(2,2,3))
y=torch.randint(6,(2,3,4))
print(x)
print(y)
print(torch.bmm(x,y))
结果:
tensor(18)
tensor([[1, 4, 5],
[6, 9, 2]])
tensor([[1, 1, 0, 0],
[1, 2, 3, 3],
[2, 4, 2, 0]])
tensor([[15, 29, 22, 12],
[19, 32, 31, 27]])
tensor([[[8, 4, 9],
[0, 7, 9]],
[[2, 7, 0],
[2, 4, 4]]])
tensor([[[0, 2, 3, 2],
[1, 5, 2, 4],
[0, 0, 5, 5]],
[[1, 3, 2, 0],
[1, 3, 2, 0],
[3, 4, 2, 0]]])
tensor([[[ 4, 36, 77, 77],
[ 7, 35, 59, 73]],
[[ 9, 27, 18, 0],
[18, 34, 20, 0]]])
PyTorch和Numpy比较
Tensor与Autograd
- 创建叶子节点(Leaf Node)的Tensor,使用requires_grad参数指定是否记录对其
的操作,以便之后利用backward()方法进行梯度求解。requires_grad参数的缺省值为
False,如果要对其求导需设置为True,然后与之有依赖关系的节点会自动变为True。 - 通过运算创建的Tensor(即非叶子节点),会自动被赋予grad_fn属性。该属性表
示梯度函数。叶子节点的grad_fn为None。 - 最后得到的Tensor执行backward()函数,此时自动计算各变量的梯度,并将累加结
果保存到grad属性中。计算完成后,非叶子节点的梯度自动释放。
标量反向传播
示例:
import torch
#z=wx+b
#定义输入张量x
x=torch.Tensor([2])
#初始化权重参数W,偏移量b、并设置require_grad属性为True,为自动求导
w=torch.randn(1,requires_grad=True)
b=torch.randn(1,requires_grad=True)
#实现前向传播
y=torch.mul(w,x) #等价于w*x
z=torch.add(y,b) #等价于y+b
#查看x,w,b叶子节点的requite_grad属性
print("x,w,b的require_grad属性分别为:{},{},{}".format(x.requires_grad,w.requires_grad,b.requires_grad))
#查看非叶子节点的requres_grad属性,
print("y,z的requires_grad属性分别为:{},{}".format(y.requires_grad,z.requires_grad))
#因与w,b有依赖关系,故y,z的requires_grad属性也是:True,True
#查看各节点是否为叶子节点
print("x,w,b,y,z的是否为叶子节点:{},{},{},{},{}".format(x.is_leaf,w.is_leaf,b.is_leaf,y.is_leaf,z.is_leaf))
#x,w,b,y,z的是否为叶子节点:True,True,True,False,False
#查看叶子节点的grad_fn属性
print("x,w,b的grad_fn属性:{},{},{}".format(x.grad_fn,w.grad_fn,b.grad_fn))
#因x,w,b为用户创建的,为通过其他张量计算得到,故x,w,b的grad_fn属性:None,None,None
#查看非叶子节点的grad_fn属性
print("y,z的是否为叶子节点:{},{}".format(y.grad_fn,z.grad_fn))
#基于z张量进行梯度反向传播,执行backward之后计算图会自动清空,
z.backward()
#如果需要多次使用backward,需要修改参数retain_graph为True,此时梯度是累加的
#z.backward(retain_graph=True)
#查看叶子节点的梯度,x是叶子节点但它无须求导,故其梯度为None
print("参数w,b的梯度分别为:{},{},{}".format(w.grad,b.grad,x.grad))
#参数w,b的梯度分别为:tensor([2.]),tensor([1.]),None
#非叶子节点的梯度,执行backward之后,会自动清空
print("非叶子节点y,z的梯度分别为:{},{}".format(y.grad,z.grad))
#非叶子节点y,z的梯度分别为:None,None
结果:
x,w,b的require_grad属性分别为:False,True,True
y,z的requires_grad属性分别为:True,True
x,w,b,y,z的是否为叶子节点:True,True,True,False,False
x,w,b的grad_fn属性:None,None,None
y,z的是否为叶子节点:<MulBackward0 object at 0x000002C3455A6DF0>,<AddBackward0 object at 0x000002C3455A6430>
参数w,b的梯度分别为:tensor([2.]),tensor([1.]),None
非叶子节点y,z的梯度分别为:None,None
非标量反向传播
如果目标张量对一个非标量调用backward(),则需要传入一个gradient参数,该参数也是张量,而且需要与调用backward()的张量形状相同。传入这个参数就是为了把张量对张量的求导转换为标量对张量的求导。
backward()函数格式:backward(gradient=None, retain_graph=None, create_graph=False)
示例: x = ( x 1 = 2 , x 2 = 3 ) , y = ( y 1 = x 1 2 + 3 x 2 , y 2 = x 2 2 + 2 x 1 ) x=(x_1=2,x_2=3),y=(y_1=x_1^2+3x_2,y_2=x_2^2+2x_1) x=(x1=2,x2=3),y=(y1=x12+3x2,y2=x22+2x1)
import torch
#定义叶子节点张量x,形状为1x2
x= torch.tensor([[2, 3]], dtype=torch.float, requires_grad=True)
#初始化Jacobian矩阵
J= torch.zeros(2 ,2)
#初始化目标张量,形状为1x2
y = torch.zeros(1, 2)
#定义y与x之间的映射关系:
#y1=x1**2+3*x2,y2=x2**2+2*x1
y[0, 0] = x[0, 0] ** 2 + 3 * x[0 ,1]
y[0, 1] = x[0, 1] ** 2 + 2 * x[0, 0]
结果:
tensor([[2., 3.]], requires_grad=True)
#调用backward来获取y对x的梯度。
#生成y1对x的梯度
y.backward(torch.Tensor([[1, 0]]),retain_graph=True)
J[0]=x.grad
#梯度是累加的,故需要对x的梯度清零
x.grad = torch.zeros_like(x.grad)
#生成y2对x的梯度
y.backward(torch.Tensor([[0, 1]]))
J[1]=x.grad
#显示jacobian矩阵的值
print(J)
结果:
tensor([[4., 3.],
[2., 6.]])
使用numpy实现机器学习
首先,给出一个数组x,然后基于表达式
y
=
3
x
2
+
2
y=3x^2+2
y=3x2+2,加上一些噪音数据到达另一组数据y。
然后,构建一个机器学习模型,学习表达式
y
=
w
x
2
+
b
y=wx^2+b
y=wx2+b的两个参数w、b。利用数组x,y的数据作为训练数据。
最后,采用梯度下降法,通过多次迭代,学习到w,b的值。
import numpy as np
%matplotlib inline
from matplotlib import pyplot as plt
#生成输入数据x及目标数据y。
#设置随机数种子,生成同一个份数据,以便用多种方法进行比较.
np.random.seed(100)
x = np.linspace(-1, 1, 100).reshape(100,1)
y = 3*np.power(x, 2) +2+ 0.2*np.random.rand(x.size).reshape(100,1)
#查看x、y数据分布情况。
plt.scatter(x, y)
plt.show()
结果:
定义损失函数,假设批量大小为100:
L o s s = 1 2 ∑ i = 1 100 ( w x i 2 + b − y i ) 2 Loss = \frac{1}{2}\sum_{i=1}^{100}(wx_i^2+b-y_i)^2 Loss=21∑i=1100(wxi2+b−yi)2
对损失函数求导:
∂ L o s s ∂ ( w ) = ∑ i = 1 100 ( w x i 2 + b − y i ) x i 2 \frac{\partial{Loss}}{\partial(w)} = \sum_{i=1}^{100}(wx_i^2+b-y_i)x_i^2 ∂(w)∂Loss=∑i=1100(wxi2+b−yi)xi2
∂ L o s s b ∂ b = ∑ i = 1 100 ( w x i 2 + b − y i ) \frac{\partial{Loss}{b}}{\partial{b}} = \sum_{i=1}^{100}(wx_i^2+b-y_i) ∂b∂Lossb=∑i=1100(wxi2+b−yi)
利用梯度下降法学习参数,学习率lr
w 1 − = l r ∗ ∂ L o s s ∂ w w_{1-} = lr*\frac{\partial{Loss}}{\partial{w}} w1−=lr∗∂w∂Loss
b 1 − = l r ∗ ∂ L o s s ∂ b b_{1-} = lr*\frac{\partial{Loss}}{\partial{b}} b1−=lr∗∂b∂Loss
代码实现:
# 随机初始化参数
w1 = np.random.rand(1,1)
b1 = np.random.rand(1,1)
lr =0.001 # 学习率
for i in range(800):
# 前向传播
y_pred = np.power(x,2)*w1 + b1
# 定义损失函数
loss = 0.5 * (y_pred - y) ** 2
loss = loss.sum()
#计算梯度
grad_w=np.sum((y_pred - y)*np.power(x,2))
grad_b=np.sum((y_pred - y))
#使用梯度下降法,是loss最小
w1 -= lr * grad_w
b1 -= lr * grad_b
plt.plot(x, y_pred,'r-',label='predict')
plt.scatter(x, y,color='blue',marker='o',label='true') # true data
plt.xlim(-1,1)
plt.ylim(2,6)
plt.legend()
plt.show()
print(w1,b1)
结果:
使用Tensor和Autograd实现机器学习
#导入需要的库
import torch as t
%matplotlib inline
from matplotlib import pyplot as plt
#生成训练数据,并可视化数据分布情况
t.manual_seed(100)
dtype = t.float
#生成x坐标数据,x为tensor.需要把x的形状转换为100*1
x = t.unsqueeze(torch.linspace(-1,1,100),dim=1)
#生成y轴坐标数据,y为tensor,形状为100*1,另加上一些噪声
y = 3*x.pow(2) +2+ 0.2*torch.rand(x.size())
#画图,把tensor数据转换为Numpy数据
plt.scatter(x.numpy(),y.numpy())
plt.show()
结果:
#随机初始化参数,参数w,b为需要学习的,顾requires_grad=True
w = t.randn(1,1,dtype=dtype,requires_grad=True)
b = t.zeros(1,1,dtype=dtype,requires_grad=True)
#训练模型
lr = 0.001 #学习率
for ii in range(800):
#前向传播,并定义损失函数loss
y_pred = x.pow(2).mm(w) + b
loss = 0.5*(y_pred - y)**2
loss = loss.sum()
#自动计算梯度,梯度存放在grad属性中
loss.backward()
#手动更新参数,需要用torch.no_grad(),使上下文环境中切断自动求导的计算
with t.no_grad():
w -= lr*w.grad
b -= lr*b.grad
#梯度清零
w.grad.zero_()
b.grad.zero_()
#可视化训练结果
plt.plot(x.numpy(),y_pred.detach().numpy(),'r-',label='predict')#predict
plt.scatter(x.numpy(), y.numpy(),color='blue',marker='o',label='true') # true data
plt.xlim(-1,1)
plt.ylim(2,6)
plt.legend()
plt.show()
print(w,b)
结果:
使用TensorFlow架构
import tensorflow.compat.v1 as tf
import numpy as np
tf.compat.v1.disable_eager_execution()#解决版本问题
#生成训练数据
np.random.seed(100)
x = np.linspace(-1, 1, 100).reshape(100,1)
y = 3*np.power(x, 2) +2+ 0.2*np.random.rand(x.size).reshape(100,1)
#初始化参数
#创建两个占位符,分别用来存放输入数据x和目标值y
#运行计算图时,导入数据。
x1 = tf.placeholder(tf.float32, shape=(None, 1))
y1 = tf.placeholder(tf.float32, shape=(None, 1))
# 创建权重变量w和b,并用随机值初始化.
# TensorFlow 的变量在整个计算图保存其值.
w = tf.Variable(tf.random_uniform([1], 0, 1.0))
b = tf.Variable(tf.zeros([1]))
#实现前向传播及损失函数
# 前向传播,计算预测值.
y_pred = np.power(x,2)*w + b
# 计算损失值
loss=tf.reduce_mean(tf.square(y-y_pred))
# 计算有关参数w、b关于损失函数的梯度.
grad_w, grad_b = tf.gradients(loss, [w, b])
#用梯度下降法更新参数.
# 执行计算图时给 new_w1 和new_w2 赋值
# 对TensorFlow 来说,更新参数是计算图的一部分内容
# 而PyTorch,这部分属于计算图之外.
learning_rate = 0.01
new_w = w.assign(w - learning_rate * grad_w)
new_b = b.assign(b - learning_rate * grad_b)
#训练模型
# 已构建计算图,接下来创建TensorFlow session,准备执行计算图.
with tf.Session() as sess:
# 执行之前需要初始化变量w、b
sess.run(tf.global_variables_initializer())
for step in range(2000):
# 循环执行计算图. 每次需要把x1、y1赋给x和y.
# 每次执行计算图时,需要计算关于new_w和new_b的损失值,
# 返回numpy多维数组
loss_value, v_w, v_b = sess.run([loss, new_w, new_b],
feed_dict={x1: x, y1: y})
if step%200==0: #每200次打印一次训练结果
print("损失值、权重、偏移量分别为{:.4f},{},{}".format(loss_value,v_w,v_b))
# 可视化结果
plt.figure()
plt.scatter(x,y)
plt.plot (x, v_b + v_w*x**2)
结果:
参考书籍:《Python深度学习:基于PyTorch》