赞
踩
接着上次的笔记,在原本自回归模型基础上加入了深度学习,即由神经网络参数化的自回归模型,算是真正意义上的“深度”生成模型了。从使用MLP建模的有限记忆自回归到使用LSTM和RNN的长距记忆自回归,最后再到作者书中提到的借用因果卷积,解决卷积神经网络面对长距依赖关系的短板,从而构建的基于卷积神经网络的自回归模型。
今天这篇笔记,开始学习和研究作者对于自回归模型实践部分的内容(作者代码)。之前都是既麻烦GPT老师帮我解释理论问题又麻烦她帮我写代码,今天咱就不麻烦GPT老师写代码了,今天麻烦她帮我读代码~~~~
下面是作者定义一维卷积的代码:
class CausalConv1d(nn.Module): """ A causal 1D convolution. """ def __init__(self, in_channels, out_channels, kernel_size, dilation, A=False, **kwargs): super(CausalConv1d, self).__init__() # attributes: self.kernel_size = kernel_size self.dilation = dilation self.A = A self.padding = (kernel_size - 1) * dilation + A * 1 # module: self.conv1d = torch.nn.Conv1d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=dilation, **kwargs) def forward(self, x): x = torch.nn.functional.pad(x, (self.padding, 0)) conv1d_out = self.conv1d(x) if self.A: return conv1d_out[:, :, : -1] else: return conv1d_out
把代码拿给GPT老师看了一眼,老师立马给出了详细解释:
“
这段代码实现了一个因果卷积(Causal Convolution)的自回归模型。下面逐行解释代码的作用:
class CausalConv1d(nn.Module)::定义了一个继承自nn.Module的类CausalConv1d,表示一个因果卷积的一维模型。
def init(self, in_channels, out_channels, kernel_size, dilation, A=False, **kwargs)::构造函数,初始化因果卷积模型的参数。
super(CausalConv1d, self).init()::调用父类的构造函数。
self.kernel_size = kernel_size:将传入的卷积核大小保存为对象的属性kernel_size。
self.dilation = dilation:将传入的扩张率保存为对象的属性dilation。
self.A = A:将传入的布尔值保存为对象的属性A,用于控制是否使用“A型”因果卷积。
self.padding = (kernel_size - 1) * dilation + A * 1:计算因果卷积的padding大小。
x = torch.nn.functional.pad(x, (self.padding, 0)):对输入进行填充,实现因果卷积。
conv1d_out = self.conv1d(x):将填充后的输入通过卷积层进行卷积操作,得到卷积输出。
if self.A::当使用“A型”因果卷积时。
else::当不使用“A型”因果卷积时。
这段代码实现了一个因果卷积模型,其特点是能够处理时序数据,并且在卷积过程中保持了因果性,即输出的每个元素仅依赖于输入序列中它之前的元素。这对于许多时序数据任务,如语音处理或时间序列预测,非常有用。
”
这详细程度,初学者也能轻松看懂代码了。令我意外的是,GPT老师竟然给出了参数A的正确释义,表示是否使用“A型”因果卷积,在上一节的笔记中我并没有解释“A型”和“B型”两种类型的因果卷积,不过我有在最后让GPT老师解释因果卷积,“它只考虑过去时刻的输入数据对当前时刻的输出有影响,而不考虑未来时刻的输入”,这里其实还并不完整,作者在书中有提到,因果卷积意味着Conv1D只可以依赖最后的k个输入,而“A型”:可以不含有当下的输入,“B型”:也可以含有当下的输入。
不过,GPT老师对于return conv1d_out[:, :, :-1]的说明没有像作者的那样说明由于是“A型”卷积,所以不可以依赖当前部分所以才删除最后一个元素。
继续往下,开始为后面训练做准备了,下面是一段用于计算对数概率和分类的代码:
EPS = 1.e-5
def log_categorical(x, p, num_classes=256, reduction=None, dim=None):
x_one_hot = F.one_hot(x.long(), num_classes=num_classes)
log_p = x_one_hot * torch.log(torch.clamp(p, EPS, 1. - EPS))
if reduction == 'avg':
return torch.mean(log_p, dim)
elif reduction == 'sum':
return torch.sum(log_p, dim)
else:
return log_p
GPT老师:
“
这段代码定义了一个用于计算对数分类交叉熵(log categorical cross entropy)的函数log_categorical。下面逐行解释代码的作用:
EPS = 1.e-5:定义一个小的常量EPS,用于避免取对数时出现除零错误。
def log_categorical(x, p, num_classes=256, reduction=None, dim=None)::定义一个函数log_categorical,用于计算对数分类交叉熵。
x_one_hot = F.one_hot(x.long(), num_classes=num_classes):将真实标签x转换为one-hot编码,得到一个维度为[batch_size, num_classes]的独热编码张量x_one_hot。
log_p = x_one_hot * torch.log(torch.clamp(p, EPS, 1. - EPS)):计算对数概率。首先使用torch.clamp函数将概率p限制在一个较小的范围内,避免对0或1取对数导致的错误。然后将每个one-hot编码的位置上的概率与对应的对数概率相乘,得到一个维度与x_one_hot相同的张量log_p。
if reduction == ‘avg’::如果指定了reduction为’avg’,表示对损失进行平均降维。
这段代码实现了对数分类交叉熵的计算,将模型的预测概率与真实标签进行比较,得到模型的损失。通过设置reduction参数,可以选择是否对损失进行降维,以便在整个批次或样本上计算平均损失或总损失。
”
作者选择在因果一维卷积后面定义自回归模型,大概是认为本章重点还是强调一个“深度”其次才是自回归模型,毕竟生成模型还是要建立在深度学习基础上才能最大发挥其威力。下面是定义自回归模型代码:
class ARM(nn.Module): def __init__(self, net, D=2, num_vals=256): super(ARM, self).__init__() print('ARM by JT.') self.net = net self.num_vals = num_vals self.D = D def f(self, x): h = self.net(x.unsqueeze(1)) h = h.permute(0, 2, 1) p = torch.softmax(h, 2) return p def forward(self, x, reduction='avg'): if reduction == 'avg': return -(self.log_prob(x).mean()) elif reduction == 'sum': return -(self.log_prob(x).sum()) else: raise ValueError('reduction could be either `avg` or `sum`.') def log_prob(self, x): mu_d = self.f(x) log_p = log_categorical(x, mu_d, num_classes=self.num_vals, reduction='sum', dim=-1).sum(-1) return log_p def sample(self, batch_size): x_new = torch.zeros((batch_size, self.D)) for d in range(self.D): p = self.f(x_new) x_new_d = torch.multinomial(p[:, d, :], num_samples=1) x_new[:, d] = x_new_d[:,0] return x_new
GPT老师:
“
这段代码定义了一个自回归模型(Autoregressive Model),用于生成具有时间序列结构的数据。下面逐行解释代码的作用:
sample()函数相当于先定义了一个全部为0的张量:x_new = torch.zeros((batch_size, self.D))
然后for循环中迭代的为像素抽样赋值
这里,由于作者的训练数据是图像,所以num_vals表示的是每一个像素所能容纳的值的数量,
因为不是很理解h = h.permute(0, 2, 1)的具体作用,所以又向GPT老师请教了一下:
“
当使用h.permute(0, 2, 1)时,它会对张量h的维度进行置换。这里的参数(0, 2, 1)指定了置换后的维度顺序,它们的含义如下:
下面要重重敲黑板啦,代码中log_prob(self, x)是这节中的重点。
还记得前面定义log_categorical()的函数吗?作者在书中提到:“因为表达图像的像素值是整数,我们会用类别分布来表达(下一章我们会具体讲到表达图像分布的几种不同方式)。我们使用CausalConv1D参数化的自回归模型ARM来为p(x)建模。所得结果用概率表达如下:”
p ( x d ∣ x < d ) = C a t e g o r i c a l ( x d ∣ θ d ( x < d ) ) = ∏ l = 1 L ( ( θ d , l ) [ x d = l ] ) p(x_{d}|x_{<d})=Categorical(x_{d}| \theta_{d}(x_{<d}))=\prod_{l=1}^{L}((\theta_{d,l})^{[x_{d}=l]}) p(xd∣x<d)=Categorical(xd∣θd(x<d))=l=1∏L((θd,l)[xd=l])
其中: [ x d = l ] [x_{d}=l] [xd=l]是艾弗森括号,当 x d = l x_{d}=l xd=l时,括号的值等于1,反之为0。
作者书中没有具体写明Categorial()具体解法是什么,那我们还是只有看代码来具体了解了。
“可以详细说下log_categorical的数学表达和数学含义吗?”
GPT老师:
“
当解释 log_categorical 函数的数学表达和数学含义时,首先需要了解分类交叉熵和对数运算。
分类交叉熵是一种常用的损失函数,用于衡量模型的预测结果与真实标签之间的差异。它基于概率分布的概念,用于度量两个概率分布之间的距离。
假设有一个分类问题,包含多个类别(例如图像分类问题中的不同物体类别)。对于每个样本,我们有一个真实标签(一个整数,表示样本所属的类别)和一个模型的预测概率分布(一个向量,表示模型对每个类别的预测概率)。
对于一个样本,log_categorical 函数的数学表达可以描述为:
log_categorical(x, p, num_classes, reduction, dim) = log(p(x))
这里的 p(x) 是模型预测概率向量 p 中与真实标签 x 对应位置的概率。函数的目标是计算对数概率 log(p(x))。
在代码中,我们首先将真实标签 x 转换为一个 one-hot 编码向量 x_one_hot,其中真实标签对应的位置为 1,其他位置为 0。然后,我们将这个向量与预测概率向量 p 中对应位置的概率相乘,得到一个新的向量 log_p。
在此过程中,我们使用对数运算(torch.log)来计算对数概率,并使用 torch.clamp 函数将概率 p 限制在一个小的范围内,避免出现取对数时的错误。
对于降维参数 reduction 和 dim,可以根据需要选择不同的方式对损失进行降维。如果 reduction 设置为 ‘avg’,则在 dim 维度上计算 log_p 的均值。如果 reduction 设置为 ‘sum’,则在 dim 维度上计算 log_p 的总和。如果没有设置 reduction,则返回与输入维度相同的 log_p。
总的来说,log_categorical 函数的数学含义是计算模型的预测概率与真实标签之间的对数概率,并根据需要对损失进行降维。这可以帮助我们度量模型的预测结果与真实标签之间的差异,并用作优化模型参数的依据。
希望这样的解释能够更清晰地说明函数的数学表达和数学含义。
”
感觉还是不太明白,发现上面ARM定义的代码中log_p的定义函数中调用了log_categorical函数,再问问GPT老师:“可以结合之前log_categorical的定义代码,具体解释上面log_p吗?给出详细的推导公式和推导过程”
GPT老师:
“
当我们调用log_prob(self, x)方法时,其中的log_p变量通过调用log_categorical()函数计算而来。下面将详细解释log_p的推导过程:
有了上述建模的过程,我们可以给上数据开始训练和测试,作者写了训练、评估和画图取样的辅助函数,这里不展开说,具体可参考作者代码。
这里我比较感兴趣的是卷积网络的构造:
先是两个超参数:
D = 64 ——输入维度
M = 256 ——“尺度和平移网络中神经元个数”,嗯~,不是太理解,不过下面代码M是神经网络的输入输出通道数,大概了解了。
代码长这样:
likelihood_type = 'categorical' num_vals = 17 kernel = 7 net = nn.Sequential( CausalConv1d(in_channels=1, out_channels=M, dilation=1, kernel_size=kernel, A=True, bias=True), nn.LeakyReLU(), CausalConv1d(in_channels=M, out_channels=M, dilation=1, kernel_size=kernel, A=False, bias=True), nn.LeakyReLU(), CausalConv1d(in_channels=M, out_channels=M, dilation=1, kernel_size=kernel, A=False, bias=True), nn.LeakyReLU(), CausalConv1d(in_channels=M, out_channels=num_vals, dilation=1, kernel_size=kernel, A=False, bias=True)) model = ARM(net, D=D, num_vals=num_vals)
作者在这里也贴心的为我们绘制了基本的结构图:
print(summary(model, torch.zeros(1, 64), show_input=False, show_hierarchical=False))
-----------------------------------------------------------------------
Layer (type) Output Shape Param # Tr. Param #
=======================================================================
CausalConv1d-1 [1, 256, 64] 2,048 2,048
LeakyReLU-2 [1, 256, 64] 0 0
CausalConv1d-3 [1, 256, 64] 459,008 459,008
LeakyReLU-4 [1, 256, 64] 0 0
CausalConv1d-5 [1, 256, 64] 459,008 459,008
LeakyReLU-6 [1, 256, 64] 0 0
CausalConv1d-7 [1, 17, 64] 30,481 30,481
=======================================================================
Total params: 950,545
Trainable params: 950,545
Non-trainable params: 0
-----------------------------------------------------------------------
summary函数是调用了pytorch_model_summary库。
这里再一次上书上的图,方便大家对比着看:
好吧,能力有限,这节的笔记就到这里,作者在书的后面还加上了掩码卷积核的应用。解决一维卷积在2D空间上有空间依赖性的问题。具体就不在这里说了。。。
感谢大家能耐心看到这里,下一节终于进入第三章,也是深度生成模型的第二个大模型——流模型。大家如果感兴趣可以继续关注,感谢大家支持。我抓紧学习。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。