【代码解析(3)】Communication-Efficient Learning of Deep Networks from Decentralized Data

update.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Python version: 3.6

import torch
from torch import nn
from torch.utils.data import DataLoader, Dataset


class DatasetSplit(Dataset):
    """An abstract Dataset class wrapped around Pytorch Dataset class.
    """
    ''''
        数据集划分
    '''
    def __init__(self, dataset, idxs):
        self.dataset = dataset
        self.idxs = [int(i) for i in idxs]

    def __len__(self):
        return len(self.idxs)

    def __getitem__(self, item):
        image, label = self.dataset[self.idxs[item]]
        # sampling中idxs = np.arange(60000)
        # [1, 2]-->>tensor([1, 2])
        return torch.tensor(image), torch.tensor(label)


class LocalUpdate(object):
    def __init__(self, args, dataset, idxs, logger):
        # 本地更新
        self.args = args
        self.logger = logger  # 记录器
        self.trainloader, self.validloader, self.testloader = \
            self.train_val_test(dataset, list(idxs))

        self.device = 'cuda' if args.gpu else 'cpu'
        # Default criterion set to NLL loss function
        self.criterion = nn.NLLLoss().to(self.device)
        '''
            默认标准设置为NLL损失函数
            类似:net = net.to(device)
            CrossEntropyLoss()=log_softmax() + NLLLoss() 
            softmax(x)+log(x)+nn.NLLLoss====>nn.CrossEntropyLoss
            其中softmax函数又称为归一化指数函数,它可以把一个多维向量压缩在(01)之间,并且它们的和为1
        '''

    def train_val_test(self, dataset, idxs):
        """
        上面调用了这个函数
        Returns train, validation and test dataloaders for a given dataset
        and user indexes.

        给定一个数据集返回:训练集,验证集,测试集
        对于一个给定数据集和用户索引
        """
        # split indexes for train, validation, and test (80, 10, 10)
        idxs_train = idxs[:int(0.8*len(idxs))]
        idxs_val = idxs[int(0.8*len(idxs)):int(0.9*len(idxs))]
        idxs_test = idxs[int(0.9*len(idxs)):]
        '''
            划分索引比例(80,10,10)
            
            
        '''

        trainloader = DataLoader(DatasetSplit(dataset, idxs_train),
                                 batch_size=self.args.local_bs, shuffle=True)
        '''
            shuffle设置为True每一个epoch都重新洗牌数据
            DataLoader(training_data, batch_size=64, shuffle=True)    
        '''
        validloader = DataLoader(DatasetSplit(dataset, idxs_val),
                                 batch_size=int(len(idxs_val)/10), shuffle=False)
        testloader = DataLoader(DatasetSplit(dataset, idxs_test),
                                batch_size=int(len(idxs_test)/10), shuffle=False)
        return trainloader, validloader, testloader

    def update_weights(self, model, global_round):
        # Set mode to train model
        '''
            更新权重
        '''
        model.train()
        '''
            告诉我们的网络,这个阶段是用来训练的,可以更新参数
        '''
        epoch_loss = []

        # Set optimizer for the local updates
        '''
            为本地更新选择优化器
        '''
        if self.args.optimizer == 'sgd':
            optimizer = torch.optim.SGD(model.parameters(), lr=self.args.lr,
                                        momentum=0.5)
        elif self.args.optimizer == 'adam':
            optimizer = torch.optim.Adam(model.parameters(), lr=self.args.lr,
                                         weight_decay=1e-4)
        # epoch
        for iter in range(self.args.local_ep):
            batch_loss = []
            for batch_idx, (images, labels) in enumerate(self.trainloader):
                '''
                    batch_idx
                    (images, labels)
                    enumerate(self.trainloader)会将训练集数据一个
                    batch一个batch地取出来用来训练
                    使用enumerate进行dataloader中的数据读取用于
                    神经网络的训练是第一种数据读取方法,其基本形式
                    即为for index, item in enumerate(dataloader['train']),
                    其中item中0为数据,1为label.
                '''
                images, labels = images.to(self.device), labels.to(self.device)

                model.zero_grad()
                '''
                    将模型的参数梯度设为0
                '''
                log_probs = model(images)
                '''
                    获得前项传播结果
                    model函数在调用的时候会调用call,于是调用forward。
                '''
                loss = self.criterion(log_probs, labels)
                '''
                    这个代码就是交叉熵:log_softmax()+NLLLoss()
                    
                    from torch import nn
                    
                    self.criterion = nn.NLLLoss().to(self.device)
                    loss=nn.NLLLoss()
                    loss(input,target)
                    
                    CrossEntropyLoss()=log_softmax() + NLLLoss() 
                    softmax(x)+log(x)+nn.NLLLoss====>nn.CrossEntropyLoss
                    
                '''
                loss.backward()
                '''
                    深度学习中最为重要的反向传播计算,
                    pytorch用非常简单的backward()函数就实现了
                '''
                optimizer.step()
                '''
                    所有的optimizer都实现了step()方法,
                    这个方法会更新所有的参数
                '''

                if self.args.verbose and (batch_idx % 10 == 0):
                    '''
                        for batch_idx, (images, labels) in enumerate(self.trainloader):
                    '''
                    print('| Global Round : {} | Local Epoch : {} | [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                        global_round,
                        iter,
                        batch_idx * len(images),
                        len(self.trainloader.dataset),
                        100. * batch_idx / len(self.trainloader),
                        loss.item()))
                    '''
                        batch_idx为batch数量吗?
                        len(images)为一个batch中的数据即batch_size
                        batch_idx * len(images)为数据量
                        
                        
                        
                    '''
                    '''
                        全局轮数:
                            本地epoch:
                                损失loss:
                    '''
                self.logger.add_scalar('loss', loss.item())
                '''
                    add_scalar(tag, scalar_value, global_step=None, ):
                    将我们所需要的数据保存在文件里面供可视化使用
                    scalar_value(浮点型或字符串):y轴数据(步数)
                '''
                batch_loss.append(loss.item())
                '''
                    batch_loss = []for 循环下一个epoch,append一次
                    
                '''
            epoch_loss.append(sum(batch_loss)/len(batch_loss))
            '''
                走完所有epoch
                epoch_loss = []
                对loss求平均
            '''
            '''
                state_dict变量存放训练过程中需要学习的权重和偏执系数
                state_dict作为python的字典对象将每一层的参数映射成tensor张量
                需要注意的是torch.nn.Module模块中的state_dict只包含卷积层和全连接层的参数
            '''
        return model.state_dict(), sum(epoch_loss) / len(epoch_loss)

    def inference(self, model):
        """ Returns the inference accuracy and loss.
        """
        '''
            返回推断准确率和损失
        '''
        model.eval()
        '''
            1.告诉我们的网络,这个阶段是用来测试的,于是模型的参数在该阶段不进行更新
            使用PyTorch进行训练和测试时一定注意要把实例化的
            model指定train/eval,eval()时,框架会自动把
            BN和DropOut固定住,不会取平均,而是用训练好的值,
            不然的话,一旦test的batch_size过小,很容易就会
            被BN层导致生成图片颜色失真极大
        '''
        loss, total, correct = 0.0, 0.0, 0.0

        for batch_idx, (images, labels) in enumerate(self.testloader):
            '''
                 
                batch_idx
                (images, labels)
                enumerate(self.trainloader)会将训练集数据一个
                batch一个batch地取出来用来训练
                使用enumerate进行dataloader中的数据读取用于
                神经网络的训练是第一种数据读取方法,其基本形式
                即为for index, item in enumerate(dataloader['train']),
                其中item中0为数据,1为label.
                
            '''
            images, labels = images.to(self.device), labels.to(self.device)

            # Inference推断
            outputs = model(images)

            batch_loss = self.criterion(outputs, labels)
            '''
                要这样写
                crossentropyloss=nn.CrossEntropyLoss()
                crossentropyloss_output=crossentropyloss(x_input,y_target)
                print('crossentropyloss_output:\n',crossentropyloss_output)
                

                bool value of Tensor with more than one value is ambiguous
                不能直接这样写:
                xx = nn.CrossEntropyLoss(x_input, y_target)
                print(xx)
            
            '''
            loss += batch_loss.item()

            # Prediction
            '''
                预测过程
            '''
            _, pred_labels = torch.max(outputs, 1)
            '''
                _是每行的最大值
                pred_labels表示每行最大值的索引
                但是为什么命名为pred_labels(预测标签)
                
                torch.max(input, dim) 
                input是softmax函数输出的一个tensor
                dim是max函数索引的维度0/10是每列的最大值,1是每行的最大值
                
                函数会返回两个tensor,
                第一个tensor是每行的最大值;
                第二个tensor是每行最大值的索引
            '''
            pred_labels = pred_labels.view(-1)
            '''
                tensor([2, 2]).view(-1)结果不变
                tensor([[1, 1, 1]]).view(-1)结果为tensor([1, 1, 1])
                
                b=torch.Tensor([[[1,2,3],[4,5,6]]])
                它的结构是
                [1,4]
                [2,5]
                [3,6]
                a=torch.Tensor([[1,2,3],[4,5,6]])
                它的结构是
                [1,2,3]
                [4,5,6]
                b=torch.Tensor([[[1,2,3]]])
                它的结构是
                [1]
                [2]
                [3]
                
                
            '''
            correct += torch.sum(torch.eq(pred_labels, labels)).item()
            # corrent存储预测标签与真实标签有多少个匹配的
            '''
            torch.eq:
                比较两个tensor对应位置数字是否相同,相同为1,否则为0
                pred_label=torch.Tensor([1,2,3])
                labels=torch.Tensor([1,0,3])
                torch.eq(pred_label, labels):
                    tensor([ True, False,  True])
                torch.sum(torch.eq(pred_label, labels):
                    tensor(2)
            torch.sum:
                就是将所有值相加,但是得到的仍然是一个tensor
                tensor(2)
            tensor.item():
                label=torch.Tensor([2])
                print(label.item())
                tensor中只有一个元素可以这样用item()
            '''
            total += len(labels)
            '''
                total多少行?
            '''

        accuracy = correct/total
        return accuracy, loss


def test_inference(args, model, test_dataset):
    # 返回测试的精度和损失
    """
        def inference(self, model):
        test推断函数和原推断函数还是不同的
        首先数据集就不同
        原推断函数是:self.testloader
            def train_val_test(self, dataset, idxs):
            用的是这个函数返回的testloader

        test推断函数:数据集是test_dataset

        Returns the test accuracy and loss.
    """

    model.eval()
    '''
       1.告诉我们的网络,这个阶段是用来测试的,于是模型的参数在该阶段不进行更新
       使用PyTorch进行训练和测试时一定注意要把实例化的
       model指定train/eval,eval()时,框架会自动把
       BN和DropOut固定住,不会取平均,而是用训练好的值,
       不然的话,一旦test的batch_size过小,很容易就会
    '''
    loss, total, correct = 0.0, 0.0, 0.0

    device = 'cuda' if args.gpu else 'cpu'
    criterion = nn.NLLLoss().to(device)

    '''
        默认标准设置为NLL损失函数
        类似:net = net.to(device)
        CrossEntropyLoss()=log_softmax() + NLLLoss() 
        softmax(x)+log(x)+nn.NLLLoss====>nn.CrossEntropyLoss
        其中softmax函数又称为归一化指数函数,它可以把一个多维向量
        压缩在(01)之间,并且它们的和为1
    '''
    testloader = DataLoader(test_dataset, batch_size=128,
                            shuffle=False)
    '''
        testloader = DataLoader(DatasetSplit(dataset, idxs_test),
                                batch_size=int(len(idxs_test)/10), shuffle=False)
    '''

    # 上面三行是与原推断函数不同的代码
    # 还有下面for循环中原推断函数是enumerate(self.testloader)

    for batch_idx, (images, labels) in enumerate(testloader):
        images, labels = images.to(device), labels.to(device)
        '''

            batch_idx
            (images, labels)
            enumerate(self.trainloader)会将训练集数据一个
            batch一个batch地取出来用来训练
            使用enumerate进行dataloader中的数据读取用于
            神经网络的训练是第一种数据读取方法,其基本形式
            即为for index, item in enumerate(dataloader['train']),
            其中item中0为数据,1为label.
        '''
        # Inference
        outputs = model(images)
        batch_loss = criterion(outputs, labels)
        '''
            要这样写
            crossentropyloss=nn.CrossEntropyLoss()
            crossentropyloss_output=crossentropyloss(x_input,y_target)
            print('crossentropyloss_output:\n',crossentropyloss_output)


            bool value of Tensor with more than one value is ambiguous
            不能直接这样写:
            xx = nn.CrossEntropyLoss(x_input, y_target)
            print(xx)

        '''
        loss += batch_loss.item()

        # Prediction
        '''
            预测
        '''
        _, pred_labels = torch.max(outputs, 1)
        '''
             _是每行的最大值
             pred_labels表示每行最大值的索引
             但是为什么命名为pred_labels(预测标签)

             torch.max(input, dim) 
             input是softmax函数输出的一个tensor
             dim是max函数索引的维度0/10是每列的最大值,1是每行的最大值

             函数会返回两个tensor,
             第一个tensor是每行的最大值;
             第二个tensor是每行最大值的索引
         '''
        pred_labels = pred_labels.view(-1)
        '''
            tensor([2, 2]).view(-1)结果不变
            tensor([[1, 1, 1]]).view(-1)结果为tensor([1, 1, 1])

            b=torch.Tensor([[[1,2,3],[4,5,6]]])
            它的结构是
            [1,4]
            [2,5]
            [3,6]
            a=torch.Tensor([[1,2,3],[4,5,6]])
            它的结构是
            [1,2,3]
            [4,5,6]
            b=torch.Tensor([[[1,2,3]]])
            它的结构是
            [1]
            [2]
            [3]


        '''
        correct += torch.sum(torch.eq(pred_labels, labels)).item()
        # corrent存储预测标签与真实标签有多少个匹配的
        '''
        torch.eq:
            比较两个tensor对应位置数字是否相同,相同为1,否则为0
            pred_label=torch.Tensor([1,2,3])
            labels=torch.Tensor([1,0,3])
            torch.eq(pred_label, labels):
                tensor([ True, False,  True])
            torch.sum(torch.eq(pred_label, labels):
                tensor(2)
        torch.sum:
            就是将所有值相加,但是得到的仍然是一个tensor
            tensor(2)
        tensor.item():
            label=torch.Tensor([2])
            print(label.item())
            tensor中只有一个元素可以这样用item()
        '''
        total += len(labels)

    accuracy = correct/total
    return accuracy, loss


  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值