使用PyTorch来进行肺癌早期检测:3、训练模型

文章详细介绍了使用PyTorch进行CT数据分类模型的训练过程,包括数据加载、模型构建、训练和验证循环、损失计算、权重初始化以及利用TensorBoard进行可视化。模型基于CNN,通过DataLoader加载数据,训练过程中记录并展示训练和验证的指标。
摘要由CSDN通过智能技术生成

上一章我们主要介绍了数据处理的代码,这章主要是训练模型的代码,书中的意思是先构建出一个CNN模型,不管模型效果,后期对模型和数据进行修改,完善。

目录

一、主要内容

二、函数说明

1、sys.argv

2、argparse.ArgumentParser()

3、时间戳 datetime

4、神经网络权重初始化

5、hasattr函数

6、detach函数

7、enumerateWithEstimate函数

三、代码说明

A、training.py

1、定义框架

2、初始化模型和优化器

3、定义数据加载器

4、main中加入循环

4、doTraning训练循环的方法

5、ComputeBatchLoss()函数

6、doValidation验证循环

7、logMetrics()函数 日志指标

B、model.py

1、主干Block设计

2、模型构建

3、定义前向传播

4、初始化参数权重

写在后面


一、主要内容

  • 使用PyTorch的DataLoader来加载数据
  • 实现一个对CT数据进行分类的模型
  • 为我们的应用程序设置基本框架
  • 记录和显示指标 (训练和验证过程的loss,accuracy等)

我们的基本结构如下:

  • 初始化我们的模型和数据加载
  • 设定迭代周期,并循环训练,执行以下步骤
  1. 循环遍历LunaDataset并获取每批训练数据
  2. 数据加载器将数据加载进来
  3. 将数据分批次传入模型得到结果
  4. 根据预测结果和真实数据的差异计算我们的损失
  5. 将关于模型性能的指标记录到临时数据结构中
  6. 通过误差反向传播来更新模型权重
  7. 获取验证集数据
  8. 加载验证集数据
  9. 使用模型预测验证集数据,并计算损失
  10. 记录模型在验证数据上的执行情况
  11. 输出迭代周期的进度和性能信息

二、函数说明

按照上篇文章提到,把代码中的函数和类与其他的复杂函数一起介绍起来太过于杂乱,这篇文章开始,就在第二部分介绍各种函数,第三部分介绍每块代码是要做什么。在第三部分代码中遇到各类小问题可以来第二部分找一找解答。

1、sys.argv

sys.argv[ ]说白了就是一个从程序外部获取参数的桥梁:

Python中sys.argv[]的用法简明解释

#...line 32 如果调用者不提供参数,则从命令行获取参数
def __init__(self, sys_argv=None):
        if sys_argv is None:
            sys_argv = sys.argv[1:]

2、argparse.ArgumentParser()

argparse 模块可以让人轻松编写用户友好的命令行接口。程序定义它需要的参数,然后 argparse 将弄清如何从 sys.argv 解析出那些参数。 argparse 模块还会自动生成帮助和使用手册,并在用户给程序传入无效参数时报出错误信息。

argparse.ArgumentParser()用法解析

3、时间戳 datetime

使用时间戳帮助了解训练运行情况

datetime.datetime.now().strftime()用法的一些参数:

datetime.datetime.now().strftime

        self.time_str = datetime.datetime.now().strftime('%Y-%m-%d_%H.%M.%S')

4、神经网络权重初始化

权重初始化的目的是防止在深度神经网络的正向(前向)传播过程中层激活函数的输出损失梯度出现爆炸或消失。如果发生任何一种情况,损失梯度太大或太小,就无法有效地向后传播,并且即便可以向后传播,网络也需要花更长时间来达到收敛。

这个网络上有通用模板,可以查找一下。

介绍:神经网络中的权重初始化方式和pytorch应用

5、hasattr函数

hasattr() 函数用于判断对象是否包含对应的属性。

hasattr(object, name)
object -- 对象。
name -- 字符串,属性名。
return
如果对象有该属性返回 True,否则返回 False。

6、detach函数

当我们再训练网络的时候可能希望保持一部分的网络参数不变,只对其中一部分的参数进行调整;或者值训练部分分支网络,并不让其梯度对主网络的梯度造成影响,这时候我们就需要使用detach()函数来切断一些分支的反向传播

pytorch的两个函数 .detach() .detach_() 的作用和区别_LoveMIss-Y的博客-CSDN博客

7、enumerateWithEstimate函数

函数位置:util\util.py

函数主要用了yield关键字,使enumerateWithEstimate函数变为一个迭代器生成器,不断的迭代加载数据集,并根据每次迭代的时间来预估加载完整个数据集所需要的总时间。

# 函数实现预估加载完整个迭代器所需要的时间。具体原理:
# step1:使用yield关键字,每次加载一部分数据集,统计这部分数据集的平均单个数据集的使用时间delta_t = 花费的时间/该部分数据集样本数
# step2:根据迭代器长度,预估加载整个数据集所花时间 t_dataset = delta_t * 数据集长度
def enumerateWithEstimate(
        iter,           # 数据集的一个迭代器。函数目的就是统计加载完整个数据集所需要的时间。
        desc_str,       # 打印log的时候的说明文本。自己随便定义就行。
        start_ndx=0,    # 开始统计前跳过的统计此时。比如start_ndx=3,则意思是第1,2次统计不打印,第三次开始打印。
        print_ndx=4,    # 相邻两次打印日志的统计次数间隔print_ndx = print_ndx * backoff,缺省的初始值为4
        backoff=None,   # 相邻两次打印日志的统计次数间隔的倍数。print_ndx = print_ndx * backoff
        iter_len=None,  # 迭代器的长度,不指定时,iter_len = len(iter)
):
    """
    In terms of behavior, `enumerateWithEstimate` is almost identical
    to the standard `enumerate` (the differences are things like how
    our function returns a generator, while `enumerate` returns a
    specialized `<enumerate object at 0x...>`).
    However, the side effects (logging, specifically) are what make the
    function interesting.
    :param iter: `iter` is the iterable that will be passed into
        `enumerate`. Required.
    :param desc_str: This is a human-readable string that describes
        what the loop is doing. The value is arbitrary, but should be
        kept reasonably short. Things like `"epoch 4 training"` or
        `"deleting temp files"` or similar would all make sense.
    :param start_ndx: This parameter defines how many iterations of the
        loop should be skipped before timing actually starts. Skipping
        a few iterations can be useful if there are startup costs like
        caching that are only paid early on, resulting in a skewed
        average when those early iterations dominate the average time
        per iteration.
        NOTE: Using `start_ndx` to skip some iterations makes the time
        spent performing those iterations not be included in the
        displayed duration. Please account for this if you use the
        displayed duration for anything formal.
        This parameter defaults to `0`.
    :param print_ndx: determines which loop interation that the timing
        logging will start on. The intent is that we don't start
        logging until we've given the loop a few iterations to let the
        average time-per-iteration a chance to stablize a bit. We
        require that `print_ndx` not be less than `start_ndx` times
        `backoff`, since `start_ndx` greater than `0` implies that the
        early N iterations are unstable from a timing perspective.
        `print_ndx` defaults to `4`.
    :param backoff: This is used to how many iterations to skip before
        logging again. Frequent logging is less interesting later on,
        so by default we double the gap between logging messages each
        time after the first.
        `backoff` defaults to `2` unless iter_len is > 1000, in which
        case it defaults to `4`.
    :param iter_len: Since we need to know the number of items to
        estimate when the loop will finish, that can be provided by
        passing in a value for `iter_len`. If a value isn't provided,
        then it will be set by using the value of `len(iter)`.
    :return:
    """
    if iter_len is None:
        iter_len = len(iter)
 
    if backoff is None:
        backoff = 2
        while backoff ** 7 < iter_len:
            backoff *= 2
 
    assert backoff >= 2
    while print_ndx < start_ndx * backoff:
        print_ndx *= backoff
 
    log.warning("{} ----/{}, starting".format(
        desc_str,
        iter_len,
    ))
    start_ts = time.time()
    for (current_ndx, item) in enumerate(iter):
        yield (current_ndx, item)
        if current_ndx == print_ndx:
            # ... <1> step1:计算若干隔数据集加载时间;step2:平均得到每个数据集加载时间;step3:乘以数据集长度得到预计加载所有数据的时间
            duration_sec = ((time.time() - start_ts)
                            / (current_ndx - start_ndx + 1)
                            * (iter_len-start_ndx)
                            )
 
            done_dt = datetime.datetime.fromtimestamp(start_ts + duration_sec)
            done_td = datetime.timedelta(seconds=duration_sec)
 
            log.info("{} {:-4}/{}, done at {}, {}".format(
                desc_str,
                current_ndx,
                iter_len,
                str(done_dt).rsplit('.', 1)[0],     # 运行了current_ndx次后,预估的加载完整个数据集后的系统时间
                str(done_td).rsplit('.', 1)[0],     # 运行了current_ndx次后,预估的加载完整个数据集所需要的秒数
            ))
 
            print_ndx *= backoff
 
        if current_ndx + 1 == start_ndx:
            start_ts = time.time()
 
    log.warning("{} ----/{}, done at {}".format(
        desc_str,
        iter_len,
        str(datetime.datetime.now()).rsplit('.', 1)[0],
    ))

三、代码说明

A、training.py

1、定义框架

创建一个用于训练的类 LunaTrainingApp,进行参数解析,定义一个主方法main,放入训练过程。

class LunaTrainingApp:
    def __init__(self, sys_argv=None):
        if sys_argv is None:
            sys_argv = sys.argv[1:]

        parser = argparse.ArgumentParser()
        parser.add_argument('--num-workers',
            help='Number of worker processes for background data loading',
            default=8,
            type=int,
        )
        parser.add_argument('--batch-size',
            help='Batch size to use for training',
            default=32,
            type=int,
        )
        parser.add_argument('--epochs',
            help='Number of epochs to train for',
            default=1,
            type=int,
        )

        parser.add_argument('--tb-prefix',
            default='p2ch11',
            help="Data prefix to use for Tensorboard run. Defaults to chapter.",
        )

        parser.add_argument('comment',
            help="Comment suffix for Tensorboard run.",
            nargs='?',
            default='dwlpt',
        )
        self.cli_args = parser.parse_args(sys_argv)
        self.time_str = datetime.datetime.now().strftime('%Y-%m-%d_%H.%M.%S')

#... line 137
 def main(self):
        log.info("Starting {}, {}".format(type(self).__name__, self.cli_args))

2、初始化模型和优化器

class LunaTrainingApp:
    def __init__(self, sys_argv=None):
# ... line 70
        self.use_cuda = torch.cuda.is_available()  #设置使用的设备
        self.device = torch.device("cuda" if self.use_cuda else "cpu")  

        self.model = self.initModel()   #对模型初始化
        self.optimizer = self.initOptimizer()  #对优化器初始化

    def initModel(self):  #定义模型初始化方法
        model = LunaModel()   #实例化一个LunaModel
        if self.use_cuda:  #如果使用的是GPU,就把模型发送到GPU上去
            log.info("Using CUDA; {} devices.".format(torch.cuda.device_count()))
            if torch.cuda.device_count() > 1:   #如果有2个或更多的GPU 开启并行
                model = nn.DataParallel(model)  #封装模型
            model = model.to(self.device)      #向GPU发送模型参数
        return model

    def initOptimizer(self):   #初始化优化器
        return SGD(self.model.parameters(), lr=0.001, momentum=0.99)
        # return Adam(self.model.parameters()) 可以选择其他优化方法

3、定义数据加载器

    def initTrainDl(self):
        train_ds = LunaDataset(    #从LunaDataset获取数据,验证集取10个
            val_stride=10,
            isValSet_bool=False,
        )

        batch_size = self.cli_args.batch_size
        if self.use_cuda:
            batch_size *= torch.cuda.device_count()

        train_dl = DataLoader(  #pytorch的类
            train_ds,
            batch_size=batch_size,
            num_workers=self.cli_args.num_workers,
            pin_memory=self.use_cuda,   #固定内存传输到GPU的速度很快
        )

        return train_dl

    def initValDl(self):  #与训练集很像
        val_ds = LunaDataset(
            val_stride=10,
            isValSet_bool=True,
        )

        batch_size = self.cli_args.batch_size
        if self.use_cuda:
            batch_size *= torch.cuda.device_count()

        val_dl = DataLoader(
            val_ds,
            batch_size=batch_size,
            num_workers=self.cli_args.num_workers,
            pin_memory=self.use_cuda,
        )

        return val_dl

#... line 137
   def main(self):
        train_dl = self.initTrainDl()
        val_dl = self.initValDl()

4、main中加入循环

    def main(self):
        log.info("Starting {}, {}".format(type(self).__name__, self.cli_args))

        train_dl = self.initTrainDl()
        val_dl = self.initValDl()

        for epoch_ndx in range(1, self.cli_args.epochs + 1):
            log.info("Epoch {} of {}, {}/{} batches of size {}*{}".format(   #日志记录
                epoch_ndx, #正在训练的代数
                self.cli_args.epochs,#设置的总代数
                len(train_dl),#训练集大小
                len(val_dl),#验证集大小
                self.cli_args.batch_size,#batch大小
                (torch.cuda.device_count() if self.use_cuda else 1),#GPU数目
            ))
            trnMetrics_t = self.doTraining(epoch_ndx, train_dl)  #实际执行训练
            self.logMetrics(epoch_ndx, 'trn', trnMetrics_t) #这个是记录结果数据

            valMetrics_t = self.doValidation(epoch_ndx, val_dl)
            self.logMetrics(epoch_ndx, 'val', valMetrics_t)#这个是记录结果数据

        if hasattr(self, 'trn_writer'):
            self.trn_writer.close()
            self.val_writer.close()

4、doTraning训练循环的方法

main循环中涉及到的训练方法

  • trnMetircs_g 张量在训练期间收集每个类的详细度量
  • 使用enumerateWithEstimate()来提供一个估计的完成时间
  • 实际损失计算放在computeBatchLoss()函数中
def doTraining(self, epoch_ndx, train_dl):
        self.model.train()  #首先执行训练 lunamodel
        trnMetrics_g = torch.zeros( #初始化一个空的指标数组(训练结果指标)
            METRICS_SIZE, 
            len(train_dl.dataset),
            device=self.device,
        )
        batch_iter = enumerateWithEstimate(  #使用时间估计构建批循环
            train_dl,
            "E{} Training".format(epoch_ndx),
            start_ndx=train_dl.num_workers,
        )
        for batch_ndx, batch_tup in batch_iter:
            self.optimizer.zero_grad()   #梯度归零 然后计算损失

            loss_var = self.computeBatchLoss(
                batch_ndx,
                batch_tup,
                train_dl.batch_size,
                trnMetrics_g            )
            loss_var.backward()   #损失反向传播
            self.optimizer.step()  #用优化器更新权重

        self.totalTrainingSamples_count += len(train_dl.dataset)

        return trnMetrics_g.to('cpu')

5、ComputeBatchLoss()函数

函数的核心功能是将批次输入到模型并计算每个批次的损失。

#有几个全局静态变量需要预先定义,用于标明索引位置,以及结果数组的大小
METRICS_LABEL_NDX=0
METRICS_PRED_NDX=1
METRICS_LOSS_NDX=2
METRICS_SIZE = 3

    def computeBatchLoss(self, batch_ndx, batch_tup, batch_size, metrics_g):
        input_t, label_t, _series_list, _center_list = batch_tup 

        input_g = input_t.to(self.device, non_blocking=True) #数据传到GPU上
        label_g = label_t.to(self.device, non_blocking=True)

        logits_g, probability_g = self.model(input_g) #使用模型预测,其中logits_g是没有经过softmax的值

        loss_func = nn.CrossEntropyLoss(reduction='none') # reduction='none'给出每个样本的损失
        loss_g = loss_func( 
            logits_g,
            label_g[:,1],  #独热编码类索引
        )
        start_ndx = batch_ndx * batch_size
        end_ndx = start_ndx + label_t.size(0)
        metrics_g[METRICS_LABEL_NDX, start_ndx:end_ndx] = \   #把结果数据放进metrics_g矩阵
            label_g[:,1].detach()
        metrics_g[METRICS_PRED_NDX, start_ndx:end_ndx] = \
            probability_g[:,1].detach()
        metrics_g[METRICS_LOSS_NDX, start_ndx:end_ndx] = \
            loss_g.detach()

        return loss_g.mean()  #将每个样本的损失重新组合为单个值

6、doValidation验证循环

类似于训练循环,但是有所简化。关键的区别是验证是只读的,具体来说,验证循环返回的损失值不会被使用,权重也不会被更新。

    def doValidation(self, epoch_ndx, val_dl):
        with torch.no_grad():
            self.model.eval()
            valMetrics_g = torch.zeros(
                METRICS_SIZE,
                len(val_dl.dataset),
                device=self.device,
            )

            batch_iter = enumerateWithEstimate(
                val_dl,
                "E{} Validation ".format(epoch_ndx),
                start_ndx=val_dl.num_workers,
            )
            for batch_ndx, batch_tup in batch_iter:
                self.computeBatchLoss(
                    batch_ndx, batch_tup, val_dl.batch_size, valMetrics_g)

        return valMetrics_g.to('cpu')

7、logMetrics()函数 日志指标

迭代周期中所做的最后一件事就是记录这个迭代周期的性能指标。一旦记录了指标,就返回到训练循环中,开始训练的下一个迭代周期。在这个过程中,记录结果和进展是很重要的,因为如果训练偏离了轨道,用深度学习的说法是模型“不收敛”,我们可以快速注意到这种情况的发生,并停止花时间训练一个不可行的模型。

    def logMetrics(   #接收参数
            self,
            epoch_ndx,  #迭代次数
            mode_str,  #当前模式,是训练还是验证
            metrics_t, #接收结果信息
            classificationThreshold=0.5, #分类判断阈值
    ):#这个初始化是为了后面写入tensorboard准备的
        self.initTensorboardWriters()
        log.info("E{} {}".format(
            epoch_ndx,
            type(self).__name__,
        ))
#构建掩码,这里面的静态变量我们上节已经声明过了,根据阈值判断获取负样本标注结果和预测结果
        negLabel_mask = metrics_t[METRICS_LABEL_NDX] <= classificationThreshold
        negPred_mask = metrics_t[METRICS_PRED_NDX] <= classificationThreshold
#这块采用了一个trick的方法来获取正样本结果,直接对上面的负样本结果取反,在二分类是可以这么操作,如果是多分类就不能这么操作了
        posLabel_mask = ~negLabel_mask
        posPred_mask = ~negPred_mask
#统计训练集正负样本数
        neg_count = int(negLabel_mask.sum())
        pos_count = int(posLabel_mask.sum())
#统计预测正确的正负样本数
        neg_correct = int((negLabel_mask & negPred_mask).sum())
        pos_correct = int((posLabel_mask & posPred_mask).sum())
#这一块在计算平均损失,总体平均损失,负样本平均损失,正样本平均损失
        metrics_dict = {}
        metrics_dict['loss/all'] = \
            metrics_t[METRICS_LOSS_NDX].mean()
        metrics_dict['loss/neg'] = \
            metrics_t[METRICS_LOSS_NDX, negLabel_mask].mean()
        metrics_dict['loss/pos'] = \
            metrics_t[METRICS_LOSS_NDX, posLabel_mask].mean()
#计算准确率
        metrics_dict['correct/all'] = (pos_correct + neg_correct) \            
           /np.float32(metrics_t.shape[1]) * 100
        metrics_dict['correct/neg'] = neg_correct / np.float32(neg_count) * 100
        metrics_dict['correct/pos'] = pos_correct / np.float32(pos_count) * 100
#紧接着就是日志记录
#整体损失和整体准确率
        log.info(  
            ("E{} {:8} {loss/all:.4f} loss, "
                 + "{correct/all:-5.1f}% correct, "
            ).format(
                epoch_ndx,
                mode_str,
                **metrics_dict,
            )
        )
#负样本损失和负类别准确率
        log.info( 
            ("E{} {:8} {loss/neg:.4f} loss, "
                 + "{correct/neg:-5.1f}% correct ({neg_correct:} of {neg_count:})"
            ).format(
                epoch_ndx,
                mode_str + '_neg',
                neg_correct=neg_correct,
                neg_count=neg_count,
                **metrics_dict,
            )
        )
#正样本损失和正类别准确率
        log.info(
            ("E{} {:8} {loss/pos:.4f} loss, "
                 + "{correct/pos:-5.1f}% correct ({pos_correct:} of {pos_count:})"
            ).format(
                epoch_ndx,
                mode_str + '_pos',
                pos_correct=pos_correct,
                pos_count=pos_count,
                **metrics_dict,
            )
        )
#下面的部分跟写入tensorboard相关
        writer = getattr(self, mode_str + '_writer')

        for key, value in metrics_dict.items():
            writer.add_scalar(key, value, self.totalTrainingSamples_count)

        writer.add_pr_curve(
            'pr',
            metrics_t[METRICS_LABEL_NDX],
            metrics_t[METRICS_PRED_NDX],
            self.totalTrainingSamples_count,
        )

        bins = [x/50.0 for x in range(51)]

        negHist_mask = negLabel_mask & (metrics_t[METRICS_PRED_NDX] > 0.01)
        posHist_mask = posLabel_mask & (metrics_t[METRICS_PRED_NDX] < 0.99)

        if negHist_mask.any():
            writer.add_histogram(
                'is_neg',
                metrics_t[METRICS_PRED_NDX, negHist_mask],
                self.totalTrainingSamples_count,
                bins=bins,
            )
        if posHist_mask.any():
            writer.add_histogram(
                'is_pos',
                metrics_t[METRICS_PRED_NDX, posHist_mask],
                self.totalTrainingSamples_count,
                bins=bins,
            )

B、model.py

尾部只使用了一个batchnorm对数据进行正则化。主干部分:包含四个同样的块,每个块的细节是右边的内容,其中包含了两次3d卷积,两次ReLU激活,最后有一个最大池化。头部结果输出到一个线性层,然后经过softmax给出预测概率。其中主干的四个块,每个块输出的通道数都扩大一倍,相当于提取更多维度的特征。

1、主干Block设计

class LunaBlock(nn.Module):
    def __init__(self, in_channels, conv_channels): #初始化方法
        super().__init__()

        self.conv1 = nn.Conv3d(# 
            in_channels, conv_channels, kernel_size=3, padding=1, bias=True,
        )
        self.relu1 = nn.ReLU(inplace=True)
        self.conv2 = nn.Conv3d(
            conv_channels, conv_channels, kernel_size=3, padding=1, bias=True,
        )
        self.relu2 = nn.ReLU(inplace=True) #ReLU激活函数

        self.maxpool = nn.MaxPool3d(2, 2) #最大池化

    def forward(self, input_batch): #前向传播
        block_out = self.conv1(input_batch)
        block_out = self.relu1(block_out)
        block_out = self.conv2(block_out)
        block_out = self.relu2(block_out)

        return self.maxpool(block_out)

2、模型构建

class LunaModel(nn.Module):
    def __init__(self, in_channels=1, conv_channels=8):
        super().__init__()
        self.tail_batchnorm = nn.BatchNorm3d(1)
        self.block1 = LunaBlock(in_channels, conv_channels)
        self.block2 = LunaBlock(conv_channels, conv_channels * 2)
        self.block3 = LunaBlock(conv_channels * 2, conv_channels * 4)
        self.block4 = LunaBlock(conv_channels * 4, conv_channels * 8)
        self.head_linear = nn.Linear(1152, 2) 
        self.head_softmax = nn.Softmax(dim=1)

3、定义前向传播

def forward(self, input_batch):
        bn_output = self.tail_batchnorm(input_batch)

        block_out = self.block1(bn_output)
        block_out = self.block2(block_out)
        block_out = self.block3(block_out)
        block_out = self.block4(block_out)
        conv_flat = block_out.view(   #改变输出的形状,转换成一维,第二部分介绍view
            block_out.size(0),
            -1,
        )
        linear_output = self.head_linear(conv_flat)

        return linear_output, self.head_softmax(linear_output)

4、初始化参数权重

权重初始化的目的是防止在深度神经网络的正向(前向)传播过程中层激活函数的输出损失梯度出现爆炸或消失。如果发生任何一种情况,损失梯度太大或太小,就无法有效地向后传播,并且即便可以向后传播,网络也需要花更长时间来达到收敛。

   def _init_weights(self):
        for m in self.modules():
            if type(m) in {
                nn.Linear,
                nn.Conv3d,
                nn.Conv2d,
                nn.ConvTranspose2d,
                nn.ConvTranspose3d,
            }:
                nn.init.kaiming_normal_(#这个让随机生成的数值符合正态分布
                    m.weight.data, a=0, mode='fan_out', nonlinearity='relu',
                )
                if m.bias is not None:
                    fan_in, fan_out = \
                        nn.init._calculate_fan_in_and_fan_out(m.weight.data)
                    bound = 1 / math.sqrt(fan_out)
                    nn.init.normal_(m.bias, -bound, bound)

写在后面

书中还介绍了用TensorBoard做展示,目前这一遍我主要是在熟悉代码和各个模块,后续有时间的话在研究TensorBoard展示。

参考文章:

20 | 使用PyTorch完成医疗图像识别大项目:编写训练模型代码 - 腾讯云开发者社区-腾讯云

五、肺癌检测-数据集训练 training.py model.py-CSDN博客

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值