Flow-based models(NICE);流模型+NICE+代码实现

news2025/1/9 16:30:39

参考:

  1. 李宏毅春季机器学习
  2. NICE: Non-linear Independent Components Estimation
  3. https://github.com/gmum/nice_pytorch

文章目录

    • 大致思想
    • 数学预备知识
      • Jacobian矩阵
      • 行列式以及其几何意义
      • Change of Variable Theorem
    • Flow-based model
    • NICE
      • 理论
      • 代码

大致思想

Flow-Based模型想要构造出一个可逆的模型,也就是说可以把图片正向输入得到一个向量,把这个向量反向输入就可以得到图片。
换句话说,Flow-Based模型是一个由输入的空间到输出的空间的双射。那么想要构造出这个双射,则需要一些数学上的知识。

数学预备知识

Jacobian矩阵

假设有这么一个函数,输入是一个向量,输出是一个向量,那么此时我们可以求这个函数的Jacobian矩阵:
y = f ( x ) J f = [ ∂ y 1 ∂ x 1 ∂ y 1 ∂ x 2 . . . ∂ y 1 ∂ x n ∂ y 2 ∂ x 1 ∂ y 2 ∂ x 2 . . . ∂ y 2 ∂ x n . . . ∂ y n ∂ x 1 ∂ y n ∂ x 2 . . . ∂ y n ∂ x n ] y=f(x)\\J_f=\begin{bmatrix} \frac{\partial y_1}{\partial x_1} & \frac{\partial y_1}{\partial x_2}...\frac{\partial y_1}{\partial x_n}\\ \frac{\partial y_2}{\partial x_1} & \frac{\partial y_2}{\partial x_2}...\frac{\partial y_2}{\partial x_n}\\ ...\\ \frac{\partial y_n}{\partial x_1} & \frac{\partial y_n}{\partial x_2}...\frac{\partial y_n}{\partial x_n} \end{bmatrix} y=f(x)Jf= x1y1x1y2...x1ynx2y1...xny1x2y2...xny2x2yn...xnyn
上面就是Jacobian矩阵,其中我们假设x和y都是n维向量。

假设f可逆,也就是 f − 1 ( y ) = x f^{-1}(y)=x f1(y)=x,那么此时Jacobian矩阵有这么一个性质:
J f − 1 = J f − 1 J_{f^{-1}}=J_f^{-1} Jf1=Jf1
也就是说该函数的反函数的Jacobian矩阵是原函数的Jacobian矩阵的逆矩阵。

行列式以及其几何意义

行列式线性代数都学过,举例来说,二阶行列式可以这么算:
∣ a b c d ∣ = a d − c b \left | \begin{matrix}a & b\\ c & d\end{matrix} \right | = ad - cb acbd =adcb
这个行列式的几何意义如下:
在这里插入图片描述
如上,把二阶行列式的两个行向量在坐标轴上画出,然后构造出平行四边形。那么这个平行四边形的面积就是行列式的值。

Change of Variable Theorem

假设有两个分布记做 π ( z ) , p ( x ) \pi(z),p(x) π(z),p(x)以及他们两个分布之间的变换函数 x = f ( z ) x=f(z) x=f(z)
在这里插入图片描述
如上图,也就是说给定上面的一个z’则对应下面p分布中的一个x’。而对应的函数的得值分别是 π ( z ′ ) , p ( x ′ ) \pi(z'),p(x') π(z),p(x)

假设,在z’上取一个很小的变动,记做 Δ z \Delta z Δz那么此时,就会得到一个区间 [ z , z + Δ z ] [z, z+\Delta z] [z,z+Δz],那么此时对应的x’也会有一个变化区间,由于这个区间过于小,所以可以近似当做是一个矩形。可以证明,此时两个矩形的面积是一致的:
在这里插入图片描述

此时就有
π ( z ′ ) Δ z = p ( x ′ ) Δ x → π ( z ′ ) = p ( x ′ ) Δ z Δ x ≈ p ( x ′ ) d z d x \pi (z')\Delta z = p(x')\Delta x \rightarrow \pi(z')=p(x')\frac{\Delta z}{\Delta x}\approx p(x')\frac{dz}{dx} π(z)Δz=p(x)Δxπ(z)=p(x)ΔxΔzp(x)dxdz
当这个区间足够小时,就可以近似微为微分。
由于有时变化反向肯能不一致,所以要加一个绝对值:
π ( z ′ ) = p ( x ′ ) ∣ d z d x ∣ \pi(z')=p(x')|\frac{dz}{dx}| π(z)=p(x)dxdz
如果换成二维分布,则可以得到:
在这里插入图片描述
此时就会得到:
在这里插入图片描述
然后仿照上述的步骤进行变换,就可以得到:
在这里插入图片描述

于是我们就得到了这么一个关系:
p ( x ′ ) ∣ d e t ( J f ) ∣ = π ( z ′ ) p ( x ′ ) = π ( z ′ ) ∣ d e t ( J f − 1 ) ∣ p(x')|det(J_f)|=\pi(z')\\p(x')=\pi(z')|det(J_{f^{-1}})| p(x)det(Jf)=π(z)p(x)=π(z)det(Jf1)

Flow-based model

在这里插入图片描述
如上图所示,生成模型的目标在于,对样本所在的目标分布 P d a t a ( x ) P_{data}(x) Pdata(x)进行建模。
我们通常的做法是,输入一个已知的分布 π ( z ) \pi(z) π(z),例如标准高斯分布。然后通过一个神经网络,学到一个变换关系,把这个分布变换成 P G ( x ) P_G(x) PG(x),最后我们期望这个最终的分布和目标的分布 P d a t a ( x ) P_{data}(x) Pdata(x)尽可能的接近。

此时一个常见的做法就是进行最大似然估计,可以证明最大似然估计等同于最小化 D K L ( P G ∣ ∣ P d a t a ) D_{KL}(P_G||P_{data}) DKL(PG∣∣Pdata)
根据上述的公式,我们其实已经知道了 z , x z,x z,x的关系,于是就有
在这里插入图片描述
然后再加上一个对数就得到:
l o g ( p G ( x i ) ) = l o g ( π ( z i ) ) + l o g ( ∣ d e t ( J G − 1 ) ∣ ) log(p_G(x^i))=log(\pi(z^i))+log(|det(J_{G^{-1}})|) log(pG(xi))=log(π(zi))+log(det(JG1))

因为要求可逆,所以要求特殊的神经网络。但是我们可以选择一些简单的网络像流一样把它们整合起来:
在这里插入图片描述
于是就有了如下的公式
l o g p k ( x i ) = l o g π ( z i ) + ∑ i = 1 k l o g ∣ d e t ( J G i − 1 ) ∣ logp_k(x^i)=log\pi(z^i)+\sum\limits_{i=1}^klog|det(J_{G_i^{-1}})| logpk(xi)=logπ(zi)+i=1klogdet(JGi1)
我们在实际训练的时候,训练的是 G − 1 G^{-1} G1,因为此时我们手上有的是 P d a t a ( x ) P_{data}(x) Pdata(x)中采样的数据。
z i = G − 1 ( x i ) z^i=G^{-1}(x^i) zi=G1(xi)
在训练结束后,反过来就可以做生成了。

NICE

理论

首先,对于输入z,我们我们把其分为两个部分,记做 [ x 1 , x 2 ] [x_1,x_2] [x1,x2]其中这两个部分不重叠。例如我们可以把前k个元素记做 x 1 x_1 x1剩下的记做 x 2 x_2 x2
此时NICE的操作如下:
h 1 1 = x 1 h 2 1 = x 2 + m 1 ( x 1 ) h_1^1 = x_1\\h_2^1=x_2+m_1(x_1) h11=x1h21=x2+m1(x1)
其中m1是一个非线性的变换函数。此时 [ h 1 1 , h 2 1 ] [h^1_1,h^1_2] [h11,h21]就是我们NICE第一层的输出了。
也就是说,我们首先对第一部分进行了直接复制,第二部分的输出则是由第二部分加上一个第一部分的非线性变换组成的。

假设NICE只有一层,那么 h 1 = [ h 1 1 , h 2 1 ] h^1=[h^1_1,h^1_2] h1=[h11,h21]就是最终输出了,可以看到整个计算过程是可逆的。也就是说反过来我们可以由 h 1 h^1 h1得到 [ x 1 , x 2 ] [x_1,x_2] [x1,x2]

我们把这一层的输出进行一下位置调转,输入到下一层,然后反复地重复这个步骤,就可以得到最终的NICE网络:
在这里插入图片描述

接下来我们来计算这个操作的Jacobian矩阵,如下:

在这里插入图片描述
可以得到其det为1。同理,后面每一层的det都是1。

假设先验分布z是高斯分布,则
l o g ( p G ( x ) ) = l o g π ( z i ) + ∑ i = 1 k l o g ∣ d e t ( J G i − 1 ) ∣ = l o g π ( f ( x i ) ) + ∑ i = 1 k l o g ∣ d e t ( J G i − 1 ) ∣ ≈ − 1 2 ∣ ∣ f ( x i ) ∣ ∣ 2 + ∑ i = 1 k l o g ∣ d e t ( J G i − 1 ) ∣ = − 1 2 ∣ ∣ f ( x i ) ∣ ∣ 2 ( 带入行列式 ) log(p_G(x))=log\pi(z^i)+\sum\limits_{i=1}^klog|det(J_{G_i^{-1}})|\\=log\pi(f(x^i))+\sum\limits_{i=1}^klog|det(J_{G_i^{-1}})|\\\approx -\frac{1}{2}||f(x^i)||^2+\sum\limits_{i=1}^klog|det(J_{G_i^{-1}})|\\=-\frac{1}{2}||f(x^i)||^2(带入行列式) log(pG(x))=logπ(zi)+i=1klogdet(JGi1)=logπ(f(xi))+i=1klogdet(JGi1)21∣∣f(xi)2+i=1klogdet(JGi1)=21∣∣f(xi)2(带入行列式)

此时还存在一个问题,那就是输入的图片大多都是稀疏的,也就是说某些维度空间可能并不重要。所以作者在最后加上了一个尺度变换的可学习向量s。
h n ⊗ s h^n\otimes s hns
此时最后一层的det不再是1了,仔细计算之后就会发现他变成了:
∑ i s i \sum_i s_i isi
于是我们损失函数变成:
− 1 2 ∣ ∣ f ( x i ) ⊗ s ∣ ∣ 2 + ∑ i s i -\frac{1}{2}||f(x^i)\otimes s||^2+\sum_i s_i 21∣∣f(xi)s2+isi

代码

注意,代码中的划分方式改成了奇偶划分,也就是按照奇偶的方式选择谁是 x 1 , x 2 x_1,x_2 x1,x2

import torch
import torch.nn as nn
import torch.nn.init as init


_get_even = lambda xs: xs[:,0::2]
_get_odd = lambda xs: xs[:,1::2]

def _interleave(first, second, order):
    """
    交叉的还原h
    """
    cols = []
    if order == 'even':
        for k in range(second.shape[1]):
            cols.append(first[:,k])
            cols.append(second[:,k])
        if first.shape[1] > second.shape[1]:
            cols.append(first[:,-1])
    else:
        for k in range(first.shape[1]):
            cols.append(second[:,k])
            cols.append(first[:,k])
        if second.shape[1] > first.shape[1]:
            cols.append(second[:,-1])
    return torch.stack(cols, dim=1)


class AdditiveCouplingLayer(nn.Module):
    def __init__(self, dim, partition, nonlinearity):
        """
        dim: 特征维度
        partition: 切分方式(first是奇数还是偶数)
        nonlinearity: 非线性函数
        """
        super(AdditiveCouplingLayer, self).__init__()
        self.dim = dim
        assert (partition in ['even', 'odd']), "错误,不存在这种切分方式" # 切分方式
        self.partition = partition
        if (partition == 'even'):
            self._first = _get_even
            self._second = _get_odd
        else:
            self._first = _get_odd
            self._second = _get_even

        self.add_module('nonlinearity', nonlinearity) # 注册非线性函数

    def forward(self, x): # forward过程,计算first和second,并且拼接
        return _interleave(
            self._first(x),
            self.coupling_law(self._second(x), self.nonlinearity(self._first(x))),
            self.partition
        )

    def inverse(self, y):# backward过程,同forward过程,但是是相减
        """Inverse mapping through the layer. Gradients should be turned off for this pass."""
        return _interleave(
            self._first(y),
            self.anticoupling_law(self._second(y), self.nonlinearity(self._first(y))),
            self.partition
        )

    def coupling_law(self, a, b):
        return a + b

    def anticoupling_law(self, a, b):
        return a - b

    
def _build_relu_network(latent_dim, hidden_dim, num_layers): # 非线性函数m
    _modules = [ nn.Linear(latent_dim, hidden_dim) ]
    for _ in range(num_layers):
        _modules.append( nn.Linear(hidden_dim, hidden_dim) )
        _modules.append( nn.ReLU() )
        _modules.append( nn.BatchNorm1d(hidden_dim) )
    _modules.append( nn.Linear(hidden_dim, latent_dim) )
    return nn.Sequential( *_modules )

class NICEModel(nn.Module):
    def __init__(self, input_dim, hidden_dim, num_layers):
        super(NICEModel, self).__init__()
        assert (input_dim % 2 == 0), "由于是奇偶切分,特征维度必须是否书"

        self.input_dim = input_dim
        half_dim = int(input_dim / 2)
        # 交叉的插入AdditiveCouplingLayer层,相当于上面说的调换位置
        self.layer1 = AdditiveCouplingLayer(input_dim, 'odd', _build_relu_network(half_dim, hidden_dim, num_layers)) 
        self.layer2 = AdditiveCouplingLayer(input_dim, 'even', _build_relu_network(half_dim, hidden_dim, num_layers)) # 
        self.layer3 = AdditiveCouplingLayer(input_dim, 'odd', _build_relu_network(half_dim, hidden_dim, num_layers))
        self.layer4 = AdditiveCouplingLayer(input_dim, 'even', _build_relu_network(half_dim, hidden_dim, num_layers))
        self.scaling_diag = nn.Parameter(torch.ones(input_dim)) # 设置一个尺度变换的向量

        # 初始化各个参数
        for p in self.layer1.parameters():
            if len(p.shape) > 1:
                init.kaiming_uniform_(p, nonlinearity='relu')
            else:
                init.normal_(p, mean=0., std=0.001)
        for p in self.layer2.parameters():
            if len(p.shape) > 1:
                init.kaiming_uniform_(p, nonlinearity='relu')
            else:
                init.normal_(p, mean=0., std=0.001)
        for p in self.layer3.parameters():
            if len(p.shape) > 1:
                init.kaiming_uniform_(p, nonlinearity='relu')
            else:
                init.normal_(p, mean=0., std=0.001)
        for p in self.layer4.parameters():
            if len(p.shape) > 1:
                init.kaiming_uniform_(p, nonlinearity='relu')
            else:
                init.normal_(p, mean=0., std=0.001)        


    def forward(self, xs):
        # 前向计算,正常的算就行
        ys = self.layer1(xs)
        ys = self.layer2(ys)
        ys = self.layer3(ys)
        ys = self.layer4(ys)
        ys = torch.matmul(ys, torch.diag(torch.exp(self.scaling_diag))) # 最后乘以尺度变换向量
        return ys


    def inverse(self, ys):
        with torch.no_grad():
            xs = torch.matmul(ys, torch.diag(torch.reciprocal(torch.exp(self.scaling_diag)))) # 除以s
            xs = self.layer4.inverse(xs) # 反向计算
            xs = self.layer3.inverse(xs)
            xs = self.layer2.inverse(xs)
            xs = self.layer1.inverse(xs)
        return xs

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1173797.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux系统化学习】开发工具——gdb(调试器)

个人主页点击直达:小白不是程序媛 Linux专栏:Linux系统化学习 个人仓库:Gitee 目录 前言: gdb版本检查和安装 Debug和Release gdb的使用 其他指令 前言: 前几篇文章分别介绍了在Linux下的代码编辑器、编译器。…

c面向对象编码风格(上)

面向对象和面向过程的基本概念 面向对象和面向过程是两种不同的编程范式,它们在软件开发中用于组织和设计代码的方式。 面向过程编程(Procedural Programming)是一种以过程(函数、方法)为核心的编程方式。在面向过程…

2021年电工杯数学建模B题光伏建筑一体化板块指数发展趋势分析及预测求解全过程论文及程序

2021年电工杯数学建模 B题 光伏建筑一体化板块指数发展趋势分析及预测 原题再现: 国家《第十四个五年规划和 2035 年远景目标纲要》中提出,将 2030 年实现“碳达峰”与 2060 年实现“碳中和”作为我国应对全球气候变暖的一个重要远景目标。光伏建筑一体…

七月论文审稿GPT第二版:从Meta Nougat、GPT4审稿到LongLora版LLaMA、Mistral

前言 如此前这篇文章《学术论文GPT的源码解读与微调:从chatpaper、gpt_academic到七月论文审稿GPT》中的第三部分所述,对于论文的摘要/总结、对话、翻译、语法检查而言,市面上的学术论文GPT的效果虽暂未有多好,可至少还过得去&am…

1.Netty概述

原生NIO存在的问题(Netty要解决的问题) 虽然JAVA NIO 和 JAVA AIO框架提供了多路复用IO/异步IO的支持,但是并没有提供给上层“信息格式”的良好封装。JAVA NIO 的 API 使用麻烦,需要熟练掌握 ByteBuffer、Channel、Selector等 , 所以用这些API实现一款真正的网络应…

题解:轮转数组及复杂度分析

文章目录 🍉前言🍉题目🍌解法一🍌解法二:以空间换时间🥝补充:memmove 🍌解法三(选看) 🍉前言 本文侧重对于复杂度的分析,题解为辅。 …

02-React组件与模块

组件与模块 前期准备 安装React官方浏览器调试工具,浏览器扩展搜索即可 比如红色的React就是本地开发模式 开启一个用React写的网站,比如美团 此时开发状态就变成了蓝色 组件也能解析出来 何为组件&模块 模块,简单来说就是JS代…

亚马逊云科技大语言模型下的六大创新应用功能

目录 前言 亚马逊云科技的AI创新应用 ​编辑 Amazon CodeWhisperer Amazon CodeWhisperer产品的优势 更快地完成更多工作 自信地进行编码 增强代码安全性 使用收藏夹工具 自定义 CodeWhisperer 以获得更好的建议 如何使用Amazon CodeWhisperer 步骤 1 步骤 2 具体…

php7.4.32如何快速正确的开启OpenSSL扩展库,最简单的办法在这里!

🚀 个人主页 极客小俊 ✍🏻 作者简介:web开发者、设计师、技术分享博主 🐋 希望大家多多支持一下, 我们一起进步!😄 🏅 如果文章对你有帮助的话,欢迎评论 💬点赞&#x1…

GNU ld链接器 lang_process()(二)

一、ldemul_create_output_section_statements() 位于lang_process()中11行 。 该函数用于创建与目标有关的输出段的语句。这些语句将用于描述输出段的属性和分配。 void ldemul_create_output_section_statements (void) {if (ld_emulation->create_output_section_sta…

PS Raw中文增效工具Camera Raw 16

Camera Raw 16 for mac(PS Raw增效工具)的功能特色包括强大的图像调整工具。例如,它提供白平衡、曝光、对比度、饱和度等调整选项,帮助用户优化图像的色彩和细节。此外,Camera Raw 16的界面简洁易用,用户可…

每日一题 187. 重复的DNA序列(中等)

由于今天做了周赛,每日一题就简单点直接暴力哈希 class Solution:def findRepeatedDnaSequences(self, s: str) -> List[str]:d defaultdict(int)ans []for i in range(len(s) - 9):t s[i: i 10]d[t] 1if d[t] 2:ans.append(t)return ans

CCC数字钥匙设计【NFC】 --通过NFC进行车主配对Phase4

1、车主配对流程介绍 车主配对可以通过车内NFC进行,若支持UWB测距,也可以通过蓝牙/UWB进行。通过NFC进行车主配对总共有5个Phase。本文档主要对Phase4进行介绍。 1) Phase0:准备阶段; 2) Phase1:启动流程&#xff1…

凸优化问题(最简单)

一、凸优化问题 1.1 概念 凸优化问题minf(x):需要同时满足两个条件:变量可行域时凸的(convex);目标函数也是凸函数(convex)。 (1)变量x的可行域Ω为凸集,即对于集合Ω中任意两点x1、x2∈Ω,他…

使用阿里云服务器,httplib库在listen过程中,出现Cannot assign requested address错误???

今天,在做一个小项目的时候,使用httplib库进行建立tcp连接,但是一旦程序开始,并没有等待tcp连接的到来,而是直接结束了。 打印一下strerror(errno) 根本就没有进行客户端的连接。 找了一下午,检测是否…

SEO优化的好帮手,5个必备的好工具

做海外市场的企业,谷歌SEO是一个非常重要的方式,帮助提高自己企业的网站曝光,起着至关重要的作用,因为人们普遍会通过网上搜索来找到那些适合的商品,与排名靠后的公司相比,出现在搜索结果顶部的公司往往能吸…

有人物联网模块连接阿里云物联网平台的方法

摘要:本文介绍有人物联网模块M100连接阿里云的参数设置,作为说明书的补充。 没有阿里云功能需求的请略过本文,不要浪费您宝贵的时间。 网络选择LTE,请先确保插入的SIM卡有流量。 接下来配置阿里云云服务。如下图所示,…

802.11 CSMA/CA协议

《计算机网络自顶向下》P351的总结提炼

Linux应用开发基础知识——交叉编译与gcc编译(一)

前言: 源文件需要经过编译才能生成可执行文件。在 Windows 下进行开发时,只需 要点几个按钮即可编译,集成开发环境(比如 Visual studio)已经将各种编译 工具的使用封装好了。Linux 下也有很优秀的集成开发工具,但是更多的时候是 直…

x264交叉编译(ubuntu+arm)

1.下载源码 https://code.videolan.org/videolan/x264 在windows下解压;复制到ubuntu; 2.进入源码文件夹-新建脚本文件 touch sp_run.sh 3.在sp_run.sh文件中输入 #!/bin/sh./configure --prefix/home/alientek/sp_test/x264/sp_install --enable-…