在构建深度学习模型时,希望模型既稳健又准确。为此,需要一种评估技术来检查模型是否正常工作。K折交叉验证是帮助评估模型的一种技术。本文将从理论部分开始,然后转向代码及其解释。编码部分将完全依赖于Python。虽然这里讨论的方法也可以应用于一些机器学习问题,但只以深度学习问题为例。如果希望了解如何将其应用于机器学习,请在评论区告诉。
让首先尝试理解在这里做什么。在创建深度学习模型时,希望模型既稳健又准确。为此,需要一些评估技术,即某种方式来检查模型是否正常工作。K折交叉验证是帮助评估模型的一种技术。可能已经多次看到K折交叉验证的使用,但在这里,不仅将使用这种技术来评估,还将用它来计算模型的结果。在使用这种技术之前,首先需要理解为什么它如此重要,以及为什么它比其他验证技术更好。
通常,使用留出法,将数据分成两部分:训练和测试,并检查在训练数据上训练的模型是否在测试数据上表现良好,使用一些错误指标。但能依赖这种方法吗?答案是不可以。这种方式的评估高度依赖于训练集中也在测试集中的数据点,因此评估高度依赖于数据分割的方法。让看看K折交叉验证如何比传统的留出法更好。
K折交叉验证是一种验证技术,将数据分成k个子集,重复k次留出法,每个k个子集用作测试集,其他k-1个子集用于训练。然后计算所有这些k次试验的平均误差,这比标准留出法更可靠。有了这种技术,不必担心数据是如何实际分割的。
要将K折交叉验证与深度学习模型一起使用,需要满足一些先决条件,如下所示。
数据集:,包含6个类别,需要DL模型进行分类。主文件夹包含2个子文件夹,即train和test,以及一个CSV文件。子文件夹包含图像,CSV包含有关训练和测试数据的所有信息。更多信息可以在下载页面上找到。可以查看那里。
对于测试,将仅使用训练数据,即只会评估训练准确性。
库:pandas, Keras, sklearn。
不会从头开始编写K折交叉验证,因为sklearn已经提供了实现。将只使用那个。
from sklearn.model_selection import KFold
kf = KFold(n_splits=5, shuffle=False, random_state=None)
让深入看看参数。所以n_splits意味着想要创建的数据子集的数量。shuffle:是否在将数据分割成批次之前对数据进行洗牌。
现在让看看将要使用的sklearn中定义的K折的修改。
from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits=5, shuffle=False, random_state=None)
分层K折:分层和普通K折之间的主要区别在于分割方式,即分层K折保证每个分割将有一定百分比的每个类别,这试图最小化每个类别对结果的影响。当拥有不平衡的数据集时,最好使用这种技术。
让创建一个自定义模型进行比较:
def get_model(IMG_SIZE):
base_model = applications.ResNet50(weights='imagenet', include_top=False, input_shape=(IMG_SIZE, IMG_SIZE, 3))
add_model = Sequential()
add_model.add(Flatten(input_shape=base_model.output_shape[1:]))
add_model.add(Dropout(0.3))
add_model.add(Dense(64, activation='relu'))
add_model.add(Dropout(0.4))
add_model.add(Dense(6, activation='softmax'))
model = Model(inputs=base_model.input, outputs=add_model(base_model.output))
model.compile(loss='categorical_crossentropy', optimizer=optimizers.SGD(lr=1e-4, momentum=0.9), metrics=['accuracy'])
return model
在上面的模型中,使用了修改后的Resnet-50,其实现已经在Keras中定义。上述模型没有优化,仅用于示例解释。
IMG_SIZE = 128
BATCH_SIZE = 16
EPOCHS = 1
N_SPLIT = 7
在上面的代码中,初始化了n_splits为7,以便在取平均值时至少有一个类别重复。有6个类别,所以如果采取小于7的分割,可能会遇到每次都有一个不同的预测的情况。因此,最后将无法找到哪个类别在大多数情况下被预测,模型将只给出随机结果。
主代码:
train_datagen = ImageDataGenerator(rescale = 1./255, shear_range = 0.2, zoom_range = 0.2, horizontal_flip = True)
validation_datagen = ImageDataGenerator(rescale = 1./255)
kfold = StratifiedKFold(n_splits=N_SPLIT, shuffle=True, random_state=42)
j = 0
for train_idx, val_idx in list(kfold.split(train_x, train_y)):
x_train_df = df.iloc[train_idx]
x_valid_df = df.iloc[val_idx]
j += 1
training_set = train_datagen.flow_from_dataframe(dataframe=x_train_df, directory=TRAIN_PATH, x_col="Image", y_col="Class", class_mode="categorical", target_size=(IMG_SIZE, IMG_SIZE), batch_size=BATCH_SIZE)
validation_set = validation_datagen.flow_from_dataframe(dataframe=x_valid_df, directory=TRAIN_PATH, x_col="Image", y_col="Class", class_mode="categorical", target_size=(IMG_SIZE, IMG_SIZE), batch_size=BATCH_SIZE)
model_test = get_model(IMG_SIZE)
history = model_test.fit_generator(training_set, validation_data=validation_set, epochs=EPOCHS, steps_per_epoch=x_train_df.shape[0] // BATCH_SIZE)
test_generator = ImageDataGenerator(rescale = 1./255)
test_set = test_generator.flow_from_dataframe(dataframe=train, directory=TRAIN_PATH, x_col="Image", y_col=None, class_mode=None, target_size=(IMG_SIZE, IMG_SIZE))
pred = model_test.predict_generator(test_set, len(train) // BATCH_SIZE)
predicted_class_indices = np.argmax(pred, axis=1)
data_kfold[j] = predicted_class_indices
gc.collect()
在上述代码中,只是重复了7次留出法,并将输出存储在data_kfold中。所以基本上data_kfold将如下所示,将拥有:
所以当看到不同训练数据时,得到不同的结果,就像在上面的例子中,对于6468的图像,可以看到相同的模型一次预测它是2,一次预测它是1。但大多数时候预测值是4。那么认为应该是什么答案呢?由于可以看到4比其他任何类别都多,所以将选择4。是的!这正是在以下代码中所做的。
labels = (training_set.class_indices)
labels2 = dict((v, k) for k, v in labels.items())
import collections
for i in range(len(data_kfold)):
co = collections.Counter(data_kfold.loc[i])
co = sorted(co.items(), key=lambda x: x[1], reverse=True)
ans.Class.loc[i] = labels2[co[0][0]]
在上述代码中,将最预测标签的最大出现次数存储到数据持有者“ans”中。labels2只不过是从整数到字符串的映射,因为原始数据包含以字符串形式的目标类别,所以需要它。
哇,完成了教程。但是结果呢?让比较平均值和标准留出法的训练准确性。
留出法的准确性:0.32168805070335443
K折方法的准确性:0.4274230947596228
这些是获得的结果。当对K折取平均值时,以及当应用留出法时。所以可以清楚地说,K折的性能要好得多。
代码可以从Kaggle和Github获得:
Kaggle:
Github:
通过上述结果,可以清楚地看到,K折平均的准确性比标准留出法要好得多,即使是一个epoch。可以训练更多的epoch,观察变化。不仅准确性高,还可以说最终模型将比普通模型更稳健。
当然,可能已经意识到,当有更多的数据和更多的类别时,这将需要更多的时间。所以这种方法只能应用于有有限的数据和有限的类别时。