大数跨境
0
0

【TensorFlow for R】使用内置的fit() 和 evaluate() 方法进行训练和评估

【TensorFlow for R】使用内置的fit() 和 evaluate() 方法进行训练和评估 我爱数据科学
2025-10-21
0
导读:设置library(tensorflow)library(keras)简介本指南涵盖了使用内置 API 进行训

设置

library(tensorflow)
library(keras)

简介

本指南涵盖了使用内置 API 进行训练和验证时的模型训练、评估和预测(推理)。

如果您有兴趣在使用 fit() 的同时指定自己的训练步骤函数,请参阅《自定义 fit() 中的操作》指南。

如果您有兴趣从头开始编写自己的训练和评估循环,请参阅《从头开始编写训练循环》指南。

一般来说,无论您是使用内置循环还是自己编写循环,模型训练和评估在各种 Keras 模型上的工作方式都是完全相同的——Sequential 模型、使用函数式 API 构建的模型以及通过模型子类化从头编写的模型。

本指南不涉及分布式训练,有关内容请参阅我们的多 GPU 和分布式训练指南。

API 概述:第一个端到端示例

向模型的内置训练循环传递数据时,您应该使用 NumPy 数组(如果您的数据量小且能放入内存)或 tf$data 数据集对象。在接下来的几段中,我们将使用 MNIST 数据集作为 NumPy 数组,以演示如何使用优化器、损失函数和指标。

让我们考虑以下模型(这里,我们使用函数式 API 构建,但它也可以是 Sequential 模型或子类化模型):

inputs <- layer_input(shape = shape(784), name = "digits")
x <- inputs %>% 
  layer_dense(units = 64, activation = "relu", name = "dense_1") %>% 
  layer_dense(units = 64, activation = "relu", name = "dense_2")
outputs <- x %>% 
  layer_dense(units = 10, activation = "softmax", name = "predictions")
model <- keras_model(inputs = inputs, outputs = outputs)

以下是典型的端到端工作流程,包括:

  • 训练
  • 在从原始训练数据生成的保留集上进行验证
  • 在测试数据上进行评估

我们将使用 MNIST 数据进行此示例。

c(c(x_train, y_train), c(x_test, y_test)) %<-% dataset_mnist()

x_train <- array_reshape(x_train, c(nrow(x_train), 784))
x_test <- array_reshape(x_test, c(nrow(x_test), 784))

# 将 RGB 值转换到 [0,1] 范围
x_train <- x_train / 255
x_test <- x_test / 255

# 保留 10,000 个样本用于验证
x_val <- tail(x_train, 10000)
y_val <- tail(y_train, 10000)
x_train <- head(x_train, 50000)
y_train <- head(y_train, 50000)

我们指定训练配置(优化器、损失函数、指标):

model %>% compile(
    optimizer = optimizer_rmsprop(),  # 优化器
    # 要最小化的损失函数
    loss = loss_sparse_categorical_crossentropy(),
    # 要监控的指标列表
    metrics = list(metric_sparse_categorical_accuracy()),
)

我们调用 fit(),它将通过将数据分割成大小为 batch_size 的“批次”来训练模型,并在给定的 epoch 数内反复迭代整个数据集。

history <- model %>% fit(
    x_train,
    y_train,
    batch_size = 64,
    epochs = 2,
    # 我们传递一些验证数据,用于在每个 epoch 结束时监控验证损失和指标
    validation_data = list(x_val, y_val),
)

返回的 history 对象记录了训练过程中的损失值和指标值:

history
Final epoch (plot to see history):
                           loss: 0.1664
    sparse_categorical_accuracy: 0.95
                       val_loss: 0.1427
val_sparse_categorical_accuracy: 0.9595 

我们通过 evaluate() 在测试数据上评估模型:

# 使用 `evaluate` 在测试数据上评估模型
results <- model %>% evaluate(x_test, y_test, batch_size = 128)
cat("test loss, test acc:", results)
test loss, test acc: 0.1450924 0.9584
# 使用 `predict` 对新数据生成预测(概率——最后一层的输出)

predictions <- predict(model, x_test[1:3,])
dim(predictions)
[1]  3 10

现在,让我们详细回顾这个工作流程的每个部分。

compile() 方法:指定损失函数、指标和优化器

要使用 fit() 训练模型,您需要指定一个损失函数、一个优化器,以及可选的一些要监控的指标。

您将这些作为参数传递给模型的 compile() 方法:

model %>% compile(
    optimizer = optimizer_rmsprop(learning_rate = 1e-3),
    loss = loss_categorical_crossentropy(),
    metrics = list(metric_sparse_categorical_accuracy())
)

metrics 参数应该是一个列表——您的模型可以有任意数量的指标。

如果您的模型有多个输出,您可以为每个输出指定不同的损失函数和指标,并且可以调整每个输出对模型总损失的贡献。您可以在“向多输入、多输出模型传递数据”部分找到更多相关细节。

请注意,如果您对默认设置满意,在许多情况下,优化器、损失函数和指标可以通过字符串标识符作为快捷方式指定:

model %>% compile(
    optimizer = "rmsprop",
    loss = "sparse_categorical_crossentropy",
    metrics = list("sparse_categorical_accuracy")
)

为了以后重用,让我们将模型定义和编译步骤放在函数中;我们将在本指南的不同示例中多次调用它们。

get_uncompiled_model <- function() {
  inputs <- layer_input(shape = shape(784), name = "digits")
  x <- inputs %>% 
    layer_dense(units = 64, activation = "relu", name = "dense_1") %>% 
    layer_dense(units = 64, activation = "relu", name = "dense_2")
  outputs <- x %>% 
    layer_dense(units = 10, activation = "softmax", name = "predictions")
  model <- keras_model(inputs = inputs, outputs = outputs)
  model
}

get_compiled_model <- function() {
  model <- get_uncompiled_model()
  model %>% compile(
    optimizer = "rmsprop",
    loss = "sparse_categorical_crossentropy",
    metrics = list("sparse_categorical_accuracy"),
  )
  model
}

许多内置的优化器、损失函数和指标可用

一般来说,您不必从头开始创建自己的损失函数、指标或优化器,因为您需要的很可能已经是 Keras API 的一部分:

优化器:

  • optimizer_sgd()(带或不带动量)
  • optimizer_rmsprop()
  • optimizer_adam()
  • 等等

损失函数:

  • loss_mean_squared_error()
  • loss_kl_divergence()
  • loss_cosine_similarity()
  • 等等

指标:

  • metric_auc()
  • metric_precision()
  • metric_recall()
  • 等等

自定义损失函数

如果您需要创建自定义损失函数,Keras 提供了两种方法。

第一种方法涉及创建一个接受输入 y_true 和 y_pred 的函数。以下示例显示了一个损失函数,它计算真实数据和预测之间的均方误差:

custom_mean_squared_error <- function(y_true, y_pred) {
  tf$math$reduce_mean(tf$square(y_true - y_pred))
}


model <- get_uncompiled_model()
model %>% compile(optimizer = optimizer_adam(), loss = custom_mean_squared_error)

# 我们需要对标签进行独热编码以使用 MSE

y_train_one_hot <- tf$one_hot(y_train, depth = 10L)
model %>% fit(x_train, y_train_one_hot, batch_size = 64, epochs = 1)

如果您需要一个除了 y_true 和 y_pred 之外还接受参数的损失函数,您可以子类化 tf losses$Loss 类并实现以下两个方法:

  • initialize():接受在调用损失函数期间传递的参数
  • call(y_true, y_pred):使用目标(y_true)和模型预测(y_pred)来计算模型的损失

假设您想使用均方误差,但添加一个惩罚项,以抑制远离 0.5 的预测值(我们假设分类目标是独热编码的,取值在 0 到 1 之间)。这会激励模型不要过于自信,这可能有助于减少过拟合(我们只有尝试后才知道是否有效!)。

以下是实现方法:

custom_mse <- new_loss_class(
  classname = "custom_mse",
  initialize = function(regularization_factor = 0.1, name = "custom_mse") {
    super()$`__init__`(name = name)
    self$regularization_factor <- regularization_factor
  },
  call = function(y_true, y_pred) {
    mse <- tf$math$reduce_mean(tf$square(y_true - y_pred))
    reg <- tf$math$reduce_mean(tf$square(0.5 - y_pred))
    mse + reg * self$regularization_factor
  }
)

model <- get_uncompiled_model()
model %>% compile(optimizer = optimizer_adam(), loss = custom_mse())

y_train_one_hot <- tf$one_hot(y_train, depth = 10L)
model %>% fit(x_train, y_train_one_hot, batch_size = 64, epochs = 1)

自定义指标

如果您需要 API 中没有的指标,您可以通过子类化 tf metrics$Metric 类轻松创建自定义指标。您需要实现 4 个方法:

  • initialize():在其中创建指标的状态变量。
  • update_state(y_true, y_pred, sample_weight = NULL):使用目标 y_true 和模型预测 y_pred 来更新状态变量。
  • result():使用状态变量计算最终结果。
  • reset_state():重新初始化指标的状态。

状态更新和结果计算是分开的(分别在 update_state() 和 result() 中),因为在某些情况下,结果计算可能非常昂贵,只会定期进行。

以下是一个简单示例,展示如何实现 CategoricalTRUEPositives 指标,该指标计算被正确分类为属于特定类别的样本数量:

categorical_true_positives <- new_metric_class(
  classname = "categorical_true_positives",
  initialize = function(name = "categorical_true_positives"...) {
    super()$`__init__`(name, ...)
    self$true_positives <- self$add_weight(name = "ctp", initializer = "zeros")
  },
  update_state = function(y_true, y_pred, sample_weight = NULL) {
    y_pred <- tf$reshape(tf$argmax(y_pred, axis = 1L), shape = c(-1L,1L))
    values <- tf$cast(y_true, "int32") == tf$cast(y_pred, "int32")
    values <- tf$cast(values, "float32")
    if (!is.null(sample_weight)) {
      sample_weight <- tf$cast(sample_weight, "float32")
      values <- tf$multiply(values, sample_weight)
    }

    self$true_positives$assign_add(tf$reduce_sum(values))
  },
  result = function() {
    self$true_positives
  },
  reset_state = function() {
    self$true_positives$assign(0.0)
  }
)

model <- get_uncompiled_model()
model %>% compile(
    optimizer = optimizer_rmsprop(learning_rate = 1e-3),
    loss = loss_sparse_categorical_crossentropy(),
    metrics = list(categorical_true_positives()),
)
model %>% fit(x_train, y_train, batch_size = 64, epochs = 3)

处理不符合标准签名的损失函数和指标

绝大多数损失函数和指标可以从 y_true 和 y_pred 计算得出,其中 y_pred 是模型的输出——但并非所有都是如此。例如,正则化损失可能只需要层的激活(在这种情况下没有目标),而这种激活可能不是模型输出。

在这种情况下,您可以从自定义层的 call 方法内部调用 self$add_loss(loss_value)。以这种方式添加的损失会在训练期间添加到“主要”损失中(传递给 compile() 的那个)。以下是一个添加活动正则化的简单示例(请注意,活动正则化在所有 Keras 层中都是内置的——这个层只是为了提供一个具体示例):

layer_activity_regularization <- new_layer_class(
  classname = "activity_regularization",
  call = function(inputs) {
    self$add_loss(tf$reduce_sum(inputs) * 0.1)
    inputs # 直通层。
  }
)

inputs <- layer_input(shape = shape(784), name = "digits")
x <- layer_dense(inputs, 64, activation = "relu", name = "dense_1")
# 插入活动正则化作为层
x <- layer_activity_regularization(x)
x <- layer_dense(x, 64, activation = "relu", name = "dense_2")
outputs <- layer_dense(x, 10, name = "predictions")

model <- keras_model(inputs = inputs, outputs = outputs)
model %>% compile(
    optimizer = optimizer_rmsprop(learning_rate = 1e-3),
    loss = loss_sparse_categorical_crossentropy(from_logits = TRUE)
)

# 由于正则化部分,显示的损失将比以前高得多
model %>% fit(x_train, y_train, batch_size = 64, epochs = 1)

您也可以使用 add_metric() 来记录指标值:

layer_metric_logging <- new_layer_class(
  "metric_logging",
  call = function(inputs) {
    self$add_metric(
      keras$backend$std(inputs), 
      name = "std_of_activation"
      aggregation = "mean"
    )
    inputs
  }
)

inputs <- layer_input(shape = shape(784), name = "digits")
x <- layer_dense(inputs, 64, activation = "relu", name = "dense_1")

# 插入标准差日志作为层。
x <- layer_metric_logging(x)
x <- layer_dense(x, 64, activation = "relu", name = "dense_2")
outputs <- layer_dense(x, 10, name = "predictions")

model <- keras_model(inputs = inputs, outputs = outputs)
model %>% compile(
    optimizer = optimizer_rmsprop(learning_rate = 1e-3),
    loss = loss_sparse_categorical_crossentropy(from_logits = TRUE)
)
model %>% fit(x_train, y_train, batch_size = 64, epochs = 1)

在函数式 API 中,您也可以调用 model add_metric(metric_tensor, name, aggregation)。

以下是一个简单示例:

inputs <- layer_input(shape = shape(784), name = "digits")
x1 <- layer_dense(inputs, 64, activation = "relu", name = "dense_1")
x2 <- layer_dense(x1, 64, activation = "relu", name = "dense_2")
outputs <- layer_dense(x2, 10, name = "predictions")
model <- keras_model(inputs = inputs, outputs = outputs)

model$add_loss(tf$reduce_sum(x1) * 0.1)
model$add_metric(
  keras$backend$std(x1), 
  name = "std_of_activation"
  aggregation = "mean"
)

model %>% compile(
    optimizer = optimizer_rmsprop(learning_rate = 1e-3),
    loss = loss_sparse_categorical_crossentropy(from_logits = TRUE)
)
model %>% fit(x_train, y_train, batch_size = 64, epochs = 1)

请注意,当您通过 add_loss() 传递损失时,可以在不使用损失函数的情况下调用 compile(),因为模型已经有了要最小化的损失。

考虑以下 LogisticEndpoint 层:它接受目标和 logits 作为输入,并通过 add_loss() 跟踪交叉熵损失。它还通过 add_metric() 跟踪分类准确率。

layer_logistic_endpoint <- new_layer_class(
  "logistic_endpoint",
  initialize = function(name = NULL) {
    super()$`__init__`(name = name)
    self$loss_fn <- loss_binary_crossentropy(from_logits = TRUE)
    self$accuracy_fn <- metric_binary_accuracy()
  },
  call = function(targets, logits, sample_weights = NULL) {
    # 计算训练时的损失值,并使用 `self$add_loss()` 将其添加到层中。
    loss <- self$loss_fn(targets, logits, sample_weights)
    self$add_loss(loss)
    
    # 将准确率记录为指标,并使用 `self$add_metric()` 将其添加到层中。
    acc <- self$accuracy_fn(targets, logits, sample_weights)
    self$add_metric(acc, name = "accuracy")
    
    # 返回推理时的预测张量(用于 `.predict()`)。
    tf$nn$softmax(logits)
  }
)

您可以在具有两个输入(输入数据和目标)的模型中使用它,无需损失参数即可编译,如下所示:

inputs <- layer_input(shape = shape(3), name = "inputs")
targets <- layer_input(shape = shape(10), name = "targets")
logits <- layer_dense(inputs, 10)
predictions <- layer_logistic_endpoint(name = "predictions")(logits, targets)

model <- keras_model(inputs = list(inputs, targets), outputs = predictions)
model %>% compile(optimizer = "adam")  # 没有损失参数!

data <- list(
    "inputs" = array(runif(3*3), dim = c(3,3)),
    "targets" = array(runif(3*10), dim = c(310))
)
model %>% fit(data, epochs = 1)

有关训练多输入模型的更多信息,请参见“向多输入、多输出模型传递数据”部分。

自动划分验证保留集

在您看到的第一个端到端示例中,我们使用 validation_data 参数将数组列表(x_val,y_val)传递给模型,以便在每个 epoch 结束时评估验证损失和验证指标。

另一个选项是:validation_split 参数允许您自动保留部分训练数据用于验证。该参数值表示要保留用于验证的数据比例,因此应设置为大于 0 且小于 1 的数字。例如,validation_split = 0.2 表示“使用 20% 的数据进行验证”,validation_split = 0.6 表示“使用 60% 的数据进行验证”。

验证的计算方式是在进行任何洗牌之前,获取 fit() 调用接收的数组的最后 x% 的样本。

请注意,只有在使用数组数据训练时,才能使用 validation_split。

model <- get_compiled_model()
model %>% fit(x_train, y_train, batch_size = 64, validation_split = 0.2, epochs = 1)

从 TensorFlow 数据集进行训练和评估

在过去的几段中,您已经了解了如何处理损失函数、指标和优化器,以及当数据作为 R 数组传递时,如何在 fit() 中使用 validation_data 和 validation_split 参数。

现在让我们看看数据以 TensorFlow 数据集对象形式存在的情况。

注意:R 中的 tfdatasets 包是 Python 中 tf.data 模块的接口。

tf.data API 是 TensorFlow 2.0 中的一组工具,用于以快速且可扩展的方式加载和预处理数据。

有关创建数据集的完整指南,请参见 tf.data 文档。

您可以将 Dataset 实例直接传递给 fit()、evaluate() 和 predict() 方法:

library(tfdatasets)
model <- get_compiled_model()

# 首先,让我们创建一个训练 Dataset 实例。
# 为了我们的示例,我们将使用与之前相同的 MNIST 数据。
train_dataset <- tensor_slices_dataset(list(x_train, y_train))
# 洗牌并切片数据集。
train_dataset <- train_dataset %>% 
  dataset_shuffle(1024) %>% 
  dataset_batch(64)

# 现在我们获取一个测试数据集。
test_dataset <- list(x_test, y_test) %>% 
  tensor_slices_dataset() %>% 
  dataset_batch(64)

# 由于数据集已经处理了批处理,我们不需要传递 `batch_size` 参数。
model %>% fit(train_dataset, epochs = 3)

# 您也可以在数据集上进行评估或预测。
result <- model %>% evaluate(test_dataset)
print(result)
                       loss sparse_categorical_accuracy 
                  0.1513888                   0.9552000 

请注意,数据集在每个 epoch 结束时会重置,因此可以在下一个 epoch 中重用。

如果您只想在该数据集的特定数量的批次上运行训练,您可以传递 steps_per_epoch 参数,该参数指定模型在进入下一个 epoch 之前应该使用该数据集运行多少个训练步骤。

如果这样做,数据集在每个 epoch 结束时不会重置,而是我们继续提取下一个批次。数据集最终会耗尽数据(除非它是一个无限循环的数据集)。

model <- get_compiled_model()

# 准备训练数据集
train_dataset <- list(x_train, y_train) %>% 
  tensor_slices_dataset() %>% 
  dataset_shuffle(1024) %>% 
  dataset_batch(64)

# 每个 epoch 只使用 100 个批次(即 64 * 100 个样本)
model %>% fit(train_dataset, epochs = 3, steps_per_epoch = 100)

使用验证数据集

您可以将 Dataset 实例作为 validation_data 参数传递给 fit():

model <- get_compiled_model()

# 准备训练数据集
train_dataset <- list(x_train, y_train) %>% 
  tensor_slices_dataset() %>% 
  dataset_shuffle(1024) %>% 
  dataset_batch(64)

# 准备验证数据集
val_dataset <- list(x_val, y_val) %>% 
  tensor_slices_dataset() %>% 
  dataset_batch(64)

model %>% fit(train_dataset, epochs = 1, validation_data = val_dataset)

在每个 epoch 结束时,模型将迭代验证数据集并计算验证损失和验证指标。

如果您只想在该数据集的特定数量的批次上运行验证,您可以传递 validation_steps 参数,该参数指定模型在中断验证并进入下一个 epoch 之前应该使用验证数据集运行多少个验证步骤:

model <- get_compiled_model()

# 准备训练数据集
train_dataset <- list(x_train, y_train) %>% 
  tensor_slices_dataset() %>% 
  dataset_shuffle(1024) %>% 
  dataset_batch(64)

# 准备验证数据集
val_dataset <- list(x_val, y_val) %>% 
  tensor_slices_dataset() %>% 
  dataset_batch(64)

model %>% fit(
    train_dataset,
    epochs = 1,
    # 使用 `validation_steps` 参数,仅使用数据集的前 10 个批次进行验证
    validation_data = val_dataset,
    validation_steps = 10,
)

请注意,验证数据集在每次使用后都会重置(因此您将在每个 epoch 始终评估相同的样本)。

当从 Dataset 对象训练时,不支持 validation_split 参数(从训练数据生成保留集),因为此功能需要能够索引数据集的样本,而这在一般情况下对于 Dataset API 是不可能的。

使用样本权重和类别权重

在默认设置下,样本的权重由其在数据集中的频率决定。有两种方法可以对数据进行加权,与样本频率无关:

  • 类别权重
  • 样本权重

类别权重

这是通过将字典传递给 Model %>% fit() 的 class_weight 参数来设置的。该字典将类别索引映射到应该用于属于该类别的样本的权重。

这可用于平衡类别而无需重采样,或者训练一个对特定类别给予更多重视的模型。

例如,如果类别“0”在您的数据中的表示是类别“1”的一半,您可以使用 Model %>% fit(..., class_weight = list(0= 1., 1= 0.5))。

以下示例中,我们使用类别权重或样本权重来更重视类别 #5(这是 MNIST 数据集中的数字“5”)的正确分类。

class_weight <- list(
    "0" = 1.0,
    "1" = 1.0,
    "2" = 1.0,
    "3" = 1.0,
    "4" = 1.0,
    # 为类别“5”设置权重“2”,使此类的重要性是原来的 2 倍
    "5" = 2.0,
    "6" = 1.0,
    "7" = 1.0,
    "8" = 1.0,
    "9" = 1.0
)

model <- get_compiled_model()
model %>% fit(x_train, y_train, class_weight = class_weight, batch_size = 64, epochs = 1)

样本权重

为了进行更精细的控制,或者如果您不是在构建分类器,您可以使用“样本权重”。

  • 从 R 数据训练时:将 sample_weight 参数传递给 Model %>% fit()。
  • 从 tfdatasets 或任何其他类型的迭代器训练时:生成(input_batch,label_batch,sample_weight_batch)元组。

“样本权重”数组是一个数字数组,指定批次中的每个样本在计算总损失时应具有的权重。它通常用于不平衡分类问题(其思想是给予很少见的类别更多的权重)。

当使用的权重是 1 和 0 时,该数组可用作损失函数的掩码(完全丢弃某些样本对总损失的贡献)。

sample_weight <- rep(1, length(y_train))
sample_weight[y_train == 5] <- 2.0

model <- get_compiled_model()
model %>% fit(x_train, y_train, sample_weight = sample_weight, batch_size = 64, epochs = 1)

以下是匹配的 Dataset 示例:

sample_weight <- rep(1, length(y_train))
sample_weight[y_train == 5] <- 2.0

# 创建一个包含样本权重的 Dataset(返回元组中的第三个元素)。
train_dataset <- list(x_train, y_train, sample_weight) %>% 
  tensor_slices_dataset()

# 洗牌并切片数据集。
train_dataset <- train_dataset %>% 
  dataset_shuffle(1024) %>% 
  dataset_batch(64)

model <- get_compiled_model()
model %>% fit(train_dataset, epochs = 1)


向多输入、多输出模型传递数据

在前面的示例中,我们考虑的是具有单个输入(形状为 (764) 的张量)和单个输出(形状为 (10) 的预测张量)的模型。但是对于具有多个输入或输出的模型呢?

考虑以下模型,它有一个形状为 (32, 32, 3) 的图像输入(即 (高度, 宽度, 通道))和一个形状为 (NULL, 10) 的时间序列输入(即 (时间步长, 特征))。我们的模型将有两个输出,通过这些输入的组合计算得出:一个“分数”(形状为 (1))和五个类别的概率分布(形状为 (5))。

image_input <- layer_input(shape = shape(32323), name = "img_input")
timeseries_input <- layer_input(shape = shape(NULL10), name = "ts_input")

x1 <- layer_conv_2d(image_input, 33)
x1 <- layer_global_max_pooling_2d(x1)

x2 <- layer_conv_1d(timeseries_input, 33)
x2 <- layer_global_max_pooling_1d(x2)

x <- layer_concatenate(list(x1, x2))

score_output <- layer_dense(x, 1, name = "score_output")
class_output <- layer_dense(x, 5, name = "class_output")

model <- keras_model(
    inputs = list(image_input, timeseries_input), 
    outputs = list(score_output, class_output)
)

让我们绘制这个模型,以便您可以清楚地看到我们在做什么(请注意,图中显示的形状是批次形状,而不是每个样本的形状)。

keras$utils$plot_model(
  model, "img/multi_input_and_output_model.png"
  show_shapes = TRUE
)
<IPython.core.display.Image object>

在编译时,我们可以通过将损失函数作为列表传递,为不同的输出指定不同的损失函数:

model %>% compile(
  optimizer = optimizer_rmsprop(1e-3),
  loss = list(
    loss_mean_squared_error(),
    loss_categorical_crossentropy()
  )
)

如果我们只向模型传递一个损失函数,同一个损失函数将应用于每个输出(这在这里是不合适的)。

指标也是如此:

model %>% compile(
  optimizer = optimizer_rmsprop(1e-3),
  loss = list(
    loss_mean_squared_error(),
    loss_categorical_crossentropy()
  ),
  metrics = list(
    list(
      metric_mean_absolute_percentage_error(),
      metric_mean_absolute_error()
    ),
    list(
      metric_categorical_accuracy()
    )
  )
)

由于我们给输出层命名了,我们也可以通过字典为每个输出指定损失函数和指标:

model %>% compile(
  optimizer = optimizer_rmsprop(1e-3),
  loss = list(
    score_output = loss_mean_squared_error(),
    class_output = loss_categorical_crossentropy()
  ),
  metrics = list(
    class_output = list(
      metric_categorical_accuracy()
    ),
    score_output = list(
      metric_mean_absolute_percentage_error(),
      metric_mean_absolute_error()
    )
  )
)

如果您有两个以上的输出,我们建议使用显式名称和字典。

可以为不同的输出特定损失赋予不同的权重(例如,在我们的示例中,可能希望优先考虑“分数”损失,给予其类损失 2 倍的重要性),使用 loss_weights 参数:

model %>% compile(
  optimizer = optimizer_rmsprop(1e-3),
  loss = list(
    score_output = loss_mean_squared_error(),
    class_output = loss_categorical_crossentropy()
  ),
  metrics = list(
    class_output = list(
      metric_categorical_accuracy()
    ),
    score_output = list(
      metric_mean_absolute_percentage_error(),
      metric_mean_absolute_error()
    )
  ),
  loss_weights = list(score_output = 2.0, class_output = 1.0)
)

您也可以选择不为某些输出计算损失,如果这些输出用于预测而不是训练:

# 列表损失版本
model %>% compile(
  optimizer = optimizer_rmsprop(1e-3),
  loss = list(
    NULL,
    loss_categorical_crossentropy()
  )
)

# 或字典损失版本
model %>% compile(
  optimizer = optimizer_rmsprop(1e-3),
  loss = list(
    class_output = loss_categorical_crossentropy()
  )
)

在 fit() 中向多输入或多输出模型传递数据的方式与在 compile 中指定损失函数类似:您可以传递 R 数组的列表(与接收损失函数的输出一一对应)或映射输出名称到 R 数组的命名列表。

model %>% compile(
  optimizer = optimizer_rmsprop(1e-3),
  loss = list(
    loss_mean_squared_error(),
    loss_categorical_crossentropy()
  )
)
# 生成虚拟 NumPy 数据

img_data <- array(runif(100*32*32*3), dim = c(10032323))
ts_data <- array(runif(100*20*10), dim = c(1002010))
score_targets <- array(runif(100), dim = c(1001))
class_targets <- array(runif(100*5), dim = c(1005))

# 在列表上拟合
model %>% fit(
  list(img_data, ts_data), 
  list(score_targets, class_targets), 
  batch_size = 32
  epochs = 1
)

# 或者,在命名列表上拟合
model %>% fit(
    list("img_input" = img_data, "ts_input" = ts_data),
    list("score_output" = score_targets, "class_output" = class_targets),
    batch_size = 32,
    epochs = 1
)

以下是 Dataset 使用案例:与我们对 R 数组所做的类似,Dataset 应该返回字典的元组。

train_dataset <- list(
  list("img_input" = img_data, "ts_input" = ts_data),
  list("score_output" = score_targets, "class_output" = class_targets)
) %>% 
  tensor_slices_dataset() %>% 
  dataset_shuffle(1024) %>% 
  dataset_batch(64)

model %>% fit(train_dataset, epochs = 1)

使用回调

Keras 中的回调是在训练的不同点(epoch 开始时、批次结束时、epoch 结束时等)被调用的对象。它们可用于实现某些行为,例如:

  • 在训练期间的不同点进行验证(超出内置的每 epoch 验证)
  • 定期或当模型超过特定精度阈值时检查点模型
  • 当训练似乎停滞时更改模型的学习率
  • 当训练似乎停滞时对顶层进行微调
  • 当训练结束或超过特定性能阈值时发送电子邮件或即时消息通知
  • 等等

可以将回调作为列表传递给您对 fit() 的调用:

model <- get_compiled_model()

callbacks <- list(
  callback_early_stopping(
    # 当 `val_loss` 不再改善时停止训练
    monitor = "val_loss",
    # “不再改善”定义为“不低于 1e-2”
    min_delta = 1e-2,
    # “不再改善”进一步定义为“至少持续 2 个 epoch”
    patience = 2,
    verbose = 1,
  )
)

model %>% fit(
    x_train,
    y_train,
    epochs = 20,
    batch_size = 64,
    callbacks = callbacks,
    validation_split = 0.2,
)

许多内置回调可用

Keras 中已经有许多内置回调,例如:

  • callback_model_checkpoint():定期保存模型。
  • callback_early_stopping():当训练不再改善验证指标时停止训练。
  • callback_tensorboard():定期写入可在 TensorBoard 中可视化的模型日志(“可视化”部分有更多细节)。
  • callback_csv_logger():将损失和指标数据流式传输到 CSV 文件。
  • 等等

完整列表请参见回调文档。

编写自己的回调

您可以通过扩展基类 keras Callback 来创建自定义回调。回调可以通过类属性 self$model 访问其关联的模型。

请务必阅读编写自定义回调的完整指南。

以下是一个简单示例,在训练期间保存每批次损失值的列表:

callback_loss_history <- new_callback_class(
  "loss_history",
  on_train_begin = function(logs) {
    self$per_batch_losses <- list()
  },
  on_batch_end = function(batch, logs) {
    self$per_batch_losses <- c(
      self$per_batch_losses,
      logs$get("loss")
    )
  }
)

模型检查点

当您在相对较大的数据集上训练模型时,频繁间隔保存模型检查点至关重要。

实现此目的的最简单方法是使用 callback_model_checkpoint() 回调:

model <- get_compiled_model()

callbacks <- list(
  callback_model_checkpoint(
    # 保存模型的路径
    # 以下两个参数意味着我们将覆盖当前检查点,当且仅当
    # `val_loss` 分数有所改善。
    # 保存的模型名称将包含当前 epoch。
    filepath = "mymodel_{epoch}",
    save_best_only = TRUE,  # 仅当 `val_loss` 改善时才保存模型。
    monitor = "val_loss",
    verbose = 1,
  )
)

model %>% fit(
    x_train, 
    y_train, 
    epochs = 2
    batch_size = 64
    callbacks = callbacks, 
    validation_split = 0.2
)

callback_model_checkpoint() 回调可用于实现容错能力:如果训练随机中断,能够从模型的最后保存状态重新开始训练。以下是一个基本示例:

# 准备一个目录来存储所有检查点。
checkpoint_dir <- "./ckpt"
dir.create(checkpoint_dir, showWarnings = FALSE)


make_or_restore_model <- function() {
# 要么恢复最新的模型,要么如果没有可用的检查点,则创建一个新模型
  checkpoints <- list.files(checkpoint_dir, full.names = TRUE)
  details <- file.info(checkpoints)
if (length(checkpoints) > 0) {
    latest_checkpoint <- checkpoints[which.max(as.POSIXct(details$mtime))]
    cat("Restoring from", latest_checkpoint)
    return(load_model_tf(latest_checkpoint))
  }

  cat("Creating a new model")
  get_compiled_model()
}

model <- make_or_restore_model()
Restoring from ./ckpt/ckpt-loss=0.30
callbacks <- list(
    # 此回调每 100 个批次保存一个 SavedModel。
    # 我们在保存的模型名称中包含训练损失。
    callback_model_checkpoint(
        filepath = paste0(checkpoint_dir, "/ckpt-loss={loss:.2f}"), 
        save_freq = 100
    )
)
model %>% fit(x_train, y_train, epochs = 1, callbacks = callbacks)

您也可以编写自己的回调来保存和恢复模型。

有关序列化和保存的完整指南,请参见保存和序列化模型指南。

使用学习率调度

训练深度学习模型时的一个常见模式是随着训练的进行逐渐降低学习率。这通常称为“学习率衰减”。

学习衰减调度可以是静态的(预先固定,作为当前 epoch 或当前批次索引的函数),也可以是动态的(响应模型的当前行为,特别是验证损失)。

向优化器传递调度

您可以通过将调度对象作为学习率参数传递给优化器,轻松使用静态学习率衰减调度:

initial_learning_rate <- 0.1
lr_schedule <- learning_rate_schedule_exponential_decay(
  initial_learning_rate = initial_learning_rate,
  decay_steps = 100000
  decay_rate = 0.96
  staircase = TRUE
)

optimizer <- keras$optimizers$RMSprop(learning_rate = lr_schedule)

有几个内置调度可用:learning_rate_schedule_cosine_decay、learning_rate_schedule_cosine_decay_restarts、learning_rate_schedule_exponential_decay、learning_rate_schedule_inverse_time_decay、learning_rate_schedule_piecewise_constant_decay、learning_rate_schedule_polynomial_decay

使用回调实现动态学习率调度

动态学习率调度(例如,当验证损失不再改善时降低学习率)无法通过这些调度对象实现,因为优化器无法访问验证指标。

然而,回调可以访问所有指标,包括验证指标!因此,您可以通过使用修改优化器上当前学习率的回调来实现此模式。实际上,这甚至作为 callback_reduce_lr_on_plateau() 回调内置。

训练期间可视化损失和指标

在训练期间关注模型的最佳方法是使用 TensorBoard——一个您可以在本地运行的基于浏览器的应用程序,它为您提供:

  • 训练和评估的损失和指标的实时图表
  • (可选)层激活直方图的可视化
  • (可选)嵌入层学习的嵌入空间的 3D 可视化

如果您已使用 pip 安装 TensorFlow,您应该能够从 R 启动 TensorBoard:

tensorflow::tensorboard(log_dir = "/full_path_to_your_logs")

使用 TensorBoard 回调

将 TensorBoard 与 Keras 模型和 fit() 方法一起使用的最简单方法是 TensorBoard 回调。

在最简单的情况下,只需指定您希望回调写入日志的位置,就可以了:

callback_tensorboard(
    log_dir = "/full_path_to_your_logs",
    histogram_freq = 0,  # 多久记录一次直方图可视化
    embeddings_freq = 0,  # 多久记录一次嵌入可视化
    update_freq = "epoch" # 多久写入一次日志(默认:每个 epoch 一次)
)  
<keras.callbacks.TensorBoard object at 0x7f8d71ee4850>

【声明】内容源于网络
0
0
我爱数据科学
精通R语言及Python,传递数据挖掘及可视化技术,关注机器学习及深度学习算法及实现,分享大模型及LangChain的使用技巧。编著多本R语言、python、深度学习等书籍。
内容 322
粉丝 0
我爱数据科学 精通R语言及Python,传递数据挖掘及可视化技术,关注机器学习及深度学习算法及实现,分享大模型及LangChain的使用技巧。编著多本R语言、python、深度学习等书籍。
总阅读130
粉丝0
内容322