[Pytorchニューラルネットワークの実用的なケース]37深い相互信頼情報モデルDIMを最大化して、最も関連性の高い写真と最も関連性の低い写真を検索します

画像サーチャーは、画像の特徴抽出とマッチングの2つの部分に分かれており、画像の特徴抽出が鍵となります。特徴抽出は、教師なしモデルベースの特徴抽出メソッド、つまりMaximizing Deep Mutual Information(DeepInfoMax、DIM)メソッドを使用して実装されます。

1最大深度相互信頼情報モデルDIMの概要

DIMモデルでは、オートエンコーダと敵対的ニューラルネットワークが組み合わされ、損失関数はMINEとf-GAN法の組み合わせを使用します。これに加えて、DMモデルは、グローバル損失、ローカル損失、および以前の損失の3つの損失からトレーニングされます。

1.1DIMモデルの原理

パフォーマンスの高いエンコーダーは、小さすぎる再構成エラーを単純に追跡するのではなく、サンプル内の最も一意で具体的な情報を抽出できる必要があります。サンプルの一意の情報は、相互情報量(Mutual Information、MI)を使用して測定できます。

したがって、DIMモデルでは、エンコーダの目的は、入力と出力のMSEを最小化することではなく、入力と出力の相互情報量を最大化することです。

1.2DIMモデルの主なアイデア

DIMモデルの相互情報量ソリューションは、主に、入力サンプルとエンコーダーによって出力された変換ベクトルとの間の相互情報量を計算し、相互情報量を最大化することによってモデルのトレーニングを実現するMINEメソッドから得られます。

1.2.1教師なしトレーニングにおけるDIMモデルの2つの制約。

  1. 入力情報と高レベルの特徴ベクトルの間の相互情報量を最大化する:モデルによって出力される低次元の特徴が入力サンプルを表すことができる場合、特徴分布と入力サンプル分布の間の相互情報量が最大である必要があります。
  2. 敵対的マッチング事前分布:エンコーダーによって出力される高レベルの特徴はガウス分布に近くなければならず、弁別器はエンコーダーによって生成されたデータ分布をガウス分布から区別する必要があります。

実装されると、DlMモデルは3つの弁別子を使用します。これは、ローカル相互情報量の最大化、グローバル相互情報量の最大化、および事前分布マッチングの最小化という3つの観点からエンコーダーの出力を制約します。(紙arXv:1808.06670、2018)

1.3ローカルおよびグローバル相互情報量最大化制約の原則

多くの表現学習は、探索されたデータ空間(ピクセルレベルと呼ばれる)のみを使用します。これは、データのごく一部がセマンティックレベルに非常に関係している場合、表現学習はトレーニングに適していないことを示しています。
    写真の場合、その関連性はよりローカルです。画像の識別と分類は、ローカルから全体へのプロセスである必要があります。つまり、グローバルフィーチャは再構成に適し、ローカルフィーチャはダウンストリーム分類タスクに適しています。
ローカル特徴は畳み込み後に得られる特徴マップとして理解でき、グローバル特徴は特徴マップをエンコードすることによって得られる特徴ベクトルとして理解できます。

DIMモデルは、ローカルとグローバルの両方の観点から、入力と出力の相互情報量計算を実行します。

1.4事前分布マッチング最小化制約の原則

事前マッチングの目的は、エンコーダーで生成されたベクトル形式をガウス分布に近づけるように制約することです。

DIMモデルのエンコーダーの主なアイデアは次のとおりです:入力データを特徴ベクトルにエンコードする一方で、特徴ベクトルが標準のガウス分布に従うことも期待されています。このアプローチにより、エンコーディングスペースがより規則的になり、その後の学習のためのデカップリング機能も容易になります。これは、変分自動エンコーディングのエンコーダと同じ使命です。

したがって、変分オートエンコーダニューラルネットワークの原理がDIMモデルに導入され、ガウス分布は、エンコーダによるベクトル出力を制約するための事前分布と見なされます。

2DIMモデルの構造

2.1DIMモデル構造図

DIMモデルの構造DIMモデルは、4つのサブモデルで構成されています。1つのエンコーダーと3つのディスクリミネーターです。デコーダーの主な機能はグラフから特徴を抽出することであり、3つの弁別子は、ローカル、グローバル、および事前マッチングの3つの観点からエンコーダーの出力を制約する必要があります。

2.2DlMモデルの特別な機能

    DlMモデルの実際の実装プロセスでは、最大相互情報量の計算は、元の入力データとエンコーダーによって出力された特徴データに対して直接実行されませんが、エンコーダーの中間プロセスの特徴マップと最終的な特徴データは相互情報量の計算を実行するために使用されます。

    MINE法によれば、ニューラルネットワークを使用して相互情報量を計算する方法は、2つのデータセットの同時分布と周辺分布の間の発散、つまり、識別器による特徴マップの処理の結果を計算することに変換できます。特徴データは同時分布と見なされ、無秩序になります。後者の特徴マップと特徴データは、エッジ分布を取得するためにディスクリミネーターに入力されます。

DIMモデルは、特徴マップのバッチシーケンスをスクランブルし、エンコーダーによって出力されたプロンプト特徴ベクトルを弁別器の入力として使用します。つまり、特徴マップと入力弁別器の特徴ベクトルは独立しています(特徴マップと特徴ベクトル)、相互情報量ニューラル推定の原理の概要を参照してください。

2.3グローバルディスクリミネーターモデル

図8-29に示すように、グローバルディスクリミネーターには、特徴マップと特徴データyの2つの入力値があります。相互情報量を計算するプロセスでは、同時分布された特徴マップと特徴データyの両方が、エンコーディングニューラルネットワークの出力から取得されます。エッジ分布を計算するための特徴マップは、特徴マップのバッチ順序を変更することによって取得され、特徴データyは、図8-30に示すように、エンコーディングニューラルネットワークの出力から取得されます。

グローバルディスクリミネーターでは、具体的な処理手順は次のとおりです。
(1)畳み込み層を使用して特徴マップを処理し、グローバルな特徴を取得します。
(2)グローバル機能と機能データyをtorch.cat()関数で接続します。
(3)接続結果を完全接続ネットワークに入力し(2つのグローバル特徴の判定)、最後に判定結果(1次元ベクトル)を出力します。

2.4ローカルディスクリミネーターモデル

図8-29に示すように、ローカルディスクリミネーターの入力値は特殊な合成ベクトルです。エンコーダーによって出力された特徴データyは、特徴マップのサイズに従ってm×mのコピーにコピーされます。特徴マップの各ピクセルを、エンコーダーによって出力されたグローバル特徴データνに接続します。このように、弁別器が行うことは、各ピクセルとグローバル特徴ベクトルとの間の相互情報量を計算することです。したがって、この弁別器はローカル弁別器と呼ばれます。
ローカル弁別器では、相互情報量を計算するための同時分布とエッジ分布はグローバル弁別器と一致しています。図8-31に示すように、ローカル弁別器は主に1×1の畳み込み演算を使用します(ストライドも1です)。この畳み込み演算は特徴マップのサイズを変更しないため(チャネル数の変換のみ)、弁別器の最終出力もサイズm×mの値になります。

ローカル弁別器は、最終的な弁別結果として使用される多層1×1畳み込み演算を実行することにより、最終的にチャネル数を1に変更します。このプロセスは、各ピクセルとグローバルフィーチャの相互情報量を同時に計算することとして理解できます。

2.5以前の弁別器モデル

以前の弁別器モデルは、主に、補助エンコーダーによって生成されたベクトルがガウス分布に近いことであり、これは一般的な敵対的ニューラルネットワークと一致しています。図に示すように、以前の弁別子モデルの出力結果は0または1のみです。弁別子にガウス分布からサンプリングされたデータをtrue(1)と判断させ、エンコーダーによって出力された特徴ベクトルをfalse(0)と判断させます。 8-32。

以前のディスクリミネーターモデルを図8-32に示します。以前のディスクリミネーターモデルへの入力は、1つの特徴ベクトルのみです。その構造は主に完全に接続されたニューラルネットワークを使用し、最終的に「真」または「偽」の決定結果を出力します。

2.6損失関数

    DIMモデルでは、相互情報量の尺度として、MINEメソッドのKLダイバージェンスがJSダイバージェンスに置き換えられています。この理由は次のとおりです。JSダイバージェンスには上限がありますが、KLダイバージェンスには上限がありません。対照的に、JS発散は、計算中に特に大きな数を生成せず、JS発散の勾配が偏っていないため、最大化タスクでの使用に適しています。

JS発散の計算式は、f-GANにあります。式(8-46)を参照してください(原理は、式(8-46)の下のヒントセクションで説明されています)。

 以前の弁別器の損失関数は非常に単純で、元のGANモデルの損失関数と一致しています(論文番号anXiv:1406.2661、2014を参照)。 3つの弁別子、次にDMモデル全体の損失関数を取得します。

3実際の戦闘事例とコード実装の概要(トレーニングモデルコード実装)

最大化相互情報量モデルを用いて画像情報を抽出し、抽出した低次元特徴を用いて画像探索器を作成する。

3.1CIFARデータセット

    この例で使用されているデータセットはClFARであり、Fashion-MNISTデータセットといくつかの写真に似ています。ClFARはFashion-MNISTよりも複雑で、カラー画像で構成されています。カラー画像は、実際のシーンと接触しているサンプルに比較的近いものです。

3.1.1CIFARデータセットの構成

CIFARデータセットのバージョンは、元のデータセットがデータを飛行機、車、鳥、猫、鹿、犬、カエル、馬、ボート、トラックの10のカテゴリに分割しているため、ClFARデータセットはCIFAR-10Namedを使用することがよくあります。 、32ピクセル×32ピクセルの60,000枚のカラー画像(50,000枚のトレーニング画像、10,000枚のテスト画像を含む)が含まれ、オーバーラップのタイプはありません。これはカラー画像であるため、このデータセットは3チャネルであり、R、G、およびBの3つのチャネルがあります。

CIFARは、データを100のカテゴリに分類する名前からわかるように、より分類されたバージョンであるClFAR-100をリリースしました。画像をより細かい部分に分割します。もちろん、これはニューラルネットワークの画像認識にとってより大きな課題です。このデータを使用して、ネットワークの最適化に全力を注ぐことができます。

 3.2データセットを取得する

ClFARデータセットはパッケージ化されたファイルであり、Pythonとバイナリbinファイルパッケージに分割されており、さまざまなプログラムが読み取るのに便利です。今回使用したデータセットは、ClFAR-10バージョンのPythonファイルパッケージと対応するファイル名です。 「cifar」-10-pyhon.tar.gz」です。このファイルは、公式Webサイトから手動でダウンロードすることも、Fashion-MNISTの取得と同様の方法を使用してPyTorchの埋め込みコードからダウンロードすることもできます。

3.3CIFARデータセットのロードと表示------DIM_CIRFAR_train.py(パート1)

import torch
from torch import nn
import torch.nn.functional as F
import torchvision
from torch.optim import Adam
from torchvision.transforms import ToTensor
from torch.utils.data import DataLoader
from torchvision.datasets.cifar import CIFAR10
from matplotlib import pyplot as plt
import numpy as np
from tqdm import tqdm
from pathlib import Path
from torchvision.transforms import ToPILImage
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"

# 1.1 获取数据集并显示数据集
# 指定运算设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)
# 加载数据集
batch_size = 512
data_dir = r'./cifar10/'
# 将CIFAR10数据集下载到本地:共有三份文件,标签说明文件batches.meta,训练样本集data_batch_x(一共五个,包含10000条训练样本),测试样本test.batch
train_dataset = CIFAR10(data_dir,download=True,transform=ToTensor())
train_loader = DataLoader(train_dataset,batch_size=batch_size,shuffle=True,drop_last=True,pin_memory=torch.cuda.is_available())
print("训练样本个数:",len(train_dataset))
# 定义函数用于显示图片
def imshowrow(imgs,nrow):
    plt.figure(dpi=200) # figsize=(9,4)
    # ToPILImage()调用PyTorch的内部转换接口,实现张量===>PLImage类型图片的转换。
    # 该接口主要实现。(1)将张量的每个元素乘以255。(2)将张量的数据类型由FloatTensor转化成uint8。(3)将张量转化成NumPy的ndarray类型。(4)对ndarray对象执行transpose(1,2,0)的操作。(5)利用Image下的fromarray()函数,将ndarray对象转化成PILImage形式。(6)输出PILImage。
    _img = ToPILImage()(torchvision.utils.make_grid(imgs,nrow=nrow)) # 传入PLlmage()接口的是由torchvision.utis.make_grid接口返回的张量对象
    plt.axis('off')
    plt.imshow(_img)
    plt.show()

# 定义标签与对应的字符
classes = ('airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# 获取一部分样本用于显示
sample = iter(train_loader)
images,labels = sample.next()
print("样本形状:",np.shape(images))
print('样本标签:',','.join('%2d:%-5s' % (labels[j],classes[labels[j]]) for j in range(len(images[:10]))))
imshowrow(images[:10],nrow=10)

出力:

3.5DIMモデルの定義------DIM_CIRFAR_train.py(パート2)

# 1.2 定义DIM模型
class Encoder(nn.Module): # 通过多个卷积层对输入数据进行编码,生成64维特征向量
    def __init__(self):
        super().__init__()
        self.c0 = nn.Conv2d(3, 64, kernel_size=4, stride=1) # 输出尺寸29
        self.c1 = nn.Conv2d(64, 128, kernel_size=4, stride=1) # 输出尺寸26
        self.c2 = nn.Conv2d(128, 256, kernel_size=4, stride=1) # 输出尺寸23
        self.c3 = nn.Conv2d(256, 512, kernel_size=4, stride=1) # 输出尺寸20
        self.l1 = nn.Linear(512*20*20, 64)
        # 定义BN层
        self.b1 = nn.BatchNorm2d(128)
        self.b2 = nn.BatchNorm2d(256)
        self.b3 = nn.BatchNorm2d(512)

    def forward(self, x):
        h = F.relu(self.c0(x))
        features = F.relu(self.b1(self.c1(h)))#输出形状[b 128 26 26]
        h = F.relu(self.b2(self.c2(features)))
        h = F.relu(self.b3(self.c3(h)))
        encoded = self.l1(h.view(x.shape[0], -1))# 输出形状[b 64]
        return encoded, features

class DeepInfoMaxLoss(nn.Module): # 实现全局、局部、先验判别器模型的结构设计,合并每个判别器的损失函数,得到总的损失函数
    def __init__(self,alpha=0.5,beta=1.0,gamma=0.1):
        super().__init__()
        # 初始化损失函数的加权参数
        self.alpha = alpha
        self.beta = beta
        self.gamma = gamma
        # 定义局部判别模型
        self.local_d = nn.Sequential(
            nn.Conv2d(192,512,kernel_size=1),
            nn.ReLU(True),
            nn.Conv2d(512,512,kernel_size=1),
            nn.ReLU(True),
            nn.Conv2d(512,1,kernel_size=1)
        )
        # 定义先验判别器模型
        self.prior_d = nn.Sequential(
            nn.Linear(64,1000),
            nn.ReLU(True),
            nn.Linear(1000,200),
            nn.ReLU(True),
            nn.Linear(200,1),
            nn.Sigmoid() # 在定义先验判别器模型的结构时,最后一层的激活函数用Sigmoid函数。这是原始GAN模型的标准用法(可以控制输出值的范围为0-1),是与损失函数配套使用的。
        )
        # 定义全局判别器模型
        self.global_d_M = nn.Sequential(
            nn.Conv2d(128,64,kernel_size=3), # 输出形状[b,64,24,24]
            nn.ReLU(True),
            nn.Conv2d(64,32,kernel_size=3), # 输出形状 [b,32,32,22]
            nn.Flatten(),
        )
        self.global_d_fc = nn.Sequential(
            nn.Linear(32*22*22+64,512),
            nn.ReLU(True),
            nn.Linear(512,512),
            nn.ReLU(True),
            nn.Linear(512,1)
        )

    def GlobalD(self, y, M):
        h = self.global_d_M(M)
        h = torch.cat((y, h), dim=1)
        return self.global_d_fc(h)
    def forward(self,y,M,M_prime):
        # 复制特征向量
        y_exp = y.unsqueeze(-1).unsqueeze(-1)
        y_exp = y_exp.expand(-1,-1,26,26) # 输出形状[b,64,26,26]
        # 按照特征图的像素连接特征向量
        y_M = torch.cat((M,y_exp),dim=1) # 输出形状[b,192,26,26]
        y_M_prime = torch.cat((M_prime,y_exp),dim=1)# 输出形状[b,192,26,26]
        # 计算局部互信息---互信息的计算
        Ej = -F.softplus(-self.local_d(y_M)).mean() # 联合分布
        Em = F.softplus(self.local_d(y_M_prime)).mean() # 边缘分布
        LOCAL = (Em - Ej) * self.beta # 最大化互信息---对互信息执行了取反操作。将最大化问题变为最小化问题,在训练过程中,可以使用最小化损失的方法进行处理。
        # 计算全局互信息---互信息的计算
        Ej = -F.softplus(-self.GlobalD(y, M)).mean() # 联合分布
        Em = F.softplus(self.GlobalD(y, M_prime)).mean() # 边缘分布
        GLOBAL = (Em - Ej) * self.alpha # 最大化互信息---对互信息执行了取反操作。将最大化问题变为最小化问题,在训练过程中,可以使用最小化损失的方法进行处理。
        # 计算先验损失
        prior = torch.rand_like(y) # 获得随机数
        term_a = torch.log(self.prior_d(prior)).mean() # GAN损失
        term_b = torch.log(1.0 - self.prior_d(y)).mean()
        PRIOR = -(term_a + term_b) * self.gamma # 最大化目标分布---实现了判别器的损失函数。判别器的目标是将真实数据和生成数据的分布最大化,因此,也需要取反,通过最小化损失的方法来实现。
        return LOCAL + GLOBAL + PRIOR

# #### 在训练过程中,梯度可以通过损失函数直接传播到编码器模型,进行联合优化,因此,不需要对编码器额外进行损失函数的定义!

3.6DIMモデルのインスタンス化とトレーニング------DIM_CIRFAR_train.py(パート3)

# 1.3 实例化DIM模型并训练:实例化模型按照指定次数迭代训练。在制作边缘分布样本时,将批次特征图的第1条放到最后,以使特征图与特征向量无法对应,实现与按批次打乱顺序等同的效果。
totalepoch = 100 # 指定训练次数
if __name__ == '__main__':
    encoder =Encoder().to(device)
    loss_fn = DeepInfoMaxLoss().to(device)
    optim = Adam(encoder.parameters(),lr=1e-4)
    loss_optim = Adam(loss_fn.parameters(),lr=1e-4)

    epoch_loss = []
    for epoch in range(totalepoch +1):
        batch = tqdm(train_loader,total=len(train_dataset)//batch_size)
        train_loss = []
        for x,target in batch: # 遍历数据集
            x = x.to(device)
            optim.zero_grad()
            loss_optim.zero_grad()
            y,M = encoder(x) # 用编码器生成特征图和特征向量
            # 制作边缘分布样本
            M_prime = torch.cat((M[1:],M[0].unsqueeze(0)),dim=0)
            loss =loss_fn(y,M,M_prime) # 计算损失
            train_loss.append(loss.item())
            batch.set_description(str(epoch) + ' Loss:%.4f'% np.mean(train_loss[-20:]))
            loss.backward()
            optim.step() # 调用编码器优化器
            loss_optim.step() # 调用判别器优化器
        if epoch % 10 == 0 : # 保存模型
            root = Path(r'./DIMmodel/')
            enc_file = root / Path('encoder' + str(epoch) + '.pth')
            loss_file = root / Path('loss' + str(epoch) + '.pth')
            enc_file.parent.mkdir(parents=True, exist_ok=True)
            torch.save(encoder.state_dict(), str(enc_file))
            torch.save(loss_fn.state_dict(), str(loss_file))
        epoch_loss.append(np.mean(train_loss[-20:])) # 收集训练损失
    plt.plot(np.arange(len(epoch_loss)), epoch_loss, 'r') # 损失可视化
    plt.show()

結果:

 

3.7モデルをロードし、画像を検索します------ DIM_CIRFAR_loadpath.py

import torch
import torch.nn.functional as F
from tqdm import tqdm
import random

# 功能介绍:载入编码器模型,对样本集中所有图片进行编码,随机取一张图片,找出与该图片最接近与最不接近的十张图片
#
# 引入本地库
#引入本地代码库
from DIM_CIRFAR_train import ( train_loader,train_dataset,totalepoch,device,batch_size,imshowrow, Encoder)

# 加载模型
model_path = r'./DIMmodel/encoder%d.pth'% (totalepoch)
encoder = Encoder().to(device)
encoder.load_state_dict(torch.load(model_path,map_location=device))

# 加载模型样本,并调用编码器生成特征向量
batchesimg = []
batchesenc = []
batch = tqdm(train_loader,total=len(train_dataset)//batch_size)
for images ,target in batch :
    images = images.to(device)
    with torch.no_grad():
        encoded,features = encoder(images) # 调用编码器生成特征向量
    batchesimg.append(images)
    batchesenc.append(encoded)
# 将样本中的图片与生成的向量沿第1维度展开
batchesenc = torch.cat(batchesenc,axis = 0)
batchesimg = torch.cat(batchesimg,axis = 0)
# 验证向量的搜索功能
index = random.randrange(0,len(batchesenc)) # 随机获取一个索引,作为目标图片
batchesenc[index].repeat(len(batchesenc),1) # 将目标图片的特征向量复制多份
# 使用F.mse_loss()函数进行特征向量间的L2计算,传入了参数reduction='none',这表明对计算后的结果不执行任何操作。如果不传入该参数,那么函数默认会对所有结果取平均值(常用在训练模型场景中)
l2_dis = F.mse_loss(batchesenc[index].repeat(len(batchesenc),1),batchesenc,reduction='none').sum(1) # 计算目标图片与每个图片的L2距离
findnum = 10 # 设置查找图片的个数
# 使用topk()方法获取L2距离最近、最远的图片。该方法会返回两个值,第一个是真实的比较值,第二个是该值对应的索引。
_,indices = l2_dis.topk(findnum,largest=False ) # 查找10个最相近的图片
_,indices_far = l2_dis.topk(findnum,) # 查找10个最不相关的图片
# 显示结果
indices = torch.cat([torch.tensor([index]).to(device),indices])
indices_far = torch.cat([torch.tensor([index]).to(device),indices_far])
rel = torch.cat([batchesimg[indices],batchesimg[indices_far]],axis = 0)
imshowrow(rel.cpu() ,nrow=len(indices))
# 结果显示:结果有两行,每行的第一列是目标图片,第一行是与目标图片距离最近的搜索结果,第二行是与目标图片距离最远的搜索结果。

 4コードの概要

4.1モデルのトレーニング:DIM_CIRFAR_train.py

import torch
from torch import nn
import torch.nn.functional as F
import torchvision
from torch.optim import Adam
from torchvision.transforms import ToTensor
from torch.utils.data import DataLoader
from torchvision.datasets.cifar import CIFAR10
from matplotlib import pyplot as plt
import numpy as np
from tqdm import tqdm
from pathlib import Path
from torchvision.transforms import ToPILImage
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"

# 1.1 获取数据集并显示数据集
# 指定运算设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)
# 加载数据集
batch_size = 512
data_dir = r'./cifar10/'
# 将CIFAR10数据集下载到本地:共有三份文件,标签说明文件batches.meta,训练样本集data_batch_x(一共五个,包含10000条训练样本),测试样本test.batch
train_dataset = CIFAR10(data_dir,download=True,transform=ToTensor())
train_loader = DataLoader(train_dataset,batch_size=batch_size,shuffle=True,drop_last=True,pin_memory=torch.cuda.is_available())
print("训练样本个数:",len(train_dataset))
# 定义函数用于显示图片
def imshowrow(imgs,nrow):
    plt.figure(dpi=200) # figsize=(9,4)
    # ToPILImage()调用PyTorch的内部转换接口,实现张量===>PLImage类型图片的转换。
    # 该接口主要实现。(1)将张量的每个元素乘以255。(2)将张量的数据类型由FloatTensor转化成uint8。(3)将张量转化成NumPy的ndarray类型。(4)对ndarray对象执行transpose(1,2,0)的操作。(5)利用Image下的fromarray()函数,将ndarray对象转化成PILImage形式。(6)输出PILImage。
    _img = ToPILImage()(torchvision.utils.make_grid(imgs,nrow=nrow)) # 传入PLlmage()接口的是由torchvision.utis.make_grid接口返回的张量对象
    plt.axis('off')
    plt.imshow(_img)
    plt.show()

# 定义标签与对应的字符
classes = ('airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# 获取一部分样本用于显示
sample = iter(train_loader)
images,labels = sample.next()
print("样本形状:",np.shape(images))
print('样本标签:',','.join('%2d:%-5s' % (labels[j],classes[labels[j]]) for j in range(len(images[:10]))))
imshowrow(images[:10],nrow=10)

# 1.2 定义DIM模型
class Encoder(nn.Module): # 通过多个卷积层对输入数据进行编码,生成64维特征向量
    def __init__(self):
        super().__init__()
        self.c0 = nn.Conv2d(3, 64, kernel_size=4, stride=1) # 输出尺寸29
        self.c1 = nn.Conv2d(64, 128, kernel_size=4, stride=1) # 输出尺寸26
        self.c2 = nn.Conv2d(128, 256, kernel_size=4, stride=1) # 输出尺寸23
        self.c3 = nn.Conv2d(256, 512, kernel_size=4, stride=1) # 输出尺寸20
        self.l1 = nn.Linear(512*20*20, 64)
        # 定义BN层
        self.b1 = nn.BatchNorm2d(128)
        self.b2 = nn.BatchNorm2d(256)
        self.b3 = nn.BatchNorm2d(512)

    def forward(self, x):
        h = F.relu(self.c0(x))
        features = F.relu(self.b1(self.c1(h)))#输出形状[b 128 26 26]
        h = F.relu(self.b2(self.c2(features)))
        h = F.relu(self.b3(self.c3(h)))
        encoded = self.l1(h.view(x.shape[0], -1))# 输出形状[b 64]
        return encoded, features

class DeepInfoMaxLoss(nn.Module): # 实现全局、局部、先验判别器模型的结构设计,合并每个判别器的损失函数,得到总的损失函数
    def __init__(self,alpha=0.5,beta=1.0,gamma=0.1):
        super().__init__()
        # 初始化损失函数的加权参数
        self.alpha = alpha
        self.beta = beta
        self.gamma = gamma
        # 定义局部判别模型
        self.local_d = nn.Sequential(
            nn.Conv2d(192,512,kernel_size=1),
            nn.ReLU(True),
            nn.Conv2d(512,512,kernel_size=1),
            nn.ReLU(True),
            nn.Conv2d(512,1,kernel_size=1)
        )
        # 定义先验判别器模型
        self.prior_d = nn.Sequential(
            nn.Linear(64,1000),
            nn.ReLU(True),
            nn.Linear(1000,200),
            nn.ReLU(True),
            nn.Linear(200,1),
            nn.Sigmoid() # 在定义先验判别器模型的结构时,最后一层的激活函数用Sigmoid函数。这是原始GAN模型的标准用法(可以控制输出值的范围为0-1),是与损失函数配套使用的。
        )
        # 定义全局判别器模型
        self.global_d_M = nn.Sequential(
            nn.Conv2d(128,64,kernel_size=3), # 输出形状[b,64,24,24]
            nn.ReLU(True),
            nn.Conv2d(64,32,kernel_size=3), # 输出形状 [b,32,32,22]
            nn.Flatten(),
        )
        self.global_d_fc = nn.Sequential(
            nn.Linear(32*22*22+64,512),
            nn.ReLU(True),
            nn.Linear(512,512),
            nn.ReLU(True),
            nn.Linear(512,1)
        )

    def GlobalD(self, y, M):
        h = self.global_d_M(M)
        h = torch.cat((y, h), dim=1)
        return self.global_d_fc(h)
    def forward(self,y,M,M_prime):
        # 复制特征向量
        y_exp = y.unsqueeze(-1).unsqueeze(-1)
        y_exp = y_exp.expand(-1,-1,26,26) # 输出形状[b,64,26,26]
        # 按照特征图的像素连接特征向量
        y_M = torch.cat((M,y_exp),dim=1) # 输出形状[b,192,26,26]
        y_M_prime = torch.cat((M_prime,y_exp),dim=1)# 输出形状[b,192,26,26]
        # 计算局部互信息---互信息的计算
        Ej = -F.softplus(-self.local_d(y_M)).mean() # 联合分布
        Em = F.softplus(self.local_d(y_M_prime)).mean() # 边缘分布
        LOCAL = (Em - Ej) * self.beta # 最大化互信息---对互信息执行了取反操作。将最大化问题变为最小化问题,在训练过程中,可以使用最小化损失的方法进行处理。
        # 计算全局互信息---互信息的计算
        Ej = -F.softplus(-self.GlobalD(y, M)).mean() # 联合分布
        Em = F.softplus(self.GlobalD(y, M_prime)).mean() # 边缘分布
        GLOBAL = (Em - Ej) * self.alpha # 最大化互信息---对互信息执行了取反操作。将最大化问题变为最小化问题,在训练过程中,可以使用最小化损失的方法进行处理。
        # 计算先验损失
        prior = torch.rand_like(y) # 获得随机数
        term_a = torch.log(self.prior_d(prior)).mean() # GAN损失
        term_b = torch.log(1.0 - self.prior_d(y)).mean()
        PRIOR = -(term_a + term_b) * self.gamma # 最大化目标分布---实现了判别器的损失函数。判别器的目标是将真实数据和生成数据的分布最大化,因此,也需要取反,通过最小化损失的方法来实现。
        return LOCAL + GLOBAL + PRIOR

# #### 在训练过程中,梯度可以通过损失函数直接传播到编码器模型,进行联合优化,因此,不需要对编码器额外进行损失函数的定义!

# 1.3 实例化DIM模型并训练:实例化模型按照指定次数迭代训练。在制作边缘分布样本时,将批次特征图的第1条放到最后,以使特征图与特征向量无法对应,实现与按批次打乱顺序等同的效果。
totalepoch = 10 # 指定训练次数
if __name__ == '__main__':
    encoder =Encoder().to(device)
    loss_fn = DeepInfoMaxLoss().to(device)
    optim = Adam(encoder.parameters(),lr=1e-4)
    loss_optim = Adam(loss_fn.parameters(),lr=1e-4)

    epoch_loss = []
    for epoch in range(totalepoch +1):
        batch = tqdm(train_loader,total=len(train_dataset)//batch_size)
        train_loss = []
        for x,target in batch: # 遍历数据集
            x = x.to(device)
            optim.zero_grad()
            loss_optim.zero_grad()
            y,M = encoder(x) # 用编码器生成特征图和特征向量
            # 制作边缘分布样本
            M_prime = torch.cat((M[1:],M[0].unsqueeze(0)),dim=0)
            loss =loss_fn(y,M,M_prime) # 计算损失
            train_loss.append(loss.item())
            batch.set_description(str(epoch) + ' Loss:%.4f'% np.mean(train_loss[-20:]))
            loss.backward()
            optim.step() # 调用编码器优化器
            loss_optim.step() # 调用判别器优化器
        if epoch % 10 == 0 : # 保存模型
            root = Path(r'./DIMmodel/')
            enc_file = root / Path('encoder' + str(epoch) + '.pth')
            loss_file = root / Path('loss' + str(epoch) + '.pth')
            enc_file.parent.mkdir(parents=True, exist_ok=True)
            torch.save(encoder.state_dict(), str(enc_file))
            torch.save(loss_fn.state_dict(), str(loss_file))
        epoch_loss.append(np.mean(train_loss[-20:])) # 收集训练损失
    plt.plot(np.arange(len(epoch_loss)), epoch_loss, 'r') # 损失可视化
    plt.show()

4.2モデルをロードします:DIM_CIRFAR_loadpath.py

import torch
import torch.nn.functional as F
from tqdm import tqdm
import random

# 功能介绍:载入编码器模型,对样本集中所有图片进行编码,随机取一张图片,找出与该图片最接近与最不接近的十张图片
#
# 引入本地库
#引入本地代码库
from DIM_CIRFAR_train import ( train_loader,train_dataset,totalepoch,device,batch_size,imshowrow, Encoder)

# 加载模型
model_path = r'./DIMmodel/encoder%d.pth'% (totalepoch)
encoder = Encoder().to(device)
encoder.load_state_dict(torch.load(model_path,map_location=device))

# 加载模型样本,并调用编码器生成特征向量
batchesimg = []
batchesenc = []
batch = tqdm(train_loader,total=len(train_dataset)//batch_size)
for images ,target in batch :
    images = images.to(device)
    with torch.no_grad():
        encoded,features = encoder(images) # 调用编码器生成特征向量
    batchesimg.append(images)
    batchesenc.append(encoded)
# 将样本中的图片与生成的向量沿第1维度展开
batchesenc = torch.cat(batchesenc,axis = 0)
batchesimg = torch.cat(batchesimg,axis = 0)
# 验证向量的搜索功能
index = random.randrange(0,len(batchesenc)) # 随机获取一个索引,作为目标图片
batchesenc[index].repeat(len(batchesenc),1) # 将目标图片的特征向量复制多份
# 使用F.mse_loss()函数进行特征向量间的L2计算,传入了参数reduction='none',这表明对计算后的结果不执行任何操作。如果不传入该参数,那么函数默认会对所有结果取平均值(常用在训练模型场景中)
l2_dis = F.mse_loss(batchesenc[index].repeat(len(batchesenc),1),batchesenc,reduction='none').sum(1) # 计算目标图片与每个图片的L2距离
findnum = 10 # 设置查找图片的个数
# 使用topk()方法获取L2距离最近、最远的图片。该方法会返回两个值,第一个是真实的比较值,第二个是该值对应的索引。
_,indices = l2_dis.topk(findnum,largest=False ) # 查找10个最相近的图片
_,indices_far = l2_dis.topk(findnum,) # 查找10个最不相关的图片
# 显示结果
indices = torch.cat([torch.tensor([index]).to(device),indices])
indices_far = torch.cat([torch.tensor([index]).to(device),indices_far])
rel = torch.cat([batchesimg[indices],batchesimg[indices_far]],axis = 0)
imshowrow(rel.cpu() ,nrow=len(indices))
# 结果显示:结果有两行,每行的第一列是目标图片,第一行是与目标图片距离最近的搜索结果,第二行是与目标图片距离最远的搜索结果。

おすすめ

転載: blog.csdn.net/qq_39237205/article/details/123791810
おすすめ