14、混合建模:结合生成模型与分类器的新方法

混合建模:结合生成模型与分类器的新方法

1. 引言

在机器学习中,仅学习条件分布 $p(y|x)$ 是不够的,我们应关注联合分布 $p(x, y)$,其可分解为 $p(x, y) = p(y|x)p(x)$。这是因为条件分布 $p(y|x)$ 无法让我们了解 $x$ 的信息,它只是尽力做出决策。例如,即使给出一个从未观测过的对象,$p(y|x)$ 仍可能对其分类有很高的确定性。而训练好 $p(x)$ 后,理论上我们能评估给定对象的概率,进而判断决策是否可靠。

之前我们主要探讨了如何单独学习 $p(x)$,涉及基于似然的模型,如自回归模型(ARMs)、基于流的模型(flows)和变分自编码器(VAEs)。现在的问题是如何将深度生成模型与分类器(或回归器)结合使用,下面我们以分类任务为例,探讨可能的方法。

1.1 方法一:简单独立训练

我们可以分别训练 $p(y|x)$ 和 $p(x)$,这样就得到了一个分类器和对象的边缘分布。这种方法用不同颜色的神经网络分别参数化两个分布,如图 1 所示。

对联合分布取对数可得:
$\ln p(x, y) = \ln p_{\alpha}(y|x) + \ln p_{\beta}(x)$
其中 $\alpha$ 和 $\beta$ 表示两个分布的参数化(即神经网络)。训练时计算关于 $\alpha$ 和 $\beta$ 的梯度:
$\nabla_{\alpha} \ln p(x, y) = \nabla_{\alpha} \ln p_{\alpha}(y|x) + \nabla_{\alpha} \ln p_{\beta}(x) = \nabla_{\alpha} \ln p_{\alpha}(y|x)$
$\nabla_{\beta} \ln p(x, y) = \nabla_{\beta} \ln p_{\alpha}(y|x) + \nabla_{\beta} \ln p_{\beta}(x) = \nabla_{\beta} \ln p_{\beta}(x)$
这意味着我们可以先用带标签的数据训练 $p_{\alpha}(y|x)$,再用所有可用数据训练 $p_{\beta}(x)$。

然而,这种方法存在潜在问题。首先,不能保证两个分布对 $x$ 的处理方式一致,可能引入误差。其次,由于训练的随机性,随机变量 $x$ 和 $y$ 之间没有信息流动,神经网络会各自寻找局部最小值,就像鸟的两个翅膀完全独立、异步运动。此外,分别训练两个模型效率低下,需要使用两个不同的神经网络,且没有权重共享。虽然这种方法可能有效,但可能得到远非最优的模型。

1.2 方法二:共享参数化

第二种方法采用部分共享参数化,即有一个神经网络处理 $x$,其输出分别输入到分类器和 $x$ 的边缘分布的神经网络中,如图 2 所示。

此时联合分布的对数为:
$\ln p(x, y) = \ln p_{\alpha, \gamma}(y|x) + \ln p_{\beta, \gamma}(x)$
两个分布部分共享参数化 $\gamma$。训练时,$x$ 和 $y$ 之间有明显的信息共享,两个分布以相同方式处理经过处理的 $x$,然后将该表示专门用于给出类别和对象的概率。

这种方法有两个优点。一是两个分布紧密相连,就像鸟的两个翅膀可以同步运动。二是从优化角度看,梯度通过 $\gamma$ 网络流动,该网络包含 $x$ 和 $y$ 的信息,有助于找到更好的解决方案。

2. 混合建模

乍一看,使用 $\ln p(x, y) = \ln p_{\alpha, \gamma}(y|x) + \ln p_{\beta, \gamma}(x)$ 作为训练目标似乎没有问题。但考虑 $y$ 和 $x$ 的维度,若 $y$ 是二进制的,只有一个比特表示类别标签,而二进制向量 $x$ 有 $D$ 个比特,两者尺度存在明显差异。

以二进制变量为例,假设神经网络返回的所有概率都为 0.5,对于独立的伯努利变量:
$\ln Bern(y|0.5) = y \ln 0.5 + (1 - y) \ln 0.5 = -\ln 2$
$\ln \prod_{d=1}^{D} Bern(x_d|0.5) = \sum_{d=1}^{D} \ln Bern(x_d|0.5) = -D \ln 2$
可以看出,$\ln p_{\beta, \gamma}(x)$ 部分比 $\ln p_{\alpha, \gamma}(y|x)$ 部分强 $D$ 倍。训练时,$\gamma$ 网络会从边缘分布获得更多信息,这可能会削弱分类部分,导致最终模型偏向边缘部分。

为解决这个问题,有两种方法:
- 方法一 :考虑 $\ln p(y|x)$ 和 $\ln p(x)$ 的凸组合作为目标函数:
$L(x, y; \lambda) = (1 - \lambda) \ln p(y|x) + \lambda \ln p(x)$
其中 $\lambda \in [0, 1]$。但这种加权方案并非基于定义良好的分布,破坏了基于似然方法的优雅性。
- 方法二 :只对 $\ln p(x)$ 进行加权:
$\ell(x, y; \lambda) = \ln p(y|x) + \lambda \ln p(x)$
其中 $\lambda \geq 0$。虽然 $\lambda$ 不是从概率角度推导出来的,但可以解释为鼓励对输入变化的鲁棒性,也可看作基于雅可比矩阵的正则化惩罚。

在这种方法中,用基于流的模型建模 $p(x)$,并将可逆神经网络(如耦合层)与分类器共享,最终层用于做出决策 $y$,目标函数为 $\ell(x, y; \lambda)$。这种方法有以下优点:
- 可逆神经网络可用于模型的生成和判别部分,使基于流的模型能获取标签信息。
- 加权因子 $\lambda$ 可控制模型更偏向判别还是生成。
- 可以使用任何基于流的模型。
- 可以使用任何分类器(或回归器)。

但这种方法的缺点是需要确定 $\lambda$,这是一个需要调整的额外超参数,且 $\lambda$ 的值会显著改变模型的性能。

3. 实现混合模型

3.1 分类器建模

我们使用全连接神经网络来建模条件分布 $p(y|x)$:
$z \to Linear(D, M) \to ReLU \to Linear(M, M) \to ReLU \to Linear(M, K) \to Softmax$
其中 $D$ 是 $x$ 的维度,$K$ 是类别的数量。对于分类任务,使用分类分布:
$p(y|x) = \prod_{k=1}^{K} \theta_k(x)^{[y=k]}$
其中 $\theta_k(x)$ 是第 $k$ 类的 softmax 值,$[y = k]$ 是艾弗森括号。

3.2 $p(x)$ 建模

我们可以使用任何边缘模型,如基于流的模型和变量变换公式:
$p(x) = \pi(z = f^{-1}(x)) |J_f(x)|^{-1}$
其中 $J_f(x)$ 是变换 $f$ 在 $x$ 处的雅可比矩阵,通常 $\pi(z) = N(z|0, 1)$,即标准高斯分布。

将这些分布代入混合建模的目标函数 $\ell(x, y; \lambda)$ 可得:
$\ell(x, y; \lambda) = \sum_{k=1}^{K} [y = k] \ln \theta_{k, g, f}(x) + \lambda N(z = f^{-1}(x)|0, 1) - \ln |J_f(x)|$
其中 $\theta_{k, g, f}$ 由流的神经网络 $f$ 和最终分类的神经网络 $g$ 参数化。

3.3 使用整数离散流(IDFs)

为了更具创新性,我们使用整数离散流(IDFs)来建模 $p(x)$。IDFs 操作于整数,且不需要计算雅可比矩阵。对于 IDFs,我们选择离散化的逻辑分布 $\pi(z) = DL(z|\mu, \nu)$,则混合建模的目标函数可重写为:
$\ell(x, y; \lambda) = \sum_{k=1}^{K} [y = k] \ln \theta_{k, g, f}(x) + \lambda DL(z = f^{-1}(x)|\mu, \nu)$

4. 代码实现

以下是实现混合整数离散流(HybridIDF)的代码:

import torch
import torch.nn as nn

# 假设 RoundStraightThrough 已定义
class RoundStraightThrough(torch.autograd.Function):
    @staticmethod
    def forward(ctx, x):
        return x.round()

    @staticmethod
    def backward(ctx, grad_output):
        return grad_output

class HybridIDF(nn.Module):
    def __init__(self, netts, classnet, num_flows, alpha=1., D=2):
        super(HybridIDF, self).__init__()
        print('HybridIDF by JT.')

        # 这里使用之前讨论的两种选项:耦合层或广义可逆变换
        if len(netts) == 1:
            self.t = torch.nn.ModuleList([netts[0]() for _ in range(num_flows)])
            self.idf_git = 1
            self.beta = nn.Parameter(torch.zeros(len(self.t)))
        elif len(netts) == 4:
            self.t_a = torch.nn.ModuleList([netts[0]() for _ in range(num_flows)])
            self.t_b = torch.nn.ModuleList([netts[1]() for _ in range(num_flows)])
            self.t_c = torch.nn.ModuleList([netts[2]() for _ in range(num_flows)])
            self.t_d = torch.nn.ModuleList([netts[3]() for _ in range(num_flows)])
            self.idf_git = 4
            self.beta = nn.Parameter(torch.zeros(len(self.t_a)))
        else:
            raise ValueError('You can provide either 1 or 4 translation nets.')

        # 用于在 z 上进行分类的额外层
        self.classnet = classnet

        # 流的数量(即 f 的数量)
        self.num_flows = num_flows

        # 舍入操作符
        self.round = RoundStraightThrough.apply

        # 基础分布 pi 的均值和对数尺度
        self.mean = nn.Parameter(torch.zeros(1, D))
        self.logscale = nn.Parameter(torch.ones(1, D))

        # 输入的维度
        self.D = D

        # 由于在 Python 中使用“lambda”会引起混淆,我们在代码中用 alpha 表示之前方程中的 lambda
        self.alpha = alpha

        # 使用 PyTorch 内置的损失函数,用于教学目的
        self.nll = nn.NLLLoss(reduction='none')  # 需要输入对数 softmax

    # 耦合层
    def coupling(self, x, index, forward=True):
        if self.idf_git == 1:
            (xa, xb) = torch.chunk(x, 2, 1)
            if forward:
                yb = xb + self.beta[index] * self.round(self.t[index](xa))
            else:
                yb = xb - self.beta[index] * self.round(self.t[index](xa))
            return torch.cat((xa, yb), 1)
        elif self.idf_git == 4:
            (xa, xb, xc, xd) = torch.chunk(x, 4, 1)
            if forward:
                ya = xa + self.beta[index] * self.round(self.t_a[index](torch.cat((xb, xc, xd), 1)))
                yb = xb + self.beta[index] * self.round(self.t_b[index](torch.cat((ya, xc, xd), 1)))
                yc = xc + self.beta[index] * self.round(self.t_c[index](torch.cat((ya, yb, xd), 1)))
                yd = xd + self.beta[index] * self.round(self.t_d[index](torch.cat((ya, yb, yc), 1)))
            else:
                yd = xd - self.beta[index] * self.round(self.t_d[index](torch.cat((xa, xb, xc), 1)))
                yc = xc - self.beta[index] * self.round(self.t_c[index](torch.cat((xa, xb, yd), 1)))
                yb = xb - self.beta[index] * self.round(self.t_b[index](torch.cat((xa, yc, yd), 1)))
                ya = xa - self.beta[index] * self.round(self.t_a[index](torch.cat((yb, yc, yd), 1)))
            return torch.cat((ya, yb, yc, yd), 1)

    # 置换层
    def permute(self, x):
        return x.flip(1)

    # 流变换的前向传播
    def f(self, x):
        z = x
        for i in range(self.num_flows):
            z = self.coupling(z, i, forward=True)
            z = self.permute(z)
        return z

    # 流变换的反向传播
    def f_inv(self, z):
        x = z
        for i in reversed(range(self.num_flows)):
            x = self.permute(x)
            x = self.coupling(x, i, forward=False)
        return x

    # 分类函数
    def classify(self, x):
        z = self.f(x)
        y_pred = self.classnet(z)  # 输出:概率(即 softmax)
        return torch.argmax(y_pred, dim=1)

    # 计算分类损失的辅助函数
    def class_loss(self, x, y):
        z = self.f(x)
        y_pred = self.classnet(z)  # 输出:概率(即 softmax)
        return self.nll(torch.log(y_pred), y)

    # 采样函数
    def sample(self, batchSize):
        # 采样 z
        z = self.prior_sample(batchSize=batchSize, D=self.D)
        # x = f^−1(z)
        x = self.f_inv(z)
        return x.view(batchSize, 1, self.D)

    # 基础分布的对数概率
    def log_prior(self, x):
        # 假设 log_integer_probability 已定义
        log_p = log_integer_probability(x, self.mean, self.logscale)
        return log_p.sum(1)

    # 从基础分布采样
    def prior_sample(self, batchSize, D=2):
        # 从逻辑分布采样
        y = torch.rand(batchSize, self.D)
        x = torch.exp(self.logscale) * torch.log(y / (1. - y)) + self.mean
        # 然后四舍五入为整数
        return torch.round(x)

    # 前向传播
    def forward(self, x, y, reduction='avg'):
        z = self.f(x)
        y_pred = self.classnet(z)  # 输出:概率(即 softmax)

        idf_loss = -self.log_prior(z)
        class_loss = self.nll(torch.log(y_pred), y)  # 记得在 softmax 上应用对数

        if reduction == 'sum':
            return (class_loss + self.alpha * idf_loss).sum()
        else:
            return (class_loss + self.alpha * idf_loss).mean()

以下是使用示例:

# 可逆变换的数量
num_flows = 2

# 这里仅展示选项 1 的 IDF
nett = lambda: nn.Sequential(nn.Linear(D // 2, M), nn.LeakyReLU(),
                             nn.Linear(M, M), nn.LeakyReLU(),
                             nn.Linear(M, D // 2))
netts = [nett]

# 三层分类器
classnet = nn.Sequential(nn.Linear(D, M), nn.LeakyReLU(),
                         nn.Linear(M, M), nn.LeakyReLU(),
                         nn.Linear(M, K),
                         nn.Softmax(dim=1))

# 初始化 HybridIDF
model = HybridIDF(netts, classnet, num_flows, D=D, alpha=alpha)

运行代码并训练 HybridIDF 后,我们可以得到类似图 4 所示的结果。

总结

本文介绍了混合建模的方法,将深度生成模型与分类器结合使用。通过分别训练和共享参数化两种方法,我们可以学习联合分布 $p(x, y)$。但在实际应用中,需要考虑 $y$ 和 $x$ 的维度差异,通过加权的方式解决模型偏向问题。最后,我们使用整数离散流(IDFs)实现了一个混合模型,并给出了代码实现。未来的研究方向包括探索混合 VAE、半监督混合学习、消除加权因子 $\lambda$ 以及研究更好的联合分布分解方式等。

以下是一个简单的流程图,展示混合建模的主要步骤:

graph TD;
    A[输入数据] --> B[分别训练 p(y|x) 和 p(x) 或共享参数化];
    B --> C[考虑维度差异,调整目标函数];
    C --> D[选择合适的模型(如 IDFs)];
    D --> E[实现混合模型];
    E --> F[训练模型];
    F --> G[评估模型性能];

表格 1:两种训练方法的比较
| 方法 | 优点 | 缺点 |
| — | — | — |
| 分别训练 | 简单直接 | 可能引入误差,效率低下,可能得到非最优模型 |
| 共享参数化 | 信息共享,有助于优化 | 无明显缺点,但需要调整超参数 |

通过这些方法和技术,我们可以构建更强大的机器学习模型,提高模型的性能和可靠性。

5. 后续研究方向

5.1 混合 VAE

混合建模的思想不仅局限于使用基于流的模型来建模 $p(x)$,还可以采用变分自编码器(VAE)。在应用变分推断后,我们可以得到混合建模目标的一个下界:
$\tilde{\ell}(x, y; \lambda) = \ln p(y|x) + \lambda E_{z \sim q(z|x)} [\ln p(x|z) + \ln p(z) - \ln q(z|x)]$
其中 $p(y|x)$ 使用了编码器 $q(z|x)$。这种方法为混合建模提供了新的思路,将 VAE 的优势融入到混合模型中。

5.2 半监督混合学习

混合建模的视角非常适合半监督学习场景。对于有标签的数据,我们可以使用目标函数 $\ell(x, y; \lambda) = \ln p(y|x) + \lambda \ln p(x)$;而对于无标签的数据,我们可以只考虑 $\ln p(x)$ 部分。这种方法在一些研究中已经得到应用,例如在 VAE 的半监督学习中。有研究提出了一种半监督 VAE 的目标函数,它类似于混合建模的目标函数,但去掉了麻烦的加权因子 $\lambda$。

5.3 加权因子 $\lambda$ 的问题与解决思路

加权因子 $\lambda$ 存在一些问题。首先,它不是从一个合适的概率分布推导出来的;其次,它需要进行调整,这增加了额外的工作量。不过,有研究表明可以去掉 $\lambda$,这为解决这个问题提供了方向。未来的研究可以探索使用不同的学习算法和参数化方式来消除 $\lambda$ 的影响,例如使用一些特殊的神经网络结构。

5.4 联合分布的分解方式

有人可能会疑惑,联合分布的分解 $p(x, y) = p(y|x) p(x)$ 是否真的比 $p(x, y) = p(x|y) p(y)$ 更好。如果我们想要从特定的类别 $y$ 中采样 $x$,那么后者可能更好。但我们更关注的是为世界分配合适的概率,因此更倾向于 $p(x, y) = p(y|x) p(x)$。未来的研究可以进一步探讨不同分解方式的优劣,寻找更适合特定任务的分解方法。

6. 总结与展望

本文围绕混合建模展开,详细介绍了将深度生成模型与分类器结合的方法。从引言部分强调学习联合分布 $p(x, y)$ 的重要性,到介绍两种不同的训练方法(分别训练和共享参数化),再到分析混合建模中维度差异带来的问题以及相应的解决方法,最后通过使用整数离散流(IDFs)实现了一个混合模型并给出代码。

6.1 主要成果总结

  • 提出了两种训练联合分布的方法,分别训练和共享参数化,各有优缺点。
  • 针对混合建模中 $y$ 和 $x$ 的维度差异问题,提出了两种加权方法来调整目标函数。
  • 使用 IDFs 实现了一个混合模型,避免了计算雅可比矩阵,简化了计算过程。
  • 给出了完整的代码实现,方便读者实践和验证。

6.2 未来研究展望

未来的研究可以在以下几个方面深入探索:
- 模型拓展 :进一步研究混合 VAE 和半监督混合学习,挖掘这些方法在不同任务中的潜力。
- 参数优化 :寻找消除加权因子 $\lambda$ 的方法,通过改进学习算法和参数化方式,提高模型的稳定性和性能。
- 分解方式研究 :探索不同联合分布分解方式的优劣,为特定任务选择更合适的分解方法。

通过不断的研究和实践,我们有望构建更强大、更智能的机器学习模型,为解决各种实际问题提供更有效的工具。

表格 2:后续研究方向总结
| 研究方向 | 主要内容 | 潜在优势 |
| — | — | — |
| 混合 VAE | 将 VAE 应用于混合建模,得到目标函数下界 | 融合 VAE 优势,可能提高模型性能 |
| 半监督混合学习 | 针对有标签和无标签数据采用不同目标函数 | 利用无标签数据,提高数据利用率 |
| 消除 $\lambda$ | 探索不同学习算法和参数化方式消除 $\lambda$ | 简化模型,减少超参数调整 |
| 联合分布分解 | 研究不同分解方式的优劣 | 为特定任务选择更合适的分解方法 |

以下是一个流程图,展示未来研究的主要方向:

graph TD;
    A[混合建模研究] --> B[混合 VAE];
    A --> C[半监督混合学习];
    A --> D[消除加权因子 $\lambda$];
    A --> E[研究联合分布分解方式];
    B --> F[提高模型性能];
    C --> G[提高数据利用率];
    D --> H[简化模型];
    E --> I[选择更合适的分解方法];

通过对这些研究方向的深入探索,我们可以不断推动混合建模领域的发展,为机器学习的进步做出贡献。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值