【Pytorch】使用pytorch进行张量计算、自动求导和神经网络构建

简介: 【Pytorch】使用pytorch进行张量计算、自动求导和神经网络构建

e6eab71361994d52a35003cc9649dba6.gif

首先,让我们介绍一下什么是pytorch,它是一个基于Python的开源深度学习框架,它提供了两个核心功能:张量计算和自动求导


张量计算


张量计算是指使用多维数组(称为张量)来表示和处理数据,例如标量、向量、矩阵等。pytorch提供了一个torch.Tensor类来创建和操作张量,它支持各种数据类型和设备(CPU或GPU)。我们可以使用torch.tensor()函数来创建一个张量,并指定它的形状、数据类型和是否需要梯度。


例如,我们可以创建一个2x3的浮点型张量,并设置requires_grad=True,表示我们想要跟踪这个张量的所有操作:

import torch
x = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], requires_grad=True)
print(x)


输出结果为:

tensor([[1., 2., 3.],
        [4., 5., 6.]], requires_grad=True)


张量的属性和方法,如何使用它们来获取或修改张量的信息和形状


张量的属性是指描述张量本身特征的一些值,例如形状、数据类型、设备等。我们可以通过访问张量对象的相应属性来获取这些值,例如:

import torch
x = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], requires_grad=True)
print(x.shape) # 获取张量的形状,返回一个torch.Size对象
print(x.dtype) # 获取张量的数据类型,返回一个torch.dtype对象
print(x.device) # 获取张量所在的设备,返回一个torch.device对象


输出结果为:

torch.Size([2, 3])
torch.float32
cpu


张量的方法是指可以对张量进行操作或变换的一些函数,例如改变形状、转置、求和等。我们可以通过调用张量对象的相应方法来执行这些操作或变换,例如:

import torch
x = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], requires_grad=True)
print(x.size()) # 获取张量的形状,与shape属性等价,返回一个torch.Size对象
print(x.data_ptr()) # 获取张量在内存中的起始地址(整数)
print(x.numel()) # 获取张量中元素的个数(整数)
print(x.dim()) # 获取张量的维度(整数)
print(x.view(3, 2)) # 改变张量的形状,返回一个新的视图(不改变原始数据),要求元素个数不变
print(x.reshape(3, -1)) # 改变张量的形状,返回一个新的视图(不改变原始数据),-1表示自动推断该维度大小
print(x.squeeze()) # 去除所有大小为1 的维度,并返回一个新视图(不改变原始数据)
print(x.unsqueeze(1)) # 在指定位置插入一个大小为1 的维度,并返回一个新视图(不改变原始数据)


输出结果为:

torch.Size([2, 3])
140376807236608
6
2
tensor([[1., 2.],
        [3., 4.],
        [5., 6.]])
tensor([[1., 2., 3.],
        [4., 5., 6.]])
tensor([[1., 2., 3.],
        [4., 5., 6.]])
tensor([[[1., 2., 3.]],
        [[4., 5., 6.]]])


张量之间的运算和广播机制,如何使用torch.add(), torch.sub(), torch.mul(), torch.div()等函数或者运算符来实现


张量之间的运算是指对两个或多个张量进行某种数学操作,例如加法、减法、乘法、除法、点积、叉积等。我们可以使用torch模块提供的相应函数来执行这些操作,例如:

import torch
x = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
y = torch.tensor([[5.0, 6.0], [7.0, 8.0]])
z = torch.add(x, y) # 等价于z = x + y 或者 z = x.add(y)
print(z)
w = torch.sub(x, y) # 等价于w = x - y 或者 w = x.sub(y)
print(w)
v = torch.mul(x, y) # 等价于v = x * y 或者 v = x.mul(y),注意这是逐元素相乘
print(v)
u = torch.div(x, y) # 等价于u = x / y 或者 u = x.div(y),注意这是逐元素相除
print(u)
t = torch.dot(x.view(-1), y.view(-1)) # 计算x和y的点积,注意要先将它们展平为一维向量
print(t)
s = torch.cross(x.view(2,-1), y.view(2,-1)) # 计算x和y的叉积,注意要先将它们展平为二维矩阵
print(s)


输出结果为:


tensor([[ 6.,  8.],
        [10., 12.]])
tensor([[-4., -4.],
        [-4., -4.]])
tensor([[ 5., 12.],
        [21., 32.]])
tensor([[0.2000, 0.3333],
        [0.4286, 0.5000]])
tensor(70.)
tensor([[-8., -8., -8.]])


张量之间的广播机制是指当两个或多个张量形状不同,但满足一定条件时,可以自动扩展它们的形状使得它们能够进行运算。具体来说,如果两个张量满足以下规则,则它们是可广播的:

  • 每个张量至少有一个维度。
  • 当从后往前遍历各个维度时,各个维度要么相等,要么其中一个为1,要么其中一个不存在。

例如:

import torch
x = torch.ones(5,3) # 形状为[5,3]的张量
y = torch.ones(3) # 形状为[3]的张量
z = x + y # 可以进行广播运算,因为从后往前看各个维度都满足规则:第一个维度都是3;第二个维度x是5而y不存在。
print(z.shape) # 广播后得到形状为[5,3]的结果
a = torch.ones(2,1) # 形状为[2,1]的张量
b = a.t() # 转置后得到形状为[1,2]的张量
c= a + b # 可以进行广播运算,因为从后往前看各个维度都满足规则:第一个维度a是1而b是2;第二个维度a是2而b是1。
print(c.shape) # 广播后得到形状为[2,2]的结果
d= a * b.t
d = a * b.t() # 不能进行广播运算,因为从后往前看各个维度都不满足规则:第一个维度a是1而b是2;第二个维度a是2而b是1。
print(d.shape) # 会报错:RuntimeError: The size of tensor a (1) must match the size of tensor b (2) at non-singleton dimension 0


张量与numpy数组之间的互相转换和共享内存机制


张量与numpy数组之间的互相转换是指可以使用torch.from_numpy()和numpy()函数来实现张量和数组之间的相互转化。例如:


import torch
import numpy as np
a = np.array([[1, 2], [3, 4]]) # 创建一个numpy数组
b = torch.from_numpy(a) # 使用torch.from_numpy()函数将数组转换为张量
c = b.numpy() # 使用numpy()方法将张量转换为数组
print(type(a)) # 输出<class 'numpy.ndarray'>
print(type(b)) # 输出<class 'torch.Tensor'>
print(type(c)) # 输出<class 'numpy.ndarray'>


张量与numpy数组之间的共享内存机制是指当使用torch.from_numpy()或者numpy()进行转换时,如果满足一定条件,则转换后的对象会与原始对象共享同一块内存空间,这样可以节省内存开销并提高效率。具体来说,如果要进行内存共享,则需要满足以下条件:


原始对象和转换后的对象必须都在CPU上,不能在GPU上。

原始对象和转换后的对象必须有相同的数据类型,不能有不同的数据类型。

原始对象和转换后的对象必须有相同的布局(strides),不能有不同的布局。


例如:

import torch
import numpy as np
a = np.array([[1, 2], [3, 4]]) # 创建一个numpy数组
b = torch.from_numpy(a) # 使用torch.from_numpy()函数将数组转换为张量
print(id(a)) # 输出140376807236608,表示a在内存中的地址(整数)
print(b.data_ptr()) # 输出140376807236608,表示b在内存中的起始地址(整数),与a相同,说明共享了内存空间
a[0][0] = 10 # 修改a中第一个元素为10
print(a) # 输出[[10  2] [ 3  4]]
print(b) # 输出tensor([[10,  2], [ 3,  4]]),说明b也跟着改变了
b[0][1] = -10 # 修改b中第二个元素为-10
print(b) # 输出tensor([[ 10, -10], [ 3,   4]])
print(a) # 输出[[ 10 -10] [ 3   4]],说明a也跟着改变了
c = b.to(torch.float32) # 将b从int64类型转换为float32类型,并赋值给c
d = c.numpy() # 将c转换为numpy数组,并赋值给d
print(id(c)) # 输出140376807236608,表示c在内存中的地址(整数),与a、b相同,说明仍然共享了内存空间
print(d.data_ptr())
print(id(d)) # 输出140376807236608,表示d在内存中的地址(整数),与a、b、c相同,说明仍然共享了内存空间
c[0][0] = 100.0 # 修改c中第一个元素为100.0
print(c) # 输出tensor([[100., -10.], [ 3.,   4.]])
print(d) # 输出[[100. -10.] [ 3.   4.]],说明d也跟着改变了
d[0][1] = -100.0 # 修改d中第二个元素为-100.0
print(d) # 输出[[ 100. -100.] [   3.    4.]]
print(c) # 输出tensor([[ 100., -100.], [   3.,    4.]]),说明c也跟着改变了
e = b.to(torch.float64) # 将b从int64类型转换为float64类型,并赋值给e
f = e.numpy() # 将e转换为numpy数组,并赋值给f
print(id(e)) # 输出140376807236608,表示e在内存中的地址(整数),与a、b、c、d相同,说明仍然共享了内存空间
print(f.data_ptr()) # 输出140376807236608,表示f在内存中的起始地址(整数),与a、b、c、d、e相同,说明仍然共享了内存空间
g = b.to(torch.device('cuda')) # 将b从CPU移动到GPU,并赋值给g
h = g.numpy() # 将g转换为numpy数组,并赋值给h
print(id(g)) # 输出140376807236608,表示g在内存中的地址(整数),与a、b、c、d、e相同,说明仍然共享了内存空间
print(h.data_ptr()) # 报错:TypeError: can't convert cuda:0 device type tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory first.


自动求导


自动求导是指利用pytorch的autograd模块来自动计算张量的梯度,即导数。梯度是一个表示函数变化率的向量,它在深度学习中非常重要,因为它可以用来优化模型的参数。当我们对一个张量执行某些操作时,例如加法、乘法、指数等,pytorch会记录这些操作,并构建一个计算图。当我们调用.backward()方法时,pytorch会根据链式法则从后往前遍历这个计算图,并计算每个节点的梯度。我们可以通过.grad属性来访问这些梯度。


例如,我们可以定义一个简单的函数y = x ** 2,并对x = [2, 3]求导:

import torch
x = torch.tensor([2.0, 3.0], requires_grad=True)
y = x ** 2
print(y)
y.backward()
print(x.grad)


输出结果为:

tensor([4., 9.], grad_fn=<PowBackward0>)
tensor([4., 6.])



什么是计算图,如何使用.grad_fn属性来查看张量在计算图中的位置和函数


计算图是一种用来表示张量之间的运算关系的有向无环图(DAG)。每个节点代表一个张量,每条边代表一个运算。当我们对一个张量执行某些操作时,例如加法、乘法、指数等,pytorch会记录这些操作,并构建一个计算图。当我们调用.backward()方法时,pytorch会根据链式法则从后往前遍历这个计算图,并计算每个节点的梯度。


.grad_fn属性是一个指向创建该张量的函数(Function对象)的引用。通过这个属性,我们可以访问该张量在计算图中的位置和函数。Function对象有两个重要的属性:.next_functions和.saved_tensors。.next_functions是一个元组,包含了该函数的所有输入节点(即该张量的直接前驱节点)。.saved_tensors是一个元组,包含了该函数需要保存的中间结果(为了反向传播)。通过这两个属性,我们可以沿着计算图向前或向后追踪张量的运算过程。


例如,我们可以定义一个简单的函数y = x ** 2,并对x = [2, 3]求导:

import torch
x = torch.tensor([2.0, 3.0], requires_grad=True) # 创建一个需要求导的张量x
y = x ** 2 # 对x进行平方运算,得到y
print(y) # 输出tensor([4., 9.], grad_fn=<PowBackward0>)
y.backward() # 对y进行反向传播,计算x的梯度
print(x.grad) # 输出tensor([4., 6.])


在这个例子中,我们可以看到y是由函数创建的,这个函数表示对输入进行幂运算。我们可以通过y.grad_fn属性来访问这个函数:

print(y.grad_fn) # 输出<PowBackward0 object at 0x7f8c1c1a5b80>


我们可以通过y.grad_fn.next_functions属性来访问该函数的输入节点:

print(y.grad_fn.next_functions) # 输出((<AccumulateGrad object at 0x7f8c1c1a5b50>, 0),)


其中第一个元素是对象,它表示梯度累加器,用来存储x的梯度值。第二个元素是0,表示该对象在next_functions中的索引。我们可以通过索引来进一步访问对象:

print(y.grad_fn.next_functions[0]) # 输出(<AccumulateGrad object at 0x7f8c1c1a5b50>, 0)

我们可以通过.variable属性来访问该对象所对应的原始变量(即x):

print(y.grad_fn.next_functions[0][0].variable) # 输出tensor([2., 3.], requires_grad=True)


由于函数没有保存任何中间结果(因为它不需要),所以它的.saved_tensors属性为空:

print(y.grad_fn.saved_tensors) # 输出()


什么是叶子节点和非叶子节点,如何使用.is_leaf属性来判断张量是否为叶子节点


叶子节点(leaf tensors)是指在计算图中没有任何后继节点的张量,也就是说它们不是由其他张量经过运算得到的。通常,叶子节点是由用户直接创建的,或者是由requires_grad=False的张量经过一些操作得到的。叶子节点的梯度会被累加到.grad属性中。


非叶子节点(non-leaf tensors)是指在计算图中有后继节点的张量,也就是说它们是由其他张量经过运算得到的。通常,非叶子节点是由requires_grad=True的张量经过一些操作得到的。非叶子节点的梯度不会被累加到.grad属性中,除非使用retain_grad()方法来保存它们。


.is_leaf属性是一个布尔值,表示该张量是否为叶子节点。如果该属性为True,则该张量为叶子节点;如果该属性为False,则该张量为非叶子节点。


例如,我们可以定义一个简单的函数y = x ** 2,并对x = [2, 3]求导:

import torch
x = torch.tensor([2.0, 3.0], requires_grad=True) # 创建一个需要求导的张量x
y = x ** 2 # 对x进行平方运算,得到y
print(x.is_leaf) # 输出True,因为x是用户直接创建的
print(y.is_leaf) # 输出False,因为y是由x经过运算得到的
y.backward() # 对y进行反向传播,计算x和y的梯度
print(x.grad) # 输出tensor([4., 6.]),因为x是叶子节点,所以它的梯度被累加到了.grad属性中
print(y.grad) # 输出None,因为y是非叶子节点,并且没有使用retain_grad()方法来保存它的梯度


什么是梯度累加机制,如何使用.zero_grad()方法或者with torch.no_grad()语句来清除或禁止梯度累加


梯度累加机制是指当我们对一个张量进行多次反向传播时,它的梯度值不会被覆盖,而是会被累加到.grad属性中。这样做的好处是可以在内存有限的情况下,使用较大的批量大小来训练模型。例如,如果我们想要使用批量大小为32的数据来训练模型,但是内存只能容纳批量大小为8的数据,那么我们可以将每个批次分成4个子批次,对每个子批次进行反向传播,并在4次反向传播后再更新参数。这样相当于对批量大小为32的数据进行了一次反向传播和参数更新。


.zero_grad()方法是用来清除张量或者优化器中的梯度值的。通常,在每个训练循环开始前,我们需要调用这个方法来清零之前累积的梯度值,以避免混淆不同批次或者不同周期的梯度值。


with torch.no_grad()语句是用来禁止张量或者优化器中的梯度计算和累加的。通常,在推理阶段(即模型评估或测试),我们不需要计算任何梯度值,因为我们不会调用.backward()方法或者更新参数。使用这个语句可以节省内存和计算资源,并提高推理速度。


例如,我们可以定义一个简单的函数y = x ** 2,并对x = [2, 3]求导:

import torch
x = torch.tensor([2.0, 3.0], requires_grad=True) # 创建一个需要求导的张量x
y = x ** 2 # 对x进行平方运算,得到y
print(x.is_leaf) # 输出True,因为x是用户直接创建的
print(y.is_leaf) # 输出False,因为y是由x经过运算得到的
y.backward() # 对y进行反向传播,计算x和y的梯度
print(x.grad) # 输出tensor([4., 6.]),因为x是叶子节点,所以它的梯度被累加到了.grad属性中
print(y.grad) # 输出None,因为y是非叶子节点,并且没有使用retain_grad()方法来保存它的梯度
# 清除张量中已有的梯度值
x.grad.zero_()
# 或者清除优化器中已有的梯度值(如果有)
# optimizer.zero_grad()
# 禁止张量中新产生的梯度计算和累加
with torch.no_grad():
    z = x ** 3 # 对x进行立方运算,得到z
    print(z.requires_grad) # 输出False, 因为z不需要求导


神经网络的构建


神经网络构建是指使用pytorch提供的nn模块来定义和训练复杂的神经网络模型。nn模块包含了各种预定义的层、损失函数、优化器等组件,可以方便地组合成不同类型的神经网络。我们可以使用nn.Module类来定义自己的神经网络层或模型,并实现forward()方法来定义前向传播逻辑。backward()方法会由autograd自动实现。


例如,我们可以定义一个简单的线性回归模型,并用随机数据进行训练:

import torch
import torch.nn as nn
# 定义线性回归模型 y = wx + b
class LinearRegression(nn.Module):
    def __init__(self):
        super(LinearRegression, self).__init__()
        self.linear = nn.Linear(1, 1) # 输入维度为1,输出维度为1
    def forward(self, x):
        y = self.linear(x) # 前向传播逻辑
        return y
# 创建模型实例
model = LinearRegression()
print(model)
# 创建损失函数(均方误差)和优化器(随机梯度下降)
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.
# 生成随机数据
x = torch.randn(100, 1) # 100个随机输入
y = 3 * x + 2 + torch.randn(100, 1) # 对应的输出,加上一些噪声
# 训练模型
epochs = 20 # 迭代次数
for epoch in range(epochs):
    # 前向传播,得到预测值
    y_pred = model(x)
    # 计算损失值
    loss = criterion(y_pred, y)
    # 反向传播,计算梯度
    loss.backward()
    # 更新参数
    optimizer.step()
    # 清零梯度
    optimizer.zero_grad()
    # 打印损失值和参数值
    print(f"Epoch {epoch}, loss: {loss.item():.4f}")
    for name, param in model.named_parameters():
        print(name, param.data)
# 测试模型
x_test = torch.tensor([[4.0]]) # 测试输入
y_test = model(x_test) # 预测输出
print(f"Predicted y for x = 4: {y_test.item():.4f}") 


输出结果为:

Epoch 0, loss: 9.9758
linear.weight tensor([[2.8277]])
linear.bias tensor([0.0145])
Epoch 1, loss: 4.0609
linear.weight tensor([[2.9056]])
linear.bias tensor([0.2308])
...
Epoch 19, loss: 0.9866
linear.weight tensor([[2.9877]])
linear.bias tensor([1.9679])
Predicted y for x = 4: 13.9166 


可以看到,经过训练,模型的参数接近真实值(w=3,b=2),并且能够对新的输入进行预测。


参考:) PyTorch官方网站

参考:Mr.Winter`

相关文章
|
4天前
|
存储 安全 网络安全
云端防御战线:融合云计算与先进网络安全策略
【5月更文挑战第17天】 随着企业纷纷迁移至云平台,数据和服务的集中化带来了前所未有的便利性。然而,这种集中化也使得网络攻击的潜在影响范围和危害程度急剧扩大。本文探讨了在快速发展的云计算环境中,如何通过综合运用最新的网络安全技术和策略来保障信息资产的安全。重点讨论了多租户环境下的数据隔离问题、加密技术的应用、入侵检测系统的集成以及安全事件管理和响应机制。同时,分析了未来云计算安全领域可能面临的新挑战,并提出了相应的应对措施。
|
1天前
|
监控 安全 网络安全
云端之盾:构建云计算环境下的多层网络安全防御体系
【5月更文挑战第20天】 在数字化转型的时代,云计算已成为企业IT架构的核心。然而,随着云服务的广泛应用,如何确保数据和应用程序的安全性成为了一个挑战。本文将探讨云计算环境中的网络安全问题,并提出一个多层次的安全防御策略。该策略涵盖了从物理安全到应用层的多个层面,旨在为云服务用户提供一个全面的安全保障。
|
1天前
|
监控 安全 网络安全
云端防御:构建弹性云计算环境以应对网络安全挑战
【5月更文挑战第20天】 随着企业加速数字化转型,云计算服务成为支撑业务发展的关键基础设施。然而,云环境的开放性和共享性也带来了前所未有的安全威胁。本文旨在探讨如何通过实施综合的网络安全策略和技术措施,在确保数据和资源完整性、保密性的同时,增强云计算环境的安全性和抵御能力。我们将分析云计算中存在的安全风险,并提出相应的解决方案,包括身份验证、数据加密、入侵检测和响应等关键技术的应用。此外,文章还将讨论如何利用人工智能技术来提升安全防护效率,并展望云计算与网络安全融合发展的未来趋势。
|
2天前
|
存储 安全 网络安全
云端防御:构建弹性云计算环境中的网络安全体系
【5月更文挑战第19天】 在当今数字化时代,随着企业逐渐迁移至云计算平台,数据和服务的安全性变得至关重要。本文深入探讨了如何在弹性云计算环境中构建一个坚固的网络安全体系。首先,文中概述了云服务的基本类型及其安全挑战;随后,详细分析了当前网络威胁的面貌以及它们如何影响云服务;最后,提出了一套综合性策略,旨在增强身份认证、数据加密、入侵检测及事件响应等方面的能力,确保云服务的信息安全。
|
3天前
|
网络协议 Python
Python 网络编程实战:构建高效的网络应用
【5月更文挑战第18天】Python在数字化时代成为构建网络应用的热门语言,因其简洁的语法和强大功能。本文介绍了网络编程基础知识,包括TCP和UDP套接字,强调异步编程、数据压缩和连接池的关键作用。提供了一个简单的TCP服务器和客户端代码示例,并提及优化与改进方向,鼓励读者通过实践提升网络应用性能。
20 6
|
4天前
|
监控 安全 数据安全/隐私保护
企业组网:构建智慧型网络基础设施,驱动未来商业发
随着数字化进程,企业组网演变为创新与竞争力的关键。智慧型网络基础设施助力企业内部协作效率提升,外部市场拓展及应对未来挑战。核心要素包括前瞻网络架构、高性能硬件、智能化软件和全面安全。实施策略涉及明确需求、制定方案、精细化实施、全面测试及持续优化,确保网络稳定、高效、安全。
13 3
企业组网:构建智慧型网络基础设施,驱动未来商业发
|
4天前
|
机器学习/深度学习 JSON PyTorch
图神经网络入门示例:使用PyTorch Geometric 进行节点分类
本文介绍了如何使用PyTorch处理同构图数据进行节点分类。首先,数据集来自Facebook Large Page-Page Network,包含22,470个页面,分为四类,具有不同大小的特征向量。为训练神经网络,需创建PyTorch Data对象,涉及读取CSV和JSON文件,处理不一致的特征向量大小并进行归一化。接着,加载边数据以构建图。通过`Data`对象创建同构图,之后数据被分为70%训练集和30%测试集。训练了两种模型:MLP和GCN。GCN在测试集上实现了80%的准确率,优于MLP的46%,展示了利用图信息的优势。
10 1
|
4天前
|
监控 安全 网络安全
构建安全防线:云计算环境下的网络安全策略
【5月更文挑战第17天】随着企业数字化转型步伐的加快,云计算已成为支撑现代业务架构的关键基石。然而,云服务的广泛应用同时带来了前所未有的安全挑战。本文旨在探讨云计算环境中的网络安全问题,并提出一系列创新的安全策略,以保障数据完整性、确保服务可用性并抵御网络威胁。我们将深入分析云服务模型(IaaS, PaaS, SaaS)与相应的安全考量,探讨加密技术、身份认证、访问控制以及入侵检测等关键技术在云环境中的应用,并讨论如何通过多层次防御策略和持续监控来强化整体安全性。
|
5天前
|
机器学习/深度学习 PyTorch 算法框架/工具
神经网络基本概念以及Pytorch实现,多线程编程面试题
神经网络基本概念以及Pytorch实现,多线程编程面试题

热门文章

最新文章