import os
from config.resnet_config import RESNET_CONFIG
default_config = RESNET_CONFIG()
os.environ["CUDA_VISIBLE_DEVICES"] = default_config.GPU_IDS

from model.pretrained_resnet18 import ResNet18
from net.base_net import BaseNet
from config.secret_config import SECRET_CONFIG
from config.resnet_config import RESNET_CONFIG
import torch
import torch.nn as nn
from torchvision import models
import torch.optim as optim


class ResNet(BaseNet):
    def __init__(self, config):
        super(ResNet, self).__init__(config)

        self.config = config
        self.data_used = config.PRETRAIN_MODEL

        if self.data_used == "ImageNet":
            resnet18 = models.resnet18(pretrained=True)     # 导入预训练的模型
        elif self.data_used == "Places":
            path = os.path.join(SECRET_CONFIG.PROJECT_PATH, "checkpoints/resnet18_places365.pth")
            resnet18 = models.__dict__["resnet18"](num_classes=365)
            checkpoint = torch.load(path, map_location=lambda storage, loc: storage)
            state_dict = {str.replace(k, 'module.', ''): v for k, v in checkpoint['state_dict'].items()}
            resnet18.load_state_dict(state_dict)
        else:
            assert 0

        self.conv1 = resnet18.conv1
        self.bn1 = resnet18.bn1
        self.relu = resnet18.relu
        self.maxpool = resnet18.maxpool
        self.layer1 = resnet18.layer1
        self.layer2 = resnet18.layer2
        self.layer3 = resnet18.layer3
        self.layer4 = resnet18.layer4

        self.avgpool = resnet18.avgpool
        self.fc = nn.Linear(512, self.config.CLASS_NUM)

        self.modality = self.config.MODALITY

        self.optimizer = self.get_optimizer()
        self.scheduler = self.get_scheduler()
        self.loss_func = nn.CrossEntropyLoss(weight=torch.FloatTensor(self.config.CLASS_WEIGHTS_TRAIN))

        self.to(self.device)

    def forward(self, x):
        output = self.maxpool(self.relu(self.bn1(self.conv1(x))))
        output = self.layer1(output)
        output = self.layer2(output)
        output = self.layer3(output)
        output = self.layer4(output)
        output = self.avgpool(output)
        output = torch.flatten(output, start_dim=1, end_dim=-1)
        output = self.fc(output)
        return output

    def get_optimizer(self):
        optimizer = optim.SGD(
            self.parameters(),
            lr=self.config.LEARN_RATE,
            momentum=self.config.MOMENTUM,
            weight_decay=self.config.WEIGHT_DECAY
        )
        return optimizer

    def get_scheduler(self):
        scheduler = optim.lr_scheduler.StepLR(
            optimizer=self.optimizer,
            step_size=3,
            gamma=0.5,
            last_epoch=-1
        )
        return scheduler

    def update_optimizer(self, epoch):
        epoch += 1  # 因为需要讨论的是下一个epoch是否需要进行修改，因此这里累加以符合直觉
        self.scheduler.step()

    def _learn_rate(self, epoch):
        current_learn_rate = self.optimizer.param_groups[0]["lr"]
        print(
            "Epoch: %d [lr=%f]" % (epoch, current_learn_rate)
        )

    def _train(self, epoch):
        self.train()
        train_correct = 0.0
        train_total = 0.0

        for data in self.train_loader:
            self.optimizer.zero_grad()

            input = data[self.modality].to(self.device)
            labels = data["label"].to(self.device)
            output = self.forward(input)

            loss = self.loss_func(output, labels)

            loss.backward()
            self.optimizer.step()

            _, train_pred = torch.max(output.data, dim=1)

            train_total += labels.size(0)
            train_correct += train_pred.eq(labels.data).cpu().sum()

        print(
            "[epoch:%d, Train]: Train Accuracy: %.2f%%"
            %
            (
                epoch,
                (train_correct / train_total) * 100
            )
        )

        self.tb_log.add_acc(train_acc=(train_correct / train_total) * 100, x=epoch)

    def _test(self, epoch):
        self.eval()

        test_total = 0.0
        test_correct = 0.0

        with torch.no_grad():
            for data in self.test_loader:
                input = data[self.modality].to(self.device)
                labels = data["label"].to(self.device)
                output = self.forward(input)

                _, test_pred = torch.max(output.data, dim=1)

                test_total += labels.size(0)
                test_correct += test_pred.eq(labels.data).cpu().sum()

            print(
                "[epoch:%d, Test]: Test Accuracy: %.2f%%"
                %
                (
                    epoch,
                    (test_correct / test_total) * 100
                )
            )

            self.tb_log.add_acc(test_acc=(test_correct / test_total) * 100, x=epoch)

    def train_net(self):
        print(self.config.all_vars())
        for epoch in range(0, self.config.EPOCH):
            self._learn_rate(epoch)
            self._train(epoch)
            self._test(epoch)
            self.update_optimizer(epoch)


if __name__ == '__main__':
    config = RESNET_CONFIG()
    config.TB_LOG_GROUP = "eval"
    resnet = ResNet(config=config)
    resnet.train_net()


import os
from config.resnet_config import RESNET_CONFIG
default_config = RESNET_CONFIG()
os.environ["CUDA_VISIBLE_DEVICES"] = default_config.GPU_IDS

from model.pretrained_resnet18 import ResNet18
from net.base_net import BaseNet
from config.secret_config import SECRET_CONFIG
from config.resnet_config import RESNET_CONFIG
import torch
import torch.nn as nn
from torchvision import models
import torch.optim as optim


class ResNet(BaseNet):
    def __init__(self, config):
        super(ResNet, self).__init__(config)

        self.config = config
        self.data_used = config.PRETRAIN_MODEL

        if self.data_used == "ImageNet":
            resnet18 = models.resnet18(pretrained=True)     # 导入预训练的模型
        elif self.data_used == "Places":
            path = os.path.join(SECRET_CONFIG.PROJECT_PATH, "checkpoints/resnet18_places365.pth")
            resnet18 = models.__dict__["resnet18"](num_classes=365)
            checkpoint = torch.load(path, map_location=lambda storage, loc: storage)
            state_dict = {str.replace(k, 'module.', ''): v for k, v in checkpoint['state_dict'].items()}
            resnet18.load_state_dict(state_dict)
        else:
            assert 0

        self.conv1 = resnet18.conv1
        self.bn1 = resnet18.bn1
        self.relu = resnet18.relu
        self.maxpool = resnet18.maxpool
        self.layer1 = resnet18.layer1
        self.layer2 = resnet18.layer2
        self.layer3 = resnet18.layer3
        self.layer4 = resnet18.layer4

        self.avgpool = resnet18.avgpool
        self.fc = nn.Linear(512, self.config.CLASS_NUM)

        self.modality = self.config.MODALITY

        self.optimizer = self.get_optimizer()
        self.scheduler = self.get_scheduler()
        self.loss_func = nn.CrossEntropyLoss(weight=torch.FloatTensor(self.config.CLASS_WEIGHTS_TRAIN))

        self.to(self.device)

    def forward(self, x):
        output = self.maxpool(self.relu(self.bn1(self.conv1(x))))
        output = self.layer1(output)
        output = self.layer2(output)
        output = self.layer3(output)
        output = self.layer4(output)
        output = self.avgpool(output)
        output = torch.flatten(output, start_dim=1, end_dim=-1)
        output = self.fc(output)
        return output

    def get_optimizer(self):
        optimizer = optim.SGD(
            self.parameters(),
            lr=self.config.LEARN_RATE,
            momentum=self.config.MOMENTUM,
            weight_decay=self.config.WEIGHT_DECAY
        )
        return optimizer

    def get_scheduler(self):
        scheduler = optim.lr_scheduler.StepLR(
            optimizer=self.optimizer,
            step_size=3,
            gamma=0.5,
            last_epoch=-1
        )
        return scheduler

    def update_optimizer(self, epoch):
        epoch += 1  # 因为需要讨论的是下一个epoch是否需要进行修改，因此这里累加以符合直觉
        self.scheduler.step()

    def _learn_rate(self, epoch):
        current_learn_rate = self.optimizer.param_groups[0]["lr"]
        print(
            "Epoch: %d [lr=%f]" % (epoch, current_learn_rate)
        )

    def _train(self, epoch):
        self.train()
        train_correct = 0.0
        train_total = 0.0

        for data in self.train_loader:
            self.optimizer.zero_grad()

            input = data[self.modality].to(self.device)
            labels = data["label"].to(self.device)
            output = self.forward(input)

            loss = self.loss_func(output, labels)

            loss.backward()
            self.optimizer.step()

            _, train_pred = torch.max(output.data, dim=1)

            train_total += labels.size(0)
            train_correct += train_pred.eq(labels.data).cpu().sum()

        print(
            "[epoch:%d, Train]: Train Accuracy: %.2f%%"
            %
            (
                epoch,
                (train_correct / train_total) * 100
            )
        )

        self.tb_log.add_acc(train_acc=(train_correct / train_total) * 100, x=epoch)

    def _test(self, epoch):
        self.eval()

        test_total = 0.0
        test_correct = 0.0

        with torch.no_grad():
            for data in self.test_loader:
                input = data[self.modality].to(self.device)
                labels = data["label"].to(self.device)
                output = self.forward(input)

                _, test_pred = torch.max(output.data, dim=1)

                test_total += labels.size(0)
                test_correct += test_pred.eq(labels.data).cpu().sum()

            print(
                "[epoch:%d, Test]: Test Accuracy: %.2f%%"
                %
                (
                    epoch,
                    (test_correct / test_total) * 100
                )
            )

            self.tb_log.add_acc(test_acc=(test_correct / test_total) * 100, x=epoch)

    def train_net(self):
        print(self.config.all_vars())
        for epoch in range(0, self.config.EPOCH):
            self._learn_rate(epoch)
            self._train(epoch)
            self._test(epoch)
            self.update_optimizer(epoch)


if __name__ == '__main__':
    config = RESNET_CONFIG()
    config.TB_LOG_GROUP = "eval"
    resnet = ResNet(config=config)
    resnet.train_net()


# 导入预训练的模型 # 因为需要讨论的是下一个epoch是否需要进行修改，因此这里累加以符合直觉