pytorch学习

1. pytorch的安装

cmd中输入 nvidia-smi 就可以看到支持的cuda版本,比如我的是cuda11.1
然后去pytorch官网选择相应的命令,运行安装即可

这里选择pip安装,后面好像不需要安装cuda,cudnn(windows下),我的命令是:
pip3 install torch==1.9.1+cu111 torchvision==0.10.1+cu111 torchaudio===0.9.1 -f https://download.pytorch.org/whl/torch_stable.html

安装好之后,运行下面代码查看gpu版本的pytorch是否安装成功了

import torch
# 测试CUDA
print('Support CUDA?:', torch.cuda.is_available())
x = torch.tensor([10.0]) # 默认是cpu上的张量
x = x.cuda() # 需要将其移动到gpu上
print(x)
# print(type(x))numpy
y = torch.randn(2, 3)
y = y.cuda() # 进行运算的两个量必须在同意设备上,比如都在gpu上
print(y)
z = x + y
print(z)
# 测试cudnn
from torch.backends import cudnn
print('Support cudnn?:', cudnn.is_available())
Support CUDA?: True
tensor([10.], device='cuda:0')
tensor([[ 0.8319, 0.6084, 1.1869],
[ 0.8798, 1.5180, -0.2546]], device='cuda:0')
tensor([[10.8319, 10.6084, 11.1869],
[10.8798, 11.5180, 9.7454]], device='cuda:0')
Support cudnn?: True

从上面结果可以看出,windows下只需要运行官网的那个pip命令,就可以安装gpu版本的pytorch了,显卡驱动电脑会自己下载好的,用nvidia-smi即可查看驱动版本!至于linux下,可能需要自己下载显卡驱动,然后再运行相应的pip命令即可。

2. tensor的基本操作与运算

numpy与tensor类似,两者可以共享内存,并且相互转换方便;不过最大的区别是numpy会把ndarray放在cpu中加速运算,而torch会把产生的Tensor放到Gpu中加速运算

2.1 创建Tensor

a = torch.Tensor([1, 2, 3]) # 根据列表创建Tensor
b = torch.Tensor(2, 3) # 根据形状创建Tensor,注意这里用torch.tensor()会报错
print(a, b)
print(a.size()) # .size()是函数,shape是属性
print(b.shape)
tensor([1., 2., 3.]) tensor([[8.4536e-24, 4.5914e-41, 8.4536e-24],
[4.5914e-41, 0.0000e+00, 0.0000e+00]])
torch.Size([3])
torch.Size([2, 3])

注意torch.Tensor与torch.tensor的几点区别:

  • torch.Tensor是torch.empty和torch.tensor之间的一种混合,但是,当传入数据时,torch.Tensor使用全局默认dtype(FloatTensor),而torch.tensor是从数据中推断数据类型。
  • torch.tensor(1)返回一个固定值1,而torch.Tensor(1)返回一个大小为1的张量,它是随机初始化的值。
torch.eye(2,2) # 生成单位矩阵
torch.zeros(2, 3) # 全为0
torch.zeros_like(torch.rand(2, 3))
torch.linspace(1, 10, 4) # 指定范围内生成均匀间隔的指定个数

tensor([ 1., 4., 7., 10.])

torch.rand(2, 3) # 满足0-1内均匀分布的随机数

tensor([[0.1784, 0.8238, 0.5557], [0.9770, 0.4440, 0.9478]])

torch.randn(2, 3) # 满足标准分布的随机数

tensor([[ 2.2847, -0.6502, 0.1585], [ 0.8759, 0.9291, 1.0564]])

# 机器学习中初始化权重w,b时常用正态分布和全零数值
w = torch.normal(0, 0.01, size=(2,3), requires_grad=True)
b = torch.zeros(10, requires_grad=True)

2.2 修改tensor的形状和数据类型

2.2.1.Tensor.view()

x = torch.randn(2, 3)
print(x.size(), x.dim())
x = x.view((3, 2))
y = x.view(-1)
print(x, y, sep='\n')

torch.Size([2, 3]) 2 tensor([[-0.1727, 0.4991], [ 0.5274, 1.1405], [-0.4631, 0.5797]]) tensor([-0.1727, 0.4991, 0.5274, 1.1405, -0.4631, 0.5797])

2.2.2.Tensor.reshape()

a = torch.rand(2, 3)
b = a.reshape(-1)
c = a.reshape(-1, 6)
print(a, b, c, sep='\n')
print(b.shape, c.shape, sep='\n')

tensor([[0.7053, 0.9004, 0.4589], [0.1943, 0.8618, 0.1296]]) tensor([0.7053, 0.9004, 0.4589, 0.1943, 0.8618, 0.1296]) tensor([[0.7053, 0.9004, 0.4589, 0.1943, 0.8618, 0.1296]]) torch.Size([6]) torch.Size([1, 6])

2.2.3.torch.unsequeeze()

y = torch.unsqueeze(y, 0) # 添加一个维度
print(y, y.size(), y.numel())

tensor([[-0.1727, 0.4991, 0.5274, 1.1405, -0.4631, 0.5797]]) torch.Size([1, 6]) 6

  • Tensor.view()不会修改原来的tensor值,会返回一个新的修改后的值
  • reshape()可以由torch.reshape(),也可由torch.Tensor.reshape()调用。但view()只可由torch.Tensor.view()来调用。
  • 如果你只想重塑张量,请使用torch.reshape。如果你还关注内存使用情况并希望确保两个张量共享相同的数据,请使用torch.view。

2.2.4.Tensor.type()修改张量的数据类型

y = torch.tensor([0, 2])
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
y_hat = y_hat.argmax(axis=1)
print(y_hat.dtype)
y_hat = y_hat.type(torch.float32) # 也可以用y_hat.type(y.dtype)传入别的Tensor的数据类型即可修改
print(y_hat.dtype)

torch.int64 torch.float32

2.3 索引操作

# 设置一个随机数种子
torch.manual_seed(100) # 里面的数字没有关系,随便写,相同数字后面的随机语句生成的内容相同
x = torch.randn(2,3)
# 基本的索引与numpy相同
print(x[0, :])
print(x[:, -1])
# 根据mask条件筛选一些值
mask = x > 0
x_ = torch.masked_select(x, mask)
print(x_)
tensor([ 0.3607, -0.2859, -0.3938])
tensor([-0.3938, -2.3134])
tensor([0.3607, 0.2429])

2.4 广播机制

import numpy as np
A = np.arange(0, 40, 10).reshape(4, 1)
B = np.arange(0, 3)
A1 = torch.from_numpy(A)
B1 = torch.from_numpy(B)
print(A1, B1, sep = '\n')
C = A1 + B1
print(C)
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)
# 手动配置上面的广播机制
B2 = B1.unsqueeze(0) # (1,3)
B3 = B2.expand(4, 3) # expand()函数用来重复数组
A2 = A1.expand(4, 3)
print(A2, B3, sep='\n')
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)

2.5 Tensor.sum()进行求和归并

t = torch.linspace(0, 10, 6)
t = t.view(2, 3)
t1 = t.sum(dim=0)
t2 = t.sum(dim=0, keepdim=True)
print(t1, t2)
tensor([ 6., 10., 14.]) tensor([[ 6., 10., 14.]])

归并操作一般涉及一个dim参数,指定沿哪个维进行归并。另一个参数是keepdim,说明输出结果中是否保留维度1,缺省情况是False,即不保留

2.6.Tensor.max()和Tensor.argmax()求最大值及最大值索引

y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
y_sum = y_hat.max(dim=1)
y_hat = y_hat.argmax(axis=1)
print(y_hat, y_sum, sep='\n')

tensor([2, 2]) torch.return_types.max( values=tensor([0.6000, 0.5000]), indices=tensor([2, 2]))

3. torch的自动求导机制-Autograd

  • 创建叶子节点(Leaf Node)的Tensor,使用requires_grad参数指定是否记录对其的操作,以便之后利用backward()方法进行梯度求解。requires_grad参数的缺省值为False,如果要对其求导需设置为True,然后与之有依赖关系的节点会自动变为True。
  • 可利用requires_grad_()方法修改Tensor的requires_grad属性。可以调用.detach()或with torch.no_grad():,将不再计算张量的梯度,跟踪张量的历史记录。这点在评估模型、测试模型阶段中常常用到
  • 通过运算创建的Tensor(即非叶子节点),会自动被赋予grad_fn属性。该属性表示梯度函数。叶子节点的grad_fn为None
  • 最后得到的Tensor执行backward()函数,此时自动计算各变量的梯度,并将累加结果保存到grad属性中。计算完成后,非叶子节点的梯度自动释放。
  • 反向传播的中间缓存会被清空,如果需要进行多次反向传播,需要指定backward中的参数retain_graph=True。多次反向传播时,梯度是累加的
  • 非叶子节点的梯度backward调用后即被清空
  • 可以通过用torch.no_grad()包裹代码块的形式来阻止autograd去跟踪那些标记为.requesgrad=True的张量的历史记录。这步在测试阶段经常使用
  • backward()函数接收参数,该参数应和调用backward()函数的Tensor的维度相同,或者是可broadcast的维度。如果求导的Tensor为标量(即一个数字),则backward中的参数可省略。
  • 在整个过程中,PyTorch采用计算图的形式进行组织,该计算图为动态图,且在每次前向传播时,将重新构建。其他深度学习架构,如TensorFlow、Keras一般为静态图。接下来我们介绍计算图,用图的形式来描述就更直观了,该计算图为有向无环图(DAG)

3.1 标量正向传播、反向传播过程

如图所示,圆形表示变量,矩表示算子。如表达式:z=wx+b,可写成两个表示式:y=wx,则z=y+b,其中x、w、b为变量,是用户创建的变量,不依赖于其他变量,故又称为叶子节点。为计算各叶子节点的梯度,需要把对应的张量参数requires_grad属性设置为True,这样就可自动跟踪其历史记录。y、z是计算得到的变量,非叶子节点,z为根节点。mul和add是算子(或操作或函数)。由这些变量及算子,就构成一个完整的计算过程(或前向传播过程)

image-20211002162348925

我们的目标是更新各叶子节点的梯度,根据复合函数导数的链式法则,不难算出各叶子节点的梯度。
PyTorch调用backward()方法的时候,将自动计算各节点的梯度,这是一个反向传播过程,这个过程可用下图表示。且在反向传播过程中,autograd沿着下图,从当前根节点z反向溯源,利用导数链式法则,计算所有叶子节点的梯度,其梯度值将累加到grad属性中。对非叶子节点的计算操作(或Function)记录在grad_fn属性中,叶子节点的grad_fn值为None

image-20211002162837702

# 标量的正向传播,反向传播过程的代码实现
import warnings
warnings.filterwarnings('ignore')
## 1)定义叶子节点和算子节点
x = torch.tensor([5])
w = torch.randn(1, requires_grad=True)
b = torch.randn(1, requires_grad=True)
## 2)前向传播的实现
y = torch.mul(w, x)
z = torch.add(y, b)
## 3)查看叶子节点和非叶子节点的requires_grad属性
print('x, w, b, y, z 的requires_grad属性:{}, {}, {}, {}, {}'.format(x.requires_grad, w.requires_grad, b.requires_grad, y.requires_grad, z.requires_grad))
## 4)查看他们的grad_fn属性和grad属性
print('x, w, b, y, z 的grad_fn属性:{}, {}, {}, {}, {}'.format(x.grad_fn, w.grad_fn, b.grad_fn, y.grad_fn, z.grad_fn))
print('x, w, b, y, z 的grad属性:{}, {}, {}, {}, {}'.format(x.grad, w.grad, b.grad, y.grad, z.grad))
## 5)自动求导,实现梯度反向传播
z.backward(retain_graph=True)
print('x, w, b, y, z 的grad_fn属性:{}, {}, {}, {}, {}'.format(x.grad_fn, w.grad_fn, b.grad_fn, y.grad_fn, z.grad_fn))
print('x, w, b, y, z 的grad属性:{}, {}, {}, {}, {}'.format(x.grad, w.grad, b.grad, y.grad, z.grad))

x, w, b, y, z 的requires_grad属性:False, True, True, True, True x, w, b, y, z 的grad_fn属性:None, None, None, <MulBackward0 object at 0x000001EA2ADDB3A0>, <AddBackward0 object at 0x000001EA2AED73D0> x, w, b, y, z 的grad属性:None, None, None, None, None x, w, b, y, z 的grad_fn属性:None, None, None, <MulBackward0 object at 0x000001EA2AED7400>, <AddBackward0 object at 0x000001EA2ABD0D90> x, w, b, y, z 的grad属性:None, tensor([5.]), tensor([1.]), None, None

由于y,z是由叶子节点w, b计算得来的,因此他们的requires_grad属性也变为了True
非叶张量的张量的.grad属性正在被访问。它的.grad属性在autograde、 .backward()期间不会被填充。如果你确实想要一个非叶张量的梯度,在非叶张量上使用.retain_grad()

z.backward()
print('x, w, b, y, z 的grad_fn属性:{}, {}, {}, {}, {}'.format(x.grad_fn, w.grad_fn, b.grad_fn, y.grad_fn, z.grad_fn))
print('x, w, b, y, z 的grad属性:{}, {}, {}, {}, {}'.format(x.grad, w.grad, b.grad, y.grad, z.grad))

x, w, b, y, z 的grad_fn属性:None, None, None, <MulBackward0 object at 0x000001EA2AD2D0A0>, <AddBackward0 object at 0x000001EA2AD2D310> x, w, b, y, z 的grad属性:None, tensor([10.]), tensor([2.]), None, None

3.2 非标量反向传播

## 1)定义叶子节点张量
x = torch.tensor([[2, 3]], dtype=torch.float, requires_grad=True)
J = torch.zeros(2, 2)
# 初始化目标张量
y = torch.zeros(1, 2)
y[0, 0] = x[0, 0]**2 + 3*x[0, 1]
y[0, 1] = x[0, 1]**2 + 2*x[0, 0]
# 生成y对x1的梯度
y.backward(torch.Tensor([[1, 0]]), retain_graph=True)
J[0] = x.grad
print(J[0])
x.grad = torch.zeros_like(x.grad)

tensor([4., 3.])

y.backward(torch.Tensor([[0, 1]]))
J[1] = x.grad
print(J)

tensor([[4., 3.], [2., 6.]])

4. 线性回归的numpy、pytorch、tensorflow的实现

线性回归模型:首先,给出一个数组x,然后基于表达式y=3x^2+2,加上一些噪音数据到达另一组数据y,然后,构建一个机器学习模型,学习表达式y=wx2+b的两个参数w、b。利用数组x,y的数据为训练数据,然后,构建一个机器学习模型,学习表达式y=wx2+b的两个参数w、b。利用数组x,y的数据为训练数据

4.1 numpy实现

import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
## 1)生成输入数据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)
## 2)画图看一下分布
plt.scatter(x, y)
plt.show()
## 3)初始化模型的权重和偏置参数
w1 = np.random.rand(1, 1)
b1 = np.random.rand(1, 1)
print(w1, b1)
## 4)训练模型
lr = 0.01
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(np.power(x, 2) * (y_pred - y)) # 手动计算梯度,写出表达式
grad_b = np.sum(y_pred - y)
w1-= lr * grad_w
b1-= lr * grad_b
## 5)可视化结果
plt.plot(x, y_pred, 'r-', label='predict')
plt.scatter(x, y, color='blue', marker='o', label='true')
plt.xlim(-1, 1)
plt.ylim(2, 6)
plt.legend()
plt.show()
print(w1, b1)

png

[[0.77828922]] [[0.7795984]]

png

[[2.99134284]] [[2.09741738]]

4.2 pytorch实现线性回归

import torch
## 1)生成训练数据,目标数据以及可视化
torch.manual_seed(100)
# x = torch.linspace(-1, 1, 100).reshape(100, 1)
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1)
y = 3 * x.pow(2) + 2 + 0.2*torch.rand(x.size())
plt.scatter(x.numpy(), y.numpy()) # 书上说要转为numpy,不过这里没有转也可以画出来
plt.show()

png

## 2)初始化权重参数
torch.manual_seed(100)
w = torch.rand(1, 1, requires_grad=True, dtype=torch.float)
b = torch.rand(1, 1, requires_grad=True, dtype=torch.float) # 默认dtype是float32,torch.float也是float32
## 3)训练模型
lr = 0.01
for i in range(800):
y_pred = x.pow(2).mm(w) + b
loss = 0.5*(y_pred - y)**2 # 定义损失函数
loss = loss.sum()
loss.backward() # 自动计算梯度,梯度放在叶子节点的grad属性中
with torch.no_grad(): # 被torch.no_grad包裹的代码不会把操作(运算等)记录到grad_fn属性中
w -= lr * w.grad
b -= lr* b.grad
w.grad.zero_() # 每一轮后,梯度要清零,不然会累加,计算不正确了
b.grad.zero_()
## 4)可视化结果
plt.plot(x.numpy(), y_pred.detach().numpy(), 'r-', label='predict')
plt.scatter(x.numpy(), y.numpy(), color='blue', marker='o', label='true')
plt.xlim(-1, 1)
plt.ylim(2, 6)
plt.legend()
plt.show()
print(w, b)

png

tensor([[2.9668]], requires_grad=True) tensor([[2.1138]], requires_grad=True)

Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead.
tensor.detach()是从计算图中脱离出来。 返回一个新的tensor,新的tensor和原来的tensor共享数据内存,但不涉及梯度计算,即requires_grad=False,这样才能调用numpy()函数,将其转为ndarray

4.3 tensorflow实现

# import tensorflow as tf
# 下面的placeholder是1.x版本的,所以不能向上面一样导入tensorflow了
import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()
INFO:tensorflow:Disabling eager execution
INFO:tensorflow:Disabling v2 tensorshape
WARNING:tensorflow:From E:\Miniconda3\lib\site-packages\tensorflow\python\compat\v2_compat.py:96: disable_resource_variables (from tensorflow.python.ops.variable_scope) is deprecated and will be removed in a future version.
Instructions for updating:
non-resource variables are not supported in the long term
INFO:tensorflow:Disabling resource variables
INFO:tensorflow:Disabling tensor equality
INFO:tensorflow:Disabling control flow v2
## 1)生成输入数据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)
## 2)初始化参数
x1 = tf.placeholder(tf.float32, shape=(None, 1)) # 创建两个占位符,分别用来存放数据x和目标值y,运行计算图时吗,导入数据
y1 = tf.placeholder(tf.float32, shape=(None, 1))
w = tf.Variable(tf.random_uniform([1], 0, 1.0))
b = tf.Variable(tf.zeros([1]))
print(w, b)
<tf.Variable 'Variable:0' shape=(1,) dtype=float32_ref> <tf.Variable 'Variable_1:0' shape=(1,) dtype=float32_ref>
## 2)正向传播和损失函数
y_pred = np.power(x, 2)*w + b
loss = tf.reduce_mean(tf.square(y-y_pred)) # 计算损失函数
grad_w, grad_b = tf.gradients(loss, [w, b]) # 计算梯度
# 梯度下降法更新参数
lr = 0.01
new_w = w.assign(w - lr*grad_w)
new_b = b.assign(b - lr*grad_b)

执行计算图时给new_w, new_b赋值
对于tf来说,更修参数是计算图内的内容,而对于pytorch,这部分属于计算图之外

## 3)训练模型
with tf.Session() as sess:
# 执行前初始化变量,w,b
sess.run(tf.global_variables_initializer())
for step in range(2000):
# 循环执行计算图时,每次都要把x,y赋值给x1, y1
loss_value, v_w, v_b = sess.run([loss, new_w, new_b], feed_dict={x1:x, y1:y})
if step%200 == 0:
print("损失值,权重,偏移量分别为:{:.4f}, {}, {}".format(loss_value, v_w, v_b))
## 4)可视化结果
plt.figure()
plt.scatter(x, y)
plt.plot(x, v_b + v_w*x**2)
损失值,权重,偏移量分别为:10.3297, [0.10263906], [0.06177498]
损失值,权重,偏移量分别为:0.1755, [1.6298081], [2.5692239]
损失值,权重,偏移量分别为:0.0919, [2.0188992], [2.457365]
损失值,权重,偏移量分别为:0.0492, [2.2914894], [2.3567092]
损失值,权重,偏移量分别为:0.0271, [2.4876125], [2.2840495]
损失值,权重,偏移量分别为:0.0157, [2.6287744], [2.231749]
损失值,权重,偏移量分别为:0.0097, [2.7303782], [2.194105]
损失值,权重,偏移量分别为:0.0066, [2.8035104], [2.1670094]
损失值,权重,偏移量分别为:0.0051, [2.8561475], [2.1475072]
损失值,权重,偏移量分别为:0.0042, [2.894034], [2.1334703]
[<matplotlib.lines.Line2D at 0x1ea2aeecaf0>]

png

1.torch与numpy的转换

a = np.arange(16).reshape(4, 4)
b = torch.from_numpy(a)
c = b.numpy()
posted @   rain-1227  阅读(82)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示