pytorch框架初上手
PyTorch 是一个针对深度学习, 并且使用 GPU 和 CPU 来优化的 tensor library (tensor库)
梯度/导数计算
# 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]:只有当满足条件时,才在指定位置停止。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战