PyTorch深度学习笔记之二(简介Numpy)

续上篇

Chapter 2. numpy 的核心数据结构

现在的主流深度学习框架 PyTorch 与 TensorFlow 中最基本的计算单元 Tensor,都与 NumPy 数组有着类似的计算逻辑。
NumPy 还被广泛用在 Pandas,SciPy 等其他数据科学与科学计算的 Python 模块中。
人脸识别技术(属于计算机视觉领域),其原理本质上就是先把图片转换成 NumPy 的数组,然后再进行一系列处理。

本章介绍 NumPy 的数组、数组的关键属性以及非常重要的轴的概念。

2.1 安装 numpy

在virtual env中运行以下命令安装numpy

pip install numpy

2.2 numpy 数组

数组是 numpy 中最核心的组成部分,称为 ndarray,是 N-dimensional array 的缩写。其中的 N 是一个数字,指代维度。

ndarray 与 Python 列表的比较:

  • Python 中的列表可以动态地改变,而 NumPy 数组是不可以的,它在创建时就有固定大小了
  • ndarray 中的数据类型必须一致,而列表中的元素可以是多种类型
  • numpy 针对 ndarray 的一系列运算进行了优化,使其速度更快,占用内存更少

创建数组

方法: 将一个列表传入 np.array()np.asarray()中。

这个列表可以是任意维度的。

  • np.array()是深拷贝
  • np.asarray()是浅拷贝
import numpy as np 

# create a 1-dim array 
a1d = np.asarray([1])
print(a1d)

# create a 2-dim array 
a2d = np.asarray([[1,2], [3,4]])
print(a2d)

数组的属性

ndim: 表示数组维度(或轴)的个数

>>>a1d.ndim
1
>>>a2d.ndim
2

shape: 表示数组的维度或形状,是一个整数元组

>>>a1d.shape
(1,)
>>>a2d.shape
(2, 2)

reshape()函数可以用来更改数组的形状。

>>> help(np.ndarray.reshape)

size: 数组元素的总数, 即shape属性中元素的乘积

dtype: 描述数组中元素类型的对象。使用 dtype 属性可以查看数组所属的数据类型

NumPy 中大部分常见的数据类型都是支持的,例如 int8、int16、int32、float32、float64 等。

可以使用 astype()改变数组的数据类型,不过改变数据类型会创建一个新的数组,而不是改变原数组的数据类型。

>>>a2d.dtype
dtype('float64')

>>>a2d.astype('int32')
array([[1, 2],
       [3, 4]], dtype=int32)
       
>>>a2d.dtype
dtype('float64')

>>>a2d_int = arr_2_d.astype('int32')

>>>arr_2_d_int.dtype
dtype('int32')

注意:
不能通过直接修改数据类型来修改数组的数据类型,这样代码虽然不会报错,但是数据会发生改变。

其他创建数组的方式

np.ones()

np.ones() 用来创建一个全 1 的数组,必填参数是数组的shape,可选参数是数组的数据类型。

>>>np.ones(shape=(2,3))
array([[1., 1., 1.],
       [1., 1., 1.]])

>>>np.ones(shape=(2,3), dtype='int32')
array([[1, 1, 1],
       [1, 1, 1]], dtype=int32)

np.zeros()

创建全0的数组,用法与上类似。

>>>np.ones((2, 3)) * 0.5
array([[0.5, 0.5, 0.5],
       [0.5, 0.5, 0.5]]

np.arragen()

np.arange([start, ]stop, [step, ]dtype=None)创建一个在 [start, stop)区间的数组,元素之间的跨度是 step

# 创建从0到4的数组
>>>np.arange(5)
array([0, 1, 2, 3, 4])

# 从2开始到4的数组
>>>np.arange(2, 5)
array([2, 3, 4])

# 从2开始,到8的数组,跨度是3
>>>np.arange(2, 9, 3)
array([2, 5, 8])

np.linspace()

可以用 np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)创建一个数组;
具体就是创建一个从开始数值到结束数值的等差数列。

>>> np.linspace(1,10,9)
array([ 1. ,  2.125,  3.25 ,  4.375,  5.5  ,  6.625,  7.75 ,  8.875, 10. ])
>>> np.linspace(1,10,10)
array([ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10.])

np.arange 与 np.linspace 也是比较常见的函数,比如你要作图的时候,可以用它们生成 x 轴的坐标。例如,我要生成一个 y=x2 的图片,x 轴可以用 np.linespace() 来生成。
代码如下:

import numpy as np
import matplotlib.pyplot as plt

X = np.arange(-50, 51, 2)
Y = X ** 2

plt.plot(X, Y, color='blue')
plt.legend()
plt.show()

这里有个点要注意,如果 import matplotlib 报错,解决办法如下:

pip install scipy
pip uninstall matplotlib
pip install matplotlib

2.3 数组的轴

有4名同学关于3款游戏的评分,形成一个(4,3)矩阵

>>>interest_score = np.random.randint(10, size=(4, 3))

>>>interest_score
array([[4, 7, 5],
       [4, 2, 5],
       [7, 2, 4],
       [1, 2, 4]])

对于二维数组,有两个轴,分别是代表行的 0 轴与代表列的 1 轴。
想象一下: 向下的是 0 轴, 向右的是 1 轴

要计算每一款游戏的评分总和,也就是沿着 0 轴的方向进行求和。所以,我们只需要在求和函数中指定沿着 0 轴的方向求和即可。

>>> np.sum(interest_score, axis=0)
array([16, 13, 18])

要计算每名同学的评分总和,也就是要沿着 1 轴方向对二维数组进行操作

>>> np.sum(interest_score, axis=1)
array([16, 11, 13,  7])

那多维数据该怎么办呢?
其实当 axis=i 时,就是按照第 i 个轴的方向进行计算的,或者可以理解为第 i 个轴的数据将会被折叠或聚合到一起。

看一个多维数组的例子

>>> a = np.arange(18).reshape(3,2,3)
>>> a
array([[[ 0,  1,  2],
        [ 3,  4,  5]],
        
       [[ 6,  7,  8],
        [ 9, 10, 11]],

       [[12, 13, 14],
        [15, 16, 17]]])

当 axis=0, 就是按第0轴聚合,会形成一个 (2,3) 的数组;

[
    [max(a000, a100, a200), max(a001, a101, a201), max(a002, a102, a202)],
    [max(a010, a110, a210), max(a011, a111, a211), max(a012, a112, a212)]
]

>>> a.max(axis=0)

array([[12, 13, 14],
       [15, 16, 17]])

当 axis=1, 就是按第1轴聚合,会形成一个 (3,3) 的数组;
当 axis=2, 就是按第2轴聚合,会形成一个 (3,2) 的数组;


Chapter 3. numpy 之 深度学习中的常用操作

图片分类问题的解决方案,可以分解成数据加载、训练、模型评估共三部分(基本所有深度学习的项目都可以这样划分)。
其中数据加载和模型评估中,就经常会用到 numPy 数组的相关操作。

3.1 数据加载阶段

数据加载阶段: 把训练数据读进来,交给模型训练使用。

训练数据包括3种:

  • 图片
  • 文本
  • 类似二维表那样的结构化数据

对于图片的处理,我们一般会使用 Pillow 与 OpenCV 这两个模块。

在 PyTorch 中,很多图片的操作都是基于 Pillow 的,所以当使用 PyTorch 编程出现问题,或者要思考、解决一些图片相关问题时,要从 Pillow 的角度出发。

以 Pillow 方式加载

from PIL import Image

im = Image.open('jk.jpg')

im.size
输出: 318, 116

Pillow 是以二进制形式读入保存的,那怎么转为 NumPy 格式呢?
利用 NumPy 的 asarray 方法,就可以将 Pillow 的数据转换为 NumPy 的数组格式。


import numpy as np

im_pillow = np.asarray(im)

im_pillow.shape
输出:(116, 318, 3)

以 OpenCV 的方式加载

OpenCV 不需要我们手动转格式,它读入图片后,就是以 NumPy 数组的形式来保存数据的。

import cv2

im_cv2 = cv2.imread('jk.jpg')

type(im_cv2)
输出:numpy.ndarray

im_cv2.shape
输出:(116, 318, 3)

注意: Pillow 读入后通道的顺序就是 R、G、B,而 OpenCV 读入后顺序是 B、G、R

模型训练时的通道顺序需与预测的通道顺序要保持一致。也就是说使用 Pillow 训练,使用 OpenCV 读入图片直接进行预测的话,不会报错,但结果会不正确,所以大家一定要注意。

3.2 索引和切片

NumPy 数组中经常会出现用冒号来检索数据的形式,如下:

im_pillow[:, :, 0]

冒号代表全部选中的意思。
通过下面的代码,我们就可以获得每个通道的数据了。

im_pillow_c1 = im_pillow[:, :, 0]
im_pillow_c2 = im_pillow[:, :, 1]
im_pillow_c3 = im_pillow[:, :, 2]


im_pillow_c1.shape
输出:(116, 318)

生成一个和 im_pillow 具有相同宽高的全0数组。

zeros = np.zeros((im_pillow.shape[0], im_pillow.shape[1], 1))

zeros.shape
输出:(116, 318, 1)

3.3 数组的拼接

刚才我们拿到了单独通道的数据,接下来就需要把一个分离出来的数据跟一个全 0 数组拼接起来。

NumPy 数组为我们提供了np.concatenate((a1, a2, …), axis=0)方法进行数组拼接。
其中,a1,a2, …就是我们要合并的数组; axis 是我们要沿着哪一个维度进行合并,默认是沿着 0 轴方向。

方法-1. 先用 np.newaxis 给数组增加一个维度,然后再合并


im_pillow_c1 = im_pillow_c1[:, :, np.newaxis]

im_pillow_c1.shape
# 输出:(116, 318, 1)

im_pillow_c1_3ch = np.concatenate((im_pillow_c1, zeros, zeros),axis=2)
im_pillow_c1_3ch.shape
输出:(116, 318, 3)

方法-2. 直接赋值

增加维度的第二个方法就是直接赋值。
先生成一个与 im_pillow 形状完全一样的全 0 数组,然后将每个通道的数值赋值为 im_pillow_c1、im_pillow_c2 与 im_pillow_c3 就可以了。

im_pillow_c2_3ch = np.zeros(im_pillow.shape)
im_pillow_c2_3ch[:,:,1] = im_pillow_c2

im_pillow_c3_3ch = np.zeros(im_pillow.shape)
im_pillow_c3_3ch[:,:,2] = im_pillow_c3

3.4 深拷贝(副本)与浅拷贝(视图)

还有一种更加简单的方式获得三个通道的 BGR 数据,只需要将图片读入后,直接将其中的两个通道赋值为 0 即可。

深拷贝使用copy()方法。

im_pillow = np.array(im)

im_pillow[:,:,1:]=0

浅拷贝或称视图,指的是与原数组共享数据的数组。
请注意,只是数据,没有共享形状。
视图通常使用view()来创建。
常见的切片操作也会返回对原数组的浅拷贝。

请看下面的代码,数组 a 与 b 的数据是相同的,形状确实不同,但是修改 b 中的数据后,a 的数据同样会发生变化。

a = np.arange(6)
print(a.shape)
输出:(6,)
print(a)
输出:[0 1 2 3 4 5]

b = a.view()
print(b.shape)
输出:(6,)
b.shape = 2, 3
print(b)
输出:[[0 1 2]
 [3 4 5]]
 
b[0,0] = 111
print(a)
输出:[111   1   2   3   4   5]

print(b)
输出:[[111   1   2]
 [  3   4   5]]

3.5 模型评估

假设现在我们的问题是将图片分为 2 个类别,包含极客时间的图片与不包含的图片。
模型会输出形状为 (2, ) 的数组,我们把它叫做 probs,它存储了两个概率,我们假设索引为 0 的概率是包含极客时间图片的概率,另一个是其它图片的概率,它们两个概率的和为 1。
如果极客时间对应的概率大,则可以推断该图片为包含极客时间的图片,否则为其他图片。

Argmax Vs Argmin:求最大 / 最小值对应的索引

NumPy 的 argmax(a, axis=None) 方法可以为我们解决求最大值索引的问题。
如果不指定 axis,则将数组默认为 1 维。
对于我们的问题,使用下述代码即可获得拥有最大概率值的图片。

np.argmax(probs)

Argmin 的用法跟 Argmax 差不多,不过它的作用是获得具有最小值的索引。

Argsort:数组排序后返回原数组的索引

把问题升级一下,比如需要你将图片分成 10 个类别,要找到具有最大概率的前三个类别。

可以借助argsort(a, axis=-1, kind=None)函数来解决该问题。

np.argsort 包括后面这几个关键参数:

  • a 是要进行排序的原数组
  • axis 是要沿着哪一个轴进行排序,默认是 -1,也就是最后一个轴
  • kind 是采用什么算法进行排序,默认是快速排序,还有其他排序算法
probs_idx_sort = np.argsort(-probs)  #注意,加了负号,是按降序排序

probs_idx_sort
输出:array([8, 7, 1, 3, 0, 2, 5, 6, 4])

#概率最大的前三个值的坐标
probs_idx_sort[:3]
输出:array([8, 7, 1])

(完)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值