Tensorflow学习:ResNet代码(详细剖析)-待补充,非最终版本

本文深入剖析Tensorflow中的ResNet代码,旨在帮助读者掌握ResNet网络结构并具备二次开发能力。通过参考链接和图例解释,包括Block的设计与VALID填充的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考链接:

感谢此位博主的工作,本博主只做进一步的剖析,目的为掌握和具备二次开发能力。
http://blog.youkuaiyun.com/superman_xxx/article/details/65452735

先贴代码:
# -*- coding: utf-8 -*-
"""
Created on Thu Aug 17 16:24:55 2017
Project: Residual Neural Network
E-mail: Eric2014_Lv@sjtu.edu.cn
Reference: 《Tensorflow实战》P143-P156
@author: DidiLv
"""

"""

Typical use:

   from tensorflow.contrib.slim.nets import resnet_v2

ResNet-101 for image classification into 1000 classes:

   # inputs has shape [batch, 224, 224, 3]
   with slim.arg_scope(resnet_v2.resnet_arg_scope(is_training)):
      net, end_points = resnet_v2.resnet_v2_101(inputs, 1000)

ResNet-101 for semantic segmentation into 21 classes:

   # inputs has shape [batch, 513, 513, 3]
   with slim.arg_scope(resnet_v2.resnet_arg_scope(is_training)):
      net, end_points = resnet_v2.resnet_v2_101(inputs,
                                                21,
                                                global_pool=False,
                                                output_stride=16)
"""
import collections # 原生的collections库
import tensorflow as tf
slim = tf.contrib.slim # 使用方便的contrib.slim库来辅助创建ResNet



# 这里值得注意的是只有定义了类。其他的并没有定义,从空格发现。这也就是书中所说的“只包含数据结构,不包含具体方法”
class Block(collections.namedtuple('Block', ['scope', 'unit_fn', 'args'])):
  '''
  使用collections.namedtuple设计ResNet基本模块组的name tuple,并用它创建Block的类
  只包含数据结构,不包含具体方法。
  定义一个典型的Block,需要输入三个参数:
  scope:Block的名称
  unit_fn:ResNet V2中的残差学习单元 
  args:Block的args。
  '''
  #图片解释:图例1


########定义一个降采样的方法########
def subsample(inputs, factor, scope=None): 
  """Subsamples the input along the spatial dimensions.
  Args:
    inputs: A `Tensor` of size [batch, height_in, width_in, channels].
    factor: The subsampling factor.(采样因子或采样率)
    scope: Optional variable_scope.

  Returns:
    output: 如果factor为1,则不做修改直接返回inputs;如果不为1,则使用
    slim.max_pool2d最大池化来实现,通过1*1的池化尺寸,stride作步长,实
    现降采样。
  """
  if factor == 1:
    return inputs
  else:
    return slim.max_pool2d(inputs, [1, 1], stride=factor, scope=scope)


########创建卷积层########
def conv2d_same(inputs, num_outputs, kernel_size, stride, scope=None): 
  """
  Args:
    inputs: A 4-D tensor of size [batch, height_in, width_in, channels].
    num_outputs: An integer, the number of output filters.
    kernel_size: An int with the kernel_size of the filters.
    stride: An integer, the output stride.
    rate: An integer, rate for atrous convolution.
    scope: Scope.

  Returns:
    output: A 4-D tensor of size [batch, height_out, width_out, channels] with
      the convolution output.
  """
  if stride == 1:
    return slim.conv2d(inputs, num_outputs, kernel_size, stride=1,
                       padding='SAME', scope=scope)
  else: 
    pad_total = kernel_size - 1
    pad_beg = pad_total // 2
    pad_end = pad_total - pad_beg
    inputs = tf.pad(inputs, # 对输入变量进行补零操作
                    [[0, 0], [pad_beg, pad_end], [pad_beg, pad_end], [0, 0]])
    # 因为已经进行了zero padding,所以只需再使用一个padding模式为VALID的slim.conv2d创建这个卷积层
    # 详细解释请见图二
    return slim.conv2d(inputs, num_outputs, kernel_size, stride=stride,
                       padding='VALID', scope=scope)


########定义堆叠Blocks的函数########
@slim.add_arg_scope
def stack_blocks_dense(net, blocks,
                       outputs_collections=None):
  """
  Args:
    net: A `Tensor` of size [batch, height, width, channels].输入。
    blocks: 是之前定义的Block的class的列表。
    outputs_collections: 收集各个end_points的collections。

  Returns:
    net: Output tensor 

  """
  # 使用两层循环,逐个Residual Unit地堆叠
  for block in blocks: # 先使用两个tf.variable_scope将残差学习单元命名为block1/unit_1的形式
    with tf.variable_scope(block.scope, 'block', [net]) as sc:
      for i, unit in enumerate(block.args):

        with tf.variable_scope('unit_%d' % (i + 1), values=[net]):
          # 在第2层循环中,我们拿到每个block中每个Residual Unit的args并展开为下面四个参数
          unit_depth, unit_depth_bottleneck, unit_stride = unit
          net = block.unit_fn(net, # 使用残差学习单元的生成函数顺序的创建并连接所有的残差学习单元
                              depth=unit_depth,
                              depth_bottleneck=unit_depth_bottleneck,
                              stride=unit_stride)
      net = slim.utils.collect_named_outputs(outputs_collections, sc.name, net) # 将输出net添加到collections中

  return net # 当所有block中的所有Residual Unit都堆叠完成之后,再返回最后的net作为stack_blocks_dense


# 创建ResNet通用的arg_scope,arg_scope用来定义某些函数的参数默认值
def resnet_arg_scope(is_training=True, # 训练标记
                     weight_decay=0.0001, # 权重衰减速率
                     batch_norm_decay=0.997, # BN的衰减速率
                     batch_norm_epsilon=1e-5, #  BN的epsilon默认1e-5
                     batch_norm_scale=True): # BN的scale默认值

  batch_norm_params = { # 定义batch normalization(标准化)的参数字典
      'is_training': is_training,
      'decay': batch_norm_decay,
      'epsilon': batch_norm_epsilon,
      'scale': batch_norm_scale,
      'updates_collections': tf.GraphKeys.UPDATE_OPS,
  }

  with slim.arg_scope( # 通过slim.arg_scope将[slim.conv2d]的几个默认参数设置好
      [slim.conv2d],
      weights_regularizer=slim.l2_regularizer(weight_decay), # 权重正则器设置为L2正则 
      weights_initializer=slim.variance_scaling_initializer(), # 权重初始化器
      activation_fn=tf.nn.relu, # 激活函数
      normalizer_fn=slim.batch_norm, # 标准化器设置为BN
      normalizer_params=batch_norm_params):
    with slim.arg_scope([slim.batch_norm], **batch_norm_params):
      with slim.arg_scope([slim.max_pool2d], padding='SAME') as arg_sc: # ResNet原论文是VALID模式,SAME模式可让特征对齐更简单
        return arg_sc # 最后将基层嵌套的arg_scope作为结果返回



# 定义核心的bottleneck残差学习单元
@slim.add_arg_scope
def bottleneck(inputs, depth, depth_bottleneck, stride,
               outputs_collections=None, scope=None):
  """
  Args:
    inputs: A tensor of size [batch, height, width, channels].
    depth、depth_bottleneck:、stride三个参数是前面blocks类中的args
    rate: An integer, rate for atrous convolution.
    outputs_collections: 是收集end_points的collection
    scope: 是这个unit的名称。
  """
  with tf.variable_scope(scope, 'bottleneck_v2', [inputs]) as sc: # slim.utils.last_dimension获取输入的最后一个维度,即输出通道数。
    depth_in = slim.utils.last_dimension(inputs.get_shape(), min_rank=4) # 可以限定最少为四个维度
    # 使用slim.batch_norm对输入进行batch normalization,并使用relu函数进行预激活preactivate
    preact = slim.batch_norm(inputs, activation_fn=tf.nn.relu, scope='preact') 

    if depth == depth_in:
      shortcut = subsample(inputs, stride, 'shortcut')
      # 如果残差单元的输入通道数和输出通道数一致,那么按步长对inputs进行降采样
    else:
      shortcut = slim.conv2d(preact, depth, [1, 1], stride=stride,
                             normalizer_fn=None, activation_fn=None,
                             scope='shortcut')
      # 如果不一样就按步长和1*1的卷积改变其通道数,使得输入、输出通道数一致

    # 先是一个1*1尺寸,步长1,输出通道数为depth_bottleneck的卷积
    residual = slim.conv2d(preact, depth_bottleneck, [1, 1], stride=1,
                           scope='conv1')
    # 然后是3*3尺寸,步长为stride,输出通道数为depth_bottleneck的卷积
    residual = conv2d_same(residual, depth_bottleneck, 3, stride,
                                        scope='conv2')
    # 最后是1*1卷积,步长1,输出通道数depth的卷积,得到最终的residual。最后一层没有正则项也没有激活函数
    residual = slim.conv2d(residual, depth, [1, 1], stride=1,
                           normalizer_fn=None, activation_fn=None,
                           scope='conv3')

    output = shortcut + residual # 将降采样的结果和residual相加

    return slim.utils.collect_named_outputs(outputs_collections, # 将output添加进collection并返回output作为函数结果
                                            sc.name,
                                            output)


########定义生成resnet_v2的主函数########
def resnet_v2(inputs, # A tensor of size [batch, height_in, width_in, channels].输入
              blocks, # 定义好的Block类的列表
              num_classes=None, # 最后输出的类数
              global_pool=True, # 是否加上最后的一层全局平均池化
              include_root_block=True, # 是否加上ResNet网络最前面通常使用的7*7卷积和最大池化
              reuse=None, # 是否重用
              scope=None): # 整个网络的名称
  # 在函数体先定义好variable_scope和end_points_collection
  with tf.variable_scope(scope, 'resnet_v2', [inputs], reuse=reuse) as sc:
    end_points_collection = sc.original_name_scope + '_end_points' # 定义end_points_collection
    with slim.arg_scope([slim.conv2d, bottleneck,
                         stack_blocks_dense],
                        outputs_collections=end_points_collection): # 将三个参数的outputs_collections默认设置为end_points_collection

      net = inputs
      if include_root_block: # 根据标记值
        with slim.arg_scope([slim.conv2d],
                            activation_fn=None, normalizer_fn=None):
          net = conv2d_same(net, 64, 7, stride=2, scope='conv1') # 创建resnet最前面的64输出通道的步长为2的7*7卷积
        net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool1') # 然后接最大池化
        # 经历过两个步长为2的层图片缩为1/4
      net = stack_blocks_dense(net, blocks) # 将残差学习模块组生成好
      net = slim.batch_norm(net, activation_fn=tf.nn.relu, scope='postnorm')

      if global_pool: # 根据标记添加全局平均池化层
        net = tf.reduce_mean(net, [1, 2], name='pool5', keep_dims=True) # tf.reduce_mean实现全局平均池化效率比avg_pool高
      if num_classes is not None:  # 是否有通道数
        net = slim.conv2d(net, num_classes, [1, 1], activation_fn=None, # 无激活函数和正则项
                          normalizer_fn=None, scope='logits') # 添加一个输出通道num_classes的1*1的卷积
      end_points = slim.utils.convert_collection_to_dict(end_points_collection) # 将collection转化为python的dict
      if num_classes is not None:
        end_points['predictions'] = slim.softmax(net, scope='predictions') # 输出网络结果
      return net, end_points
#------------------------------ResNet的生成函数定义好了----------------------------------------



def resnet_v2_50(inputs, # 图像尺寸缩小了32倍
                 num_classes=None,
                 global_pool=True,
                 reuse=None, # 是否重用
                 scope='resnet_v2_50'):
  blocks = [
      Block('block1', bottleneck, [(256, 64, 1)] * 2 + [(256, 64, 2)]),



      # Args::
      # 'block1':Block名称(或scope)
      # bottleneck:ResNet V2残差学习单元
      # [(256, 64, 1)] * 2 + [(256, 64, 2)]:Block的Args,Args是一个列表。其中每个元素都对应一个bottleneck
      #                                     前两个元素都是(256, 64, 1),最后一个是(256, 64, 2)。每个元素
      #                                     都是一个三元tuple,即(depth,depth_bottleneck,stride)。
      # (256, 64, 3)代表构建的bottleneck残差学习单元(每个残差学习单元包含三个卷积层)中,第三层输出通道数
      # depth为256,前两层输出通道数depth_bottleneck为64,且中间那层步长3。这个残差学习单元结构为:
      # [(1*1/s1,64),(3*3/s2,64),(1*1/s1,256)]



      Block(
          'block2', bottleneck, [(512, 128, 1)] * 3 + [(512, 128, 2)]),
      Block(
          'block3', bottleneck, [(1024, 256, 1)] * 5 + [(1024, 256, 2)]),
      Block(
          'block4', bottleneck, [(2048, 512, 1)] * 3)]
  return resnet_v2(inputs, blocks, num_classes, global_pool,
                   include_root_block=True, reuse=reuse, scope=scope)


def resnet_v2_101(inputs, # unit提升的主要场所是block3
                  num_classes=None,
                  global_pool=True,
                  reuse=None,
                  scope='resnet_v2_101'):
  """ResNet-101 model of [1]. See resnet_v2() for arg and return description."""
  blocks = [
      Block(
          'block1', bottleneck, [(256, 64, 1)] * 2 + [(256, 64, 2)]),
      Block(
          'block2', bottleneck, [(512, 128, 1)] * 3 + [(512, 128, 2)]),
      Block(
          'block3', bottleneck, [(1024, 256, 1)] * 22 + [(1024, 256, 2)]),
      Block(
          'block4', bottleneck, [(2048, 512, 1)] * 3)]
  return resnet_v2(inputs, blocks, num_classes, global_pool,
                   include_root_block=True, reuse=reuse, scope=scope)


def resnet_v2_152(inputs, # unit提升的主要场所是block3
                  num_classes=None,
                  global_pool=True,
                  reuse=None,
                  scope='resnet_v2_152'):
  """ResNet-152 model of [1]. See resnet_v2() for arg and return description."""
  blocks = [
      Block(
          'block1', bottleneck, [(256, 64, 1)] * 2 + [(256, 64, 2)]),
      Block(
          'block2', bottleneck, [(512, 128, 1)] * 7 + [(512, 128, 2)]),
      Block(
          'block3', bottleneck, [(1024, 256, 1)] * 35 + [(1024, 256, 2)]),
      Block(
          'block4', bottleneck, [(2048, 512, 1)] * 3)]
  return resnet_v2(inputs, blocks, num_classes, global_pool,
                   include_root_block=True, reuse=reuse, scope=scope)


def resnet_v2_200(inputs, # unit提升的主要场所是block2
                  num_classes=None,
                  global_pool=True,
                  reuse=None,
                  scope='resnet_v2_200'):
  """ResNet-200 model of [2]. See resnet_v2() for arg and return description."""
  blocks = [
      Block(
          'block1', bottleneck, [(256, 64, 1)] * 2 + [(256, 64, 2)]),
      Block(
          'block2', bottleneck, [(512, 128, 1)] * 23 + [(512, 128, 2)]),
      Block(
          'block3', bottleneck, [(1024, 256, 1)] * 35 + [(1024, 256, 2)]),
      Block(
          'block4', bottleneck, [(2048, 512, 1)] * 3)]
  return resnet_v2(inputs, blocks, num_classes, global_pool,
                   include_root_block=True, reuse=reuse, scope=scope)



from datetime import datetime
import math
import time


#-------------------评测函数---------------------------------
# 测试152层深的ResNet的forward性能
def time_tensorflow_run(session, target, info_string):
    num_steps_burn_in = 10
    total_duration = 0.0
    total_duration_squared = 0.0
    for i in range(num_batches + num_steps_burn_in):
        start_time = time.time()
        _ = session.run(target)
        duration = time.time() - start_time
        if i >= num_steps_burn_in:
            if not i % 10:
                print ('%s: step %d, duration = %.3f' %
                       (datetime.now(), i - num_steps_burn_in, duration))
            total_duration += duration
            total_duration_squared += duration * duration
    mn = total_duration / num_batches
    vr = total_duration_squared / num_batches - mn * mn
    sd = math.sqrt(vr)
    print ('%s: %s across %d steps, %.3f +/- %.3f sec / batch' %
           (datetime.now(), info_string, num_batches, mn, sd))

batch_size = 32
height, width = 224, 224
inputs = tf.random_uniform((batch_size, height, width, 3))
with slim.arg_scope(resnet_arg_scope(is_training=False)): # is_training设置为false
   net, end_points = resnet_v2_152(inputs, 1000)

init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)  
num_batches=100
time_tensorflow_run(sess, net, "Forward") 

# forward计算耗时相比VGGNet和Inception V3大概只增加了50%,是一个实用的卷积神经网络。
图例解释:
  • 图例1(Block):
    图例1(Block):

  • 图例二(padding方式介绍):
    这里写图片描述
    由于在代码中已经做了零填充,所以直接用VALID方式进行填充。

<think>首先,用户的问题是关于深度学习的保留交叉验证概念及其应用。用户询问:“我想了解深度学习中的保留交叉验证概念及其应用 请问深度学习中什么是保留交叉验证 交叉验证的作用和实现方法” 我需要根据系统级指令来构建回答: 1. **行内数学表达式**:使用 `$...$` 格式。例如,如果有数学符号,就用这个格式。 2. **独立公式**:使用 `$$...$$` 格式并单独成段。 3. **LaTeX语法正确**:确保所有数学表达式正确。 4. **使用中文回答**:整个回答用中文。 5. **生成相关问题**:在回答结束后,生成相关问题,以“§§相关问题§§”开头。 6. **引用标识**:在回答中引用的段落末尾自然地添加引用标识,如 `[^1]`。参考提供的引用:[^1], [^2], [^3]。这些引用是关于交叉验证的,但不是用户直接需求的一部分。我应该基于用户的问题和这些引用来构建回答,但不要直接复制。 用户的问题集中在“保留交叉验证”上。在提供的引用中,提到的是交叉验证的一般概念,但没有直接说“保留交叉验证”。我回想一下,保留交叉验证(Holdout Cross-Validation)是一种简单的交叉验证方法,其中数据集被分成训练集和测试集(或训练集、验证集、测试集)。 从引用中: - [^1] 提到:在深度学习中,交叉验证涉及划分数据为训练集、验证集和测试集。 - [^2] 提到:交叉验证用于评估模型性能,常见方法如K折交叉验证。 - [^3] 提到:分层随机交叉验证,确保类别比例相同。 所以,保留交叉验证可能指的是Holdout方法,即简单地分割数据集。 结构回答: - **回答结构清晰**:帮助用户逐步解决问题。确保真实可靠。 - **步骤**: 1. 解释什么是保留交叉验证。 2. 描述其作用。 3. 说明实现方法。 4. 讨论在深度学习中的应用。 - **添加引用**:在相关部分末尾添加引用标识,如 `[^1]`。 最终,生成相关问题。 **回答大纲**: 1. **引言**:简要介绍交叉验证的概念。 2. **什么是保留交叉验证**:解释Holdout Cross-Validation。 3. **作用**:为什么使用它,在深度学习中的作用。 4. **实现方法**:如何实现,包括数据划分比例。 5. **应用**:在深度学习中的具体应用。 6. **优缺点**:简要讨论优缺点。 7. **相关问题**:结束时生成相关问题。 确保使用中文。 数学表达式:如果有数学,比如比例,用 `$...$`。例如,数据划分比例如 `$70\%$` 训练集等。 引用标识:在引用想法时添加。例如,根据引用[^1],划分数据为训练集、验证集和测试集。 完整回答。</think>### 深度学习中的保留交叉验证概念及其应用 在深度学习中,交叉验证是一种重要的模型评估技术,用于在有限数据下可靠地估计模型的泛化能力。保留交叉验证(Holdout Cross-Validation)是其中最简单和常用的方法之一。下面我将逐步解释其概念、作用、实现方法以及在深度学习中的应用,确保回答清晰可靠。部分内容参考了站内引用资料[^1][^2][^3]。 #### 1. **什么是保留交叉验证?** 保留交叉验证是一种数据划分方法,它将数据集简单地分成两部分:训练集(用于模型训练)和测试集(用于模型评估)。在深度学习场景中,通常还会进一步划分出一个验证集(用于超参数调优),形成训练集、验证集和测试集的三元组。这种方法的核心是“保留”一部分数据不参与训练,直接用于测试模型性能,从而避免过拟合训练数据[^1][^2]。例如: - 训练集:用于模型参数的学习- 验证集:用于调整超参数(如学习率、层数)。 - 测试集:用于最终评估模型泛化能力。 保留交叉验证的优点是简单高效,特别适合大数据集或计算资源有限的情况。但它依赖于随机划分,如果数据分布不均匀,可能导致评估偏差[^3]。 #### 2. **交叉验证的作用** 交叉验证的主要作用是提供更稳健的模型性能评估,避免单次数据划分的随机性影响。在深度学习中,其具体作用包括: - **评估泛化能力**:通过测试集估计模型在未见数据上的表现,减少过拟合风险[^2]。 - **超参数优化**:验证集帮助选择最佳超参数,提升模型效果(如使用网格搜索)。 - **数据利用效率**:在数据量有限时(如医疗图像数据集),最大化利用可用数据,避免浪费[^1]。 - **模型比较**:公平比较不同模型架构(如CNN vs. RNN)的性能。 数学上,模型性能指标(如准确率)的期望值可以通过测试集计算:设测试集大小为 $n_{\text{test}}$,模型预测正确数为 $c$,则准确率为 $ \text{Accuracy} = \frac{c}{n_{\text{test}}} $。交叉验证确保这个估计更可靠[^2]。 #### 3. **保留交叉验证的实现方法** 实现保留交叉验证涉及数据划分、训练和评估步骤。以下是详细方法(以Python代码示例说明),参考了深度学习中的常见实践[^1][^3]: **步骤1: 数据划分** - 将数据集随机分割为训练集、验证集和测试集。典型比例是: - 训练集: $70\%-80\%$ - 验证集: $10\%-15\%$ - 测试集: $10\%-15\%$ 在类别不平衡数据中,可使用分层抽样确保各类别比例一致(类似于分层随机交叉验证[^3])。 - 示例代码(使用scikit-learn库): ```python from sklearn.model_selection import train_test_split import numpy as np # 假设X为特征数据,y为标签数据 X = np.array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]) y = np.array([0, 1, 0, 1, 0]) # 先划分训练+验证集与测试集(例如80%训练+验证,20%测试) X_temp, X_test, y_temp, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) # 再划分训练集和验证集(例如从80%中取75%训练,25%验证) X_train, X_val, y_train, y_val = train_test_split(X_temp, y_temp, test_size=0.25, random_state=42, stratify=y_temp) print(f"训练集大小: {len(X_train)}, 验证集大小: {len(X_val)}, 测试集大小: {len(X_test)}") ``` **步骤2: 模型训练与评估** - 使用训练集训练深度学习模型(如TensorFlow或PyTorch模型)。 - 在验证集上调整超参数。 - 最终在测试集上评估性能指标(如准确率、F1分数)。 - 示例伪代码: ```python import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense # 构建简单神经网络模型 model = Sequential([ Dense(64, activation='relu', input_shape=(X_train.shape[1],)), Dense(1, activation='sigmoid') ]) model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy']) # 训练模型(使用训练集) model.fit(X_train, y_train, epochs=10, validation_data=(X_val, y_val)) # 在测试集上评估 test_loss, test_acc = model.evaluate(X_test, y_test) print(f"测试集准确率: {test_acc:.4f}") ``` **关键注意事项**: - **随机种子**:设置 `random_state` 确保结果可复现。 - **分层抽样**:当数据类别不平衡时,使用 `stratify` 参数保持比例,避免偏差[^3]。 - **深度学习特殊性**:在大型数据集(如ImageNet)中,保留法更高效,而K折交叉验证可能计算开销过大[^1]。 #### 4. **在深度学习中的应用** 保留交叉验证广泛应用于深度学习项目,尤其在以下场景: - **图像分类**:例如,在CNN模型中划分图像数据集,用测试集评估模型鲁棒性[^1]。 - **自然语言处理**:用于文本分类任务(如情感分析),确保模型在未见文本上的泛化能力。 - **超参数搜索**:结合验证集进行自动调参(如使用Keras Tuner库),提升模型精度。 - **资源优化**:当训练深度网络(如ResNet)耗时较长时,保留法比K折法更节省计算资源[^2]。 然而,保留法也有局限性:如果数据量小(如少于1000样本),单次划分可能导致评估不稳定;此时推荐使用K折交叉验证作为补充[^2][^3]。 #### 总结 保留交叉验证是深度学习中一种基础且实用的评估方法,通过简单数据划分实现模型性能的可靠估计。它强调“保留”测试集以避免过拟合,适合大数据集和快速迭代场景[^1][^2]。实际应用中,需根据数据大小和分布选择合适的划分策略,并结合其他交叉验证方法(如K折)以提升鲁棒性。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值