深度学习 tensor及其相关操作

 

目录

Tensor

概念

数据类型

创建tensor

基本创建方式

1、 torch.tensor()

2、torch.Tensor()

3、torch.IntTensor() 等

创建线性张量和随机张量

1、创建线性张量

2、创建随机张量

切换设备

类型转换

与 Numpy 数据转换

1、张量转 Numpy

2、Numpy 转张量

tensor常见操作

1、获取元素值

2、元素值运算

3、tensor相乘

4、形状操作

view():

reshape():

5、维度转换

transpose(dim0, dim1):两个维度转换

permute(dim0, dim1, ... , dimN):重排所有维度

6、升维降维

unsqueeze():升维

squeeze():降维


PyTorch是一个基于Python的深度学习框架,它提供了一种灵活、高效、易于学习的方式来实现深度学习模型,最初由Facebook开发,被广泛应用于计算机视觉、自然语言处理、语音识别等领域。PyTorch提供了许多高级功能,如自动微分(automatic differentiation)自动求导(automatic gradients)等,这些功能可以帮助我们更好地理解模型的训练过程,并提高模型训练效率。

没错苯人开始 pytorch的学习了,这篇来写一下 Torch 的一些基本API。

Tensor

概念

 PyTorch会将数据全都封装成张量(Tensor)进行计算,所谓张量就是元素为相同类型的多维矩阵,或者多维数组,通俗来说可以看作是扩展了标量、向量、矩阵的更高维度的数组。张量的维度决定了它的形状(Shape),例如:

  • 标量 是 0 维张量,如 a = torch.tensor(5)

  • 向量 是 1 维张量,如 b = torch.tensor([1, 2, 3])

  • 矩阵 是 2 维张量,如 c = torch.tensor([[1,2], [3,4]])

  • 更高维度的张量,如3维、4维等,通常用于表示图像、视频数据等复杂结构。

数据类型

  PyTorch中有3种数据类型:浮点数类型、整数类型、布尔类型。其中,浮点数和整数又分为8位、16位、32位、64位,加起来共9种。为什么要分这么多种类型呢是因为场景不同,对数据的精度和速度要求不同。通常,移动或嵌入式设备追求速度,对精度要求相对低一些。精度越高,往往效果也越好,自然硬件开销就比较高。

创建tensor

以下讲的创建 tensor 的函数中有两个有默认值的参数 dtype device, 分别代表数据类型和计算设备,可以通过属性 dtype 和 device 获取,如 print(t1.dtype)

基本创建方式

1、 torch.tensor()

代码示例:

def test1():# 创建张量t1 = torch.tensor(88) #创建标量t2 = torch.tensor(np.random.randn(3, 2)) #用numpy随机数组创建1维张量t3 = torch.tensor([[1, 2, 3], [4, 5, 6]]) #创建2维张量print(t1)print(t2)print(t2.shape)print(t3)print(t3.shape)

运行结果:

注意,如果出现 UserWarning: Failed to initialize NumPy: _ARRAY_API not found 错误,一般是因为 numpy和pytorch版本不兼容,可以降低 numpy版本,直接卸载重装,最好是1.x版本

2、torch.Tensor()

代码示例:

def test2():#torch.Tenor()创建张量t1 = torch.Tensor(3) #根据形状创建print(t1)t2 = torch.Tensor([[1,2,3],[4,5,6]]) #根据具体的值创建print(t2)print(t2.shape)

运行结果:

这里可以看到,当传入的是一个整数时,tensor() 是直接创建标量,而 Tensor() 则是初始化一个形状为该整数的张量。除此之外,它俩的区别还在于:

特性torch.Tensor()torch.tensor()
数据类型推断强制转为 torch.float32根据输入数据自动推断(如整数→int64
显式指定 dtype不支持支持(如 dtype=torch.float64
设备指定不支持支持(如 device='cuda'
输入为张量时的行为创建新副本(不继承原属性)默认共享数据(除非 copy=True
推荐使用场景需要快速创建浮点张量需要精确控制数据类型或设备

一般是使用 torch.tensor() 更多,更安全、功能更全,可避免未初始化张量的隐患。

3、torch.IntTensor() 等

 用于创建指定类型的张量,诸如此类还有 torch.FloatTensor(32位浮点数)、 torch.DoubleTensor(64位浮点数)、torch.ShortTensor(16位整数)、 torch.LongTensor(64位整数)......等。如果数据类型不匹配,那么在创建的过程中会进行类型转换,防止数据丢失。

代码示例:

def test3():#创建指定类型的张量tt1 = torch.IntTensor(3,3)print(tt1)tt2 = torch.FloatTensor(3, 3)print(tt2, tt2.dtype)tt3 = torch.DoubleTensor(3, 3)print(tt3, tt3.dtype)tt4 = torch.LongTensor(3, 3)print(tt4, tt4.dtype)tt5 = torch.ShortTensor(3, 3)print(tt5, tt5.dtype)

运行结果就不贴了

创建线性张量和随机张量

1、创建线性张量

可使用 torch.arange() 和 torch.linspace() 创建一维线性张量

torch.arange(start, end, step):生成一个等间隔的一维张量(序列),区间为 [start, end)(左闭右开),步长由 step 决定。

torch.linspace(start, end, step):生成一个在区间 [start, end](左右均闭合)内均匀分布的 steps 个元素的一维张量(相当于等差数列)

代码示例如下:

def test4():# 创建线性张量t1 = torch.arange(0, 10, step=2)print(t1)t2 = torch.linspace(1, 2, steps=5)print(t2)

运行结果:

2、创建随机张量

使用torch.randn 和 torch.randint 创建随机张量。

torch.randn(size,  dtype , device , requires_grad) :生成服从标准正态分布(均值为 0,标准差为 1)的随机张量,size 可以传元组或者直接传数字,生成的都是二维张量;后两个参数就不过多介绍;requires_grad 表示是否需要计算梯度,默认为False,这个参数很重要,可能下一篇会说。

torch.randint(low, high, size):在指定的[ low, high)范围内生成离散的、均匀分布的整数,low 为最小值,high 为最大值,size 为张量的形状

另外还要介绍一下随机数种子:随机数种子(Random Seed) 是控制计算机生成“随机数”的一个关键参数,它的存在让“随机”变得可复现,设置种子后算法会固定从同一个“起点”开始计算,生成的随机序列完全一致,至于设置什么数字并不重要,只是不同的数字生成的数字序列不同罢了,API为  torch.manual_seed()

代码实例:

def test5():'''创建随机张量'''torch.manual_seed(42)# 设置随机数种子t1 = torch.randn(2,3) #生成随机张量,标准正态分布,均值 0,标准差 1t2 = torch.rand(2,3) #生成随机张量,均匀分布,范围在 [0, 1)t3 = torch.randint(0, 10, (2, 3)) #在0到10内(不包括10)生成 2x3 的张量print(t1)print(t2)print(t3)

运行结果:

这里因为设置了随机数种子所以每次输出的结果都相同,如果将第一行注释掉,每次生成的张量就不一样了,同时注意一下 size 有没有括号都是生成的二维数组,当然也可以生成三维张量,例如

torch.rand(2, 3, 4)

切换设备

tensor 相关操作默认在cpu上运行,但是可以显式地切换到GPU,且不同设备上的数据是不能相互运算的。切换操作如下:

def test6():'''切换设备'''t1 = torch.tensor([1,2,3])print(t1.shape, t1.dtype, t1.device)#切换到GPU 方法一device = "cuda" if torch.cuda.is_available() else "cpu"print(t1.device)# 切换到GPU 方法二 to()方法t1 = t1.to(device="cuda")print(t1.device)

运行结果:

因为苯人电脑没有显卡所以转不过去,这里只是一个代码示范

或者直接用 cuda 进行转换:

t1 = t1.cuda()

当然也可以直接在 GPU 上创建张量:

data = torch.tensor([1, 2, 3], device='cuda')
print(data.device)

类型转换

 在写代码时,类型的不同有时也会导致各种错误,所以类型的转换也是很重要的,有三种类型转换的方法:

def test7():'''类型转换'''t1 = torch.tensor([1,2,3])print(t1.dtype) #torch.int64# 1、使用 type() 进行转换t1 = t1.type(torch.float32)print(t1.dtype) #torch.float32# 2、使用类型方法转换t1 = t1.float()print(t1.dtype)  # float32t1 = t1.half()print(t1.dtype)  # float16t1 = t1.double()print(t1.dtype)  # float64t1 = t1.long()print(t1.dtype)  # int64t1 = t1.int()print(t1.dtype)  # int32# 3、使用 dtype 属性指定t1 = torch.tensor([1,2,3], dtype=torch.float32)print(t1.dtype) #torch.float32

与 Numpy 数据转换

1、张量转 Numpy

浅拷贝:内存共享,修改 Numpy数组也会改变原张量,API:numpy()

深拷贝:内存不共享,相当于建了一个独立的副本,API:numpy().copy()

代码示例(浅拷贝):

def test8():'''张量转numpy'''#浅拷贝t1 = torch.tensor([[1,2,3], [4,5,6],[7,8,9]])t2 = t1.numpy() 转 numpyprint(t1)print(f't2:{t2}')t2[0][1] = 666 #内存共享print(t1)print(f't2:{t2}')

运行结果:

可以看到,t1 转成 t2 后,修改 t2 还是会改变 t1,如果想要避免内存共享可以用深拷贝:

    #深拷贝# t2 = t1.numpy().copy()

其他同上

2、Numpy 转张量

同样分为内存共享和内存不共享:

浅拷贝:内存共享,修改 tensor 也会改变原数组,API:torch.from_numpy()

深拷贝:内存不共享,相当于建了一个独立的副本,API:torch.tensor()

代码示例(深拷贝):

'''numpy转张量'''# 深拷贝n1 = np.array([[11,22,33],[44,55,66]])n1_tensor = torch.tensor(n1) #转张量print(f'n1:{n1}')print(f'n1_tensor:{n1_tensor}')n1_tensor[0][1] = 88 #内存不共享print(f'n1:{n1}')print(f'n1_tensor:{n1_tensor}')

运行结果:

可以看到,修改 n1_tensor 不会改变 n1,如果想要浅拷贝的话:

 #浅拷贝# n1_tensor = torch.from_numpy(n1)

tensor常见操作

 接下来介绍一些在深度学习中基本的 tensor 操作:

1、获取元素值

我们可以把只含有单个元素的 tensor 转换为Python数值,这和 tensor 的维度无关,但若有多个元素则会报错,且仅适用于 CPU张量。

API为 item():

def test9():'''获取元素值'''t1 = torch.tensor([[[18]]])t2 = torch.tensor([20,13])print(t1.item())print(t2.item())

运行结果:

可以看到佑报错,这就是因为 t2 含有不止一个元素所以不能用 item()

2、元素值运算

 常见的加、减、乘、除、次方、取反、开方等各种操作,但是也分是否修改原始值

不修改原始值代码示例:

'''元素值运算'''t1 = torch.randint(0, 10, (2,3))print(f't1:{t1}')print(t1.add(1))print(t1.sub(1))print(t1.mul(2))print(t1.div(2))print(f't1:{t1}')

运行结果:

可以看到,经过加减乘除后 t1 的元素并没有被改变

修改原始值:

t2 = torch.randint(0, 10, (2, 3))print(f't2:{t2}')t2.add_(1)print(f't2:{t2}')

运行结果:

可以看到 t2 已被改变,所以带下划线 “_” 的运算会改变原始值

3、tensor相乘

tensor 之间的乘法有两种,一种是矩阵乘法,还有一种是阿达玛积。

矩阵乘法是线性代数中的一种基本运算,用于将两个矩阵相乘,生成一个新的矩阵。

举个例子,假设有两个矩阵:

  • 矩阵 A的形状为 m×n(m行 n列)。

  • 矩阵 B的形状为 n×p(n行 p列)。

则矩阵 A和 B的乘积 C=A×B,是一个形状为 m×p的矩阵,可以理解为前一个矩阵控制行数,后一个矩阵控制列数,计算公式为:

要求如果第一个矩阵的shape是 (N, M),那么第二个矩阵 shape必须是 (M, P),最后两个矩阵点积运算的shape为 (N, P),而在 pytorch 中,使用 matmul() 或者 @ 来完成 tensor 的乘法

代码示例如下:

import torchdef test1():'''矩阵乘法'''t1 = torch.tensor([[1,2,3],[4,5,6]])t2 = torch.tensor([[7,8],[9,10],[11,12]])# 两种方法print(t1 @ t2)print(t1.matmul(t2))if __name__ == '__main__':test1()

运行结果:

下面介绍阿达玛积。阿达玛积是指两个形状相同的矩阵或张量对应位置的元素相乘。它与矩阵乘法不同,矩阵乘法是线性代数中的标准乘法,而阿达玛积是逐元素操作。

假设有两个形状相同的矩阵 A和 B,则它们的阿达玛积 C=A∘B定义为:

在 pytorch 中,可以使用 * 或者 mul() 来实现,代码如下:

'''阿达玛积'''t3 = torch.tensor([[1,2,3],[4,5,6]])t4 = torch.tensor([[7,8,9],[11,22,33]])#两种方法print(t3 * t4)print(t3.mul(t4))

运行结果:

4、形状操作

 在 PyTorch 中,张量的形状操作是非常重要的,因为它允许你灵活地调整张量的维度和结构,以适应不同的计算需求。 下面介绍两种方法:

view():

快速改变张量的形状,但不复制数据,新张量与原始张量共享内存。前提条件是原张量在内存中连续,可用 is_contiguous() 判断,连续会输出 True,否则需要先调用 contiguous() 转成连续的

API为 view(size),size即新形状,可以是元组或者 -1,单独的 -1 表示展平向量,放在元组中表示自动计算某一维度的元素数量(必须与总元素一致)

代码如下:

def test2():'''形状操作'''# view()t1 = torch.arange(6) #创建一个0到5的一维张量print(t1.is_contiguous())t2 = t1.view(-1) #展平 虽然本来就是一维张量t3 = t1.view(2,3) #转成 2x3 的张量t4 = t1.view(3,-1) #转成3行 至于几列由电脑自动计算 只要保证总元素相同print(f't1:{t1}')print(f't2:{t2}')print(f't3:{t3}')print(f't4:{t4}')

运行结果:

若t1 不连续,那么用 t1.contiguous().view() 进行操作

reshape():

更灵活的变形方法,自动处理连续性(如果原始张量不连续,会复制数据),连续的话内存就共享,否则相当于独立数据。

API为 reshape(size),用法与 view() 相同,这里就不再展示代码了,看一下两者的区别吧:
 

特性

view()

reshape()
共享内存总是共享(需连续)可能共享(若连续)或复制
连续性要求必须连续自动处理连续性
使用场景确定张量连续时的高效操作不确定连续性时的通用操作

5、维度转换

pytorch 中维度转换分为两个维度转换和多个维度转换,分别用不同的方法:

transpose(dim0, dim1):两个维度转换

transpose() 仅用于两个维度的转换如矩形转置,返回的是原张量的视图,参数 dim0 是要交换的第一个维度,dim1 是要交换的第二个,代码如下:

    t1 = torch.tensor([[1,2],[3,4]])t2 = t1.transpose(0,1) #交换维度,相当于转置print(t1)print(t2)

运行结果:

 t2 = t1.transpose(0,1) 表示交换第0维和第1维,第0维表示张量的最外层结构,也就是行方向,第1维就表示列方向,所以交换的是行列方向,就相当于矩阵转置了

permute(dim0, dim1, ... , dimN):重排所有维度

 permute() 方法自由重新排列所有维度,可一次性交换多个维度,适用于复杂的维度重组(如调整图像通道顺序),代码如下:

    #多维度重排 permutet3 = torch.rand(2,3,4)t4 = t3.permute(2,1,0)print(t4.shape)

运行结果:

两者的区别在于:

特性transpose(dim0, dim1)permute(dim0, dim1, ..., dimN)
交换维度数量只能交换两个维度可以一次性重排所有维度
灵活性
常用场景矩阵转置、简单维度交换复杂的维度重组(如NCHW→NHWC)
连续性通常输出不连续通常输出不连续

注意,使用了这两个方法后输出的新张量通常不连续,所以后续可能要用 contiguous() 转换

6、升维降维

 升维和降维是常用的操作,需要掌握:

unsqueeze():升维

 升维操作是在一个指定的位置插入一个大小为 1 的新维度,API为 unsqueeze(dim)dim 为指定要增加维度的位置(从 0 开始索引),代码示例如下:

def test4():'''升维 unsqueeze()'''t1 = torch.rand(2,3,4)t1 = t1.unsqueeze(2)print(t1.shape)

运行结果:

squeeze():降维

 降维是去除不必要的维度,API为 squeeze(dim),dim 是指定要移除的维度。如果指定了 dim,则只移除该维度(前提是该维度大小为 1),如果不指定,则移除所有大小为 1 的维度,代码示例:
 

'''降维squeeze()'''t2 = torch.tensor([[[1,2,3]]]) #形状为 (1,1,3)t3 = t2.squeeze() #不指定dimt4 = t2.squeeze(0) #指定dim为0print(t2.shape)print(t3.shape)print(t4.shape)

运行结果:

可以看到,当不指定 dim 时移除了所有大小为1的维度。指定之后只移除了第0维的1。

这篇就到这里,但其实还有很多其他的操作只是我不想写了[晕],居然写了8000多字。。下一篇写啥我也没想好,先这样吧(๑•̀ㅂ•́)و✧

以上有问题可以指出

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/88900.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/88900.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何将FPGA设计的验证效率提升1000倍以上(4)

本文为系列文章的完结篇。用户应用设计中的信号,在经历编译器的多次迭代优化之后,在FPGA芯片内部运行时,可能已经被重新命名、“改头换面”或“机里机气”。要想以人工经验进行追踪,构建目标寄存器信号与RTL设计源码之间的映射关系…

Linux驱动11 --- buildroot杂项驱动开发方法

目录 一、Buildroot 1.1介绍 文件系统 1.一个完整的操作系统需要包含大量的文件 2.在嵌入式开发中目前应用最广泛的文件系统制作工具就是 buildroot,busybox 3.buildroot 制作文件系统(了解) 二、杂项驱动编程 1.1 驱动编程做的内容 2.2…

Unity物理系统由浅入深第三节:物理引擎底层原理剖析

Unity物理系统由浅入深第一节:Unity 物理系统基础与应用 Unity物理系统由浅入深第二节:物理系统高级特性与优化 Unity物理系统由浅入深第三节:物理引擎底层原理剖析 Unity物理系统由浅入深第四节:物理约束求解与稳定性 Unity 物理…

Docker一键安装中间件(RocketMq、Nginx、MySql、Minio、Jenkins、Redis)脚步

1、Docker安装RocketMq 2、Docker安装Nginx 3、Docker安装MySql 4、Docker安装Minio 5、Docker安装jenkins 6、Docker安装Redis 1、Docker安装RocketMq #!/bin/bash# 定义变量 NAMESRV_CONTAINER"rocketmq-namesrv" BROKER_CONTAINER"rocketmq-broker&quo…

WPF学习笔记(27)科学计算器

科学计算器1. 前端界面2. 功能代码3. 效果展示1. 前端界面 <Window x:Class"Cal.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http:/…

【Linux系列】unzip file 命令

博客目录掌握 unzip -o 命令&#xff1a;高效解压并覆盖文件的完整指南一、unzip 命令基础二、-o 选项的核心作用三、典型应用场景四、与其他选项的组合使用五、注意事项与风险防范六、替代方案与高级技巧掌握 unzip -o 命令&#xff1a;高效解压并覆盖文件的完整指南 在日常的…

1965–2022年中国大陆高分辨率分部门用水数据集,包含:灌溉用水、工业制造用水、生活用水和火电冷却

1965–2022年中国大陆高分辨率部门用水数据集 高质量用水数据集对推进变化环境下的水资源研究至关重要。然而&#xff0c;现有中国用水数据通常按行政区划或流域汇总&#xff0c;时空分辨率不足&#xff0c;难以支撑人类用水模式及时空变化特征的精细化分析。为此&#xff0c;…

git中的指令解释

在 Git 的 diff 输出中&#xff0c;---、 和 ... 块的含义如下&#xff1a; 1. --- a/1.py 和 b/1.py --- a/1.py&#xff1a;表示旧版本的文件路径&#xff08;通常是工作目录或上一次提交的版本&#xff09;。 b/1.py&#xff1a;表示新版本的文件路径&#xff08;通常是暂存…

STM32实现四自由度机械臂(SG90舵机)多功能控制(软件篇freertos)

书接上回的硬件篇STM32控制四自由度机械臂&#xff08;SG90舵机&#xff09;&#xff08;硬件篇&#xff09;&#xff08;简单易复刻&#xff09;-CSDN博客 此时硬件平台已经搭建完毕&#xff0c;软件总共设计了三种模式&#xff0c;分别为 模式1&#xff1a;摇杆&蓝牙模…

docker常用命令集(2)

接前一篇文章&#xff1a;docker常用命令集&#xff08;1&#xff09; 本文内容参考&#xff1a; Docker build 命令 | 菜鸟教程 docker基础(二)之docker build-CSDN博客 Docker push 命令 | 菜鸟教程 Docker pull 命令 | 菜鸟教程 特此致谢&#xff01; 3. docker build …

舒尔特方格训练小游戏流量主微信小程序开源

功能特点 游戏核心功能&#xff1a; 随机生成55舒尔特方格 按顺序点击数字1-25 实时计时和尝试次数统计 错误点击反馈&#xff08;视觉和触觉&#xff09; 数据统计&#xff1a; 记录每次完成时间 保存历史最佳成绩 保存最近5次尝试记录 统计尝试次数&#xff08;错误点击&…

在Spring Boot 开发中 Bean 的声明和依赖注入最佳的组合方式是什么?

在Spring Boot 开发中&#xff0c;社区和 Spring 官方已经形成了一套非常明确的最佳实践。这个黄金组合就是&#xff1a; Bean 声明&#xff1a;使用构造型注解&#xff08;Stereotype Annotations&#xff09;&#xff0c;如 Service, Repository, Component 等。依赖注入&…

Oxygen XML Editor 26.0编辑器

Oxygen XML Editor 26.0编辑器 欢迎使用Oxygen XML Editor 26.0编辑器准备工作安装javajdk安装jdk验证Oxygen XML Editor 26.0安装欢迎使用Oxygen XML Editor 26.0编辑器 准备工作安装java Java官网下载地址:https://www.oracle.com/java/technologies/ Oxygen XML Editor 2…

AWS Lambda Container 方式部署 Flask 应用并通过 API Gateway 提供访问

前言 一年前写过一篇 Lambda 运行 Flask 应用的博文: https://lpwmm.blog.csdn.net/article/details/139756140 当时使用的是 ZIP 包方式部署应用代码, 对于简单的 API 开发用起来还是可以的, 但是如果需要集成到 CI/CD pipeline 里面就有点不太优雅. 本文将介绍使用容器方式…

React虚拟DOM的进化之路

引言 在Web前端开发中&#xff0c;用户交互的流畅性和页面性能一直是核心挑战。早期&#xff0c;开发者直接操作真实DOM&#xff08;Document Object Model&#xff09;时&#xff0c;频繁的重排&#xff08;reflow&#xff09;和重绘&#xff08;repaint&#xff09;导致性能…

(7)机器学习小白入门 YOLOv:机器学习模型训练详解

— (1)机器学习小白入门YOLOv &#xff1a;从概念到实践 (2)机器学习小白入门 YOLOv&#xff1a;从模块优化到工程部署 (3)机器学习小白入门 YOLOv&#xff1a; 解锁图片分类新技能 (4)机器学习小白入门YOLOv &#xff1a;图片标注实操手册 (5)机器学习小白入门 YOLOv&#xff…

初识MySQL(三)之主从配置与读写分离实战

主重复制 主重复制原理master开启二进制日志记录slave开启IO进程&#xff0c;从master中读取二进制日志并写入slave的中继日志slave开启SQL进程&#xff0c;从中继日志中读取二进制日志并进行重放最终&#xff0c;达到slave与master中数据一致的状态&#xff0c;我们称作为主从…

RabbitMQ面试精讲 Day 2:RabbitMQ工作模型与消息流转

【RabbitMQ面试精讲 Day 2】RabbitMQ工作模型与消息流转 开篇 欢迎来到"RabbitMQ面试精讲"系列的第2天&#xff0c;今天我们将深入探讨RabbitMQ的工作模型与消息流转机制。这是面试中最常被问到的核心知识点之一&#xff0c;90%的RabbitMQ面试都会涉及消息流转流程…

基于SpringBoot3集成Kafka集群

1. build.gradle依赖引入 implementation org.springframework.kafka:spring-kafka:3.2.02. 新增kafka-log.yml文件 在resource/config下面新增kafka-log.yml&#xff0c;配置主题与消费者组 # Kafka消费者群组 kafka:consumer:group:log-data: log-data-grouptopic:log-data: …

wpf Canvas 导出图片

在WPF中将Canvas导出为图片主要涉及以下关键步骤和注意事项: ‌核心实现方法‌使用RenderTargetBitmap将Canvas渲染为位图,再通过PngBitmapEncoder保存为PNG文件。需注意临时移除Canvas的布局变换(LayoutTransform)以避免渲染异常‌1。示例代码片段:CanvasExporter.cs pu…