【问题标题】:Create generator that shuffle training data for Keras in R/train a Keras model with lowspec computer创建生成器,在 R 中为 Keras 打乱训练数据/使用低规格计算机训练 Keras 模型
【发布时间】:2020-05-28 08:13:44
【问题描述】:

我有一个类似于 MNIST 的数据集(200.000 行 784 像素 + 1 个分类输出(785 列)),我想使用 R 中的 Keras 库(在 RStudio 中)训练 MLP 和 CNN。我目前正在使用一台具有 32 GB RAM 和英特尔 i7-8700 @3.2 GHz 的计算机,使用 Keras 的 fit() 函数训练这个神经网络时我没有任何问题(训练时间约 4 分钟)。然而,当我在我的笔记本电脑上执行相同的脚本(8 GB RAM,intel i5-6300 @2.3 GHz)时,它甚至不能在至少 10 分钟内完成一个 epoch。

我在大学担任实验室教授,我担心我的学生由于缺乏计算能力而无法在他们的笔记本电脑上运行带有该数据库的脚本。我的想法是使用带有fit_generator() 函数的生成器来拟合相同的模型,并在每次调用生成器函数时加载部分数据集而不是整个数据集(为了使用比加载整个数据集和结果更少的内存在更快的训练中)。但是,这会产生一些意想不到的结果。 fit() 函数在训练(120.000 行)中达到的准确率约为 98.8%,在测试(80.000 行)中达到 ~98.4%,但使用 fit_generator() 函数在相同训练中达到 ~1.05%,在测试中达到 ~1.01%同样的测试。我发现了相关问题herehereherehere,似乎问题在于fit_generator() 没有对训练数据进行洗牌,这导致网络总是用相同的批量(例如,计算反向传播时使用相同的梯度)并且不能很好地表示整个数据集,从而导致准确性差。我已经使用fit() 训练了模型,但是将shuffle 参数设置为FALSE,并且准确度下降到0.1%,因此它证实了对训练数据进行改组对于训练模型至关重要。

我的问题是:

  • 在使用规格较低的计算机时使用生成器来避免问题/减少培训时间是个好主意还是有更好的解决方案?
  • 我通过将steps_per_epoch 参数设置为等于ceil(nrow(train_dataset)/batch_size) 来使用整个数据集训练模型,因此在使用fit()fit_generator() 时应该使用相同的数据,除了“洗牌”部分对吗?
  • 如果使用加载部分数据集的生成器是使用低规格计算机训练模型的良好解决方案,我如何使用生成器以有效的方式对训练数据进行混洗?

我见过的所有生成器都获取整个数据集,并在每次调用中生成一批样本,或者不打乱数据。我用下面的代码创建了一个生成器。它以datafile 带有数据(训练数据或测试数据)的文件作为参数,batch_size 在每次调用中生成的批处理大小,mlp 以处理数据以训练 MLP 或 CNN , val 以便开始为验证数据生成不同索引的批次,shuffle 表示我们是否要对数据进行混洗。我对数据进行洗牌的想法是创建一个随机索引,并为索引中的每个数字只读取文件的一行(使用read.table() 中的skipnrow 参数)。由于多次调用read.table(),这是非常低效的:

data_generator <- function(datafile, batch_size = 128, mlp = TRUE, val = TRUE, shuffle = TRUE) {
  nrow_file <- R.utils::countLines(datafile) - 1
  if (!val) {
    skip <- 0 
  } else {
    skip <- nrow_file / 2
  }
  function() {
    # Calculate the rows to read in this epoch
    rows_to_read <- batch_size
    if (skip + batch_size > nrow_file) {
      rows_to_read <- nrow_file - skip
    }

    if (shuffle) {
      index <- sample (c(1:nrow_file), size=batch_size, replace =F)
    } else {
      index <- (skip + 1):(skip + rows_to_read)
    }

    # Load only the rows that we want to use in training
    trData <- as.list(numeric(batch_size))
    for(i in index) {
      ii <- i - 1 
      trData[[which(i == index)]] <- read.table(datafile, sep = ";", header = TRUE,
                              skip = ii, nrows = 1)
    }
    trData <- do.call("rbind",trData)
    # Upload the rows to train
    skip <<- skip + batch_size
    if (skip >= nrow_file) {
      skip <<- 0
    }
    # Build inputs and output
    y_train <- trData[,1]
    x_train <- trData[,-1]
    if (mlp) {
      # Return data as is for mlp
      list(data.matrix(x_train), data.matrix(y_train))  
    } else {
      # Return data reshaped for CNN
      list(array_reshape(data.matrix(x_train), c(nrow(x_train), 28, 28, 1)),
           data.matrix(y_train))
    }
  }
}

我用来训练 MLP 模型(类似于 CNN)的代码是:

没有生成器

MLP_model <- keras_model_sequential()

MLP_model %>% 
  layer_dense(units = 500, activation = 'relu', input_shape = c(784),
              kernel_regularizer = regularizer_l2(l = 0.0001),
              bias_regularizer = regularizer_l2(l = 0.0001)) %>% 
  layer_dropout(rate = 0.4, seed = 150)  %>% 
  layer_batch_normalization() %>%
  layer_dense(units = 300, activation = 'relu',
              kernel_regularizer = regularizer_l2(l = 0.001),
              bias_regularizer = regularizer_l2(l = 0.001)) %>%
  layer_dropout(rate = 0.3, seed = 150) %>%
  layer_batch_normalization() %>%
  layer_dense(units = 10, activation = 'softmax',
              kernel_regularizer = regularizer_l2(l = 0.001),
              bias_regularizer = regularizer_l2(l = 0.001))


MLP_model %>% compile(
  loss = loss_categorical_crossentropy,
  optimizer = optimizer_adam(),
  metrics = c('accuracy')
)

history <- MLP_model %>% fit(
  x_train_mlp, y_train, 
  epochs = 20, batch_size = 124, 
  validation_split = 0.2, 
  shuffle = TRUE
)

带生成器:

MLP_model <- keras_model_sequential()

MLP_model %>% 
  layer_dense(units = 500, activation = 'relu', input_shape = c(784),
              kernel_regularizer = regularizer_l2(l = 0.0001),
              bias_regularizer = regularizer_l2(l = 0.0001)) %>% 
  layer_dropout(rate = 0.4, seed = 150)  %>% 
  layer_batch_normalization() %>%
  layer_dense(units = 300, activation = 'relu',
              kernel_regularizer = regularizer_l2(l = 0.001),
              bias_regularizer = regularizer_l2(l = 0.001)) %>%
  layer_dropout(rate = 0.3, seed = 150) %>%
  layer_batch_normalization() %>%
  layer_dense(units = 10, activation = 'softmax',
              kernel_regularizer = regularizer_l2(l = 0.001),
              bias_regularizer = regularizer_l2(l = 0.001))

MLP_model %>% compile(
  loss = loss_categorical_crossentropy,
  optimizer = optimizer_adam(),
  metrics = c('accuracy')
)

history <- MLP_model %>% fit_generator(
  data_generator(traindatafile,
                 batch_size = 128, mlp = TRUE, val = FALSE), 
  steps_per_epoch = round((R.utils::countLines(traindatafile)-1) / (128)),
  epochs = 10)

提前致谢!

【问题讨论】:

标签: r performance keras rstudio generator


【解决方案1】:

回答我自己的问题并感谢 @user12728748 的 cmets,我将生成器更改为从文件中读取随机样本:

data_generator <- function(datafile, batch_size = 128, 
                           mlp, val, 
                           shuffle = TRUE, validation_split = 0) {
  nrow_file <- py_eval(paste("sum(1 for line in open('", datafile, "'))", sep = '')) - 1
  skip <- 0 

  if (val) {
    nrow_file <- validation_split * nrow_file
  } else {
    nrow_file <- (1 - validation_split) * nrow_file
  }


  if (nrow_file > 0) {
    function() {
        # Calculate the rows to read in this epoch
        rows_to_read <- batch_size
        if (skip + batch_size > nrow_file) {
          rows_to_read <- nrow_file - skip
        }

        if (shuffle) {
          index <- sample (c(1:nrow_file), size=batch_size, replace =F)
        } else {
          index <- (skip + 1):(skip + rows_to_read)
        }

        # Create rows to skip
        if (val) {
          # in validation, skip training rows and validation rows that are not found in index
          rows_to_skip <- c(1:ifelse(validation_split > 0,((1 - validation_split) * nrow_file / validation_split),1),
                            setdiff(1:nrow_file, index) + (1 - validation_split) * nrow_file / validation_split)
        } else {
          # in training, skip validation rows and training rows that are not found in index
          rows_to_skip <- c(ifelse(validation_split > 0,
                                   nrow_file + 1, 0):ifelse(validation_split > 0,
                                                            nrow_file/(1 - validation_split), 0),
                            setdiff(1:nrow_file, index))
          if (rows_to_skip[1] == 0) rows_to_skip <- rows_to_skip[-1]
        }


        trData <- import("pandas")$read_csv(datafile, 
                                            skiprows = rows_to_skip, 
                                            sep = ";")

        # Upload the rows to train
        skip <<- skip + batch_size
        if (skip >= nrow_file) {
          skip <<- 0
        }
        # Build inputs and output
        y_train <- to_categorical(trData[,1], num_classes = 10)
        x_train <- trData[,-1]
        if (mlp) {
          # Return data as is for mlp
          list(data.matrix(x_train), data.matrix(y_train))  
        } else {
          # Return data reshaped for CNN
          list(array_reshape(data.matrix(x_train), c(nrow(x_train), 28, 28, 1)),
               data.matrix(y_train))
        }
      }
  } else {
    NULL
  }
}

我添加了validation_split 参数来设置我们想要用作验证的训练数据的百分比。

但是,在使用低规格计算机时,使用生成器训练模型似乎并没有缩短训练时间,但它使用的 RAM 减少了近 4 GB(尤其是在使用小批量(约 128 个样本)进行训练时),并且您可以在执行代码的同时使用计算机执行其他任务,而不会导致程序崩溃。

在这里,我将代码留给您使用生成器训练和评估 MLP 模型:

MLP_model <- keras_model_sequential()

MLP_model %>% 
  layer_dense(units = 500, activation = 'relu', input_shape = c(784),
              kernel_regularizer = regularizer_l2(l = 0.0001),
              bias_regularizer = regularizer_l2(l = 0.0001)) %>% 
  layer_dropout(rate = 0.4, seed = 150)  %>% 
  layer_batch_normalization() %>%
  layer_dense(units = 300, activation = 'relu',
              kernel_regularizer = regularizer_l2(l = 0.001),
              bias_regularizer = regularizer_l2(l = 0.001)) %>%
  layer_dropout(rate = 0.3, seed = 150) %>%
  layer_batch_normalization() %>%
  layer_dense(units = 10, activation = 'softmax',
              kernel_regularizer = regularizer_l2(l = 0.001),
              bias_regularizer = regularizer_l2(l = 0.001))

MLP_model %>% compile(
  loss = loss_categorical_crossentropy,
  optimizer = optimizer_adam(),
  metrics = c('accuracy')
)

validation_split <- 0.2
history <- MLP_model %>% fit_generator(
  data_generator(traindatafile,
                 batch_size_train, mlp = TRUE, 
                 val = FALSE, validation_split = validation_split), 
  steps_per_epoch = round((1 - validation_split) * (py_eval(paste("sum(1 for line in open('", traindatafile, "'))", sep = '')) - 1) / (batch_size_train)),
  validation_data = data_generator(traindatafile,
                                   batch_size_train, mlp = TRUE, 
                                   val = TRUE, validation_split = validation_split),
  validation_steps = round((validation_split) * (py_eval(paste("sum(1 for line in open('", traindatafile, "'))", sep = '')) - 1) / (batch_size_train)),
  callbacks = c(early_stopping),
  epochs = 10)

MLP_metrics_train <- MLP_model %>% 
  evaluate_generator(data_generator(traindatafile,
                                    batch_size_eval, 
                                    mlp = TRUE, 
                                    val = FALSE,
                                    shuffle = FALSE),
                     steps = ceiling((py_eval(paste("sum(1 for line in open('", traindatafile, "'))", sep = '')) - 1) / (batch_size_eval)))
MLP_metrics_test <- MLP_model %>% 
  evaluate_generator(data_generator(testdatafile,
                                    batch_size_eval, 
                                    mlp = TRUE, 
                                    val = FALSE,
                                    shuffle = FALSE),
                     steps = ceiling((py_eval(paste("sum(1 for line in open('", testdatafile, "'))", sep = '')) - 1) / (batch_size_eval)))


y_pred_mlp <- MLP_model %>% 
  predict_generator(data_generator(testdatafile,
                                   batch_size_eval, 
                                   mlp = TRUE, 
                                   val = FALSE,
                                   shuffle = FALSE),
                    steps = ceiling((py_eval(paste("sum(1 for line in open('", testdatafile, "'))", sep = '')) - 1) / (batch_size_eval)))

希望这对某人有所帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-22
    • 1970-01-01
    • 2018-12-15
    • 2019-01-20
    • 1970-01-01
    • 2017-06-27
    • 2018-01-30
    • 1970-01-01
    相关资源
    最近更新 更多