Pytorch学习_02 NumPy(下):深度学习中的常用操作
目录
其实基本所有深度学习的项目都可以这样划分(分解成数据加载、训练与模型评估三部分),其中数据加载跟模型评估中,就经常会用到NumPy数组的相关操作。就以传统的解决图片分类问题来说,先来看看数据的加载。
数据加载阶段
这个阶段我们要做的就是把训练数据读进来,然后给模型训练使用。
训练数据不外乎这三种:图片、文本以及类似二维表那样的结构化数据。
不管使用PyTorch还是TensorFlow,或者是传统机器学习的scikit-learn,我们在读入数据这一块,都会先把数据转换成NumPy的数组,然后再进行后续的一系列操作。
对于图片的处理,一般会使用Pillow与OpenCV这两个模块。
虽然Pillow和OpenCV功能看上去都差不多,但还是有区别的。在PyTorch中,很多图片的操作都是基于Pillow的,所以当使用PyTorch编程出现问题,或者要思考、解决一些图片相关问题时,要从Pillow的角度出发。
下面我们先以单张图片为例,将下面这个动物图片分别用Pillow与OpenCV读入,然后转换为NumPy的数组。
Pillow方式
首先,我们需要使用Pillow中的下述代码读入上面的图片。
Pillow是以二进制形式读入保存的,那怎么转为NumPy格式呢?这个并不难,我们只需要利用NumPy的asarray方法,就可以将Pillow的数据转换为NumPy的数组格式。
OpenCV方式
OpenCV的话,不再需要我们手动转格式,它直接读入图片后,就是以NumPy数组的形式来保存数据的,如下面的代码所示。
结合代码输出可以发现,我们读入后的数组的最后一个维度是3,这是因为图片的格式是RGB格式,表示有R、G、B三个通道。对于计算视觉任务来说,绝大多数处理的图片都是RGB格式,如果不是RGB格式的话,要记得事先转换成RGB格式。这里有个地方需要关注,Pillow读入后通道的顺序就是R、G、B (也就是说,当你用Pillow读取一张图像时,图像的第一个通道是红色,第二个通道是绿色,第三个通道是蓝色。),而OpenCV读入后顺序是B、G、R。
模型训练时的通道顺序需与预测的通道顺序要保持一致。也就是说使用Pillow训练,使用OpenCV读入图片直接进行预测的话,不会报错,但结果会不正确,一定要注意。
接下来,我们验证一下Pillow与OpenCV读入数据通道的顺序是否如此,借此引出有关Numpy数组索引与切片、合并等常见问题。
怎么验证这条结论呢?只需要将R、G、B三个通道的数据单独提取出来,然后令另外两个通道的数据全为0即可。
为什么要这样做?
RGB色彩模式是工业界的一种颜色标准,RGB分别代表红、绿、蓝三个通道的颜色,将这三种颜色混合在一起,就形成了我们眼睛所能看到的所有颜色。
RGB三个通道各有256个亮度,分别用数字0到255表示,数字越高代表亮度越强,数字0则是代表最弱的亮度。
在一个例子中,如果一个通道的数据再加另外两个全0的通道(相当于关闭另外两个通道),最终图像以红色格调呈现出来的话,我们就可以认为该通道的数据是来源于R通道,G与B通道的证明同样可以如此。
首先我们提取出RGB三个通道的数据,这可以从数组的索引与切片说起。
索引与切片
NumPy数组的索引方式与Python的列表的索引方式相同,也同样支持切片索引。
这里需要你注意的是在NumPy数组中经常会出现用冒号来检索数据的形式,如下所示:
这是什么意思呢?“:”代表全部选中的意思。我们的图片读入后,会以下图的状态保存在数组中。
上述代码的含义就是取第三个维度索引为0的全部数据,换句话说就是,取图片第0个通道的所有数据。
这样的话,通过下面的代码,我们就可以获得每个通道的数据了。
获得了每个通道的数据,接下来就需要生成一个全0数组,该数组要与im_pillow具有相同的宽高。
全0数组你还记得怎么生成吗?生成的代码如下所示。
然后,我们只需要将全0的数组与im_pillow_c1、im_pillow_c2、im_pillow_c3进行拼接,就可以获得对应通道的图像数据了。
数组的拼接
刚才我们拿到了单独通道的数据,接下来就需要把一个分离出来的数据跟一个全0数组拼接起来。如下图所示,红色的可以看作单通道数据,白色的为全0数据。
NumPy数组为我们提供了np.concatenate((a1, a2, …), axis=0)方法进行数组拼接。其中,a1,a2, …就是我们要合并的数组;axis是我们要沿着哪一个维度进行合并,默认是沿着0轴方向。
对于我们的问题,是要沿着2轴的方向进行合并,也是我们最终的目标是要获得下面的三幅图像
那么,我们先将im_pillow_c1与全0数组进行合并,生成上图中最左侧的数组,有了图像的数组才能获得最终图像。合并的代码跟输出结果如下:
错误的原因是在2维数组中,axis如果等于2的话会越界。
- 使用
axis
时: 指明的轴需要存在于所有数组的维度中。对于二维数组,轴0
和1
是有效的,而axis=2
会导致错误。
我们看看im_pillow_c1与zeros的形状。
我们要合并的两个数组维度并不一样。那么如何统一维度呢?
将im_pillow_c1变成(116, 318, 1)即可。
方法一:使用np.newaxis
我们可以使用np.newaxis让数组增加一个维度,使用方式如下。
运行上面的代码,就可以将2个维度的数组转换为3个维度的数组了。
这个操作在看深度学习相关代码的时候经常会看到,只不过PyTorch中的函数名unsqueeze(), TensorFlow的话是与NumPy有相同的名字,直接使用tf.newaxis就可以了。
然后我们再次将im_pillow_c1与zeros进行合并,这时就不会报错了,代码如下所示:
方法二:直接赋值
增加维度的第二个方法就是直接赋值,其实我们完全可以生成一个与im_pillow形状完全一样的全0数组,然后将每个通道的数值赋值为im_pillow_c1、im_pillow_c2与im_pillow_c3就可以了。我们用这种方式生成上图中的中间与右边图像的数组。
这样的话,我们就可以将三个通道的RGB图片打印出来了。
代码如下:
使用Pillow读取图像并显示通道代码
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
# 使用Pillow读取图像
image_path = 'F:/anim.jpg'
image = Image.open(image_path)
# 验证图像的大小和模式
print("Original Image Size:", image.size) # (宽度, 高度)
print("Original Image Mode:", image.mode) # Mode should be 'RGB'
# 将图像转换为NumPy数组
rgb_image = np.array(image)
# 分离 R、G、B 通道
r_channel = rgb_image[:, :, 0] # 红色通道
g_channel = rgb_image[:, :, 1] # 绿色通道
b_channel = rgb_image[:, :, 2] # 蓝色通道
# 只保留红色通道
red_only = np.zeros_like(rgb_image) # 创建与原图相同形状的全零数组
red_only[:, :, 0] = r_channel # 只将红色通道保留下来
# 只保留绿色通道
green_only = np.zeros_like(rgb_image) # 创建与原图相同形状的全零数组
green_only[:, :, 1] = g_channel # 只将绿色通道保留下来
# 只保留蓝色通道
blue_only = np.zeros_like(rgb_image) # 创建与原图相同形状的全零数组
blue_only[:, :, 2] = b_channel # 只将蓝色通道保留下来
# 转换为 RGB 以便使用 Matplotlib 显示
# 因为Pillow读取的是RGB格式,所以不需要进行色彩空间转换
red_only_pil = Image.fromarray(red_only)
green_only_pil = Image.fromarray(green_only)
blue_only_pil = Image.fromarray(blue_only)
# 显示结果
plt.figure(figsize=(10, 8))
plt.subplot(2, 2, 1)
plt.title('Original Image (RGB)')
plt.imshow(image) # 显示原图
plt.axis('off')
plt.subplot(2, 2, 2)
plt.title('Red Channel Only')
plt.imshow(red_only_pil) # 显示只包含红色通道的图像
plt.axis('off')
plt.subplot(2, 2, 3)
plt.title('Green Channel Only')
plt.imshow(green_only_pil) # 显示只包含绿色通道的图像
plt.axis('off')
plt.subplot(2, 2, 4)
plt.title('Blue Channel Only')
plt.imshow(blue_only_pil) # 显示只包含蓝色通道的图像
plt.axis('off')
# 显示图像
plt.show()
深拷贝(副本)与浅拷贝(视图)
刚才我们通过获取图片通道数据的练习,不过操作确实比较繁琐,介绍这些方法也主要是为了更好掌握切片索引和数组拼接的知识点。
其实我们还有一种更加简单的方式获得三个通道的BGR数据,只需要将图片读入后,直接将其中的两个通道赋值为0即可。代码如下所示:
Pillow 图像转换为 NumPy 数组时,一些情况下生成的数组是只读的,导致无法直接进行切片赋值。
我们将刚才报错的程序修改成下面的形式就可以了。
1:
表示选择从第二个通道(索引1,通常对应绿色通道)开始到最后一个通道的所有通道(也就是说,选择绿色和蓝色通道)。
使用Pillow库读入图像,然后直接将其中的两个通道赋值为0,以便得到每个颜色通道的单独图像,上面代码的提取通道部分可以这样修改:
我们可以使用copy来复制一个数组。前面说到创建数组时就提过,np.array()属于深拷贝,np.asarray()则是浅拷贝。
浅拷贝(视图): 浅拷贝是指创建一个新的数组,但这个新数组会与原数组共享同样的数据。这意味着对新数组的修改会影响原数组,因为它们实际上指向同一块内存。
视图我们通常使用view()来创建。常见的切片操作也会返回对原数组的浅拷贝
举例:
shallow_copy
是original_array
的一个视图。尽管shallow_copy
是一个新数组,但它的前几个元素指向的是original_array
中的同一数据。- 修改
shallow_copy
的元素会影响original_array
中对应位置的元素,因为它们共享同一块内存。
深拷贝: 深拷贝则是创建原数据的完全独立副本。对新数组的修改不会影响原数组。
如果希望创建一个与原数组完全独立的副本,可以使用
np.copy()
方法。举例:
使用OpenCV读取图像并显示通道代码
用上述的方法对OpenCV读取图片读入通道顺序进行验证,以下是完整代码:
只需要更改图像读取和通道提取部分的代码
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 使用 OpenCV 读取图像
bgr_image = cv2.imread('F:/anim.jpg')
# 验证图像的形状
print("Original BGR image shape:", bgr_image.shape) # (高度, 宽度, 3)
# 分离 B、G、R 通道
b_channel = bgr_image[:, :, 0] # 蓝色通道
g_channel = bgr_image[:, :, 1] # 绿色通道
r_channel = bgr_image[:, :, 2] # 红色通道
# 只保留红色通道
red_only = np.zeros_like(bgr_image) # 创建与原图相同形状的全零数组
red_only[:, :, 2] = r_channel # 只将红色通道保留下来
# 只保留绿色通道
green_only = np.zeros_like(bgr_image) # 创建与原图相同形状的全零数组
green_only[:, :, 1] = g_channel # 只将绿色通道保留下来
# 只保留蓝色通道
blue_only = np.zeros_like(bgr_image) # 创建与原图相同形状的全零数组
blue_only[:, :, 0] = b_channel # 只将蓝色通道保留下来
# 转换为 RGB 以便使用 Matplotlib 显示
red_only_rgb = cv2.cvtColor(red_only, cv2.COLOR_BGR2RGB)
green_only_rgb = cv2.cvtColor(green_only, cv2.COLOR_BGR2RGB)
blue_only_rgb = cv2.cvtColor(blue_only, cv2.COLOR_BGR2RGB)
# 显示结果
plt.figure(figsize=(10, 8))
plt.subplot(2, 2, 1)
plt.title('Original Image (RGB)')
plt.imshow(cv2.cvtColor(bgr_image, cv2.COLOR_BGR2RGB)) # 显示原图
plt.axis('off')
plt.subplot(2, 2, 2)
plt.title('Red Channel Only')
plt.imshow(red_only_rgb) # 显示只包含红色通道的图像
plt.axis('off')
plt.subplot(2, 2, 3)
plt.title('Green Channel Only')
plt.imshow(green_only_rgb) # 显示只包含绿色通道的图像
plt.axis('off')
plt.subplot(2, 2, 4)
plt.title('Blue Channel Only')
plt.imshow(blue_only_rgb) # 显示只包含蓝色通道的图像
plt.axis('off')
# 显示图像
plt.show()
结果:
cv2.cvtColor()
是 OpenCV 中的一个函数,它用于转换图像的颜色空间。OpenCV 默认以 BGR(蓝色、绿色、红色)的顺序读取图像,而大多数图像显示库(如 Matplotlib)则默认使用 RGB(红色、绿色、蓝色)的顺序。如果直接将 BGR 图像传递给 Matplotlib 显示,颜色会出现扭曲,图像的颜色看起来会不正常。
模型评估
在模型评估时,我们一般会将模型的输出转换为对应的标签。
假设现在我们的问题是将图片分为2个类别,包含动物的图片与不包含动物的图片。
模型会输出形状为(2, )的数组,我们把它叫做probs,它存储了两个概率,我们假设索引为0的概率是包含动物图片的概率,另一个是其它图片的概率,它们两个概率的和为1。如果动物对应的概率大,则可以推断该图片为包含动物的图片,否则为其他图片。
简单的做法就是判断probs[0]是否大于0.5,如果大于0.5,则可以认为图片是我们要寻找的。
但是如果我们需要判断图片的类别有很多很多种呢?
例如,有1000个类别的ImageNet,那可以遍历这个数组,求出最大值对应的索引。
那如果让你找出概率最大的前5个类别呢?
Argmax / Argmin:求最大/最小值对应的索引
NumPy的argmax(a, axis = None )方法可以为我们解决求最大值索引的问题。如果不指定axis,则将数组默认为1维。
获得拥有最大概率值的图片:
np.argmax(probs)
Argmin的用法跟Argmax差不多,它的作用是获得具有最小值的索引。
Argsort:数组排序后返回原数组的索引
比如需要你将图片分成10个类别,要找到具有最大概率的前三个类别。
模型输出的概率如下:
probs = np.array([0.075, 0.15, 0.075, 0.15, 0.0, 0.05, 0.05, 0.2, 0.25])
我们就可以借助argsort ( a , axis = -1, kind = None )函数来解决该问题。np.argsort的作用是对原数组进行从小到大的排序,返回的是对应元素在原数组中的索引。
np.argsort包括后面这几个关键参数:
- a是要进行排序的原数组;
- axis是要沿着哪一个轴进行排序,默认是-1,也就是最后一个轴;
- kind是采用什么算法进行排序,默认是快速排序,还有其他排序算法
小结
对于NumPy的学习,数组的轴不是很好理解,后面花时间再学习一下。
原文地址:https://blog.csdn.net/2301_77254487/article/details/144403342
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!