[
  {
    "path": ".gitignore",
    "content": "*.py[cod]\n*.so\n*.egg\n*.egg-info\n*.DS_Store\n"
  },
  {
    "path": "Code/1_data_prepare/1_1_cifar10_to_png.py",
    "content": "# coding:utf-8\n\"\"\"\n    将cifar10的data_batch_12345 转换成 png格式的图片\n    每个类别单独存放在一个文件夹，文件夹名称为0-9\n\"\"\"\nfrom imageio import imwrite\nimport numpy as np\nimport os\nimport pickle\n\n\nbase_dir = \"D:/python   11/新建文件夹/practise/pytorch\" #修改为当前Data 目录所在的绝对路径\ndata_dir = os.path.join(base_dir, \"Data\", \"cifar-10-batches-py\")\ntrain_o_dir = os.path.join( base_dir, \"Data\", \"cifar-10-png\", \"raw_train\")\ntest_o_dir = os.path.join( base_dir, \"Data\", \"cifar-10-png\", \"raw_test\")\n\nTrain = False   # 不解压训练集，仅解压测试集\n\n# 解压缩，返回解压后的字典\ndef unpickle(file):\n    with open(file, 'rb') as fo:\n        dict_ = pickle.load(fo, encoding='bytes')\n    return dict_\n\ndef my_mkdir(my_dir):\n    if not os.path.isdir(my_dir):\n        os.makedirs(my_dir)\n\n\n# 生成训练集图片，\nif __name__ == '__main__':\n    if Train:\n        for j in range(1, 6):\n            data_path = os.path.join(data_dir, \"data_batch_\" + str(j))  # data_batch_12345\n            train_data = unpickle(data_path)\n            print(data_path + \" is loading...\")\n\n            for i in range(0, 10000):\n                img = np.reshape(train_data[b'data'][i], (3, 32, 32))\n                img = img.transpose(1, 2, 0)\n\n                label_num = str(train_data[b'labels'][i])\n                o_dir = os.path.join(train_o_dir, label_num)\n                my_mkdir(o_dir)\n\n                img_name = label_num + '_' + str(i + (j - 1)*10000) + '.png'\n                img_path = os.path.join(o_dir, img_name)\n                imwrite(img_path, img)\n            print(data_path + \" loaded.\")\n\n    print(\"test_batch is loading...\")\n\n    # 生成测试集图片\n    test_data_path = os.path.join(data_dir, \"test_batch\")\n    test_data = unpickle(test_data_path)\n    for i in range(0, 10000):\n        img = np.reshape(test_data[b'data'][i], (3, 32, 32))\n        img = img.transpose(1, 2, 0)\n\n        label_num = str(test_data[b'labels'][i])\n        o_dir = os.path.join(test_o_dir, label_num)\n        my_mkdir(o_dir)\n\n        img_name = label_num + '_' + str(i) + '.png'\n        img_path = os.path.join(o_dir, img_name)\n        imwrite(img_path, img)\n\n    print(\"test_batch loaded.\")\n"
  },
  {
    "path": "Code/1_data_prepare/1_2_split_dataset.py",
    "content": "# coding: utf-8\n\"\"\"\n    将原始数据集进行划分成训练集、验证集和测试集\n\"\"\"\n\nimport os\nimport glob\nimport random\nimport shutil\n\ndataset_dir = os.path.join(\"..\", \"..\", \"Data\", \"cifar-10-png\", \"raw_test\")\ntrain_dir = os.path.join(\"..\", \"..\", \"Data\", \"train\")\nvalid_dir = os.path.join(\"..\", \"..\", \"Data\", \"valid\")\ntest_dir = os.path.join(\"..\", \"..\", \"Data\", \"test\")\n\ntrain_per = 0.8\nvalid_per = 0.1\ntest_per = 0.1\n\n\ndef makedir(new_dir):\n    if not os.path.exists(new_dir):\n        os.makedirs(new_dir)\n\n\nif __name__ == '__main__':\n\n    for root, dirs, files in os.walk(dataset_dir):\n        for sDir in dirs:\n            imgs_list = glob.glob(os.path.join(root, sDir, '*.png'))\n            random.seed(666)\n            random.shuffle(imgs_list)\n            imgs_num = len(imgs_list)\n\n            train_point = int(imgs_num * train_per)\n            valid_point = int(imgs_num * (train_per + valid_per))\n\n            for i in range(imgs_num):\n                if i < train_point:\n                    out_dir = os.path.join(train_dir, sDir)\n                elif i < valid_point:\n                    out_dir = os.path.join(valid_dir, sDir)\n                else:\n                    out_dir = os.path.join(test_dir, sDir)\n\n                makedir(out_dir)\n                out_path = os.path.join(out_dir, os.path.split(imgs_list[i])[-1])\n                shutil.copy(imgs_list[i], out_path)\n\n            print('Class:{}, train:{}, valid:{}, test:{}'.format(sDir, train_point, valid_point-train_point, imgs_num-valid_point))\n"
  },
  {
    "path": "Code/1_data_prepare/1_3_generate_txt.py",
    "content": "# coding:utf-8\nimport os\n'''\n    为数据集生成对应的txt文件\n'''\n\ntrain_txt_path = os.path.join(\"..\", \"..\", \"Data\", \"train.txt\")\ntrain_dir = os.path.join(\"..\", \"..\", \"Data\", \"train\")\n\nvalid_txt_path = os.path.join(\"..\", \"..\", \"Data\", \"valid.txt\")\nvalid_dir = os.path.join(\"..\", \"..\", \"Data\", \"valid\")\n\n\ndef gen_txt(txt_path, img_dir):\n    f = open(txt_path, 'w')\n    \n    for root, s_dirs, _ in os.walk(img_dir, topdown=True):  # 获取 train文件下各文件夹名称\n        for sub_dir in s_dirs:\n            i_dir = os.path.join(root, sub_dir)             # 获取各类的文件夹 绝对路径\n            img_list = os.listdir(i_dir)                    # 获取类别文件夹下所有png图片的路径\n            for i in range(len(img_list)):\n                if not img_list[i].endswith('png'):         # 若不是png文件，跳过\n                    continue\n                label = img_list[i].split('_')[0]\n                img_path = os.path.join(i_dir, img_list[i])\n                line = img_path + ' ' + label + '\\n'\n                f.write(line)\n    f.close()\n\n\nif __name__ == '__main__':\n    gen_txt(train_txt_path, train_dir)\n    gen_txt(valid_txt_path, valid_dir)\n\n"
  },
  {
    "path": "Code/1_data_prepare/1_3_mydataset.py",
    "content": "# coding: utf-8\nfrom PIL import Image\nfrom torch.utils.data import Dataset\n\n\nclass MyDataset(Dataset):\n    def __init__(self, txt_path, transform=None, target_transform=None):\n        fh = open(txt_path, 'r')\n        imgs = []\n        for line in fh:\n            line = line.rstrip()\n            words = line.split()\n            imgs.append((words[0], int(words[1])))\n\n        self.imgs = imgs        # 最主要就是要生成这个list， 然后DataLoader中给index，通过getitem读取图片数据\n        self.transform = transform\n        self.target_transform = target_transform\n\n    def __getitem__(self, index):\n        fn, label = self.imgs[index]\n        img = Image.open(fn).convert('RGB')     # 像素值 0~255，在transfrom.totensor会除以255，使像素值变成 0~1\n\n        if self.transform is not None:\n            img = self.transform(img)   # 在这里做transform，转为tensor等等\n\n        return img, label\n\n    def __len__(self):\n        return len(self.imgs)"
  },
  {
    "path": "Code/1_data_prepare/1_5_compute_mean.py",
    "content": "# coding: utf-8\n\nimport numpy as np\nimport cv2\nimport random\nimport os\n\n\"\"\"\n    随机挑选CNum张图片，进行按通道计算均值mean和标准差std\n    先将像素从0～255归一化至 0-1 再计算\n\"\"\"\n\n\ntrain_txt_path = os.path.join(\"..\", \"..\", \"Data/train.txt\")\n\nCNum = 2000     # 挑选多少图片进行计算\n\nimg_h, img_w = 32, 32\nimgs = np.zeros([img_w, img_h, 3, 1])\nmeans, stdevs = [], []\n\nwith open(train_txt_path, 'r') as f:\n    lines = f.readlines()\n    random.shuffle(lines)   # shuffle , 随机挑选图片\n\n    for i in range(CNum):\n        img_path = lines[i].rstrip().split()[0]\n\n        img = cv2.imread(img_path)\n        img = cv2.resize(img, (img_h, img_w))\n\n        img = img[:, :, :, np.newaxis]\n        imgs = np.concatenate((imgs, img), axis=3)\n        print(i)\n\nimgs = imgs.astype(np.float32)/255.\n\n\nfor i in range(3):\n    pixels = imgs[:,:,i,:].ravel()  # 拉成一行\n    means.append(np.mean(pixels))\n    stdevs.append(np.std(pixels))\n\nmeans.reverse() # BGR --> RGB\nstdevs.reverse()\n\nprint(\"normMean = {}\".format(means))\nprint(\"normStd = {}\".format(stdevs))\nprint('transforms.Normalize(normMean = {}, normStd = {})'.format(means, stdevs))\n\n"
  },
  {
    "path": "Code/2_model/2_finetune.py",
    "content": "# coding: utf-8\n\nimport torch\nfrom torch.utils.data import DataLoader\nimport torchvision.transforms as transforms\nimport numpy as np\nimport os\nfrom torch.autograd import Variable\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nimport sys\nsys.path.append(\"..\")\nfrom utils.utils import MyDataset, validate, show_confMat\nfrom datetime import datetime\n\ntrain_txt_path = os.path.join(\"..\", \"..\", \"Data\", \"train.txt\")\nvalid_txt_path = os.path.join(\"..\", \"..\", \"Data\", \"valid.txt\")\n\nclasses_name = ['plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']\n\ntrain_bs = 16\nvalid_bs = 16\nlr_init = 0.001\nmax_epoch = 1\n\n# log\nresult_dir = os.path.join(\"..\", \"..\", \"Result\")\n\nnow_time = datetime.now()\ntime_str = datetime.strftime(now_time, '%m-%d_%H-%M-%S')\n\nlog_dir = os.path.join(result_dir, time_str)\nif not os.path.exists(log_dir):\n    os.makedirs(log_dir)\n\n# -------------------------------------------- step 1/5 : 加载数据 -------------------------------------------\n\n# 数据预处理设置\nnormMean = [0.4948052, 0.48568845, 0.44682974]\nnormStd = [0.24580306, 0.24236229, 0.2603115]\nnormTransform = transforms.Normalize(normMean, normStd)\ntrainTransform = transforms.Compose([\n    transforms.Resize(32),\n    transforms.RandomCrop(32, padding=4),\n    transforms.ToTensor(),\n    normTransform\n])\n\nvalidTransform = transforms.Compose([\n    transforms.ToTensor(),\n    normTransform\n])\n\n# 构建MyDataset实例\ntrain_data = MyDataset(txt_path=train_txt_path, transform=trainTransform)\nvalid_data = MyDataset(txt_path=valid_txt_path, transform=validTransform)\n\n# 构建DataLoder\ntrain_loader = DataLoader(dataset=train_data, batch_size=train_bs, shuffle=True)\nvalid_loader = DataLoader(dataset=valid_data, batch_size=valid_bs)\n\n# ------------------------------------ step 2/5 : 定义网络 ------------------------------------\n\n\nclass Net(nn.Module):\n    def __init__(self):\n        super(Net, self).__init__()\n        self.conv1 = nn.Conv2d(3, 6, 5)\n        self.pool1 = nn.MaxPool2d(2, 2)\n        self.conv2 = nn.Conv2d(6, 16, 5)\n        self.pool2 = nn.MaxPool2d(2, 2)\n        self.fc1 = nn.Linear(16 * 5 * 5, 120)\n        self.fc2 = nn.Linear(120, 84)\n        self.fc3 = nn.Linear(84, 10)\n\n    def forward(self, x):\n        x = self.pool1(F.relu(self.conv1(x)))\n        x = self.pool2(F.relu(self.conv2(x)))\n        x = x.view(-1, 16 * 5 * 5)\n        x = F.relu(self.fc1(x))\n        x = F.relu(self.fc2(x))\n        x = self.fc3(x)\n        return x\n\n    # 定义权值初始化\n    def initialize_weights(self):\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                torch.nn.init.xavier_normal_(m.weight.data)\n                if m.bias is not None:\n                    m.bias.data.zero_()\n            elif isinstance(m, nn.BatchNorm2d):\n                m.weight.data.fill_(1)\n                m.bias.data.zero_()\n            elif isinstance(m, nn.Linear):\n                torch.nn.init.normal_(m.weight.data, 0, 0.01)\n                m.bias.data.zero_()\n\n\nnet = Net()     # 创建一个网络\n\n# ================================ #\n#        finetune 权值初始化\n# ================================ #\n\n# load params\npretrained_dict = torch.load('net_params.pkl')\n\n# 获取当前网络的dict\nnet_state_dict = net.state_dict()\n\n# 剔除不匹配的权值参数\npretrained_dict_1 = {k: v for k, v in pretrained_dict.items() if k in net_state_dict}\n\n# 更新新模型参数字典\nnet_state_dict.update(pretrained_dict_1)\n\n# 将包含预训练模型参数的字典\"放\"到新模型中\nnet.load_state_dict(net_state_dict)\n\n# ------------------------------------ step 3/5 : 定义损失函数和优化器 ------------------------------------\n# ================================= #\n#         按需设置学习率\n# ================================= #\n\n# 将fc3层的参数从原始网络参数中剔除\nignored_params = list(map(id, net.fc3.parameters()))\nbase_params = filter(lambda p: id(p) not in ignored_params, net.parameters())\n\n# 为fc3层设置需要的学习率\noptimizer = optim.SGD([\n    {'params': base_params},\n    {'params': net.fc3.parameters(), 'lr': lr_init*10}],  lr_init, momentum=0.9, weight_decay=1e-4)\n\ncriterion = nn.CrossEntropyLoss()                                                   # 选择损失函数\nscheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.1)     # 设置学习率下降策略\n\n# ------------------------------------ step 4/5 : 训练 --------------------------------------------------\n\nfor epoch in range(max_epoch):\n\n    loss_sigma = 0.0    # 记录一个epoch的loss之和\n    correct = 0.0\n    total = 0.0\n    scheduler.step()  # 更新学习率\n\n    for i, data in enumerate(train_loader):\n        # 获取图片和标签\n        inputs, labels = data\n        inputs, labels = Variable(inputs), Variable(labels)\n\n        # forward, backward, update weights\n        optimizer.zero_grad()\n        outputs = net(inputs)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        # 统计预测信息\n        _, predicted = torch.max(outputs.data, 1)\n        total += labels.size(0)\n        correct += (predicted == labels).squeeze().sum().numpy()\n        loss_sigma += loss.item()\n\n        # 每10个iteration 打印一次训练信息，loss为10个iteration的平均\n        if i % 10 == 9:\n            loss_avg = loss_sigma / 10\n            loss_sigma = 0.0\n            print(\"Training: Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}\".format(\n                epoch + 1, max_epoch, i + 1, len(train_loader), loss_avg, correct / total))\n            print('参数组1的学习率:{}, 参数组2的学习率:{}'.format(scheduler.get_lr()[0], scheduler.get_lr()[1]))\n    # ------------------------------------ 观察模型在验证集上的表现 ------------------------------------\n    loss_sigma = 0.0\n    cls_num = len(classes_name)\n    conf_mat = np.zeros([cls_num, cls_num])  # 混淆矩阵\n    net.eval()\n    for i, data in enumerate(valid_loader):\n\n        # 获取图片和标签\n        images, labels = data\n        images, labels = Variable(images), Variable(labels)\n\n        # forward\n        outputs = net(images)\n        outputs.detach_()\n\n        # 计算loss\n        loss = criterion(outputs, labels)\n        loss_sigma += loss.item()\n\n        # 统计\n        _, predicted = torch.max(outputs.data, 1)\n        # labels = labels.data    # Variable --> tensor\n\n        # 统计混淆矩阵\n        for j in range(len(labels)):\n            cate_i = labels[j].numpy()\n            pre_i = predicted[j].numpy()\n            conf_mat[cate_i, pre_i] += 1.0\n\n    print('{} set Accuracy:{:.2%}'.format('Valid', conf_mat.trace() / conf_mat.sum()))\nprint('Finished Training')\n\n# ------------------------------------ step5: 绘制混淆矩阵图 ------------------------------------\n\nconf_mat_train, train_acc = validate(net, train_loader, 'train', classes_name)\nconf_mat_valid, valid_acc = validate(net, valid_loader, 'valid', classes_name)\n\nshow_confMat(conf_mat_train, classes_name, 'train', log_dir)\nshow_confMat(conf_mat_valid, classes_name, 'valid', log_dir)"
  },
  {
    "path": "Code/3_optimizer/3_1_lossFunction/1_L1Loss.py",
    "content": "# coding: utf-8\n\nimport torch\nimport torch.nn as nn\n\n# ----------------------------------- L1 Loss\n\n# 生成网络输出 以及 目标输出\noutput = torch.ones(2, 2, requires_grad=True)*0.5\ntarget = torch.ones(2, 2)\n\n# 设置三种不同参数的L1Loss\nreduce_False = nn.L1Loss(size_average=True, reduce=False)\nsize_average_True = nn.L1Loss(size_average=True, reduce=True)\nsize_average_False = nn.L1Loss(size_average=False, reduce=True)\n\no_0 = reduce_False(output, target)\no_1 = size_average_True(output, target)\no_2 = size_average_False(output, target)\n\nprint('\\nreduce=False, 输出同维度的loss:\\n{}\\n'.format(o_0))\nprint('size_average=True，\\t求平均:\\t{}'.format(o_1))\nprint('size_average=False，\\t求和:\\t{}'.format(o_2))\n"
  },
  {
    "path": "Code/3_optimizer/3_1_lossFunction/2_MSELoss.py",
    "content": "# coding: utf-8\n\nimport torch\nimport torch.nn as nn\n\n# ----------------------------------- MSE loss\n\n# 生成网络输出 以及 目标输出\noutput = torch.ones(2, 2, requires_grad=True) * 0.5\ntarget = torch.ones(2, 2)\n\n# 设置三种不同参数的L1Loss\nreduce_False = nn.MSELoss(size_average=True, reduce=False)\nsize_average_True = nn.MSELoss(size_average=True, reduce=True)\nsize_average_False = nn.MSELoss(size_average=False, reduce=True)\n\n\no_0 = reduce_False(output, target)\no_1 = size_average_True(output, target)\no_2 = size_average_False(output, target)\n\nprint('\\nreduce=False, 输出同维度的loss:\\n{}\\n'.format(o_0))\nprint('size_average=True，\\t求平均:\\t{}'.format(o_1))\nprint('size_average=False，\\t求和:\\t{}'.format(o_2))\n"
  },
  {
    "path": "Code/3_optimizer/3_1_lossFunction/3_CrossEntropyLoss.py",
    "content": "# coding: utf-8\n\nimport torch\nimport torch.nn as nn\nimport numpy as np\nimport math\n\n# ----------------------------------- CrossEntropy loss: base\n\nloss_f = nn.CrossEntropyLoss(weight=None, size_average=True, reduce=False)\n# 生成网络输出 以及 目标输出\noutput = torch.ones(2, 3, requires_grad=True) * 0.5      # 假设一个三分类任务，batchsize=2，假设每个神经元输出都为0.5\ntarget = torch.from_numpy(np.array([0, 1])).type(torch.LongTensor)\n\nloss = loss_f(output, target)\n\nprint('--------------------------------------------------- CrossEntropy loss: base')\nprint('loss: ', loss)\nprint('由于reduce=False，所以可以看到每一个样本的loss，输出为[1.0986, 1.0986]')\n\n\n# 熟悉计算公式，手动计算第一个样本\noutput = output[0].detach().numpy()\noutput_1 = output[0]              # 第一个样本的输出值\ntarget_1 = target[0].numpy()\n\n# 第一项\nx_class = output[target_1]\n# 第二项\nexp = math.e\nsigma_exp_x = pow(exp, output[0]) + pow(exp, output[1]) + pow(exp, output[2])\nlog_sigma_exp_x = math.log(sigma_exp_x)\n# 两项相加\nloss_1 = -x_class + log_sigma_exp_x\nprint('---------------------------------------------------  手动计算')\nprint('第一个样本的loss：', loss_1)\n\n\n# ----------------------------------- CrossEntropy loss: weight\n\nweight = torch.from_numpy(np.array([0.6, 0.2, 0.2])).float()\nloss_f = nn.CrossEntropyLoss(weight=weight, size_average=True, reduce=False)\noutput = torch.ones(2, 3, requires_grad=True) * 0.5  # 假设一个三分类任务，batchsize为2个，假设每个神经元输出都为0.5\ntarget = torch.from_numpy(np.array([0, 1])).type(torch.LongTensor)\nloss = loss_f(output, target)\nprint('\\n\\n--------------------------------------------------- CrossEntropy loss: weight')\nprint('loss: ', loss)  #\nprint('原始loss值为1.0986, 第一个样本是第0类，weight=0.6,所以输出为1.0986*0.6 =', 1.0986*0.6)\n\n# ----------------------------------- CrossEntropy loss: ignore_index\n\nloss_f_1 = nn.CrossEntropyLoss(weight=None, size_average=False, reduce=False, ignore_index=1)\nloss_f_2 = nn.CrossEntropyLoss(weight=None, size_average=False, reduce=False, ignore_index=2)\n\noutput = torch.ones(3, 3, requires_grad=True) * 0.5  # 假设一个三分类任务，batchsize为2个，假设每个神经元输出都为0.5\ntarget = torch.from_numpy(np.array([0, 1, 2])).type(torch.LongTensor)\n\nloss_1 = loss_f_1(output, target)\nloss_2 = loss_f_2(output, target)\n\nprint('\\n\\n--------------------------------------------------- CrossEntropy loss: ignore_index')\nprint('ignore_index = 1: ', loss_1)     # 类别为1的样本的loss为0\nprint('ignore_index = 2: ', loss_2)     # 类别为2的样本的loss为0\n"
  },
  {
    "path": "Code/3_optimizer/3_1_lossFunction/4_NLLLoss.py",
    "content": "# coding: utf-8\n\nimport torch\nimport torch.nn as nn\nimport numpy as np\n\n# ----------------------------------- log likelihood loss\n\n# 各类别权重\nweight = torch.from_numpy(np.array([0.6, 0.2, 0.2])).float()\n\n# 生成网络输出 以及 目标输出\noutput = torch.from_numpy(np.array([[0.7, 0.2, 0.1], [0.4, 1.2, 0.4]])).float()  \noutput.requires_grad = True\ntarget = torch.from_numpy(np.array([0, 0])).type(torch.LongTensor)\n\n\nloss_f = nn.NLLLoss(weight=weight, size_average=True, reduce=False)\nloss = loss_f(output, target)\n\nprint('\\nloss: \\n', loss)\nprint('\\n第一个样本是0类，loss = -(0.6*0.7)={}'.format(loss[0]))\nprint('\\n第二个样本是0类，loss = -(0.6*0.4)={}'.format(loss[1]))"
  },
  {
    "path": "Code/3_optimizer/3_1_lossFunction/5_PoissonNLLLoss.py",
    "content": "# coding: utf-8\n\nimport torch\nimport torch.nn as nn\nimport numpy as np\n\n# ----------------------------------- Poisson NLLLoss\n\n# 生成网络输出 以及 目标输出\nlog_input = torch.randn(5, 2, requires_grad=True)\ntarget = torch.randn(5, 2)\n\nloss_f = nn.PoissonNLLLoss()\nloss = loss_f(log_input, target)\nprint('\\nloss: \\n', loss)\n\n"
  },
  {
    "path": "Code/3_optimizer/3_1_lossFunction/6_KLDivLoss.py",
    "content": "# coding: utf-8\n\nimport torch\nimport torch.nn as nn\nimport numpy as np\n\n# -----------------------------------  KLDiv loss\n\nloss_f = nn.KLDivLoss(size_average=False, reduce=False)\nloss_f_mean = nn.KLDivLoss(size_average=True, reduce=True)\n\n# 生成网络输出 以及 目标输出\noutput = torch.from_numpy(np.array([[0.1132, 0.5477, 0.3390]])).float()\noutput.requires_grad = True\ntarget = torch.from_numpy(np.array([[0.8541, 0.0511, 0.0947]])).float()\n\nloss_1 = loss_f(output, target)\nloss_mean = loss_f_mean(output, target)\n\nprint('\\nloss: ', loss_1)\nprint('\\nloss_mean: ', loss_mean)\n\n\n# 熟悉计算公式，手动计算样本的第一个元素的loss，注意这里只有一个样本，是 element-wise计算的\n\noutput = output[0].detach().numpy()\noutput_1 = output[0]           # 第一个样本的第一个元素\ntarget_1 = target[0][0].numpy()\n\nloss_1 = target_1 * (np.log(target_1) - output_1)\n\nprint('\\n第一个样本第一个元素的loss：', loss_1)\n\n\n\n"
  },
  {
    "path": "Code/3_optimizer/3_2_optimizer/1_param_groups.py",
    "content": "# coding: utf-8\n\nimport torch\nimport torch.optim as optim\n\n\nw1 = torch.randn(2, 2)\nw1.requires_grad = True\n\nw2 = torch.randn(2, 2)\nw2.requires_grad = True\n\nw3 = torch.randn(2, 2)\nw3.requires_grad = True\n\n# 一个参数组\noptimizer_1 = optim.SGD([w1, w3], lr=0.1)\nprint('len(optimizer.param_groups): ', len(optimizer_1.param_groups))\nprint(optimizer_1.param_groups, '\\n')\n\n# 两个参数组\noptimizer_2 = optim.SGD([{'params': w1, 'lr': 0.1},\n                         {'params': w2, 'lr': 0.001}])\nprint('len(optimizer.param_groups): ', len(optimizer_2.param_groups))\nprint(optimizer_2.param_groups)\n"
  },
  {
    "path": "Code/3_optimizer/3_2_optimizer/2_zero_grad.py",
    "content": "# coding: utf-8\n\nimport torch\nimport torch.optim as optim\n\n# ----------------------------------- zero_grad\n\nw1 = torch.randn(2, 2)\nw1.requires_grad = True\n\nw2 = torch.randn(2, 2)\nw2.requires_grad = True\n\noptimizer = optim.SGD([w1, w2], lr=0.001, momentum=0.9)\n\noptimizer.param_groups[0]['params'][0].grad = torch.randn(2, 2)\n\nprint('参数w1的梯度：')\nprint(optimizer.param_groups[0]['params'][0].grad, '\\n')  # 参数组，第一个参数(w1)的梯度\n\noptimizer.zero_grad()\nprint('执行zero_grad()之后，参数w1的梯度：')\nprint(optimizer.param_groups[0]['params'][0].grad)  # 参数组，第一个参数(w1)的梯度\n"
  },
  {
    "path": "Code/3_optimizer/3_2_optimizer/3_state_dict.py",
    "content": "# coding: utf-8\n\nimport torch.nn as nn\nimport torch.nn.functional as F\n\n\n# ----------------------------------- state_dict\nclass Net(nn.Module):\n    def __init__(self):\n        super(Net, self).__init__()\n        self.conv1 = nn.Conv2d(3, 1, 3)\n        self.pool = nn.MaxPool2d(2, 2)\n        self.fc1 = nn.Linear(1 * 3 * 3, 2)\n\n    def forward(self, x):\n        x = self.pool(F.relu(self.conv1(x)))\n        x = x.view(-1, 1 * 3 * 3)\n        x = F.relu(self.fc1(x))\n        return x\n\n\nnet = Net()\n\n# 获取网络当前参数\nnet_state_dict = net.state_dict()\n\nprint('net_state_dict类型：', type(net_state_dict))\nprint('net_state_dict管理的参数: ', net_state_dict.keys())\nfor key, value in net_state_dict.items():\n    print('参数名: ', key, '\\t大小: ',  value.shape)\n"
  },
  {
    "path": "Code/3_optimizer/3_2_optimizer/4_load_state_dict.py",
    "content": "# coding: utf-8\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\n\n# ----------------------------------- load_state_dict\n\nclass Net(nn.Module):\n    def __init__(self):\n        super(Net, self).__init__()\n        self.conv1 = nn.Conv2d(3, 1, 3)\n        self.pool = nn.MaxPool2d(2, 2)\n        self.fc1 = nn.Linear(1 * 3 * 3, 2)\n\n    def forward(self, x):\n        x = self.pool(F.relu(self.conv1(x)))\n        x = x.view(-1, 1 * 3 * 3)\n        x = F.relu(self.fc1(x))\n        return x\n\n    def zero_param(self):\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                torch.nn.init.constant_(m.weight.data, 0)\n                if m.bias is not None:\n                    m.bias.data.zero_()\n            elif isinstance(m, nn.Linear):\n                torch.nn.init.constant_(m.weight.data, 0)\n                m.bias.data.zero_()\nnet = Net()\n\n# 保存，并加载模型参数(仅保存模型参数)\ntorch.save(net.state_dict(), 'net_params.pkl')   # 假设训练好了一个模型net\npretrained_dict = torch.load('net_params.pkl')\n\n# 将net的参数全部置0，方便对比\nnet.zero_param()\nnet_state_dict = net.state_dict()\nprint('conv1层的权值为:\\n', net_state_dict['conv1.weight'], '\\n')\n\n# 通过load_state_dict 加载参数\nnet.load_state_dict(pretrained_dict)\nprint('加载之后，conv1层的权值变为:\\n', net_state_dict['conv1.weight'])\n"
  },
  {
    "path": "Code/3_optimizer/3_2_optimizer/5_add_param_group.py",
    "content": "# coding: utf-8\n\nimport torch\nimport torch.optim as optim\n\n# ----------------------------------- add_param_group\n\nw1 = torch.randn(2, 2)\nw1.requires_grad = True\n\nw2 = torch.randn(2, 2)\nw2.requires_grad = True\n\nw3 = torch.randn(2, 2)\nw3.requires_grad = True\n\n# 一个参数组\noptimizer_1 = optim.SGD([w1, w2], lr=0.1)\nprint('当前参数组个数: ', len(optimizer_1.param_groups))\nprint(optimizer_1.param_groups, '\\n')\n\n# 增加一个参数组\nprint('增加一组参数 w3\\n')\noptimizer_1.add_param_group({'params': w3, 'lr': 0.001, 'momentum': 0.8})\n\nprint('当前参数组个数: ', len(optimizer_1.param_groups))\nprint(optimizer_1.param_groups, '\\n')\n\nprint('可以看到，参数组是一个list，一个元素是一个dict，每个dict中都有lr, momentum等参数，这些都是可单独管理，单独设定，十分灵活！')\n\n"
  },
  {
    "path": "Code/4_viewer/1_tensorboardX_demo.py",
    "content": "# coding: utf-8\nimport os\nimport torch\nimport torchvision.utils as vutils\nimport numpy as np\nimport torchvision.models as models\nfrom torchvision import datasets\nfrom tensorboardX import SummaryWriter\n\nresnet18 = models.resnet18(False)\nwriter = SummaryWriter(os.path.join(\"..\", \"..\", \"Result\", \"runs\"))\nsample_rate = 44100\nfreqs = [262, 294, 330, 349, 392, 440, 440, 440, 440, 440, 440]\n\ntrue_positive_counts = [75, 64, 21, 5, 0]\nfalse_positive_counts = [150, 105, 18, 0, 0]\ntrue_negative_counts = [0, 45, 132, 150, 150]\nfalse_negative_counts = [0, 11, 54, 70, 75]\nprecision = [0.3333333, 0.3786982, 0.5384616, 1.0, 0.0]\nrecall = [1.0, 0.8533334, 0.28, 0.0666667, 0.0]\n\n\nfor n_iter in range(100):\n    s1 = torch.rand(1)  # value to keep\n    s2 = torch.rand(1)\n    # data grouping by `slash`\n    writer.add_scalar(os.path.join(\"data\", \"scalar_systemtime\"), s1[0], n_iter)\n    # data grouping by `slash`\n    writer.add_scalar(os.path.join(\"data\", \"scalar_customtime\"), s1[0], n_iter, walltime=n_iter)\n    writer.add_scalars(os.path.join(\"data\", \"scalar_group\"), {\"xsinx\": n_iter * np.sin(n_iter),\n                                             \"xcosx\": n_iter * np.cos(n_iter),\n                                             \"arctanx\": np.arctan(n_iter)}, n_iter)\n    x = torch.rand(32, 3, 64, 64)  # output from network\n    if n_iter % 10 == 0:\n        x = vutils.make_grid(x, normalize=True, scale_each=True)\n        writer.add_image('Image', x, n_iter)  # Tensor\n        # writer.add_image('astronaut', skimage.data.astronaut(), n_iter) # numpy\n        # writer.add_image('imread',\n        # skimage.io.imread('screenshots/audio.png'), n_iter) # numpy\n        x = torch.zeros(sample_rate * 2)\n        for i in range(x.size(0)):\n            # sound amplitude should in [-1, 1]\n            x[i] = np.cos(freqs[n_iter // 10] * np.pi *\n                          float(i) / float(sample_rate))\n        writer.add_audio('myAudio', x, n_iter)\n        writer.add_text('Text', 'text logged at step:' + str(n_iter), n_iter)\n        writer.add_text('markdown Text', '''a|b\\n-|-\\nc|d''', n_iter)\n        for name, param in resnet18.named_parameters():\n            if 'bn' not in name:\n                writer.add_histogram(name, param, n_iter)\n        writer.add_pr_curve('xoxo', np.random.randint(2, size=100), np.random.rand(\n            100), n_iter)  # needs tensorboard 0.4RC or later\n        writer.add_pr_curve_raw('prcurve with raw data', true_positive_counts,\n                                false_positive_counts,\n                                true_negative_counts,\n                                false_negative_counts,\n                                precision,\n                                recall, n_iter)\n# export scalar data to JSON for external processing\nwriter.export_scalars_to_json(os.path.join(\"..\", \"..\", \"Result\", \"all_scalars.json\"))\n\ndataset = datasets.MNIST(os.path.join(\"..\", \"..\", \"Data\", \"mnist\"), train=False, download=True)\nimages = dataset.test_data[:100].float()\nlabel = dataset.test_labels[:100]\nfeatures = images.view(100, 784)\nwriter.add_embedding(features, metadata=label, label_img=images.unsqueeze(1))\nwriter.add_embedding(features, global_step=1, tag='noMetadata')\ndataset = datasets.MNIST(os.path.join(\"..\", \"..\", \"Data\", \"mnist\"), train=True, download=True)\nimages_train = dataset.train_data[:100].float()\nlabels_train = dataset.train_labels[:100]\nfeatures_train = images_train.view(100, 784)\n\nall_features = torch.cat((features, features_train))\nall_labels = torch.cat((label, labels_train))\nall_images = torch.cat((images, images_train))\ndataset_label = ['test'] * 100 + ['train'] * 100\nall_labels = list(zip(all_labels, dataset_label))\n\nwriter.add_embedding(all_features, metadata=all_labels, label_img=all_images.unsqueeze(1),\n                     metadata_header=['digit', 'dataset'], global_step=2)\n\n# VIDEO\nvid_images = dataset.train_data[:16 * 48]\nvid = vid_images.view(16, 1, 48, 28, 28)  # BxCxTxHxW\n\nwriter.add_video('video', vid_tensor=vid)\nwriter.add_video('video_1_fps', vid_tensor=vid, fps=1)\n\nwriter.close()"
  },
  {
    "path": "Code/4_viewer/2_visual_weights.py",
    "content": "# coding: utf-8\nimport os\nimport torch\nimport torchvision.utils as vutils\nfrom tensorboardX import SummaryWriter\nimport torch.nn as nn\nimport torch.nn.functional as F\n\n\nclass Net(nn.Module):\n    def __init__(self):\n        super(Net, self).__init__()\n        self.conv1 = nn.Conv2d(3, 6, 5)\n        self.pool1 = nn.MaxPool2d(2, 2)\n        self.conv2 = nn.Conv2d(6, 16, 5)\n        self.pool2 = nn.MaxPool2d(2, 2)\n        self.fc1 = nn.Linear(16 * 5 * 5, 120)\n        self.fc2 = nn.Linear(120, 84)\n        self.fc3 = nn.Linear(84, 10)\n\n    def forward(self, x):\n        x = self.pool1(F.relu(self.conv1(x)))\n        x = self.pool2(F.relu(self.conv2(x)))\n        x = x.view(-1, 16 * 5 * 5)\n        x = F.relu(self.fc1(x))\n        x = F.relu(self.fc2(x))\n        x = self.fc3(x)\n        return x\n\n    # 定义权值初始化\n    def initialize_weights(self):\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                torch.nn.init.xavier_normal_(m.weight.data)\n                if m.bias is not None:\n                    m.bias.data.zero_()\n            elif isinstance(m, nn.BatchNorm2d):\n                m.weight.data.fill_(1)\n                m.bias.data.zero_()\n            elif isinstance(m, nn.Linear):\n                torch.nn.init.normal_(m.weight.data, 0, 0.01)\n                m.bias.data.zero_()\n\n\nnet = Net()     # 创建一个网络\npretrained_dict = torch.load(os.path.join(\"..\", \"2_model\", \"net_params.pkl\"))\nnet.load_state_dict(pretrained_dict)\n\nwriter = SummaryWriter(log_dir=os.path.join(\"..\", \"..\", \"Result\", \"visual_weights\"))\nparams = net.state_dict()\nfor k, v in params.items():\n    if 'conv' in k and 'weight' in k:\n\n        c_int = v.size()[1]     # 输入层通道数\n        c_out = v.size()[0]     # 输出层通道数\n\n        # 以feature map为单位，绘制一组卷积核，一张feature map对应的卷积核个数为输入通道数\n        for j in range(c_out):\n            print(k, v.size(), j)\n            kernel_j = v[j, :, :, :].unsqueeze(1)       # 压缩维度，为make_grid制作输入\n            kernel_grid = vutils.make_grid(kernel_j, normalize=True, scale_each=True, nrow=c_int)   # 1*输入通道数, w, h\n            writer.add_image(k+'_split_in_channel', kernel_grid, global_step=j)     # j 表示feature map数\n\n        # 将一个卷积层的卷积核绘制在一起，每一行是一个feature map的卷积核\n        k_w, k_h = v.size()[-1], v.size()[-2]\n        kernel_all = v.view(-1, 1, k_w, k_h)\n        kernel_grid = vutils.make_grid(kernel_all, normalize=True, scale_each=True, nrow=c_int)  # 1*输入通道数, w, h\n        writer.add_image(k + '_all', kernel_grid, global_step=666)\nwriter.close()"
  },
  {
    "path": "Code/4_viewer/3_visual_featuremaps.py",
    "content": "# coding: utf-8\nimport os\nimport torch\nimport torchvision.utils as vutils\nimport numpy as np\nfrom tensorboardX import SummaryWriter\nimport torch.nn.functional as F\nimport torchvision.transforms as transforms\nimport sys\nsys.path.append(\"..\")\nfrom utils.utils import MyDataset, Net, normalize_invert\nfrom torch.utils.data import DataLoader\n\n\nvis_layer = 'conv1'\nlog_dir = os.path.join(\"..\", \"..\", \"Result\", \"visual_featuremaps\")\ntxt_path = os.path.join(\"..\", \"..\", \"Data\", \"visual.txt\")\npretrained_path = os.path.join(\"..\", \"..\", \"Data\", \"net_params_72p.pkl\")\n\nnet = Net()\npretrained_dict = torch.load(pretrained_path)\nnet.load_state_dict(pretrained_dict)\n\n# 数据预处理\nnormMean = [0.49139968, 0.48215827, 0.44653124]\nnormStd = [0.24703233, 0.24348505, 0.26158768]\nnormTransform = transforms.Normalize(normMean, normStd)\ntestTransform = transforms.Compose([\n    transforms.Resize((32, 32)),\n    transforms.ToTensor(),\n    normTransform\n])\n# 载入数据\ntest_data = MyDataset(txt_path=txt_path, transform=testTransform)\ntest_loader = DataLoader(dataset=test_data, batch_size=1)\nimg, label = iter(test_loader).next()\n\nx = img\nwriter = SummaryWriter(log_dir=log_dir)\nfor name, layer in net._modules.items():\n\n    # 为fc层预处理x\n    x = x.view(x.size(0), -1) if \"fc\" in name else x\n\n    # 对x执行单层运算\n    x = layer(x)\n    print(x.size())\n\n    # 由于__init__()相较于forward()缺少relu操作，需要手动增加\n    x = F.relu(x) if 'conv' in name else x\n\n    # 依据选择的层，进行记录feature maps\n    if name == vis_layer:\n        # 绘制feature maps\n        x1 = x.transpose(0, 1)  # C，B, H, W  ---> B，C, H, W\n        img_grid = vutils.make_grid(x1, normalize=True, scale_each=True, nrow=2)  # B，C, H, W\n        writer.add_image(vis_layer + '_feature_maps', img_grid, global_step=666)\n\n        # 绘制原始图像\n        img_raw = normalize_invert(img, normMean, normStd)  # 图像去标准化\n        img_raw = np.array(img_raw * 255).clip(0, 255).squeeze().astype('uint8')\n        writer.add_image('raw img', img_raw, global_step=666)  # j 表示feature map数\nwriter.close()"
  },
  {
    "path": "Code/4_viewer/4_hist_grad_weight.py",
    "content": "# coding: utf-8\n\nimport torch\nfrom torch.utils.data import DataLoader\nimport torchvision.transforms as transforms\nimport numpy as np\nimport os\nfrom torch.autograd import Variable\nimport torch.nn as nn\nimport torch.optim as optim\nimport sys\nimport os\nsys.path.append(\"..\")\nfrom utils.utils import MyDataset, validate, show_confMat, Net\nfrom tensorboardX import SummaryWriter\nfrom datetime import datetime\n\ntrain_txt_path = os.path.join(\"..\", \"..\", \"Data\", \"train.txt\")\nvalid_txt_path = os.path.join(\"..\", \"..\", \"Data\", \"valid.txt\")\n\nclasses_name = ['plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']\n\ntrain_bs = 16\nvalid_bs = 16\nlr_init = 0.001\nmax_epoch = 1\n\n# log\nlog_dir = os.path.join(\"..\", \"..\", \"Result\", \"hist_grad_weight\")\n\nwriter = SummaryWriter(log_dir=log_dir)\n\n# ------------------------------------ step 1/4 : 加载数据-------------------------------------------------\n\n# 数据预处理设置\nnormMean = [0.4948052, 0.48568845, 0.44682974]\nnormStd = [0.24580306, 0.24236229, 0.2603115]\nnormTransform = transforms.Normalize(normMean, normStd)\ntrainTransform = transforms.Compose([\n    transforms.Resize(32),\n    transforms.RandomCrop(32, padding=4),\n    transforms.ToTensor(),\n    normTransform\n])\n\nvalidTransform = transforms.Compose([\n    transforms.ToTensor(),\n    normTransform\n])\n\n# 构建MyDataset实例\ntrain_data = MyDataset(txt_path=train_txt_path, transform=trainTransform)\nvalid_data = MyDataset(txt_path=valid_txt_path, transform=validTransform)\n\n# 构建DataLoder\ntrain_loader = DataLoader(dataset=train_data, batch_size=train_bs, shuffle=True)\nvalid_loader = DataLoader(dataset=valid_data, batch_size=valid_bs)\n\n# ------------------------------------ step 2/4 : 网络初始化----------------------------------------------\n\nnet = Net()     # 创建一个网络\nnet.initialize_weights()    # 初始化权值\n\n# ------------------------------------ step 3/4 : 定义损失函数和优化器 ------------------------------------\n\ncriterion = nn.CrossEntropyLoss()                                                   # 选择损失函数\noptimizer = optim.SGD(net.parameters(), lr=lr_init, momentum=0.9, dampening=0.1)    # 选择优化器\nscheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.1)     # 设置学习率下降策略\n\n# ------------------------------------ step 4/4 : 训练 --------------------------------------------------\n\nfor epoch in range(max_epoch):\n\n    loss_sigma = 0.0    # 记录一个epoch的loss之和\n    correct = 0.0\n    total = 0.0\n    scheduler.step()  # 更新学习率\n\n    for i, data in enumerate(train_loader):\n        # 获取图片和标签\n        inputs, labels = data\n        inputs, labels = Variable(inputs), Variable(labels)\n\n        # forward, backward, update weights\n        optimizer.zero_grad()\n        outputs = net(inputs)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        # 统计预测信息\n        _, predicted = torch.max(outputs.data, 1)\n        total += labels.size(0)\n        correct += (predicted == labels).squeeze().sum().numpy()\n        loss_sigma += loss.item()\n\n        # 每10个iteration 打印一次训练信息，loss为10个iteration的平均\n        if i % 10 == 9:\n            loss_avg = loss_sigma / 10\n            loss_sigma = 0.0\n            print(\"Training: Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}\".format(\n                epoch + 1, max_epoch, i + 1, len(train_loader), loss_avg, correct / total))\n\n    # 每个epoch，记录梯度，权值\n    for name, layer in net.named_parameters():\n        writer.add_histogram(name + '_grad', layer.grad.cpu().data.numpy(), epoch)\n        writer.add_histogram(name + '_data', layer.cpu().data.numpy(), epoch)\n\nprint('Finished Training')\n"
  },
  {
    "path": "Code/4_viewer/5_Show_ConfMat.py",
    "content": "# coding: utf-8\nimport numpy as np\nimport os\nimport matplotlib.pyplot as plt\n\n\n\ndef show_confMat(confusion_mat, classes_name, set_name, out_dir):\n    \"\"\"\n    可视化混淆矩阵，保存png格式\n    :param confusion_mat: nd-array\n    :param classes_name: list,各类别名称\n    :param set_name: str, eg: 'valid', 'train'\n    :param out_dir: str, png输出的文件夹\n    :return:\n    \"\"\"\n    # 归一化\n    confusion_mat_N = confusion_mat.copy()\n    for i in range(len(classes_name)):\n        confusion_mat_N[i, :] = confusion_mat[i, :] / confusion_mat[i, :].sum()\n\n    # 获取颜色\n    cmap = plt.cm.get_cmap('Greys')  # 更多颜色: http://matplotlib.org/examples/color/colormaps_reference.html\n    plt.imshow(confusion_mat_N, cmap=cmap)\n    plt.colorbar()\n\n    # 设置文字\n    xlocations = np.array(range(len(classes_name)))\n    plt.xticks(xlocations, classes_name, rotation=60)\n    plt.yticks(xlocations, classes_name)\n    plt.xlabel('Predict label')\n    plt.ylabel('True label')\n    plt.title('Confusion_Matrix_' + set_name)\n\n    # 打印数字\n    for i in range(confusion_mat_N.shape[0]):\n        for j in range(confusion_mat_N.shape[1]):\n            plt.text(x=j, y=i, s=int(confusion_mat[i, j]), va='center', ha='center', color='red', fontsize=10)\n    # 保存\n    plt.savefig(os.path.join(out_dir, 'Confusion_Matrix_' + set_name + '.png'))\n    plt.close()\n\nif __name__ == '__main__':\n\n    print('QQ group: {} or {} or {} or {}, password: {}'.format(671103375, 773031536, 514974779, 854620826, 2018))\n\n"
  },
  {
    "path": "Code/4_viewer/6_hook_for_grad_cam.py",
    "content": "# coding: utf-8\n\"\"\"\n通过实现Grad-CAM学习module中的forward_hook和backward_hook函数\n\"\"\"\nimport cv2\nimport os\nimport numpy as np\nfrom PIL import Image\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torchvision.transforms as transforms\n\n\nclass Net(nn.Module):\n    def __init__(self):\n        super(Net, self).__init__()\n        self.conv1 = nn.Conv2d(3, 6, 5)\n        self.pool1 = nn.MaxPool2d(2, 2)\n        self.conv2 = nn.Conv2d(6, 16, 5)\n        self.pool2 = nn.MaxPool2d(2, 2)\n        self.fc1 = nn.Linear(16 * 5 * 5, 120)\n        self.fc2 = nn.Linear(120, 84)\n        self.fc3 = nn.Linear(84, 10)\n\n    def forward(self, x):\n        x = self.pool1(F.relu(self.conv1(x)))\n        x = self.pool1(F.relu(self.conv2(x)))\n        x = x.view(-1, 16 * 5 * 5)\n        x = F.relu(self.fc1(x))\n        x = F.relu(self.fc2(x))\n        x = self.fc3(x)\n        return x\n\n\ndef img_transform(img_in, transform):\n    \"\"\"\n    将img进行预处理，并转换成模型输入所需的形式—— B*C*H*W\n    :param img_roi: np.array\n    :return:\n    \"\"\"\n    img = img_in.copy()\n    img = Image.fromarray(np.uint8(img))\n    img = transform(img)\n    img = img.unsqueeze(0)    # C*H*W --> B*C*H*W\n    return img\n\n\ndef img_preprocess(img_in):\n    \"\"\"\n    读取图片，转为模型可读的形式\n    :param img_in: ndarray, [H, W, C]\n    :return: PIL.image\n    \"\"\"\n    img = img_in.copy()\n    img = cv2.resize(img,(32, 32))\n    img = img[:, :, ::-1]   # BGR --> RGB\n    transform = transforms.Compose([\n        transforms.ToTensor(),\n        transforms.Normalize([0.4948052, 0.48568845, 0.44682974], [0.24580306, 0.24236229, 0.2603115])\n    ])\n    img_input = img_transform(img, transform)\n    return img_input\n\n\ndef backward_hook(module, grad_in, grad_out):\n    grad_block.append(grad_out[0].detach())\n\n\ndef farward_hook(module, input, output):\n    fmap_block.append(output)\n\n\ndef show_cam_on_image(img, mask, out_dir):\n    heatmap = cv2.applyColorMap(np.uint8(255*mask), cv2.COLORMAP_JET)\n    heatmap = np.float32(heatmap) / 255\n    cam = heatmap + np.float32(img)\n    cam = cam / np.max(cam)\n\n    path_cam_img = os.path.join(out_dir, \"cam.jpg\")\n    path_raw_img = os.path.join(out_dir, \"raw.jpg\")\n    if not os.path.exists(out_dir):\n        os.makedirs(out_dir)\n    cv2.imwrite(path_cam_img, np.uint8(255 * cam))\n    cv2.imwrite(path_raw_img, np.uint8(255 * img))\n\n\ndef comp_class_vec(ouput_vec, index=None):\n    \"\"\"\n    计算类向量\n    :param ouput_vec: tensor\n    :param index: int，指定类别\n    :return: tensor\n    \"\"\"\n    if not index:\n        index = np.argmax(ouput_vec.cpu().data.numpy())\n    else:\n        index = np.array(index)\n    index = index[np.newaxis, np.newaxis]\n    index = torch.from_numpy(index)\n    one_hot = torch.zeros(1, 10).scatter_(1, index, 1)\n    one_hot.requires_grad = True\n    class_vec = torch.sum(one_hot * output)  # one_hot = 11.8605\n\n    return class_vec\n\n\ndef gen_cam(feature_map, grads):\n    \"\"\"\n    依据梯度和特征图，生成cam\n    :param feature_map: np.array， in [C, H, W]\n    :param grads: np.array， in [C, H, W]\n    :return: np.array, [H, W]\n    \"\"\"\n    cam = np.zeros(feature_map.shape[1:], dtype=np.float32)  # cam shape (H, W)\n\n    weights = np.mean(grads, axis=(1, 2))  #\n\n    for i, w in enumerate(weights):\n        cam += w * feature_map[i, :, :]\n\n    cam = np.maximum(cam, 0)\n    cam = cv2.resize(cam, (32, 32))\n    cam -= np.min(cam)\n    cam /= np.max(cam)\n\n    return cam\n\n\nif __name__ == '__main__':\n\n    BASE_DIR = os.path.dirname(os.path.abspath(__file__))\n    path_img = os.path.join(BASE_DIR, \"..\", \"..\", \"Data\", \"cam_img\", \"test_img_8.png\")\n    path_net = os.path.join(BASE_DIR, \"..\", \"..\", \"Data\", \"net_params_72p.pkl\")\n    output_dir = os.path.join(BASE_DIR, \"..\", \"..\", \"Result\", \"backward_hook_cam\")\n\n    classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')\n    fmap_block = list()\n    grad_block = list()\n\n    # 图片读取；网络加载\n    img = cv2.imread(path_img, 1)  # H*W*C\n    img_input = img_preprocess(img)\n    net = Net()\n    net.load_state_dict(torch.load(path_net))\n\n    # 注册hook\n    net.conv2.register_forward_hook(farward_hook)\n    net.conv2.register_backward_hook(backward_hook)\n\n    # forward\n    output = net(img_input)\n    idx = np.argmax(output.cpu().data.numpy())\n    print(\"predict: {}\".format(classes[idx]))\n\n    # backward\n    net.zero_grad()\n    class_loss = comp_class_vec(output)\n    class_loss.backward()\n\n    # 生成cam\n    grads_val = grad_block[0].cpu().data.numpy().squeeze()\n    fmap = fmap_block[0].cpu().data.numpy().squeeze()\n    cam = gen_cam(fmap, grads_val)\n\n    # 保存cam图片\n    img_show = np.float32(cv2.resize(img, (32, 32))) / 255\n    show_cam_on_image(img_show, cam, output_dir)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Code/main_training/main.py",
    "content": "# coding: utf-8\n\nimport torch\nfrom torch.utils.data import DataLoader\nimport torchvision.transforms as transforms\nimport numpy as np\nimport os\nfrom torch.autograd import Variable\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nimport sys\nsys.path.append(\"..\")\nfrom utils.utils import MyDataset, validate, show_confMat\nfrom tensorboardX import SummaryWriter\nfrom datetime import datetime\n\ntrain_txt_path = os.path.join(\"..\", \"..\", \"Data\", \"train.txt\")\nvalid_txt_path = os.path.join(\"..\", \"..\", \"Data\", \"valid.txt\")\n\nclasses_name = ['plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']\n\ntrain_bs = 16\nvalid_bs = 16\nlr_init = 0.001\nmax_epoch = 1\n\n# log\nresult_dir = os.path.join(\"..\", \"..\", \"Result\")\n\nnow_time = datetime.now()\ntime_str = datetime.strftime(now_time, '%m-%d_%H-%M-%S')\n\nlog_dir = os.path.join(result_dir, time_str)\nif not os.path.exists(log_dir):\n    os.makedirs(log_dir)\n\nwriter = SummaryWriter(log_dir=log_dir)\n\n# ------------------------------------ step 1/5 : 加载数据------------------------------------\n\n# 数据预处理设置\nnormMean = [0.4948052, 0.48568845, 0.44682974]\nnormStd = [0.24580306, 0.24236229, 0.2603115]\nnormTransform = transforms.Normalize(normMean, normStd)\ntrainTransform = transforms.Compose([\n    transforms.Resize(32),\n    transforms.RandomCrop(32, padding=4),\n    transforms.ToTensor(),\n    normTransform\n])\n\nvalidTransform = transforms.Compose([\n    transforms.ToTensor(),\n    normTransform\n])\n\n# 构建MyDataset实例\ntrain_data = MyDataset(txt_path=train_txt_path, transform=trainTransform)\nvalid_data = MyDataset(txt_path=valid_txt_path, transform=validTransform)\n\n# 构建DataLoder\ntrain_loader = DataLoader(dataset=train_data, batch_size=train_bs, shuffle=True)\nvalid_loader = DataLoader(dataset=valid_data, batch_size=valid_bs)\n\n# ------------------------------------ step 2/5 : 定义网络------------------------------------\n\n\nclass Net(nn.Module):\n    def __init__(self):\n        super(Net, self).__init__()\n        self.conv1 = nn.Conv2d(3, 6, 5)\n        self.pool1 = nn.MaxPool2d(2, 2)\n        self.conv2 = nn.Conv2d(6, 16, 5)\n        self.pool2 = nn.MaxPool2d(2, 2)\n        self.fc1 = nn.Linear(16 * 5 * 5, 120)\n        self.fc2 = nn.Linear(120, 84)\n        self.fc3 = nn.Linear(84, 10)\n\n    def forward(self, x):\n        x = self.pool1(F.relu(self.conv1(x)))\n        x = self.pool2(F.relu(self.conv2(x)))\n        x = x.view(-1, 16 * 5 * 5)\n        x = F.relu(self.fc1(x))\n        x = F.relu(self.fc2(x))\n        x = self.fc3(x)\n        return x\n\n    # 定义权值初始化\n    def initialize_weights(self):\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                torch.nn.init.xavier_normal_(m.weight.data)\n                if m.bias is not None:\n                    m.bias.data.zero_()\n            elif isinstance(m, nn.BatchNorm2d):\n                m.weight.data.fill_(1)\n                m.bias.data.zero_()\n            elif isinstance(m, nn.Linear):\n                torch.nn.init.normal_(m.weight.data, 0, 0.01)\n                m.bias.data.zero_()\n\n\nnet = Net()     # 创建一个网络\nnet.initialize_weights()    # 初始化权值\n\n# ------------------------------------ step 3/5 : 定义损失函数和优化器 ------------------------------------\n\ncriterion = nn.CrossEntropyLoss()                                                   # 选择损失函数\noptimizer = optim.SGD(net.parameters(), lr=lr_init, momentum=0.9, dampening=0.1)    # 选择优化器\nscheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.1)     # 设置学习率下降策略\n\n# ------------------------------------ step 4/5 : 训练 --------------------------------------------------\n\nfor epoch in range(max_epoch):\n\n    loss_sigma = 0.0    # 记录一个epoch的loss之和\n    correct = 0.0\n    total = 0.0\n    scheduler.step()  # 更新学习率\n\n    for i, data in enumerate(train_loader):\n        # if i == 30 : break\n        # 获取图片和标签\n        inputs, labels = data\n        inputs, labels = Variable(inputs), Variable(labels)\n\n        # forward, backward, update weights\n        optimizer.zero_grad()\n        outputs = net(inputs)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        # 统计预测信息\n        _, predicted = torch.max(outputs.data, 1)\n        total += labels.size(0)\n        correct += (predicted == labels).squeeze().sum().numpy()\n        loss_sigma += loss.item()\n\n        # 每10个iteration 打印一次训练信息，loss为10个iteration的平均\n        if i % 10 == 9:\n            loss_avg = loss_sigma / 10\n            loss_sigma = 0.0\n            print(\"Training: Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}\".format(\n                epoch + 1, max_epoch, i + 1, len(train_loader), loss_avg, correct / total))\n\n            # 记录训练loss\n            writer.add_scalars('Loss_group', {'train_loss': loss_avg}, epoch)\n            # 记录learning rate\n            writer.add_scalar('learning rate', scheduler.get_lr()[0], epoch)\n            # 记录Accuracy\n            writer.add_scalars('Accuracy_group', {'train_acc': correct / total}, epoch)\n\n    # 每个epoch，记录梯度，权值\n    for name, layer in net.named_parameters():\n        writer.add_histogram(name + '_grad', layer.grad.cpu().data.numpy(), epoch)\n        writer.add_histogram(name + '_data', layer.cpu().data.numpy(), epoch)\n\n    # ------------------------------------ 观察模型在验证集上的表现 ------------------------------------\n    if epoch % 2 == 0:\n        loss_sigma = 0.0\n        cls_num = len(classes_name)\n        conf_mat = np.zeros([cls_num, cls_num])  # 混淆矩阵\n        net.eval()\n        for i, data in enumerate(valid_loader):\n\n            # 获取图片和标签\n            images, labels = data\n            images, labels = Variable(images), Variable(labels)\n\n            # forward\n            outputs = net(images)\n            outputs.detach_()\n\n            # 计算loss\n            loss = criterion(outputs, labels)\n            loss_sigma += loss.item()\n\n            # 统计\n            _, predicted = torch.max(outputs.data, 1)\n            # labels = labels.data    # Variable --> tensor\n\n            # 统计混淆矩阵\n            for j in range(len(labels)):\n                cate_i = labels[j].numpy()\n                pre_i = predicted[j].numpy()\n                conf_mat[cate_i, pre_i] += 1.0\n\n        print('{} set Accuracy:{:.2%}'.format('Valid', conf_mat.trace() / conf_mat.sum()))\n        # 记录Loss, accuracy\n        writer.add_scalars('Loss_group', {'valid_loss': loss_sigma / len(valid_loader)}, epoch)\n        writer.add_scalars('Accuracy_group', {'valid_acc': conf_mat.trace() / conf_mat.sum()}, epoch)\nprint('Finished Training')\n\n# ------------------------------------ step5: 保存模型 并且绘制混淆矩阵图 ------------------------------------\nnet_save_path = os.path.join(log_dir, 'net_params.pkl')\ntorch.save(net.state_dict(), net_save_path)\n\nconf_mat_train, train_acc = validate(net, train_loader, 'train', classes_name)\nconf_mat_valid, valid_acc = validate(net, valid_loader, 'valid', classes_name)\n\nshow_confMat(conf_mat_train, classes_name, 'train', log_dir)\nshow_confMat(conf_mat_valid, classes_name, 'valid', log_dir)"
  },
  {
    "path": "Code/utils/__init__.py",
    "content": ""
  },
  {
    "path": "Code/utils/utils.py",
    "content": "# coding: utf-8\nfrom PIL import Image\nfrom torch.utils.data import Dataset\nimport numpy as np\nimport torch\nfrom torch.autograd import Variable\nimport os\nimport matplotlib.pyplot as plt\nimport torch.nn as nn\nimport torch.nn.functional as F\n\n\nclass Net(nn.Module):\n    def __init__(self):\n        super(Net, self).__init__()\n        self.conv1 = nn.Conv2d(3, 6, 5)\n        self.pool1 = nn.MaxPool2d(2, 2)\n        self.conv2 = nn.Conv2d(6, 16, 5)\n        self.pool2 = nn.MaxPool2d(2, 2)\n        self.fc1 = nn.Linear(16 * 5 * 5, 120)\n        self.fc2 = nn.Linear(120, 84)\n        self.fc3 = nn.Linear(84, 10)\n\n    def forward(self, x):\n        x = self.pool1(F.relu(self.conv1(x)))\n        x = self.pool2(F.relu(self.conv2(x)))\n        x = x.view(-1, 16 * 5 * 5)\n        x = F.relu(self.fc1(x))\n        x = F.relu(self.fc2(x))\n        x = self.fc3(x)\n        return x\n\n    # 定义权值初始化\n    def initialize_weights(self):\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                torch.nn.init.xavier_normal_(m.weight.data)\n                if m.bias is not None:\n                    m.bias.data.zero_()\n            elif isinstance(m, nn.BatchNorm2d):\n                m.weight.data.fill_(1)\n                m.bias.data.zero_()\n            elif isinstance(m, nn.Linear):\n                torch.nn.init.normal_(m.weight.data, 0, 0.01)\n                m.bias.data.zero_()\n\nclass MyDataset(Dataset):\n    def __init__(self, txt_path, transform = None, target_transform = None):\n        fh = open(txt_path, 'r')\n        imgs = []\n        for line in fh:\n            line = line.rstrip()\n            words = line.split()\n            imgs.append((words[0], int(words[1])))\n\n        self.imgs = imgs        # 最主要就是要生成这个list， 然后DataLoader中给index，通过getitem读取图片数据\n        self.transform = transform\n        self.target_transform = target_transform\n\n    def __getitem__(self, index):\n        fn, label = self.imgs[index]\n        img = Image.open(fn).convert('RGB')     # 像素值 0~255，在transfrom.totensor会除以255，使像素值变成 0~1\n\n        if self.transform is not None:\n            img = self.transform(img)   # 在这里做transform，转为tensor等等\n\n        return img, label\n\n    def __len__(self):\n        return len(self.imgs)\n\n\ndef validate(net, data_loader, set_name, classes_name):\n    \"\"\"\n    对一批数据进行预测，返回混淆矩阵以及Accuracy\n    :param net:\n    :param data_loader:\n    :param set_name:  eg: 'valid' 'train' 'tesst\n    :param classes_name:\n    :return:\n    \"\"\"\n    net.eval()\n    cls_num = len(classes_name)\n    conf_mat = np.zeros([cls_num, cls_num])\n\n    for data in data_loader:\n        images, labels = data\n        images = Variable(images)\n        labels = Variable(labels)\n\n        outputs = net(images)\n        outputs.detach_()\n\n        _, predicted = torch.max(outputs.data, 1)\n\n        # 统计混淆矩阵\n        for i in range(len(labels)):\n            cate_i = labels[i].numpy()\n            pre_i = predicted[i].numpy()\n            conf_mat[cate_i, pre_i] += 1.0\n\n    for i in range(cls_num):\n        print('class:{:<10}, total num:{:<6}, correct num:{:<5}  Recall: {:.2%} Precision: {:.2%}'.format(\n            classes_name[i], np.sum(conf_mat[i, :]), conf_mat[i, i], conf_mat[i, i] / (1 + np.sum(conf_mat[i, :])),\n                                                                conf_mat[i, i] / (1 + np.sum(conf_mat[:, i]))))\n\n    print('{} set Accuracy:{:.2%}'.format(set_name, np.trace(conf_mat) / np.sum(conf_mat)))\n\n    return conf_mat, '{:.2}'.format(np.trace(conf_mat) / np.sum(conf_mat))\n\n\ndef show_confMat(confusion_mat, classes, set_name, out_dir):\n\n    # 归一化\n    confusion_mat_N = confusion_mat.copy()\n    for i in range(len(classes)):\n        confusion_mat_N[i, :] = confusion_mat[i, :] / confusion_mat[i, :].sum()\n\n    # 获取颜色\n    cmap = plt.cm.get_cmap('Greys')  # 更多颜色: http://matplotlib.org/examples/color/colormaps_reference.html\n    plt.imshow(confusion_mat_N, cmap=cmap)\n    plt.colorbar()\n\n    # 设置文字\n    xlocations = np.array(range(len(classes)))\n    plt.xticks(xlocations, list(classes), rotation=60)\n    plt.yticks(xlocations, list(classes))\n    plt.xlabel('Predict label')\n    plt.ylabel('True label')\n    plt.title('Confusion_Matrix_' + set_name)\n\n    # 打印数字\n    for i in range(confusion_mat_N.shape[0]):\n        for j in range(confusion_mat_N.shape[1]):\n            plt.text(x=j, y=i, s=int(confusion_mat[i, j]), va='center', ha='center', color='red', fontsize=10)\n    # 保存\n    plt.savefig(os.path.join(out_dir, 'Confusion_Matrix' + set_name + '.png'))\n    plt.close()\n\n\ndef normalize_invert(tensor, mean, std):\n    for t, m, s in zip(tensor, mean, std):\n        t.mul_(s).add_(m)\n    return tensor\n"
  },
  {
    "path": "Data/visual.txt",
    "content": "../../Data/cat.png 3"
  },
  {
    "path": "readme.md",
    "content": "﻿# Pytorch模型训练实用教程\n<img src=\"./Data/cover.png\" alt=\"Image text\" style=\"zoom:33%;\" />\n\n---\n\n📢：《PyTorch实用教程》（第二版）已开源，欢迎阅读：https://tingsongyu.github.io/PyTorch-Tutorial-2nd/\n\n📢：《PyTorch实用教程》（第二版）已开源，欢迎阅读：https://tingsongyu.github.io/PyTorch-Tutorial-2nd/\n\n📢：《PyTorch实用教程》（第二版）已开源，欢迎阅读：https://tingsongyu.github.io/PyTorch-Tutorial-2nd/\n\n第二版新增丰富的**深度学习应用案例**和**推理部署框架**，包括CV、NLP和LLM的十多个实战项目，以及ONNX和TensorRT的教程。\n\n# 1.简介\n\n本代码为教程——《Pytorch模型训练实用教程》中配套代码；<br/>\n《Pytorch模型训练实用教程》可通过如下方式获取：<br/>\n\n1. https://github.com/tensor-yu/PyTorch_Tutorial/tree/master/Data<br/>\n2. QQ群： 四群：854620826  <br/>\n\n\n# 2.环境配置\n代码在以下两种环境测试过：<br/>\n1. win10 64位 + python3.5 + pytorch==0.4.0 <br/>\n2. mac + python3.6 + pytorch==0.4.1/ pytorch==1.0.0 <br/>\n\n**第一步 安装各依赖包：**<br/>\npip install -r requirements.txt\n\n**第二步 手动安装pytorch及torchvision：**<br/>\n均选择无gpu版本进行安装，进入官网选择相应的指令进行安装\nhttps://pytorch.org/get-started/locally/\n\n\n# 3.问题反馈\n若发现任何问题和改进意见，请您随时联系我。<br/>\n联系方式：yts3221@126.com<br/>\n读者qq群：\n\n​\t一群：671103375 (已满)  <br/>\n\n​\t二群：773031536 (已满）<br/>\n\n​    三群：514974779 (已满）<br/>\n\n​    四群：854620826(已满)\n\n​    五群：1021300804\n\n# 4.修改记录\n0.0.5：\n1. 1.6小节勘误，将36\\*36改为40\\*40；\n2. 2.3小节删除注释；\n3. 修改权值初始化杂谈中的理解错误；\n4. 全文代码缩进。\n\n---\n\n如果本教程对你有帮助😀😀，请作者喝杯茶吧🍵🍵🥂🥂\n\nWeChat：<img src=\"https://img.picgo.net/2024/05/18/wechat2859fc4f155e9302.jpg\" style=\"width:20%;\"/>               Alipay:<img src=\"https://img.picgo.net/2024/05/18/alipayd96d6d0a325ef7e0.jpg\" style=\"width:20%;\"/>\n\n\n\n\n\n---\n\n\n## Stargazers over time\n\n[![Stargazers over time](https://starchart.cc/TingsongYu/PyTorch_Tutorial.svg)](https://starchart.cc/TingsongYu/PyTorch_Tutorial)\n"
  },
  {
    "path": "requirements.txt",
    "content": "absl-py==0.6.1\nastor==0.7.1\ncycler==0.10.0\ngast==0.2.0\ngrpcio==1.16.0\nh5py==2.8.0\nKeras-Applications==1.0.6\nKeras-Preprocessing==1.0.5\nkiwisolver==1.0.1\nMarkdown==3.0.1\nmatplotlib==2.2.3\nnumpy==1.15.1\nopencv-python==3.4.3.18\nPillow==5.2.0\nprotobuf==3.6.1\npyparsing==2.2.0\npython-dateutil==2.7.3\npytz==2018.5\nscipy==1.1.0\nsix==1.11.0\ntensorboard==1.12.0\ntensorboardX==1.4\ntensorflow==1.12.0\ntermcolor==1.1.0\nWerkzeug==0.14.1\n"
  }
]