前言
近期在摸索深度学习,本系列博客将会围绕基于PyTorch框架实现算法网络,后续中所谈到的内容均来自有关资料书籍,予以记录与诸君分享,当然也欢迎大家批评指正。
一、Numpy简介
在机器学习和深度学习中,图像、声音、文本等输入数据最终都要转换 为数组或矩阵。如何有效地进行数组和矩阵的运算?这就需要充分利用 Numpy。Numpy是数据科学的通用语言,而且与PyTorch关系非常密切,它 是科学计算、深度学习的基石。尤其对PyTorch而言,其重要性更加明显。 PyTorch中的Tensor与Numpy非常相似,它们之间可以非常方便地进行转 换,故此掌握好Numpy是掌握好Pytorch的重要基础。
二、Numpy使用
1.从已有数据中创建数组
直接对Python的基础数据类型(如列表、元组等)进行转换来生成 ndarray:
1)将列表转换成ndarray:
import numpy as np
#将列表转换成ndarray:
lst1 = [3.14, 2.17, 0, 1, 2] #注意列表有逗号
nd1 = np.array(lst1)
print(nd1) # [3.14 2.17 0. 1. 2. ]#数组没有逗号
print(type(nd1)) # <class 'numpy.ndarray'>
2)嵌套列表可以转换成多维ndarray:
lst2 = [[3.14, 2.17, 0, 1, 2], [1, 2, 3, 4, 5]] #注意这里是二维,一维可以看作表格中的一行,二维则是两行
nd2 = np.array(lst2)
print(nd2) # [[3.14 2.17 0. 1. 2. ] # [1. 2. 3. 4. 5. ]]
print(type(nd2)) # <class 'numpy.ndarray'>
注:如果把上面示例中的列表换成元组也同样适用。
2.利用random模块生成数组
在深度学习中,我们经常需要对一些参数进行初始化,因此为了更有效 地训练模型,提高模型的性能,有些初始化还需要满足一定的条件,如满足 正态分布或均匀分布等。这里介绍了几种常用的方法,如表1-1所示列举了 np.random模块常用的函数。
下面来看一些函数的具体使用:
nd3 = np.random.random([3, 3])
print(nd3)
# [[0.43007219 0.87135582 0.45327073]
# [0.7929617 0.06584697 0.82896613]
# [0.62518386 0.70709239 0.75959122]]
# print("nd3的形状为:",nd3.shape) # nd3的形状为: (3, 3)
#为了每次生成同一份数据,可以指定一个随机种子,使用shuffle函数打乱生成的随机数
nd4 = np.random.randn(2, 3)
print(nd4)
np.random.shuffle(nd4)
print("随机打乱后数据:")
print(nd4)
print(type(nd4))
# [[-0.23042745 0.67994083 -1.45989658]
# [ 1.56310165 -1.07267341 0.54721811]]
# 随机打乱后数据:
# [[ 1.56310165 -1.07267341 0.54721811]
# [-0.23042745 0.67994083 -1.45989658]]
# <class 'numpy.ndarray'>
3.创建特定形状的多维数组
参数初始化时,有时需要生成一些特殊矩阵,如全是0或1的数组或矩 阵,这时我们可以利用np.zeros、np.ones、np.diag来实现,具体函数如下表所示
下面通过几个示例说明:
# 生成全是 0 的 3x3 矩阵
nd5 =np.zeros([3, 3])
print(nd5)
print("*"*20,"分隔符","*"*20)
# #生成与nd5形状一样的全0矩阵
nd6=np.zeros_like(nd5)
print(nd6)
print("*"*20,"分隔符","*"*20)
# 生成全是 1 的 3x3 矩阵
nd7 = np.ones([3, 3])
print(nd7)
print("*"*20,"分隔符","*"*20)
# 生成 3 阶的单位矩阵
nd8 = np.eye(3)
print(nd8)
print("*"*20,"分隔符","*"*20)
# 生成 3 阶对角矩阵
nd9 = np.diag([1, 2, 3])
print(nd9)
# [[0. 0. 0.]
# [0. 0. 0.]
# [0. 0. 0.]]
# ******************** 分隔符 ********************
# [[0. 0. 0.]
# [0. 0. 0.]
# [0. 0. 0.]]
# ******************** 分隔符 ********************
# [[1. 1. 1.]
# [1. 1. 1.]
# [1. 1. 1.]]
# ******************** 分隔符 ********************
# [[1. 0. 0.]
# [0. 1. 0.]
# [0. 0. 1.]]
# ******************** 分隔符 ********************
# [[1 0 0]
# [0 2 0]
# [0 0 3]]
有时还可能需要把生成的数据暂时保存起来,以便后续使用
nd9 =np.random.random([5, 5])
np.savetxt(X=nd9, fname='./test1.txt')
nd10 = np.loadtxt('./test1.txt')
print("查看文件数据:",nd10)
4.利用arange、linspace函数生成数组
arange是numpy模块的函数,定义为:arange([start,] stop[,step,], dtype=None)。其中start与stop用来指定范围,step用来设定步长。在生成一个ndarray 时,start默认为0,步长step可为小数。Python有个内置函数range,其功能与 此类似。
print(np.arange(10))
# [0 1 2 3 4 5 6 7 8 9]
print(np.arange(0, 10))
# [0 1 2 3 4 5 6 7 8 9]
print(np.arange(1, 4, 0.5))
# [1. 1.5 2. 2.5 3. 3.5]
print(np.arange(9, -1, -1))
# [9 8 7 6 5 4 3 2 1 0]
linspace也是numpy模块中常用的函数,其格式为:np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
linspace可以根据输入的指定数据范围以及等份数量,自动生成一个线 性等分向量,其中endpoint(包含终点)默认为True,等分数量num默认为 50。如果将retstep设置为True,则会返回一个带步长的ndarray。
print(np.linspace(0, 1, 10))
#[0. 0.11111111 0.22222222 0.33333333 0.44444444 0.55555556 # 0.66666667 0.77777778 0.88888889 1. ]
值得一提的是,这里并没有像我们预期的那样,生成0.1,0.2,...,1.0这样 步长为0.1的ndarray,这是因为linspace必定会包含数据起点和终点,那么其 步长则为(1-0)/9=0.11111111。如果需要产生0.1,0.2,...,1.0这样的数据,只需 要将数据起点0修改为0.1即可。 除了上面介绍到的arange和linspace,Numpy还提供了logspace函数,该 函数的使用方法与linspace的使用方法一样
5.获取元素
前面介绍了生成ndarray的几种方法。那么在数据生成之后,如何获取到我们所需要的数据呢?接下来将会介绍几种常用获取数据的方法。
np.random.seed(2019)
nd11 = np.random.random([10])
nd11[3] #获取指定位置的数据,获取第4个元素
nd11[3:6] #截取一段数据
nd11[1:6:2] #截取固定间隔数据
nd11[::-2] #倒序取数
nd12 = np.arange(25).reshape([5, 5])
print(nd12)
nd12[1:3, 1:3] #截取一个多维数组的一个区域内数据
nd12[(nd12 > 3) & (nd12 < 10)] #截取一个多维数组中,数值在一个值域之内的数据
nd12[1:3, :] #截取多维数组中,指定的行,如读取第2,3行
nd12[:, 1:3] #截取多维数组中,指定的列,如读取第2,3列
获取数组中的部分元素除了通过指定索引标签来实现外,还可以通过使 用一些函数来实现,如通过random.choice函数从指定的样本中随机抽取数 据
from numpy import random as nr
a = np.arange(1, 25, dtype=float)
c1 = nr.choice(a, size=(3, 4))
#size指定输出数组形状
c2 = nr.choice(a, size=(3, 4), replace=False) #replace缺省为True,即可重复抽取。
#下式中参数p指定每个元素对应的抽取概率,缺省为每个元素被抽取的概率相同。
c3 = nr.choice(a, size=(3, 4), p=a / np.sum(a))
print("随机可重复抽取:")
print(c1)
print("随机但不重复抽取:")
print(c2)
print("随机但按制度概率抽取:")
print(c3)
随机可重复抽取:
# [[ 1. 11. 1. 12.]
# [ 4. 12. 12. 20.]
# [10. 11. 12. 10.]]
# 随机但不重复抽取:
# [[12. 19. 16. 10.]
# [23. 24. 4. 18.]
# [ 9. 7. 6. 13.]]
# 随机但按制度概率抽取:
# [[18. 17. 22. 20.]
# [22. 24. 21. 23.]
# [21. 20. 20. 14.]]
6.Numpy的算术运算
在机器学习和深度学习中,涉及大量的数组或矩阵运算,本节我们将重 点介绍两种常用的运算。一种是对应元素相乘,又称为逐元乘法(Element- Wise Product),运算符为np.multiply(),或*。另一种是点积或内积元素,运 算符为np.dot()。
1)对应运算相乘
对应元素相乘(Element-Wise Product)是两个矩阵中对应元素乘积。 np.multiply函数用于数组或矩阵对应元素相乘,输出与相乘数组或矩阵的大 小一致,其格式如下:numpy.multiply(x1, x2, /, out=None, *, where=True,casting='same_kind', order='K', dtype=N)其中x1、x2之间的对应元素相乘遵守广播规则,Numpy的广播规则在 将在后续通过一些示例来进一步说明
A = np.array([[1, 2], [-1, 4]])
B = np.array([[2, 0], [3, 4]])
print(A * B)
print("*" * 20, "分隔符", "*" * 20)
# #或另一种表示方法
print(np.multiply(A, B))
#运算结果
# [[ 2 0]
# [-3 16]]
# ******************** 分隔符 ********************
# [[ 2 0]
# [-3 16]]
Numpy数组不仅可以和数组进行对应元素相乘,还可以和单一数值(或 称为标量)进行运算。运算时,Numpy数组中的每个元素都和标量进行运 算,其间会用到广播机制
print(A*2.0)
print("*" * 20, "分隔符", "*" * 20)
print(A/2.0)
# [[ 2. 4.]
# [-2. 8.]]
# ******************** 分隔符 ********************
# [[ 0.5 1. ]
# [-0.5 2. ]]
2)点积运算
点积运算(Dot Product)又称为内积,在Numpy用np.dot表示,其一般 格式为:
numpy.dot(a, b, out=None)以下通过一个示例来说明dot的具体使用方法及注意事项。X1=np.array([[1,2],[3,4]]) X2=np.array([[5,6,7],[8,9,10]]) X3=np.dot(X1,X2) print(X3) # [[21 24 27] # [47 54 61]]
图示:可理解为线性代数当中的矩阵乘法
7.更改数组的形状
修改指定数组的形状是Numpy中最常见的操作之一,常见的方法有很 多,下表列出了一些常用函数。
1)reshape:改变向量的维度(不修改向量本身):
arr =np.arange(10)
print(arr)
# 将向量 arr 维度变换为2行5列
print(arr.reshape(2, 5))
# 指定维度时可以只指定行数或列数, 其他用 -1 代替
print(arr.reshape(5, -1)) #指定行数
print(arr.reshape(-1, 5))#指定列数
# [0 1 2 3 4 5 6 7 8 9]
# [[0 1 2 3 4]
# [5 6 7 8 9]]
# [[0 1]
# [2 3]
# [4 5]
# [6 7]
# [8 9]]
# [[0 1 2 3 4]
# [5 6 7 8 9]]
2)resize:改变向量的维度(修改向量本身):
arr =np.arange(10)
print(arr)
# 将向量 arr 维度变换为2行5列
arr.resize(2, 5)
print(arr)
# [0 1 2 3 4 5 6 7 8 9]
# [[0 1 2 3 4]
# [5 6 7 8 9]]
3)T:向量转置
arr = np.arange(12).reshape(3, 4) # 向量 arr 为3行4列
print(arr)
# 将向量 arr 进行转置为4行3列
print(arr.T)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
# [[ 0 4 8]
# [ 1 5 9]
# [ 2 6 10]
# [ 3 7 11]]
4)ravel:向量展平
arr =np.arange(6).reshape(2, -1)
print(arr)
# 按照列优先,展平
print("按照列优先,展平")
print(arr.ravel('F'))
# 按照行优先,展平
print("按照行优先,展平")
print(arr.ravel())
# [[0 1 2]
# [3 4 5]]
# 按照列优先,展平
# [0 3 1 4 2 5]
# 按照行优先,展平
# [0 1 2 3 4 5]
5)flatten:把矩阵转换为向量,这种需求经常出现在卷积网络与全连接层之间
a =np.floor(10*np.random.random((3,4)))
print(a)
print(a.flatten())
# [[6. 7. 4. 0.]
# [3. 6. 1. 2.]
# [9. 4. 6. 8.]]
# [6. 7. 4. 0. 3. 6. 1. 2. 9. 4. 6. 8.]
6)squeeze:主要用来降维的函数,把矩阵中含1的维度去掉。在PyTorch中 还有一种与之相反的操作——torch.unsqueeze
arr = np.arange(3).reshape(3, 1)
print(arr.shape) #(3,1)
print(arr.squeeze().shape) #(3,)
arr1 = np.arange(6).reshape(3, 1, 2, 1)
print(arr1.shape) #(3, 1, 2, 1)
print(arr1.squeeze().shape) #(3, 2)
7.合并数组
合并数组也是最常见的操作之一,下表列举了常见的用于数组或向量 合并的方法。
说明:
1)append、concatenate以及stack都有一个axis参数,用于控制数组的合并方式是按行还是按列。
2)对于append和concatenate,待合并的数组必须有相同的行数或列数。
3)stack、hstack、dstack,要求待合并的数组必须具有相同的形状
下面举一些例子:
1).append:合并一维数组
#合并一维
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
c = np.append(a, b)
print(c)
print("*" * 20, "分隔符", "*" * 20)
#合并多维
a = np.arange(4).reshape(2, 2)
print(a)
print("*" * 20, "分隔符", "*" * 20)
b = np.arange(4).reshape(2, 2) #按行合并
print(b)
c = np.append(a, b, axis=0)
print('按行合并后的结果')
print(c)
print('合并后数据维度', c.shape) #按列合并
d = np.append(a, b, axis=1)
print('按列合并后的结果')
print(d)
print('合并后数据维度', d.shape)
# [1 2 3 4 5 6]
# ******************** 分隔符 ********************
# [[0 1]
# [2 3]]
# ******************** 分隔符 ********************
# [[0 1]
# [2 3]]
# 按行合并后的结果
# [[0 1]
# [2 3]
# [0 1]
# [2 3]]
# 合并后数据维度 (4, 2)
# 按列合并后的结果
# [[0 1 0 1]
# [2 3 2 3]]
# 合并后数据维度 (2, 4)
2).concatenate:沿指定轴连接数组或矩阵
a=np.array([[1,2],[3,4]])
b=np.array([[5,6]])
c=np.concatenate((a,b),axis=0)
print(c)
d=np.concatenate((a,b.T),axis=1)
print(d)
# [[1 2]
# [3 4]
# [5 6]]
# [[1 2 5]
# [3 4 6]]
8.批量处理
1)在深度学习中,由于源数据都比较大,所以通常需要用到批处理。如利用批量来计算梯度的随机梯度法就是一个典型应用。深度学习的计算一般比较复杂,并且数据量一般比较大,如果一次处理整个数据,较大概率会出现资源瓶颈。为了更有效地计算,一般将整个数据集分批次处理。与处理整个数据集相反的另一个极端是每次只处理一条记录,这种方法也不科学,一次处理一条记录无法充分发挥GPU、Numpy的平行处理优势。因此,在实际使用中往往采用批量处理(Mini-Batch)的方法。
如何把大数据拆分成多个批次呢?可采用如下步骤:
1)得到数据集
2)随机打乱数据
3)定义批大小
4)批处理数据集
下面我们通过一个示例来具体说明:
import numpy as np
#生成10000个形状为2X3的矩阵
data_train = np.random.randn(10000, 2, 3)
#这是一个3维矩阵,第1个维度为样本数,后两个是数据形状
print(data_train.shape)
#(10000,2,3)
#打乱这10000条数据
np.random.shuffle(data_train)
#定义批量大小
batch_size = 100
#进行批处理
for i in range(0, len(data_train), batch_size):
x_batch_sum = np.sum(data_train[i:i + batch_size])
print("第{}批次,该批次的数据之和:{}".format(i, x_batch_sum))
2.循环与向量运算比较
充分使用Python的Numpy库中的内建函数(Built-inFunction),来实现计算的向量化,可大大地提高运行速度。Numpy库中的内建函数使用了 SIMD指令。如下使用的向量化要比使用循环计算速度快得多。如果使用 GPU,其性能将更强大,不过Numpy不支持GPU,PyTorch支持GPU。
总结
本章主要介绍了Numpy模块的常用操作,尤其涉及对矩阵的操作,这些 操作在后续程序中经常使用。Numpy内容很丰富,这里只列了一些主要内容,如果你想了解更多内容,可登录Numpy官网http://www.Numpy.org/,查看更多感兴趣内容。