Bootstrap

PyTorch基础学习03_数学运算&自动微分

目录

一、数学运算

1、基本操作

2、三角函数

3、统计学函数

二、保存和加载

三、并行化

四、自动微分

1、相关概念

2、计算梯度

1.标量梯度计算

2.向量梯度计算

3.多标量梯度计算

4.多向量梯度计算

5.矩阵梯度计算

3、梯度上下文控制

1、梯度控制

2、梯度更新

3、叶子节点


导入torch库:

import torch

一、数学运算

1、基本操作

  • floor: 向下取整;

  • ceil:向上取整;

  • round:四舍五入;

  • trunc:裁剪,只保留整数部分;

  • frac:只保留小数部分;

  • fix:向零方向舍入;

  • %:取余;

  • abs:取绝对值。

data = torch.tensor(
    [
        [1, 2, -3.5],  # 1
        [4, 5, 6],  # 2
        [10.5, 18.6, 19.6],  # 3
        [11.05, 19.3, 20.6],  # 4
    ]
)
print(data)
# 向下取整(往小取 返回不大于每个元素的最大整数)
x1 = torch.floor(data)
print(x1)
# 向上取整(往大取 返回不小于每个元素的最小整数)
x2 = torch.ceil(data)
print(x2)
# 四舍五入 与python round()一致 四舍六入五看齐(看个位奇偶,奇进偶舍)
x3 = torch.round(data)
print(x3)
# 截断,只保留整数部分
x4 = torch.trunc(data)
print(x4)
# 截断,只保留小数部分
x5 = torch.frac(data)
print(x5)
# 向零方向舍入(舍入到最接近零的整数)
x6 = torch.fix(data)
print(x6)
# 取余
x7 = torch.fmod(data, 2)
print(x7)
# 绝对值
x8 = torch.abs(data)
print(x8)

2、三角函数

  • torch.cos(input,out=None)

  • torch.cosh(input,out=None) # 双曲余弦函数

  • torch.sin(input,out=None)

  • torch.sinh(input,out=None) # 双曲正弦函数

  • torch.tan(input,out=None)

  • torch.tanh(input,out=None) # 双曲正切函数

# torch.set_printoptions(sci_mode=False)

print(torch.pi) # 圆周率
deg = torch.pi/180
data = torch.tensor([0,90*deg,45*deg])
x1 = torch.sin(data)
print(x1)
x2 = torch.cos(data)
print(x2)
x3 = torch.sinh(data)
print(x3)
x4 = torch.cosh(data)
print(x4)
x5 = torch.tan(data)
print(x5)
x6 = torch.tanh(data)
print(x6)

3、统计学函数

  1. torch.mean(): 计算张量的平均值。

  2. torch.sum(): 计算张量的元素之和。

  3. torch.std(): 计算张量的标准差。

  4. torch.var(): 计算张量的方差。

  5. torch.median(): 计算张量的中位数。

  6. torch.max(): 计算张量的最大值。

  7. torch.min(): 计算张量的最小值。

  8. torch.mode():  计算张量众数

  9. torch.sort(): 对张量进行排序。 返回排序后的张量values和索引indices

  10. torch.topk(): 返回张量中的前 k 个最大值或最小值。

  11. torch.histc(): 计算张量的直方图。

  12. torch.unique(): 返回张量中的唯一值。

  13. torch.bincount(): 计算张量中每个元素的出现次数。

torch.manual_seed(66)
x = torch.tensor([1,2,2,3,3,3,4,4,5]).type(torch.float32)
print(x)
# 平均值
x1 = x.mean()
print(x1)
# 求和
x2 = x.sum()
print(x2)
# 标准差
x3 = x.std()
print(x3)
# 方差
x4 = x.var()
print(x4)
# 中位数
x5 = x.median()
print(x5)
# 最大值
x6 = x.max()
print(x6)
# 最小值
x7 = x.min()
print(x7)
# 众数
x8 = torch.mode(x)
print(x8)
# 排序 返回排序后的张量values和索引indices
x9 = torch.sort(x)
print(x9)

x = torch.tensor([1,2,2,3,3,3,4,4,5,2,3,4,6]).type(torch.float32)
print(torch.topk(x,3)) # 返回前3个最大值
print(torch.histc(x,bins=10, min=2,max=4)) # 区间[min,max]分成bins份 表示各个区间的元素计数
print(torch.unique(x)) # 返回分类的数据集中的数据类型

x = torch.tensor([1,2,2,3,3,3,4,4,5,2,3,4,6])
print(torch.bincount(x)) # 返回张量中每个元素的出现次数

二、保存和加载

# 保存
x = torch.tensor([1,2,3])
torch.save(x,'./data/tensor01.pth')

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 加载
y = torch.load('./data/tensor01.pth',map_location=device)
print(y)
print(y.device)

三、并行化

在 PyTorch 中,可以查看和设置用于 CPU 运算的线程数。PyTorch 使用多线程来加速 CPU 运算,但有时可能需要调整线程数来优化性能。

# torch.get_num_threads() 来查看当前 PyTorch 使用的线程数
def test01():
    count = torch.get_num_threads()
    print(count)


# torch.set_num_threads() 设置 PyTorch 使用的线程数
def test02():
    torch.set_num_threads(4)
    count = torch.get_num_threads()
    print(count)

if __name__ == '__main__':
    test01()
    test02()

 设置线程数时,确保考虑到你的 CPU 核心数和其他进程的资源需求,以获得最佳性能。

注意事项

  • 线程数设置过高可能会导致线程竞争,反而降低性能;

  • 线程数设置过低可能会导致计算资源未得到充分利用;

  • 当使用 GPU 进行计算时,线程数设置对性能影响较小,因为 GPU 计算并不依赖于 CPU 线程数。

四、自动微分

自动微分模块torch.autograd负责自动计算张量操作的梯度,具有自动求导功能。自动微分模块是构成神经网络训练的必要模块,可以实现网络权重参数的更新,使得反向传播算法的实现变得简单而高效。

1、相关概念

  1. 张量

    Torch中的对象都为张量,属性requires_grad决定是否对其进行梯度计算。默认是 False,如需计算梯度则设置为True。

  2. 计算图

    torch.autograd通过创建一个动态计算图来跟踪张量的操作,每个张量是计算图中的一个节点,节点之间的操作构成图的边。

  3. 反向传播

    使用tensor.backward()方法执行反向传播,从而计算张量的梯度。这个过程会自动计算每个张量对损失函数的梯度。

  4. 梯度

    计算得到的梯度通过tensor.grad访问,这些梯度用于优化模型参数,以最小化损失函数。

2、计算梯度

使用tensor.backward()方法执行反向传播,以计算张量的梯度。

1.标量梯度计算
# 创建张量:要计算梯度则必须为浮点类型
x = torch.tensor(3, require_grad=True, dtype=torch.float64)
# 定义函数
y = x**2 + 2*x - 5
# 计算梯度,也就是反向传播
y.backward() # # 梯度计算 1.y函数对x求导函数 y'=2*x+2 2.代入x的当前值 x=3 3.求出导数值 y'=2*3+2
# 读取梯度值
print(x.grad) # tensor(8.)
2.向量梯度计算
# 向量梯度计算
x = torch.tensor([1,2,3],requires_grad=True,dtype=torch.float64)
y = x**2 + 2*x - 5
print(y) # tensor([-2.,  3., 10.], dtype=torch.float64, grad_fn=<SubBackward0>)

y = y.sum() # y必须是一个标量才能用这个标量对x求导 设置一个复合函数

y.backward() # y'=2x+2
print(x.grad) # tensor([4., 6., 8.], dtype=torch.float64)
3.多标量梯度计算
# 多标量的梯度计算
x1 = torch.tensor(1,requires_grad=True,dtype=torch.float64)
x2 = torch.tensor(2,requires_grad=True,dtype=torch.float64)
y = x1**2 + 3*x2 - 5

y.backward() # 偏导数

print(x1.grad)
print(x2.grad)
4.多向量梯度计算
# 多向量的梯度计算
x1 = torch.tensor([1,2,3],requires_grad=True,dtype=torch.float64)
x2 = torch.tensor([4,5,6],requires_grad=True,dtype=torch.float64)
y = x1**2 + 3*x2 - 5

y = y.sum()

y.backward() # 偏导数
print(x1.grad) # 2*x1 tensor(2., dtype=torch.float64)
print(x2.grad) # 3 tensor(3., dtype=torch.float64)
5.矩阵梯度计算
# 矩阵梯度计算
x = torch.tensor([[1,2],[3,4]],requires_grad=True,dtype=torch.float64)
y = x**2 + 2*x - 5

y = y.sum()

y.backward()
print(x.grad)

3、梯度上下文控制

梯度计算的上下文控制和设置对于管理计算图、内存消耗、以及计算效率至关重要。

1、梯度控制

梯度计算是有性能开销的,有些时候只是简单的运算,此时并不需要梯度。

# 正常情况下
def test01():
    x = torch.tensor(10.5, requires_grad=True)
    print(x.requires_grad)  # True

    # 默认y的requires_grad=True
    y = x**2 + 2 * x + 3
    print(y.requires_grad)  # True


def test02():
    x = torch.tensor(5, requires_grad=True,dtype=torch.float64)
    # 关闭y的梯度计算 使用with进行上下文管理
    with torch.no_grad():
        y = x**2 + 2*x - 5
        print(y.requires_grad) # False

# 使用装饰器
@torch.no_grad()
def test03():
    x = torch.tensor([1,2,3], requires_grad=True,dtype=torch.float64)
    y = x**2 + 2*x - 5
    y = y.sum()
    print(y.requires_grad) # False
    

# 自己创建装饰器
def my_no_grad(func):
    def wrapper(): 
        with torch.no_grad():
            re = func()
            return re
    return wrapper


@my_no_grad
def test04():
    x = torch.tensor([1,2,3], requires_grad=True,dtype=torch.float64)
    y = x**2 + 2*x - 5
    y = y.sum()
    print(y.requires_grad) # False


# 全局设置关闭梯度计算
def test05():
    x = torch.tensor([1,2,3], requires_grad=True,dtype=torch.float64)
    torch.set_grad_enabled(False)
    y = x**2 + 2*x - 5
    y = y.sum()
    y.backward()
    print(x.requires_grad)
    print(y.requires_grad)
    print(x.grad)


# 累计梯度
def test06():
    x = torch.tensor(1, requires_grad=True,dtype=torch.float64)
    y = x**2 + 2*x - 5
    y.backward()
    print(x.grad)

    y = 2*x**2 + 7
    y.backward()
    print(x.grad)

# 累计梯度02
def test07():
    x = torch.tensor(4, requires_grad=True,dtype=torch.float64)
    for _ in range(4):
            y = x**2 + 2*x - 5
            y.backward()
            print(x.grad)

# 梯度清零
def test08():
    x = torch.tensor(4, requires_grad=True,dtype=torch.float64)
    y = x**2 + 2*x - 5
    y.backward() # 2*x + 2
    print(x.grad)
    
    z = 3*x**2 + 7*x
    # 清零
    x.grad.zero_() # 不清零则为 10+31=41
    z.backward() # 清零后为 6*4+7=31
    print(x.grad)

    # 综合梯度清零操作
    for _ in range(3):
        y = x**2 + 2 * x + 7
        z = y.mean()
        # 反向传播之前先对梯度进行清零
        if x.grad is not None:
            x.grad.zero_()
            
        z.backward()
        print(x.grad)



if __name__ == '__main__':
    test01()
    test02()
    test03()
    test04()
    test05()
    test06()
    test07()
    test08()
2、梯度更新

大多数情况下是不需要梯度累加的,反向传播之前可以先用 .zero_() 对梯度进行清零。

更新梯度时,注意更新的数据应是data,直接改变原tensor会导致其变成新的数据。

# 标量训练
def test01():
    # 生成初始化w
    w = torch.tensor(25., requires_grad=True)
    # 训练参数
    lr = 0.01
    epoch = 5
    for i in range(epoch):
        # 生成损失函数
        loss = 3*w**2 + 2*w - 5
        # 梯度清零
        if w.grad is not None: 
            w.grad.zero_()
        # 反向传播 求当前w的导数值:梯度值,斜率
        loss.backward()
        # 当前斜率
        print(w.grad)
        # 更新梯度
        # w的tensor不能修改 避免w变成新的数据 应修改w.data
        w.data = w.data - lr*w.grad.data
        print(w)
    # 访问训练后的w的值
    print(w.data)

# 向量训练
def test02():
    # 生成初始化w
    w = torch.tensor([10,20,30], requires_grad=True,dtype=torch.float64)
    # 训练参数
    lr = 0.01
    epoch = 5
    for i in range(epoch):
        # 生成损失函数
        loss = 3*w**2 + 2*w - 5
        loss = loss.sum() # 设置一个复合函数
        # 梯度清零
        if w.grad is not None: 
            w.grad.zero_()
        # 反向传播 求当前w的导数值:梯度值,斜率
        loss.backward()
        # 当前斜率
        print(w.grad)
        # 更新梯度
        # w的tensor不能修改 避免w变成新的数据 应修改w.data
        w.data = w.data - lr*w.grad.data
        print(w)

    # 访问训练后的w的值
    print(w.data)
    # 保存训练好的权重数据
    torch.save(w.data,'./data/weight.pth')

# 加载训练好的权重数据
def detect():
    w = torch.load('./data/weight.pth',map_location='cuda')
    # 使用w
    print(w)


if __name__ == '__main__':
    # test01()
    test02()
    detect()
3、叶子节点

当想使用不含求导功能的tensor时,可以使用detach()创建张量,该张量是作为叶子结点存在的,并且该张量和原张量共享数据,只是该张量不需要计算梯度。

原因:可以求导的张量不能直接当作普通的tensor使用,如转换为numpy数组操作: .numpy()

# detach()产生的张量是作为叶子结点存在的,并且该张量和原张量共享数据,只是该张量不需要计算梯度。
def test01():
    x = torch.tensor([1, 2, 3], requires_grad=True,dtype=torch.float32)
    # x1 = x.numpy() # 报错 如果x是一个可以求导的张量,那么它就不能直接当作普通的tensor使用


def test02():
    x = torch.tensor([1, 2, 3], requires_grad=True,dtype=torch.float32)
    x2 = x.detach() # 创建一个叶子结点,并且和x共享数据,但是不需要计算梯度
    print(x) # tensor([1., 2., 3.], requires_grad=True)
    print(x2) # tensor([1., 2., 3.])
    
    print(id(x),id(x2)) # 两个对象
    print(id(x.data),id(x2.data)) # 数据共享

    x2[0] = 5
    print(x,x2)


if __name__ == '__main__':
    test02()

;