jibinghu

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
  7 随笔 :: 0 文章 :: 0 评论 :: 1365 阅读

pytorch框架初上手

PyTorch 是一个针对深度学习, 并且使用 GPU 和 CPU 来优化的 tensor library (tensor库)

中文文档: https://pytorch.org/resources

梯度/导数计算

# linear.py

import torch
import numpy as np

x = torch.tensor(3,)
w = torch.tensor(4.,requires_grad=True)
b = torch.tensor(5.,requires_grad=True)

y = w * x + b
y.backward()

print('dy/dx:', x.grad)
print('dy/dw:', w.grad)
print('dy/db:', b.grad)

# print(x,w.item,b)

线性回归

# linear_stastic.py

import numpy as np
import torch

# Input (temp, rainfall, humidity)
inputs = np.array([[73, 67, 43], 
                   [91, 88, 64], 
                   [87, 134, 58], 
                   [102, 43, 37], 
                   [69, 96, 70]], dtype='float32')

# Targets (apples, oranges)
targets = np.array([[56, 70], 
                    [81, 101], 
                    [119, 133], 
                    [22, 37], 
                    [103, 119]], dtype='float32')

inputs = torch.from_numpy(inputs)
targets = torch.from_numpy(targets)

# 随机生成满足shape类型的tensor,并加入grad传导
w = torch.randn(2,3,requires_grad=True)
b = torch.randn(2,requires_grad=True)

# 加载线性函数
def model(x):
    return x @ w.t() + b

# print(predict)
# 求均值方差
def mes(t1,t2):
    diff = t1 - t2
    return torch.sum(diff * diff) / diff.numel()

# epoch = 1000
for i in range(1000):
    predict = model(inputs)
    loss = mes(predict,targets)
    print("loss",i+1,"=",loss)
    loss.backward()
    with torch.no_grad():
        # 1e-5 means learning rate
        w -= w.grad * 1e-5
        b -= b.grad * 1e-5
        w.grad.zero_()
        b.grad.zero_()
        print("w,b",i+1,"=",w,b)

print(targets,'\n',predict)

torch内置函数实现线性回归

# linear_stastic_intorch.py

import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
import torch.nn.functional as F

# 加载训练集和测试集
# Input (temp, rainfall, humidity)
inputs = np.array([[73, 67, 43], 
                   [91, 88, 64], 
                   [87, 134, 58], 
                   [102, 43, 37], 
                   [69, 96, 70], 
                   [74, 66, 43], 
                   [91, 87, 65], 
                   [88, 134, 59], 
                   [101, 44, 37], 
                   [68, 96, 71], 
                   [73, 66, 44], 
                   [92, 87, 64], 
                   [87, 135, 57], 
                   [103, 43, 36], 
                   [68, 97, 70]], 
                  dtype='float32')

# Targets (apples, oranges)
targets = np.array([[56, 70], 
                    [81, 101], 
                    [119, 133], 
                    [22, 37], 
                    [103, 119],
                    [57, 69], 
                    [80, 102], 
                    [118, 132], 
                    [21, 38], 
                    [104, 118], 
                    [57, 69], 
                    [82, 100], 
                    [118, 134], 
                    [20, 38], 
                    [102, 120]], 
                   dtype='float32')

inputs = torch.from_numpy(inputs)
targets = torch.from_numpy(targets)

# 利用内置数据集类型加载数据
train_dataset = TensorDataset(inputs,targets)
# print(train_dataset[0:5])
batch_size = 5
train_dataloader = DataLoader(train_dataset,batch_size,shuffle=True)
'''
for x,y in train_dataloader:
    print(x,y)
'''

# 使用nn.linear自动完成初始化,随机生成model.weight和model.bias
model = nn.Linear(3,2)
# print(model.weight,model.bias)
# print(list(model.parameters()))
# 声明均值平方差loss函数
loss_fn = F.mse_loss
loss = loss_fn(model(inputs),targets)

# 声明优化函数(误差梯度下降)
opt = torch.optim.SGD(model.parameters(),lr=1e-5)

# 定义训练过程
def fit(num_epoch,model,loss_fn,opt):
    for epoch in range(num_epoch):
        for xb,yb in train_dataloader:
            # 线性计算
            predict = model(xb)
            # 损失计算
            loss = loss_fn(predict,yb)
            # 梯度计算
            loss.backward()
            # 梯度下降实现
            opt.step()
            # 梯度初始化
            opt.zero_grad()
        # 日志
        if epoch % 10 == 0:
            print("Epoch:{}/{},Loss:{:.4f}".format(epoch+1,num_epoch,loss.item()))

fit(100,model,loss_fn,opt)

AVX加速卷积操作

pre:

检查自己电脑支持的SIMD指令集:

  • Windows:

    1. 任务管理器:
    右键点击任务栏上的“开始”按钮,选择“任务管理器”。
    切换到“性能”选项卡。
    选择“CPU”,在右侧的屏幕中查看“指令集”或者“功能”,看是否列出了AVX、AVX2或者其他相关的信息。
    2. 系统信息:
    按下Win键,输入“系统信息”,然后选择“系统信息”应用。
    在“系统摘要”中查找“指令集”一栏,看是否包含AVX或者其他SIMD指令集。
    CPU-Z:
    下载并安装CPU-Z程序(它提供了关于CPU及其功能的详细信息)。
    运行CPU-Z,然后切换到“CPU”或者“指令集”选项卡查看你的CPU支持哪些指令集。
    3. 命令行:
    使用wmic命令行工具来获取处理器的信息。在命令提示符下输入以下命令:
    wmic CPU get caption, deviceID, name, numberOfCores, numberOfLogicalProcessors, architecture, family, manufacturer, status
    这将列出你的CPU的基本信息(但不一定会直接显示AVX或NEON支持)
    
  • Linux:

    1. proc/cpuinfo:
    打开一个终端窗口。
    输入cat /proc/cpuinfo或者grep flags /proc/cpuinfo命令查看CPU的详细信息。
    在列出的标志中,查找avx、avx2等关键词。
    2. lscpu:
    输入lscpu命令也能提供CPU的详细信息。
    查找“Flags”或者“Features”部分以确定是否支持AVX指令集。
    
  • MacOS:

    1. 关于本机:
    点击苹果菜单,选择“关于本机”。
    点击“系统报告”。
    在硬件部分,查看“处理器”信息,尽管这里不会直接显示AVX或NEON的支持。
    2. 命令行:
    打开终端。
    输入sysctl -a | grep machdep.cpu.features来查看CPU支持的特性。
    这应该会显示出包括AVX在内的所有支持的指令集。
    

首先看给出的
卷积源代码:

// conv.cpp

//  __restrict__ 关键字来声明指针参数。通过不同的 __restrict__ 指针访问的内存区域不会重叠,这可以帮助编译器生成更优化的代码。
bool Convolve1D_Ks5_F64_cpp(double *__restrict__ y, const double * __restrict__ x, const double*  __restrict__ kernel,int64_t num_pts)
{
  // constexpr表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。声明为constexpr的变量一定是一个const变量,而且必须用常量表达式初始化
 constexpr int64_t kernel_size = 5;
 constexpr int64_t ks2 = kernel_size / 2;
 
 if (num_pts < kernel_size){
   return false;
 }
/*  for (int64_t i = ks2; i < num_pts - ks2; i++)
  {
   double y_val = 0.0;
      y_val += x[i - (-ks2)] * kernel[(-ks2) + ks2];
      y_val += x[i - (-ks2+1)] * kernel[(-ks2+1) + ks2];
      y_val += x[i - (-ks2+2)] * kernel[(-ks2+2) + ks2];
      y_val += x[i - (-ks2+3)] * kernel[(-ks2+3) + ks2];
      y_val += x[i - (-ks2+4)] * kernel[(-ks2+4) + ks2];
      
   y[i] = y_val;
  }
*/

  for (int64_t i = ks2; i < num_pts - ks2; i++)
  {
   double temp0[4] = {0.0}, temp1[4] = {0.0}, 
   temp2[4]={0.0},temp3[4]={0.0},temp4[4]={0.0};

   temp0[0] = kernel[ks2 - 2] * x[i + 2];
   temp0[1] = kernel[ks2 - 2] * x[i + 3];
   temp0[2] = kernel[ks2 - 2] * x[i + 4];
   temp0[3] = kernel[ks2 - 2] * x[i + 5];

   temp1[0] = kernel[ks2 - 1] * x[i + 1];
   temp1[1] = kernel[ks2 - 1] * x[i + 2];
   temp1[2] = kernel[ks2 - 1] * x[i + 3];
   temp1[3] = kernel[ks2 - 1] * x[i + 4];

   temp2[0] = kernel[ks2 - 0] * x[i + 0];
   temp2[1] = kernel[ks2 - 0] * x[i + 1];
   temp2[2] = kernel[ks2 - 0] * x[i + 2];
   temp2[3] = kernel[ks2 - 0] * x[i + 3];
 
   temp3[0] = kernel[ks2 - (-1)] * x[i + (-1)];
   temp3[1] = kernel[ks2 - (-1)] * x[i + 0];
   temp3[2] = kernel[ks2 - (-1)] * x[i + 1];
   temp3[3] = kernel[ks2 - (-1)] * x[i + 2];
   
   temp4[0] = kernel[ks2 - (-2)] * x[i + (-2)];
   temp4[1] = kernel[ks2 - (-2)] * x[i + (-1)];
   temp4[2] = kernel[ks2 - (-2)] * x[i + 0];
   temp4[3] = kernel[ks2 - (-2)] * x[i + 1];
    
   y[i] = temp0[0] + temp1[0] +temp2[0] + temp3[0] +temp4[0];
   y[i+1] = temp0[1] + temp1[1] +temp2[1] + temp3[1] +temp4[1];
   y[i+2] = temp0[2] + temp1[2] +temp2[2] + temp3[2] +temp4[2];
   y[i+3] = temp0[3] + temp1[3] +temp2[3] + temp3[3] +temp4[3];
     
 }
  return true;
}

通过分析Convolve1D_Ks5_F64_cpp函数可以得到,参数列表中有:y存储卷积操作的结果;x表示要处理的信号或数据;kernel表示卷积核数组,用constexpr关键字声明为常量5;num_pts表示输入元素的长度,即x的长度。首先进行了卷积核与信号大小的比较来进行边界检查,防止输入信号大小小于卷积核大小。截止在注释部分给出了顺序执行下的一维卷积过程,kernel_size为constexpr关键字声明为常量5以及(kernel_size/2)的声明。在for循环中,起始和终止位置分别为ks2和num_pts - ks2,避免了边界的padding。同时,在卷积操作中同时对结果y中的4个元素进行计算,利用类似循环展开的方式实现SIMD操作。
执行结果:

利用AVX指令集修改后的代码

// cons_avx.cpp

bool Convolve1D_Ks5_F64_AVX(double* __restrict__ y, const double* __restrict__ x, const double* __restrict__ kernel, int64_t num_pts) {
    constexpr int64_t kernel_size = 5;
    constexpr int64_t ks2 = kernel_size / 2;

    // Ensure the input size is sufficient for the convolution operation
    if (num_pts < kernel_size) {
        return false;
    }

    // Load the convolution kernel into AVX2 registers
    __m256d k0 = _mm256_set1_pd(kernel[0]);
    __m256d k1 = _mm256_set1_pd(kernel[1]);
    __m256d k2 = _mm256_set1_pd(kernel[2]);
    __m256d k3 = _mm256_set1_pd(kernel[3]);
    __m256d k4 = _mm256_set1_pd(kernel[4]);

    // Perform convolution operations in chunks using AVX2
    for (int64_t i = ks2; i <= num_pts - kernel_size; i += 4) {
        // Load chunks of the input signal into AVX2 registers
        std::cout << i << '\n';
        __m256d x0 = _mm256_load_pd(&x[i - 2]);
        __m256d x1 = _mm256_load_pd(&x[i - 1]);
        __m256d x2 = _mm256_load_pd(&x[i]);
        __m256d x3 = _mm256_load_pd(&x[i + 1]);
        __m256d x4 = _mm256_load_pd(&x[i + 2]);

        // Perform element-wise multiplication and sum the results
        __m256d y_val = _mm256_add_pd(
            _mm256_add_pd(
                _mm256_mul_pd(x0, k0),
                _mm256_mul_pd(x1, k1)),
            _mm256_add_pd(
                _mm256_mul_pd(x2, k2),
                _mm256_add_pd(
                    _mm256_mul_pd(x3, k3),
                    _mm256_mul_pd(x4, k4))));

        // Store the convolution results back to the output array
        _mm256_store_pd(&y[i], y_val);
    }

    // Handle the remainder of the elements that don't fit into a multiple of 4
    for (int64_t i = num_pts - ks2; i < num_pts; i++) {
        double result = 0.0;
        for (int64_t k = -ks2; k <= ks2; k++) {
            result += x[i - k] * kernel[k + ks2];
        }
        y[i] = result;
    }

    return true;
}

但运行时发生了Segmentation fault (core dumped)段错误,进入gdb进行调试,发现是_mm256_load_pd函数内存对齐的问题。于是首先将存取改为不对齐的形式:

_mm256_loadu_pd

bool Convolve1D_Ks5_F64_AVX(double* __restrict__ y, const double* __restrict__ x, const double* __restrict__ kernel, int64_t num_pts) {
    constexpr int64_t kernel_size = 5;
    constexpr int64_t ks2 = kernel_size / 2;

    // Ensure the input size is sufficient for the convolution operation
    if (num_pts < kernel_size) {
        return false;
    }

    // Load the convolution kernel into AVX2 registers
    __m256d k0 = _mm256_set1_pd(kernel[0]);
    __m256d k1 = _mm256_set1_pd(kernel[1]);
    __m256d k2 = _mm256_set1_pd(kernel[2]);
    __m256d k3 = _mm256_set1_pd(kernel[3]);
    __m256d k4 = _mm256_set1_pd(kernel[4]);

    // Perform convolution operations in chunks using AVX2
    for (int64_t i = ks2; i <= num_pts - kernel_size; i += 4) {
        // Load chunks of the input signal into AVX2 registers
        // std::cout << i << '\n';
        __m256d x0 = _mm256_loadu_pd(&x[i + 2]);
        __m256d x1 = _mm256_loadu_pd(&x[i + 1]);
        __m256d x2 = _mm256_loadu_pd(&x[i]);
        __m256d x3 = _mm256_loadu_pd(&x[i - 1]);
        __m256d x4 = _mm256_loadu_pd(&x[i - 2]);

        // Perform element-wise multiplication and sum the results
        __m256d y_val = _mm256_add_pd(
            _mm256_add_pd(
                _mm256_mul_pd(x0, k0),
                _mm256_mul_pd(x1, k1)),
            _mm256_add_pd(
                _mm256_mul_pd(x2, k2),
                _mm256_add_pd(
                    _mm256_mul_pd(x3, k3),
                    _mm256_mul_pd(x4, k4))));

        // Store the convolution results back to the output array
        _mm256_storeu_pd(&y[i], y_val);
    }

    // Handle the remainder of the elements that don't fit into a multiple of 4
    for (int64_t i = num_pts - ks2; i < num_pts; i++) {
        double result = 0.0;
        for (int64_t k = -ks2; k <= ks2; k++) {
            result += x[i - k] * kernel[k + ks2];
        }
        y[i] = result;
    }

    return true;
}

成功执行,输出结果:

最后还有一点可以看出来,由于给出的卷积核是左右对称的,所以不按照逆向卷积得出的结果也是正确的。

GDB基本调试命令

启动GDB:

对已编译好的程序启动GDB:gdb [program],其中[program]是你的编译后的可执行文件。
设置断点:

在函数开始设置断点:break [function name]
在指定文件的指定行设置断点:break [filename]:[line number]
运行程序:

run [arguments]:运行你的程序,[arguments]是传给程序的任何参数。
检查程序状态:

info registers:显示所有寄存器的当前值。
info locals:显示当前栈帧局部变量的值。
print [expression]:打印表达式的值,[expression]可以是变量名或任何有效的C表达式。
backtrace 或 bt:显示当前的调用栈。
步进和步过:

next 或 n:执行下一行代码,如果当前行调用了函数,则不进入该函数内部。
step 或 s:执行下一行代码,如果当前行调用了函数,则进入该函数内部。
继续执行:

continue 或 c:从当前位置继续执行程序,直到遇到下一个断点。
退出GDB:

quit 或 q:退出GDB。
查看和修改变量:

set var [variable]=[value]:设置变量的值。
print [variable]:打印变量的当前值。
查看源代码:

list [line number]:显示指定行号周围的源代码。
list [function]:显示指定函数的源代码。
条件断点:

break [location] if [condition]:只有当满足条件时,才在指定位置停止。
posted on   zombie_black  阅读(75)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示