蟒蛇的NumPy包提供了各种用于执行涉及随机性的操作的方法,例如从给定的数字列表中随机选择一个或多个数字的方法,或者生成给定范围内的随机数的方法,或者从给定分布。
所有这些方法均在random
NumPy 包的模块。
其中一种方法是numpy.random.shuffle
method.
此方法用于随机打乱给定“可变”迭代的元素。
Note可迭代对象可变的原因是混洗操作涉及项目重新分配,而不可变对象不支持这一点。
洗牌有什么好处?
混洗操作对于许多应用程序来说是基础,在这些应用程序中,我们希望在处理给定的数据集时引入机会元素。
当我们希望避免在处理数据的顺序中引入任何类型的偏差时,它特别有用。
混洗操作通常用于批量处理数据的机器学习管道中。
每次从数据集中随机选择一个批次时,都会先进行洗牌操作。
它还可用于从给定集合中随机抽取项目而不进行替换。
如何打乱 NumPy 数组?
让我们看看它的基本用法np.random.shuffle
method.
我们将打乱一个一维 NumPy 数组。
import numpy as np
for i in range(5):
a=np.array([1,2,4,5,6])
print(f"a = {a}")
np.random.shuffle(a)
print(f"shuffled a = {a}\n")
Output:
每次我们打电话shuffle
方法,我们得到了不同顺序的数组a。
Note 运行此代码时得到的输出可能与我得到的输出不同,因为正如我们所讨论的,随机播放是一种随机操作。
在后面的部分中,我们将学习如何使这些随机操作具有确定性,以使结果可重现。
将多个 NumPy 数组混在一起
我们了解了如何打乱单个 NumPy 数组。有时我们想要将多个相同长度的数组以相同的顺序混在一起。
虽然shuffle
方法不能接受超过 1 个数组,有一种方法可以通过使用 random 模块的另一个重要方法来实现这一点 -np.random.permutation
.
x = np.array([1,2,3,4,5,6])
y = np.array([10,20,30,40,50,60])
print(f"x = {x}, y = {y}")
shuffled_indices = np.random.permutation(len(x)) #return a permutation of the indices
print(f"shuffled indices: {shuffled_indices}")
x = x[shuffled_indices]
y = y[shuffled_indices]
print(f"shuffled x = {x}\nshuffled y {y}")
Output:
我们首先生成 [0, len(x)) 范围内整数值的随机排列,然后使用相同的排列来索引两个数组。
如果您正在寻找一种同时接受多个数组并对它们进行打乱的方法,那么 scikit-learn 包中就有一个 -sklearn.utils.shuffle
.
此方法接受您想要打乱的任意数量的数组,并返回打乱后的数组。
from sklearn.utils import shuffle
x = np.array([1,2,3,4,5,6])
y = np.array([10,20,30,40,50,60])
x_shuffled, y_shuffled = shuffle(x,y)
print(f"shuffled x = {x_shuffled}\nshuffled y={y_shuffled}")
print(f"original x = {x}, original y = {y}")
Output:
请注意,此方法不会像np.random.shuffle
相反,它返回打乱后的数组而不修改输入数组。
由于此方法不涉及就地项目重新分配,因此我们还可以使用此方法对不可变的可迭代对象进行洗牌。
打乱二维数组
我们已经看到了NumPy的效果shuffle
一维数组上的方法。
让我们看看它对二维数组做了什么。
x = np.random.randint(1,100, size=(3,3))
print(f"x:\n{x}\n")
np.random.shuffle(x)
print(f"shuffled x:\n{x}")
Output:
如果仔细观察输出,会发现各行中值的顺序没有改变;然而,数组中行的位置已被打乱。
So the shuffle
method 打乱行顺序默认情况下是二维数组。
打乱 2D NumPy 数组的列
我们在上一节中看到了shuffle
二维数组上的方法。
它将数组的行就地打乱。
但是,如果我们想对数组的列进行打乱,该怎么办呢?
shuffle 方法不需要任何额外的参数来指定我们要执行 shuffle 的轴。
因此,如果我们想使用以下方法对 2D 数组的列进行打乱np.random.shuffle
方法,我们必须找到一种方法将列视为行或将列与行交换.
这可以通过转置操作来实现。
我们将对 2D 数组的转置版本执行洗牌,并且由于洗牌就地发生,因此它将有效地对原始数组的列进行洗牌。
x = np.random.randint(1,100, size=(3,3))
print(f"x:\n{x}\n")
np.random.shuffle(x.T) #shuffling transposed form of x
print(f"column-wise shuffled x:\n{x}")
Output:
数组 x 的列现在已被打乱,而不是行。
打乱多维 NumPy 数组
我们已经看到了shuffle
一维和二维数组上的方法。现在让我们尝试了解如果将更高维数组传递给此方法会发生什么。
让我们将一个 3 维数组传递给np.random.shuffle
method.
x = np.random.randint(1,100, size=(4,3,3))
print(f"x:\n{x}\n")
np.random.shuffle(x)
print(f"shuffled x:\n{x}")
Output:
这里各个 3×3 数组的位置已被打乱。
这种行为类似于我们在二维数组中观察到的行为。
The shuffle
默认情况下,该方法会沿第一维(即 axis=0)对任何高维数组进行混洗。
沿轴随机播放
如果我们希望数组沿着任何其他轴进行洗牌,我们可以使用上一节中讨论的技术。
我们可以沿该轴生成索引的随机排列,并用它来索引数组。
让我们沿着轴 1 和轴 2 打乱 4x3x3 数组。
x = np.random.randint(1,100, size=(4,3,3))
print(f"x:\n{x}, shape={x.shape}\n")
indices_1 = np.random.permutation(x.shape[1])
x_1 = x[:,indices_1,:]
print(f"shuffled x along axis=1:\n{x_1}, shape={x_1.shape}\n")
indices_2 = np.random.permutation(x.shape[2])
x_2 = x[:,:,indices_2]
print(f"shuffled x along axis=2:\n{x_2}, shape={x_2.shape}\n")
Output:
在第一个输出中,当我们沿着 axis=1 进行洗牌时,每个 3×3 数组的行都已被洗牌。
类似地,当我们沿着 axis-2 进行洗牌时,数组的列也被洗牌了。
随机播放列表
在前面的章节中,我们讨论了其中一个条件np.random.shuffle
工作的方法是输入必须是可变对象,因为该方法涉及就地项目重新分配。
任何洗牌方法起作用的另一个条件是输入对象必须是可下标的。这意味着可以使用输入的各个元素的位置或索引来识别和访问它们。
在Python提供的基本数据结构中,列表是唯一满足这两个条件的数据结构。
集合和字典是可变的,但不可下标。
元组和字符串是可下标的,但不可变。
让我们使用以下方法打乱 Python 列表np.random.shuffle
method.
a = [5.4, 10.2, "hello", 9.8, 12, "world"]
print(f"a = {a}")
np.random.shuffle(a)
print(f"shuffle a = {a}")
Output:
如果我们想要对字符串或元组进行打乱,我们可以先将其转换为列表,打乱它,然后将其转换回字符串/元组;
或者我们可以使用 scikit-learnshuffle
方法来获取它的打乱副本。
用种子洗牌
如果您在关注本博客时一直在实现代码片段,您一定已经注意到,执行随机播放操作后获得的结果与此处输出中显示的结果不同。
这是因为洗牌是随机操作,因此结果不可重现。
编程语言中的随机运算有不是真正随机的。这些操作是在一个伪随机数生成器,它是通过对称为“种子”的数字执行一系列数学运算而获得的。
如果我们在执行随机操作甚至一系列随机操作之前固定种子的值,则最终输出将变得确定,并且每次都可以使用相同的种子进行再现。
让我们回到我们在本博客中执行的第一个随机操作。
我们使用 for 循环将 NumPy 数组连续打乱五次,每次都会得到不同的输出。
现在让我们设置一个固定的随机种子每次在对原始数组进行洗牌之前,看看我们是否得到相同的输出或不同的输出。
for i in range(5):
a=np.array([1,2,4,5,6])
print(f"a = {a}")
np.random.seed(42) #setting the random seed
np.random.shuffle(a)
print(f"shuffled a = {a}\n")
Output:
我们正在使用设置随机种子np.random.seed()
每次调用之前np.random.shuffle
使洗牌操作具有确定性。
但是,不必在每次调用随机操作之前设置随机种子。
如果您在代码中的不同实例中执行一系列随机操作之前设置一次随机种子;您可以稍后在另一天或另一台机器上通过在代码开头设置相同的种子来重现代码的输出。
打乱 NumPy 数组的维度
到目前为止,我们已经对 NumPy 数组执行了洗牌操作,而没有影响数组的形状。
我们一直在沿着选定的轴打乱数组的内容。
但是,如果我们想要打乱数组的轴而不是其元素怎么办?
NumPy 数组有一个名为转置,它接受轴索引的元组并根据传递的轴的顺序重塑数组。
让我们构建一个形状为 (2,3,2,4) 的 4 维数组,然后打乱其维度。
np.random.seed(0)
a = np.arange(48).reshape(2,3,2,4)
print(f"array a:\n{a}, shape={a.shape}\n")
shuffled_dimensions = np.random.permutation(a.ndim)
print(f"shuffled dimensions = {shuffled_dimensions}\n")
a_shuffled = a.transpose(shuffled_dimensions)
print(f"array a with shuffled dimensions:\n{a_shuffled}, shape={a_shuffled.shape}")
Output:
原始数组的形状为 (2,3,2,4)。
在我们打乱它的尺寸之后,它被转变为形状(2,4,3,2)。
随机播放与排列
我们已经在本博客的多个部分中看到了 NumPy 如何permutation
方法可用于执行洗牌操作。
不仅np.random.permutation
帮助以以下方式洗牌数组np.random.shuffle
cannot,
但它也可以达到同样的结果shuffle
在列表和数组上生成。
在本节中,我们将了解这两种方法之间的各种异同。
我们首先谈谈这两种方法可以接受的输入类型。
而 shuffle 方法严格只接受可下标、可变的迭代;permutation
另一方面,接受不可变的可迭代对象和整数,以及可变的可迭代对象。
当你传递一个整数到np.random.permutation
,它返回从 0 到该整数的整数范围的排列。
np.random.seed(42)
print(np.random.permutation(10))
Output:
接下来我们就来说一下这两个方法是如何进行shuffle操作的。
The shuffle
方法对我们传递给该方法的原始可迭代对象执行就地改组,因此它返回无。所以原来的可迭代对象最终会被修改。
另一方面,permutation
始终返回 NumPy 数组无论输入类型如何,因此它不会修改原始输入可迭代。
Let us also compare the time it takes for the two methods to shuffle the same array.
We will run the two methods on the same array and log the time it takes for them to shuffle it.
We will log times for arrays of lengths ranging from 102 to 109, with increments of orders of 10.
import numpy as np
import time as time
permutation_time_log = []
shuffle_time_log = []
for i in range(2,10):
print(f"shuffling array of length 10^{i}")
a = np.random.randint(100, size=(10**i))
t1 = time.time()
np.random.permutation(a)
t2 = time.time()
permutation_time_log.append(t2-t1)
t1 = time.time()
np.random.shuffle(a)
t2 = time.time()
shuffle_time_log.append(t2-t1)
del a
Note我们在每个循环结束时删除数组以释放内存;这可以避免以后迭代期间的任何内存开销。
我们记录了这两种方法对于长度递增的数组所消耗的时间。
现在让我们使用 pyplot 绘制它们。
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(111)
ax.plot(permutation_time_log, label="permutation")
ax.plot(shuffle_time_log, label="shuffle")
ax.set_xlabel("length of array")
ax.set_ylabel("time for shuffling(s)")
ax.set_xticks(range(8))
ax.set_xticklabels([f"10^{i}" for i in range(2,10)])
ax.legend()
plt.show()
Output:
It is evident from the figure that the two methods take almost the same time for arrays up to length 108,
and the difference between their times becomes more prominent beyond this point.
For arrays of lengths higher than 108, the shuffle
method performs shuffling faster than permutation
,
and its performance over the latter becomes more significant with increasing lengths.