复现DDH论文遇到的问题记录


论文地址:Discriminative Deep Hashing for Scalable Face Image Retrieval(DDH), IJCAI 2017

Github地址:GitHub - 1113609151/pytorch_DDH

1. torch.nn.CrossEntropyLoss的使用

  • torch.nn.CrossEntropyLoss(input, target)中的标签target既可以使用one-hot形式,也可以是类别的序号。形如 target = [1, 3, 2]或者target = [[1, 0, 0], [0, 0, 1], [0, 1, 0]]。表示3个样本分别属于第1类、第3类、第2类。不过官方建议以类别作为输入,性能会更高。官方示例代码如下。
>>> # Example of target with class indices
>>> loss = nn.CrossEntropyLoss()
>>> input = torch.randn(3, 5, requires_grad=True)
>>> target = torch.empty(3, dtype=torch.long).random_(5)
>>> output = loss(input, target)
>>> output.backward()
>>>
>>> # Example of target with class probabilities
>>> input = torch.randn(3, 5, requires_grad=True)
>>> target = torch.randn(3, 5).softmax(dim=1)
>>> output = loss(input, target)
>>> output.backward()
  • torch.nn.CrossEntropyLoss(input, target)input是没有归一化的每个类的得分,而不是softmax之后的分布。softmax过程在模块内计算,无需在外部计算。

2. Pytorch版的Locally connected layers的实现

3. 记录numpy相关函数的使用

  • np.vstack([List]), np.hstack([List]),np.concatenate([List], axis=)
  • np.vstack():垂直堆叠(vertical stack)函数。它将多个数组按垂直方向堆叠在一起,即沿着行的方向进行堆叠。这意味着它将数组按行连接起来,生成一个新的数组。例如,假设有两个数组array1array2,它们的形状都是(m,n),其中m表示行数,n表示列数。使用np.vstack()函数将它们垂直堆叠在一起,将生成一个新的数组,形状为(2m,n),即行数变为原来的两倍。等价于np.concatenate((array1,array2), axis=0)
  • np.hstack():水平堆叠(horizontal stack)函数。它将多个数组按水平方向堆叠在一起,即沿着列的方向进行堆叠。这意味着它将数组按列连接起来,生成一个新的数组。例如,假设有两个数组array1array2,它们的形状都是(m, n),其中m表示行数,n表示列数。使用np.hstack()函数将它们水平堆叠在一起,将生成一个新的数组,形状为(m, 2n),即列数变为原来的两倍。等价于np.concatenate((array1,array2), axis=1)
#在项目中的运用
datas  = []
labels = []
for file_name in file_names:
    with open(file_name, 'rb') as f:
        x, y = pickle.load(f, encoding='latin1')
    datas.append(x)
    labels.append(y)
data_array = np.vstack(datas)
label_array = np.hstack(labels)

4. 关于torchvision.transforms

  • 在使用torchvision.transforms对图像进行transform时,要将图片的维度调整为[Height, Width, Channels]。若使用PIL读取图片,需要将图片convert to RGB。ToTensor()shape(H, W, C)nump.ndarrayimg转为shape(C, H, W)tensor,其将每一个数值归一化到[0,1],其归一化方法比较简单,直接除以255即可。

5. 创建和读取checkpoints

torch.save(model.state_dict(), WEIGHTS_SAVE_PATH + WEIGHTS_FILE_NAME)
model.load_state_dict(torch.load(WEIGHTS_SAVE_PATH + WEIGHTS_FILE_NAME))

6. CrossEntropyLoss基本不下降

经过200个epcho的训练,CrossEntropyLoss从7.43下降为7.37,基本没有变化。损失函数的公式如下所示。代码中分别取为0.0001,为0.01。其中经过训练,模型参数损失由于量级较小,变化不大;01损失由53.75下降为1.6105,变化明显。
min ⁡ ∑ i = 1 N ∑ j = 1 M − Y i j log ⁡ e w j T b i ∑ k = 1 M e w k T b i + α 2 ( ∥ W ∥ F 2 + Ω ) + β ∑ i = 1 N ∥ ∣ b i ∣ − 1 ∥ 1 \begin{aligned}\min&\sum_{i=1}^N\sum_{j=1}^M-Y_{ij}\log\frac{e^{\mathbf{w}_j^T\mathbf{b}_i}}{\sum_{k=1}^Me^{\mathbf{w}_k^T\mathbf{b}_i}}+\frac{\alpha}{2}(\|\mathbf{W}\|_F^2+\Omega)+\beta\sum_{i=1}^N\||\mathbf{b}_i|-\mathbf{1}\|_1\end{aligned} mini=1Nj=1MYijlogk=1MewkTbiewjTbi+2α(WF2+Ω)+βi=1N∥∣bi11
经过训练,模型的输出哈希码大多分布在-1与1之中,效果良好;但CrossEntropyLoss变化不大,导致模型无法分辨人像所属类别。因此暂且仅保留CrossEntropyLoss进行训练,找出问题所在。在仅保留CrossEntropyLoss的情况下,经过训练200个epcho,CrossEntropyLoss依旧由7.43下降为7.36,下降幅度依旧不大,证明该问题与另外两个Loss之间的关系不大。

  1. 由于torch.nn.CrossEntropyLoss(input,target)自带softmax操作,如果model内有softmax层则会导致进行了双层softmax,使梯度无法传达。经检查不存在此问题。
  2. 检查torch.nn.CrossEntropyLoss(input,target)前的语句。
for batch_idx, (data, target) in enumerate(train_loader):
	data, target = data.to(device), target.to(device)
	optimizer.zero_grad()
	output, prob = model(data)
	label = torch.argmax(target, dim=1)
    loss_cel = nn.CrossEntropyLoss()(prob, label)

​其中,output(size=[batch, hash_num])为输出的长度为hash_num的哈希码,prob(size=[batch, class_num])为长度为class_num的向量,对应每个种类的概率。由于target(size=[batch class_num])one-hot类型,在计算前需要转化为label(size = [batch,])形式。经检验,转换后的label与原始label值相同,因此不存在问题。

data_array, label_array = load_data_xy(get_files(TRAIN_SET_PATH))
dataset = DDH_train_dataset(TRAIN_SET_PATH)
dataloader = DataLoader(dataset, batch_size=32, shuffle=False, num_workers=0)
data, target = next(iter(dataloader))
labels = torch.argmax(target, dim=1)
true_label = label_array[:32]

print(torch.from_numpy(true_label) == labels)
#tensor([True, True, True, True, True, True, True, True, True, True, True, True,
        #True, True, True, True, True, True, True, True, True, True, True, True,
        #True, True, True, True, True, True, True, True])
  1. 检查模型架构,发现问题!!!
    在模型代码中,有些网络并没有在__init__层被定义,而是直接在forward函数中创建并进行使用,如下所示。代码块中,nn.Linear(x.shape[1], self.hash_num * self.split_num)nn.Linear(slice_array.shape[1], 1)均为原地创建并使用,此处出现了问题
def forward(self, x):
		...
        x = nn.Linear(x.shape[1], self.hash_num * self.split_num).cuda()(x)
        x = self.C5_block(x)

        outs = []
        for i in range(self.hash_num):
            slice_array = x[:, i * self.split_num : (i + 1) * self.split_num]
            fuse_layer = nn.Linear(slice_array.shape[1], 1).cuda()
            outs.append(fuse_layer(slice_array))

		...
        return x, prob

测试代码如下。输出为空,表示model没有可优化的参数。说明在forward层中创建的layer不会参与优化过程!!

class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()

    def forward(self, x):
        x = nn.Linear(10, 5)(x)  # 在forward方法中创建的网络层
        return x

model = MyModel()
for name, param in model.named_parameters():
    if param.requires_grad:
        print(name, param.data)

#输出为空

修改后,代码如下,问题解决!

        self.module_list = nn.ModuleList([])
        for _ in range(self.hash_num):
            self.module_list.append(nn.Linear(self.split_num, 1))

7.关于model.eval()

model.eval()主要是针对model 在训练时和评价时不同的 Batch NormalizationDropout 方法模式。
如果模型中有 Batch Normalization 层和 Dropout层,需要在训练时添加 model.train(),在测试时添加 model.eval()
model.eval() 时,pytorch 会自动把 BN 和 DropOut 固定住,不会取平均,而是用训练好的值。不然的话,一旦 test 的 batch_size 过小,很容易就会被 BN 层导致生成图片颜色失真极大。model.eval() 在非训练的时候是需要加的,没有这句代码,一些网络层的值会发生变动,不会固定,你神经网络每一次生成的结果也是不固定的,生成质量可能好也可能不好。

8.关于logging

import logging
import os
import time
import utils.config as cf

def getLogger():
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)  # Log等级总开关
    formatter = logging.Formatter(fmt="[%(asctime)s|%(filename)s|%(levelname)s] %(message)s",
                                  datefmt="%a %b %d %H:%M:%S %Y")
    # StreamHandler
    sHandler = logging.StreamHandler()
    sHandler.setFormatter(formatter)
    logger.addHandler(sHandler)

    # FileHandler
    work_dir = os.path.join(cf.train_log,
                            time.strftime("%Y-%m-%d-%H.%M", time.localtime()))  # 日志文件写入目录
    if not os.path.exists(work_dir):
        os.makedirs(work_dir)
    fHandler = logging.FileHandler(work_dir + '/log.txt', mode='w')
    fHandler.setLevel(logging.DEBUG)  # 输出到file的log等级的开关
    fHandler.setFormatter(formatter)  # 定义handler的输出格式
    logger.addHandler(fHandler)  # 将logger添加到handler里面

    return logger

logger = get_logger()
logger.info(f'los = {}')
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值