作者: Sayak Paul
创建日期: 2021/10/08
最后修改: 2021/10/10
描述: 缓解训练集和测试集之间的分辨率不一致。
在训练和测试视觉模型时,使用相同的输入图像分辨率是一种常见做法。然而,正如Touvron等人的研究所证明的,这种做法会导致次优性能。数据增强是深度神经网络训练过程中的一个不可或缺的部分。对于视觉模型,我们通常在训练期间使用随机裁剪,而在推理期间使用中心裁剪。这在训练和推理期间引入了物体大小的差异。正如Touvron等人所示,如果我们能修复这种差异,就可以显著提升模型性能。
在这个例子中,我们实现了由Touvron等人提出的FixRes技术,以修复这种差异。
import keras
from keras import layers
import tensorflow as tf # 仅用于图像处理和管道
import tensorflow_datasets as tfds
tfds.disable_progress_bar()
import matplotlib.pyplot as plt
tf_flowers
数据集train_dataset, val_dataset = tfds.load(
"tf_flowers", split=["train[:90%]", "train[90%:]"], as_supervised=True
)
num_train = train_dataset.cardinality()
num_val = val_dataset.cardinality()
print(f"训练示例的数量: {num_train}")
print(f"验证示例的数量: {num_val}")
训练示例的数量: 3303
验证示例的数量: 367
我们创建三个数据集:
我们将对较大分辨率的数据集应用不同的数据增强变换。
FixRes的思路是先在较小分辨率数据集上训练模型,然后在较大分辨率数据集上微调。这个简单而有效的方案带来了显著的性能提升。有关结果,请参阅原始论文。
# 参考: https://github.com/facebookresearch/FixRes/blob/main/transforms_v2.py.
batch_size = 32
auto = tf.data.AUTOTUNE
smaller_size = 128
bigger_size = 224
size_for_resizing = int((bigger_size / smaller_size) * bigger_size)
central_crop_layer = layers.CenterCrop(bigger_size, bigger_size)
def preprocess_initial(train, image_size):
"""初步预处理函数,用于在较小分辨率上训练。
对于训练,进行随机水平翻转 -> 随机裁剪。
对于验证,只进行缩放。
不使用颜色抖动。
"""
def _pp(image, label, train):
if train:
channels = image.shape[-1]
begin, size, _ = tf.image.sample_distorted_bounding_box(
tf.shape(image),
tf.zeros([0, 0, 4], tf.float32),
area_range=(0.05, 1.0),
min_object_covered=0,
use_image_if_no_bounding_boxes=True,
)
image = tf.slice(image, begin, size)
image.set_shape([None, None, channels])
image = tf.image.resize(image, [image_size, image_size])
image = tf.image.random_flip_left_right(image)
else:
image = tf.image.resize(image, [image_size, image_size])
return image, label
return _pp
def preprocess_finetune(image, label, train):
"""用于在较高分辨率上微调的预处理函数。
对于训练,缩放到更大的分辨率以保持比例 ->
随机水平翻转 -> 中心裁剪。
对于验证,以相同方式处理,不进行水平翻转。
不使用颜色抖动。
"""
image = tf.image.resize(image, [size_for_resizing, size_for_resizing])
if train:
image = tf.image.random_flip_left_right(image)
image = central_crop_layer(image[None, ...])[0]
return image, label
def make_dataset(
dataset: tf.data.Dataset,
train: bool,
image_size: int = smaller_size,
fixres: bool = True,
num_parallel_calls=auto,
):
if image_size not in [smaller_size, bigger_size]:
raise ValueError(f"{image_size} 分辨率不被支持。")
# 确定我们正在使用的预处理函数。
if image_size == smaller_size:
preprocess_func = preprocess_initial(train, image_size)
elif not fixres and image_size == bigger_size:
preprocess_func = preprocess_initial(train, image_size)
else:
preprocess_func = preprocess_finetune
dataset = dataset.map(
lambda x, y: preprocess_func(x, y, train),
num_parallel_calls=num_parallel_calls,
)
dataset = dataset.batch(batch_size)
if train:
dataset = dataset.shuffle(batch_size * 10)
return dataset.prefetch(num_parallel_calls)
Notice how the augmentation transforms vary for the kind of dataset we are preparing.
initial_train_dataset = make_dataset(train_dataset, train=True, image_size=smaller_size)
initial_val_dataset = make_dataset(val_dataset, train=False, image_size=smaller_size)
finetune_train_dataset = make_dataset(train_dataset, train=True, image_size=bigger_size)
finetune_val_dataset = make_dataset(val_dataset, train=False, image_size=bigger_size)
vanilla_train_dataset = make_dataset(
train_dataset, train=True, image_size=bigger_size, fixres=False
)
vanilla_val_dataset = make_dataset(
val_dataset, train=False, image_size=bigger_size, fixres=False
)
def visualize_dataset(batch_images):
plt.figure(figsize=(10, 10))
for n in range(25):
ax = plt.subplot(5, 5, n + 1)
plt.imshow(batch_images[n].numpy().astype("int"))
plt.axis("off")
plt.show()
print(f"Batch shape: {batch_images.shape}.")
# 更小的分辨率。
initial_sample_images, _ = next(iter(initial_train_dataset))
visualize_dataset(initial_sample_images)
# 更大的分辨率,仅用于微调。
finetune_sample_images, _ = next(iter(finetune_train_dataset))
visualize_dataset(finetune_sample_images)
# 更大的分辨率,采用与更小分辨率数据集相同的增强变换。
vanilla_sample_images, _ = next(iter(vanilla_train_dataset))
visualize_dataset(vanilla_sample_images)
批次形状: (32, 128, 128, 3)。
批次形状: (32, 224, 224, 3)。
批次形状: (32, 224, 224, 3)。
我们训练多个变体的 ResNet50V2 (He et al.):
作为提醒,较大分辨率数据集在增强变换方面有所不同。
def get_training_model(num_classes=5):
inputs = layers.Input((None, None, 3))
resnet_base = keras.applications.ResNet50V2(
include_top=False, weights=None, pooling="avg"
)
resnet_base.trainable = True
x = layers.Rescaling(scale=1.0 / 127.5, offset=-1)(inputs)
x = resnet_base(x)
outputs = layers.Dense(num_classes, activation="softmax")(x)
return keras.Model(inputs, outputs)
def train_and_evaluate(
model,
train_ds,
val_ds,
epochs,
learning_rate=1e-3,
use_early_stopping=False,
):
optimizer = keras.optimizers.Adam(learning_rate=learning_rate)
model.compile(
optimizer=optimizer,
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)
if use_early_stopping:
es_callback = keras.callbacks.EarlyStopping(patience=5)
callbacks = [es_callback]
else:
callbacks = None
model.fit(
train_ds,
validation_data=val_ds,
epochs=epochs,
callbacks=callbacks,
)
_, accuracy = model.evaluate(val_ds)
print(f"验证集上的 Top-1 准确率: {accuracy*100:.2f}%.")
return model
epochs = 30
smaller_res_model = get_training_model()
smaller_res_model = train_and_evaluate(
smaller_res_model, initial_train_dataset, initial_val_dataset, epochs
)
Epoch 1/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 56s 299ms/step - 准确率: 0.4146 - 损失: 1.7349 - 验证准确率: 0.2234 - 验证损失: 2.0703
Epoch 2/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.5062 - 损失: 1.2458 - 验证准确率: 0.3896 - 验证损失: 1.5800
Epoch 3/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.5262 - 损失: 1.1733 - 验证准确率: 0.5940 - 验证损失: 1.0160
Epoch 4/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 37ms/step - 准确率: 0.5740 - 损失: 1.1021 - 验证准确率: 0.5967 - 验证损失: 1.6164
Epoch 5/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6160 - 损失: 1.0289 - 验证准确率: 0.5313 - 验证损失: 1.2465
Epoch 6/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6137 - 损失: 1.0286 - 验证准确率: 0.6431 - 验证损失: 0.8564
Epoch 7/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6237 - 损失: 0.9760 - 验证准确率: 0.6240 - 验证损失: 1.0114
Epoch 8/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6029 - 损失: 0.9994 - 验证准确率: 0.5804 - 验证损失: 1.0331
Epoch 9/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6419 - 损失: 0.9555 - 验证准确率: 0.6403 - 验证损失: 0.8417
Epoch 10/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6513 - 损失: 0.9333 - 验证准确率: 0.6376 - 验证损失: 1.0658
Epoch 11/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6316 - 损失: 0.9637 - 验证准确率: 0.5913 - 验证损失: 1.5650
Epoch 12/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6542 - 损失: 0.9047 - 验证准确率: 0.6458 - 验证损失: 0.9613
Epoch 13/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6551 - 损失: 0.8946 - 验证准确率: 0.6866 - 验证损失: 0.8427
Epoch 14/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6617 - 损失: 0.8848 - 验证准确率: 0.7003 - 验证损失: 0.9339
Epoch 15/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6455 - 损失: 0.9293 - 验证准确率: 0.6757 - 验证损失: 0.9453
Epoch 16/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6821 - 损失: 0.8481 - 验证准确率: 0.7466 - 验证损失: 0.7237
Epoch 17/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6750 - 损失: 0.8449 - 验证准确率: 0.5967 - 验证损失: 1.5579
Epoch 18/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 37ms/step - 准确率: 0.6765 - 损失: 0.8605 - 验证准确率: 0.6921 - 验证损失: 0.8136
Epoch 19/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6969 - 损失: 0.8140 - 验证准确率: 0.6131 - 验证损失: 1.0785
Epoch 20/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6831 - 损失: 0.8257 - 验证准确率: 0.7221 - 验证损失: 0.7480
Epoch 21/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6988 - 损失: 0.8008 - 验证准确率: 0.7193 - 验证损失: 0.7953
Epoch 22/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.7172 - 损失: 0.7578 - 验证准确率: 0.6730 - 验证损失: 1.1628
Epoch 23/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.6935 - 损失: 0.8126 - 验证准确率: 0.7357 - 验证损失: 0.6565
Epoch 24/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.7149 - 损失: 0.7568 - 验证准确率: 0.7439 - 验证损失: 0.8830
Epoch 25/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.7151 - 损失: 0.7510 - 验证准确率: 0.7248 - 验证损失: 0.7459
Epoch 26/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.7133 - 损失: 0.7838 - 验证准确率: 0.7084 - 验证损失: 0.7140
Epoch 27/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.7314 - 损失: 0.7386 - 验证准确率: 0.6730 - 验证损失: 1.5988
Epoch 28/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.7259 - 损失: 0.7417 - 验证准确率: 0.7275 - 验证损失: 0.7255
Epoch 29/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.7006 - 损失: 0.7863 - 验证准确率: 0.6621 - 验证损失: 1.5714
Epoch 30/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 4s 36ms/step - 准确率: 0.7115 - 损失: 0.7498 - 验证准确率: 0.7548 - 验证损失: 0.7067
12/12 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - 准确率: 0.7207 - 损失: 0.8735
验证集上的Top-1准确率: 75.48%.
为了微调,我们只训练两个层:
我们解冻最后的批归一化层,以补偿全局平均池化层之前激活统计的变化。如 论文中所示,解冻最后的批归一化层就足够了。
有关在 Keras 中微调模型的综合指南,请参考 本教程。
for layer in smaller_res_model.layers[2].layers:
layer.trainable = False
smaller_res_model.layers[2].get_layer("post_bn").trainable = True
epochs = 10
# 在微调期间使用较低的学习率。
bigger_res_model = train_and_evaluate(
smaller_res_model,
finetune_train_dataset,
finetune_val_dataset,
epochs,
learning_rate=1e-4,
)
第 1 轮/10 轮
104/104 ━━━━━━━━━━━━━━━━━━━━ 26s 158ms/step - 准确率: 0.6890 - 损失: 0.8791 - 验证准确率: 0.7548 - 验证损失: 0.7801
第 2 轮/10 轮
104/104 ━━━━━━━━━━━━━━━━━━━━ 6s 34ms/step - 准确率: 0.7372 - 损失: 0.8209 - 验证准确率: 0.7466 - 验证损失: 0.7866
第 3 轮/10 轮
104/104 ━━━━━━━━━━━━━━━━━━━━ 6s 34ms/step - 准确率: 0.7532 - 损失: 0.7925 - 验证准确率: 0.7520 - 验证损失: 0.7779
第 4 轮/10 轮
104/104 ━━━━━━━━━━━━━━━━━━━━ 6s 34ms/step - 准确率: 0.7417 - 损失: 0.7833 - 验证准确率: 0.7439 - 验证损失: 0.7625
第 5 轮/10 轮
104/104 ━━━━━━━━━━━━━━━━━━━━ 6s 34ms/step - 准确率: 0.7508 - 损失: 0.7624 - 验证准确率: 0.7439 - 验证损失: 0.7449
第 6 轮/10 轮
104/104 ━━━━━━━━━━━━━━━━━━━━ 6s 34ms/step - 准确率: 0.7542 - 损失: 0.7406 - 验证准确率: 0.7493 - 验证损失: 0.7220
第 7 轮/10 轮
104/104 ━━━━━━━━━━━━━━━━━━━━ 6s 34ms/step - 准确率: 0.7471 - 损失: 0.7716 - 验证准确率: 0.7520 - 验证损失: 0.7111
第 8 轮/10 轮
104/104 ━━━━━━━━━━━━━━━━━━━━ 6s 35ms/step - 准确率: 0.7580 - 损失: 0.7082 - 验证准确率: 0.7548 - 验证损失: 0.6939
第 9 轮/10 轮
104/104 ━━━━━━━━━━━━━━━━━━━━ 6s 34ms/step - 准确率: 0.7571 - 损失: 0.7121 - 验证准确率: 0.7520 - 验证损失: 0.6915
第 10 轮/10 轮
104/104 ━━━━━━━━━━━━━━━━━━━━ 6s 34ms/step - 准确率: 0.7482 - 损失: 0.7285 - 验证准确率: 0.7520 - 验证损失: 0.6830
12/12 ━━━━━━━━━━━━━━━━━━━━ 0s 34ms/step - 准确率: 0.7296 - 损失: 0.7253
验证集上的 Top-1 准确率: 75.20%。
现在,我们在更大分辨率的数据集上从头开始训练另一个模型。请回忆一下, 该数据集中使用的增强变换与之前不同。
epochs = 30
vanilla_bigger_res_model = get_training_model()
vanilla_bigger_res_model = train_and_evaluate(
vanilla_bigger_res_model, vanilla_train_dataset, vanilla_val_dataset, epochs
)
Epoch 1/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 58s 318ms/step - 准确率: 0.4148 - 损失: 1.6685 - 验证准确率: 0.2807 - 验证损失: 1.5614
Epoch 2/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.5137 - 损失: 1.2569 - 验证准确率: 0.3324 - 验证损失: 1.4950
Epoch 3/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.5582 - 损失: 1.1617 - 验证准确率: 0.5395 - 验证损失: 1.0945
Epoch 4/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.5559 - 损失: 1.1420 - 验证准确率: 0.5123 - 验证损失: 1.5154
Epoch 5/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.6036 - 损失: 1.0731 - 验证准确率: 0.4823 - 验证损失: 1.2676
Epoch 6/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.5376 - 损失: 1.1810 - 验证准确率: 0.4496 - 验证损失: 3.5370
Epoch 7/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.6216 - 损失: 0.9956 - 验证准确率: 0.5804 - 验证损失: 1.0637
Epoch 8/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.6209 - 损失: 0.9915 - 验证准确率: 0.5613 - 验证损失: 1.1856
Epoch 9/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.6229 - 损失: 0.9657 - 验证准确率: 0.6076 - 验证损失: 1.0131
Epoch 10/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.6322 - 损失: 0.9654 - 验证准确率: 0.6022 - 验证损失: 1.1179
Epoch 11/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.6223 - 损失: 0.9634 - 验证准确率: 0.6458 - 验证损失: 0.8731
Epoch 12/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.6414 - 损失: 0.9838 - 验证准确率: 0.6975 - 验证损失: 0.8202
Epoch 13/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.6635 - 损失: 0.8912 - 验证准确率: 0.6730 - 验证损失: 0.8018
Epoch 14/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.6571 - 损失: 0.8915 - 验证准确率: 0.5640 - 验证损失: 1.2489
Epoch 15/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.6725 - 损失: 0.8788 - 验证准确率: 0.6240 - 验证损失: 1.0039
Epoch 16/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.6776 - 损失: 0.8630 - 验证准确率: 0.6322 - 验证损失: 1.0803
Epoch 17/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.6728 - 损失: 0.8673 - 验证准确率: 0.7330 - 验证损失: 0.7256
Epoch 18/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 85ms/step - 准确率: 0.6969 - 损失: 0.8069 - 验证准确率: 0.7275 - 验证损失: 0.8264
Epoch 19/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 85ms/step - 准确率: 0.6891 - 损失: 0.8271 - 验证准确率: 0.6594 - 验证损失: 0.9932
Epoch 20/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 85ms/step - 准确率: 0.6678 - 损失: 0.8630 - 验证准确率: 0.7221 - 验证损失: 0.7238
Epoch 21/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.6980 - 损失: 0.7991 - 验证准确率: 0.6267 - 验证损失: 0.8916
Epoch 22/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 85ms/step - 准确率: 0.7187 - 损失: 0.7546 - 验证准确率: 0.7466 - 验证损失: 0.6844
Epoch 23/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 85ms/step - 准确率: 0.7210 - 损失: 0.7491 - 验证准确率: 0.6676 - 验证损失: 1.1051
Epoch 24/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.6930 - 损失: 0.7762 - 验证准确率: 0.7493 - 验证损失: 0.6720
Epoch 25/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.7192 - 损失: 0.7706 - 验证准确率: 0.7357 - 验证损失: 0.7281
Epoch 26/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.7227 - 损失: 0.7339 - 验证准确率: 0.7602 - 验证损失: 0.6618
Epoch 27/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.7108 - 损失: 0.7641 - 验证准确率: 0.7057 - 验证损失: 0.8372
Epoch 28/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.7186 - 损失: 0.7644 - 验证准确率: 0.7657 - 验证损失: 0.5906
Epoch 29/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.7166 - 损失: 0.7394 - 验证准确率: 0.7820 - 验证损失: 0.6294
Epoch 30/30
104/104 ━━━━━━━━━━━━━━━━━━━━ 10s 84ms/step - 准确率: 0.7122 - 损失: 0.7655 - 验证准确率: 0.7139 - 验证损失: 0.8012
12/12 ━━━━━━━━━━━━━━━━━━━━ 0s 33ms/step - 准确率: 0.6797 - 损失: 0.8819
在验证集上的 Top-1 准确率: 71.39%。
从上面的单元格中我们可以看出,FixRes 提供了更好的性能。FixRes 的另一个优点是改善了总训练时间并减少了 GPU 内存使用。FixRes 是与模型无关的,您可以在任何图像分类模型上使用它来潜在提高性能。
您可以在 这里 找到更多结果,这些结果是通过运行相同的代码并使用不同的随机种子收集的。