生成模型實戰 | GLOW詳解與實現

生成模型實戰 | GLOW詳解與實現

    • 0. 前言
    • 1. 歸一化流模型
      • 1.1 歸一化流與變換公式
      • 1.2 RealNVP 的通道翻轉
    • 2. GLOW 架構
      • 2.1 ActNorm
      • 2.2 可逆 1×1 卷積
      • 2.3 仿射耦合層
      • 2.4 多尺度架構
    • 3. 使用 PyTorch 實現 GLOW
      • 3.1 數據處理
      • 3.2 模型構建
      • 3.3 模型訓練

0. 前言

GLOW (Generative Flow) 是一種基于歸一化流的生成模型,通過在每個流步驟中引入可逆的 1 × 1 卷積層,替代了 RealNVP 中通道翻轉或固定置換的策略,從而使通道重排更具表達力,同時保持雅可比行列式和逆變換的高效計算能力。本文首先回顧歸一化流與 RealNVP 的基本原理,接著剖析 GLOW 的四大核心模塊:ActNorm、可逆 1×1 卷積、仿射耦合層和多尺度架構,隨后基于 PyTorch 實現 GLOW 模型,并在 CIFAR-10 數據集上進行訓練。

1. 歸一化流模型

1.1 歸一化流與變換公式

在本節中,我們首先簡要回顧歸一化流模型的核心原理,歸一化流利用可逆映射 fff 將簡單分布 pZ(z)p_Z(z)pZ?(z) 轉換到樣本分布 pX?(x)p_X?(x)pX??(x),并通過以下變換公式實現實現精確對數似然計算和采樣:
pX(x)=pZ(f(x))∣?det?(??f(x)?x)∣?p_X(x)=p_Z(f(x))?|\text{?det}? (?\frac {?f(x)}{?x})| ?pX?(x)=pZ?(f(x))?∣?det?(??x?f(x)?)∣?

1.2 RealNVP 的通道翻轉

RealNVP 通過交替使用掩碼耦合層 (masking coupling) 和按通道翻轉 (reverse channels) 或固定置換,保證每個通道都能被多次變換。

2. GLOW 架構

2.1 ActNorm

ActNorm 是一種專為流模型設計的通道級歸一化方法,于 GLOW 中首次提出。該層對輸入激活 xxx 執行可學習仿射變換:
y=s⊙x+by=s⊙x+b y=sx+b
其中 s,b∈RCs,b∈\mathbb R^Cs,bRC 分別為每個通道的尺度與偏移參數。這些參數在首次前向傳播時通過對一個 minibatch 計算輸出通道的均值 μμμ 和標準差 σσσ 進行數據依賴的初始化,使得初始化后的 yyy 滿足 E[y]=0\mathbb E[y]=0E[y]=0Var[y]=1Var[y]=1Var[y]=1
與批歸一化不同,ActNorm 僅在初始化時依賴 minibatch,之后無需維護運行時統計量,從而提升了小批數據和解耦訓練的穩定性。

import torch.nn as nn
import torchdef mean_dim(tensor, dim=None, keepdims=False):if dim is None:return tensor.mean()else:if isinstance(dim, int):dim = [dim]dim = sorted(dim)for d in dim:tensor = tensor.mean(dim=d, keepdim=True)if not keepdims:for i, d in enumerate(dim):tensor.squeeze_(d-i)return tensorclass ActNorm(nn.Module):def __init__(self, num_features, scale=1., return_ldj=False):super(ActNorm, self).__init__()self.register_buffer('is_initialized', torch.zeros(1))self.bias = nn.Parameter(torch.zeros(1, num_features, 1, 1))self.logs = nn.Parameter(torch.zeros(1, num_features, 1, 1))self.num_features = num_featuresself.scale = float(scale)self.eps = 1e-6self.return_ldj = return_ldjdef initialize_parameters(self, x):if not self.training:returnwith torch.no_grad():bias = -mean_dim(x.clone(), dim=[0, 2, 3], keepdims=True)v = mean_dim((x.clone() + bias) ** 2, dim=[0, 2, 3], keepdims=True)logs = (self.scale / (v.sqrt() + self.eps)).log()self.bias.data.copy_(bias.data)self.logs.data.copy_(logs.data)self.is_initialized += 1.def _center(self, x, reverse=False):if reverse:return x - self.biaselse:return x + self.biasdef _scale(self, x, sldj, reverse=False):logs = self.logsif reverse:x = x * logs.mul(-1).exp()else:x = x * logs.exp()if sldj is not None:ldj = logs.sum() * x.size(2) * x.size(3)if reverse:sldj = sldj - ldjelse:sldj = sldj + ldjreturn x, sldjdef forward(self, x, ldj=None, reverse=False):if not self.is_initialized:self.initialize_parameters(x)if reverse:x, ldj = self._scale(x, ldj, reverse)x = self._center(x, reverse)else:x = self._center(x, reverse)x, ldj = self._scale(x, ldj, reverse)if self.return_ldj:return x, ldjreturn x

2.2 可逆 1×1 卷積

GLOW 中的可逆 1×1 卷積用一個 C×CC×CC×C 的可學習矩陣 WWW 取代了 RealNVP 中的固定通道翻轉或置換操作。在空間位置 (i,j)(i,j)(i,j) 上,其映射可寫為:
yi,j=Wxi,jy_{i,j}=W?x_{i,j} yi,j?=W?xi,j?
對應的對數行列式為:
log??det???∣?y?x∣=H×W×log∣?detW∣\text {log}\ \text{?det}???|\frac{?y}{?x}|=H×W×\text {log}|\text{?det}W| log??det???∣?x?y?=H×W×log?detW
其中 H,WH,WH,W 分別為空間高寬。
為了進一步加速行列式與逆矩陣的計算,通常將 WWW 參數化為 LU 分解形式,即 W=PLUW=PLUW=PLU,只需學習下三角矩陣 LLL 和上三角矩陣 UUU 的非對角元素,行列式則為 ∏iUii∏_iU_{ii}i?Uii?
通過這種可學習的通道重排,模型能夠自動挖掘最優的特征混合方式,從而在生成質量與訓練效率上均取得顯著提升。

import numpy as npclass InvConv(nn.Module):def __init__(self, num_channels):super(InvConv, self).__init__()self.num_channels = num_channels# Initialize with a random orthogonal matrixw_init = np.random.randn(num_channels, num_channels)w_init = np.linalg.qr(w_init)[0].astype(np.float32)self.weight = nn.Parameter(torch.from_numpy(w_init))def forward(self, x, sldj, reverse=False):ldj = torch.slogdet(self.weight)[1] * x.size(2) * x.size(3)if reverse:weight = torch.inverse(self.weight.double()).float()sldj = sldj - ldjelse:weight = self.weightsldj = sldj + ldjweight = weight.view(self.num_channels, self.num_channels, 1, 1)z = F.conv2d(x, weight)return z, sldj

2.3 仿射耦合層

仿射耦合層最早在 RealNVP 中提出,是 GLOW 中不可或缺的組成部分。該層將輸入 x∈RC×H×Wx∈\mathbb R^{C×H×W}xRC×H×W 沿通道維度劃分為兩部分 (xa,xb)(x_a,x_b)(xa?,xb?),并通過神經網絡生成尺度和平移參數保證了整個變換的可逆性:
(s,t)=NN(xb),ya=s(xb)⊙xa+t(xb),yb=xb(s,t)=NN(x_b),ya=s(x_b)⊙x_a+t(x_b),y_b=x_b (s,t)=NN(xb?),ya=s(xb?)xa?+t(xb?),yb?=xb?

其對數雅可比行列式可高效地計算為:
∑h,w,c∈alog?sc(xb[h,w])∑_{h,w,??c∈a}\text {log}?s_c(x_b[h,w]) h,w,??ca?log?sc?(xb?[h,w])
僅與輸出尺度參數 sss 的元素相加相關,計算復雜度隨輸入維度線性增長。

import torch.nn.functional as Fclass Coupling(nn.Module):def __init__(self, in_channels, mid_channels):super(Coupling, self).__init__()self.nn = NN(in_channels, mid_channels, 2 * in_channels)self.scale = nn.Parameter(torch.ones(in_channels, 1, 1))def forward(self, x, ldj, reverse=False):x_change, x_id = x.chunk(2, dim=1)st = self.nn(x_id)s, t = st[:, 0::2, ...], st[:, 1::2, ...]s = self.scale * torch.tanh(s)# Scale and translateif reverse:x_change = x_change * s.mul(-1).exp() - tldj = ldj - s.flatten(1).sum(-1)else:x_change = (x_change + t) * s.exp()ldj = ldj + s.flatten(1).sum(-1)x = torch.cat((x_change, x_id), dim=1)return x, ldjclass NN(nn.Module):def __init__(self, in_channels, mid_channels, out_channels,use_act_norm=False):super(NN, self).__init__()norm_fn = ActNorm if use_act_norm else nn.BatchNorm2dself.in_norm = norm_fn(in_channels)self.in_conv = nn.Conv2d(in_channels, mid_channels,kernel_size=3, padding=1, bias=False)nn.init.normal_(self.in_conv.weight, 0., 0.05)self.mid_norm = norm_fn(mid_channels)self.mid_conv = nn.Conv2d(mid_channels, mid_channels,kernel_size=1, padding=0, bias=False)nn.init.normal_(self.mid_conv.weight, 0., 0.05)self.out_norm = norm_fn(mid_channels)self.out_conv = nn.Conv2d(mid_channels, out_channels,kernel_size=3, padding=1, bias=True)nn.init.zeros_(self.out_conv.weight)nn.init.zeros_(self.out_conv.bias)def forward(self, x):x = self.in_norm(x)x = F.relu(x)x = self.in_conv(x)x = self.mid_norm(x)x = F.relu(x)x = self.mid_conv(x)x = self.out_norm(x)x = F.relu(x)x = self.out_conv(x)return x

2.4 多尺度架構

GLOW 延續了 RealNVP 的多尺度架構思想,通過分層的流步驟和因子化操作將中間表示逐級分解。整體模型由 LLL 個尺度 (level) 組成,每個尺度內部包含 KKK 次完整的流步驟 (step),每步依次執行 ActNorm、可逆 1×1 卷積和仿射耦合層。
在每個尺度結束時,先通過 squeeze 操作將特征圖空間大小減少至原像素的四分之一(同時通道數擴大四倍),然后使用 split 操作將部分通道因子化為潛變量 zzz,余下通道繼續進入下一級流。
這種多尺度分解在保持對數似然精度的同時,有效降低了計算與存儲開銷,并在不同尺度上捕捉圖像的全局與局部結構信息。

3. 使用 PyTorch 實現 GLOW

在本節中,使用 PyTorch 實現 GLOW,并在 CIFAR-10 數據集上進行訓練。

3.1 數據處理

torchvision.transformsCIFAR-10 圖像進行處理:

transform_train = transforms.Compose([transforms.RandomHorizontalFlip(),transforms.ToTensor()])transform_test = transforms.Compose([transforms.ToTensor()
])trainset = torchvision.datasets.CIFAR10(root='data', train=True, download=True, transform=transform_train)
trainloader = data.DataLoader(trainset, batch_size=batch_size, shuffle=True, num_workers=num_workers)testset = torchvision.datasets.CIFAR10(root='data', train=False, download=True, transform=transform_test)
testloader = data.DataLoader(testset, batch_size=batch_size, shuffle=False, num_workers=num_workers)

3.2 模型構建

基于 ActNorm、可逆 1×1 卷積、仿射耦合層和多尺度架構實現 GLOW 模型:

class Glow(nn.Module):def __init__(self, num_channels, num_levels, num_steps):super(Glow, self).__init__()# Use bounds to rescale images before converting to logits, not learnedself.register_buffer('bounds', torch.tensor([0.9], dtype=torch.float32))self.flows = _Glow(in_channels=4 * 3,  # RGB image after squeezemid_channels=num_channels,num_levels=num_levels,num_steps=num_steps)def forward(self, x, reverse=False):if reverse:sldj = torch.zeros(x.size(0), device=x.device)else:# Expect inputs in [0, 1]if x.min() < 0 or x.max() > 1:raise ValueError('Expected x in [0, 1], got min/max {}/{}'.format(x.min(), x.max()))# De-quantize and convert to logitsx, sldj = self._pre_process(x)x = squeeze(x)x, sldj = self.flows(x, sldj, reverse)x = squeeze(x, reverse=True)return x, sldjdef _pre_process(self, x):y = (x * 255. + torch.rand_like(x)) / 256.y = (2 * y - 1) * self.boundsy = (y + 1) / 2y = y.log() - (1. - y).log()# Save log-determinant of Jacobian of initial transformldj = F.softplus(y) + F.softplus(-y) \- F.softplus((1. - self.bounds).log() - self.bounds.log())sldj = ldj.flatten(1).sum(-1)return y, sldjclass _Glow(nn.Module):def __init__(self, in_channels, mid_channels, num_levels, num_steps):super(_Glow, self).__init__()self.steps = nn.ModuleList([_FlowStep(in_channels=in_channels,mid_channels=mid_channels)for _ in range(num_steps)])if num_levels > 1:self.next = _Glow(in_channels=2 * in_channels,mid_channels=mid_channels,num_levels=num_levels - 1,num_steps=num_steps)else:self.next = Nonedef forward(self, x, sldj, reverse=False):if not reverse:for step in self.steps:x, sldj = step(x, sldj, reverse)if self.next is not None:x = squeeze(x)x, x_split = x.chunk(2, dim=1)x, sldj = self.next(x, sldj, reverse)x = torch.cat((x, x_split), dim=1)x = squeeze(x, reverse=True)if reverse:for step in reversed(self.steps):x, sldj = step(x, sldj, reverse)return x, sldjclass _FlowStep(nn.Module):def __init__(self, in_channels, mid_channels):super(_FlowStep, self).__init__()# Activation normalization, invertible 1x1 convolution, affine couplingself.norm = ActNorm(in_channels, return_ldj=True)self.conv = InvConv(in_channels)self.coup = Coupling(in_channels // 2, mid_channels)def forward(self, x, sldj=None, reverse=False):if reverse:x, sldj = self.coup(x, sldj, reverse)x, sldj = self.conv(x, sldj, reverse)x, sldj = self.norm(x, sldj, reverse)else:x, sldj = self.norm(x, sldj, reverse)x, sldj = self.conv(x, sldj, reverse)x, sldj = self.coup(x, sldj, reverse)return x, sldjdef squeeze(x, reverse=False):b, c, h, w = x.size()if reverse:# Unsqueezex = x.view(b, c // 4, 2, 2, h, w)x = x.permute(0, 1, 4, 2, 5, 3).contiguous()x = x.view(b, c // 4, h * 2, w * 2)else:# Squeezex = x.view(b, c, h // 2, 2, w // 2, 2)x = x.permute(0, 1, 3, 5, 2, 4).contiguous()x = x.view(b, c * 2 * 2, h // 2, w // 2)return x

3.3 模型訓練

實例化模型、損失函數和優化器,并進行訓練:

net = Glow(num_channels=num_channels,num_levels=num_levels,num_steps=num_steps)
net = net.to(device)loss_fn = NLLLoss().to(device)
optimizer = optim.Adam(net.parameters(), lr=lr)
scheduler = sched.LambdaLR(optimizer, lambda s: min(1., s / warm_up))@torch.enable_grad()
def train(epoch, net, trainloader, device, optimizer, scheduler, loss_fn, max_grad_norm):global global_stepprint('\nEpoch: %d' % epoch)net.train()loss_meter = AverageMeter()with tqdm(total=len(trainloader.dataset)) as progress_bar:for x, _ in trainloader:x = x.to(device)optimizer.zero_grad()z, sldj = net(x, reverse=False)loss = loss_fn(z, sldj)loss_meter.update(loss.item(), x.size(0))loss.backward()if max_grad_norm > 0:clip_grad_norm(optimizer, max_grad_norm)optimizer.step()scheduler.step(global_step)progress_bar.set_postfix(nll=loss_meter.avg,bpd=bits_per_dim(x, loss_meter.avg),lr=optimizer.param_groups[0]['lr'])progress_bar.update(x.size(0))global_step += x.size(0)@torch.no_grad()
def sample(net, batch_size, device):z = torch.randn((batch_size, 3, 32, 32), dtype=torch.float32, device=device)x, _ = net(z, reverse=True)x = torch.sigmoid(x)return x@torch.no_grad()
def test(epoch, net, testloader, device, loss_fn, num_samples):global best_lossnet.eval()loss_meter = AverageMeter()with tqdm(total=len(testloader.dataset)) as progress_bar:for x, _ in testloader:x = x.to(device)z, sldj = net(x, reverse=False)loss = loss_fn(z, sldj)loss_meter.update(loss.item(), x.size(0))progress_bar.set_postfix(nll=loss_meter.avg,bpd=bits_per_dim(x, loss_meter.avg))progress_bar.update(x.size(0))# Save checkpointif loss_meter.avg < best_loss:print('Saving...')state = {'net': net.state_dict(),'test_loss': loss_meter.avg,'epoch': epoch,}os.makedirs('ckpts', exist_ok=True)torch.save(state, 'ckpts/best.pth.tar')best_loss = loss_meter.avg# Save samples and dataimages = sample(net, num_samples, device)os.makedirs('samples', exist_ok=True)images_concat = torchvision.utils.make_grid(images, nrow=int(num_samples ** 0.5), padding=2, pad_value=255)torchvision.utils.save_image(images_concat, 'samples/epoch_{}.png'.format(epoch))start_epoch = 0
for epoch in range(start_epoch, start_epoch + num_epochs):train(epoch, net, trainloader, device, optimizer, scheduler,loss_fn, max_grad_norm)test(epoch, net, testloader, device, loss_fn, num_samples)

100epoch 后,模型可生成逼真度較高的 32 × 32 彩色圖像,樣本在多通道細節和整體結構上均有良好效果,下圖展示了訓練過程中,不同 epoch 生成的圖像對比:

生成結果

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/91262.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/91262.shtml
英文地址,請注明出處:http://en.pswp.cn/web/91262.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

行業案例:杰和科技為智慧教育構建數字化硬件底座

清晨8點10分&#xff0c;深圳某學生踏入校園&#xff0c;智慧門閘識別身份&#xff0c;并同步發給家長&#xff1b;走廊里的“智慧班牌”向他們展示今日的課表&#xff1b;課堂上&#xff0c;教室前方的多媒體播放器里&#xff0c;老師引導學生學習“居民樓消防隱患”知識&…

Redis與MySQL數據同步:從“雙寫一致性”到實戰方案

Redis與MySQL數據同步&#xff1a;從“雙寫一致性”到實戰方案 在分布式系統中&#xff0c;Redis作為高性能緩存被廣泛使用——它能將熱點數據從MySQL中“搬運”到內存&#xff0c;大幅降低數據庫壓力、提升接口響應速度。但隨之而來的核心問題是&#xff1a;當MySQL數據更新時…

Java源碼構建智能名片小程序

在移動互聯網時代&#xff0c;紙質名片的局限性日益凸顯——信息更新不便、客戶管理困難、營銷效果難以追蹤。智能電子名片小程序以其便捷、高效、智能的特點&#xff0c;正成為商務人士的"數字營銷門戶"。而基于Java技術棧開發的智能名片系統&#xff0c;憑借其穩定…

如何在短時間內顯著提升3D效果圖渲染速度?

在建筑設計、游戲開發、影視制作等行業&#xff0c;3D效果圖的渲染速度是項目進度與效率的關鍵瓶頸。面對復雜場景時&#xff0c;漫長的渲染等待尤為突出。要在保證質量的前提下大幅縮短渲染時間&#xff0c;以下優化策略至關重要&#xff1a; 1. 升級硬件配置&#xff1a;渲染…

配置daemon.json使得 Docker 容器能夠使用服務器GPU【驗證成功】

&#x1f947; 版權: 本文由【墨理學AI】原創首發、各位讀者大大、敬請查閱、感謝三連 文章目錄&#x1f50d;你遇到的錯誤&#xff1a;&#x1f50d; 根本原因? 解決方案&#xff1a;正確安裝 NVIDIA Container Toolkit? 第一步&#xff1a;卸載舊版本&#xff08;如果存在&…

Linux 系統進程管理與計劃任務詳解

Linux 系統進程管理與計劃任務詳解 一、程序與進程的基本概念 程序&#xff1a;保存在外部存儲介質中的可執行機器代碼和數據的靜態集合。進程&#xff1a;在CPU及內存中處于動態執行狀態的計算機程序。關系&#xff1a;每個程序啟動后&#xff0c;可創建一個或多個進程。 二、…

【圖像處理】直方圖均衡化c++實現

直方圖均衡化是一種通過調整圖像像素灰度值分布&#xff0c;來增強圖像對比度的經典數字圖像處理技術。其核心在于將原始圖像的灰度直方圖從集中的某個區間“拉伸”或“均衡”到更廣泛的區間&#xff0c;讓圖像的明暗細節更清晰&#xff0c;關鍵在于利用累積分布函數實現灰度值…

Web前端實戰:Vue工程化+ElementPlus

1.Vue工程化 1.1介紹 模塊化&#xff1a;將js和css等&#xff0c;做成一個個可復用模塊組件化&#xff1a;我們將UI組件&#xff0c;css樣式&#xff0c;js行為封裝成一個個的組件&#xff0c;便于管理規范化&#xff1a;我們提供一套標準的規范的目錄接口和編碼規范&#xff0…

ECMAScript2021(ES12)新特性

概述 ECMAScript2021于2021年6月正式發布&#xff0c; 本文會介紹ECMAScript2021(ES12)&#xff0c;即ECMAScript的第12個版本的新特性。 以下摘自官網&#xff1a;ecma-262 ECMAScript 2021, the 12th edition, introduced the replaceAll method for Strings; Promise.any,…

Tlias 案例-整體布局(前端)

開發流程前端開發和后端開發是一樣的&#xff0c;都需要閱讀接口文檔。 準備工作&#xff1a; 1&#xff1a;導入項目中準備的基礎過程到 VsCode。2&#xff1a;啟動前端項目&#xff0c;訪問該項目3&#xff1a;熟悉一下基本的布局<script setup></script><tem…

三十二、【Linux網站服務器】搭建httpd服務器演示虛擬主機配置、網頁重定向功能

httpd服務器功能演示一、虛擬主機配置虛擬主機技術全景虛擬主機目錄規范1. 基于端口的虛擬主機&#xff08;8080/8081&#xff09;2. 基于IP的虛擬主機&#xff08;192.168.1.100/192.168.1.101&#xff09;3. 基于域名的虛擬主機&#xff08;site1.com/site2.com&#xff09;二…

串行化:MYSQL事務隔離級別中的終極防護

在現代應用程序中&#xff0c;數據的一致性和可靠性至關重要。想象一下&#xff0c;如果在一個銀行系統中&#xff0c;兩個用戶同時試圖轉賬到同一個賬戶&#xff0c;最終的數據結果可能會出乎意料。為了避免這種情況&#xff0c;MYSQL提供了不同的事務隔離級別&#xff0c;其中…

RAG:檢索增強生成的范式演進、技術突破與前沿挑戰

1 核心定義與原始論文 RAG&#xff08;Retrieval-Augmented Generation&#xff09;由Facebook AI Research團隊于2020年提出&#xff0c;核心思想是將參數化記憶&#xff08;預訓練語言模型&#xff09;與非參數化記憶&#xff08;外部知識庫檢索&#xff09;結合&#xff0c…

2024年藍橋杯Scratch10月圖形化stema選拔賽真題——旋轉的圖形

旋轉的圖形編程實現旋轉的圖形。具體要求1&#xff09;點擊綠旗&#xff0c;在舞臺上出現滑桿形式的變量 r&#xff0c;取值范圍為-1、0、1&#xff0c;默認值為 0&#xff0c;如圖所示&#xff1b;2&#xff09;1秒后&#xff0c;在舞臺上繪制出一個紅色正方形&#xff08;邊長…

【音視頻】WebRTC 開發環境搭建-Web端

一、開發環境搭建 1.1 安裝vscode 下載VSCode&#xff1a;https://code.visualstudio.com/&#xff0c;下載后主要用于開發Web前端頁面&#xff0c;編寫前端代碼 安裝完成后下載Live Server插件&#xff0c;用于本地開發&#xff0c;實時加載前端頁面 1.1.1 前端代碼測試 下…

力扣54:螺旋矩陣

力扣54:螺旋矩陣題目思路代碼題目 給你一個 m 行 n 列的矩陣 matrix &#xff0c;請按照 順時針螺旋順序 &#xff0c;返回矩陣中的所有元素。 思路 思路很簡單創建一個二維數組然后按照箭頭所示的順序一層一層的給二維數組相應的位置賦值即可。難點是我們是一層一層的賦值…

【CSS】設置表格表頭固定

1.設置thead樣式在thead元素中增加樣式&#xff1a;position: sticky;top: 0;2.設置table樣式在table元素中增加樣式&#xff1a;border-collapse: separate; /* 分離邊框模式 */ border-spacing: 0;3.設置表頭偽元素樣式增加樣式&#xff1a;th::after {content: ;position: a…

Baumer工業相機堡盟工業相機如何通過YoloV8深度學習模型實現標簽條碼一維碼的檢測(C#代碼,UI界面版)

Baumer工業相機堡盟工業相機如何通過YoloV8深度學習模型實現標簽條碼一維碼的檢測&#xff08;C#代碼&#xff0c;UI界面版&#xff09;&#xff09;工業相機使用YoloV8模型實現標簽條碼一維碼的檢測工業相機通過YoloV8模型實現標簽條碼的檢測的技術背景在相機SDK中獲取圖像轉換…

如何編寫好的測試用例?

&#x1f345; 點擊文末小卡片 &#xff0c;免費獲取軟件測試全套資料&#xff0c;資料在手&#xff0c;漲薪更快對于軟件測試工程師來說&#xff0c;設計測試用例和提交缺陷報告是最基本的職業技能。是非常重要的部分。一個好的測試用例能夠指示測試人員如何對軟件進行測試。在…

《Java 程序設計》第 12 章 - 異常處理

大家好&#xff01;今天我們來學習《Java 程序設計》中的第 12 章 —— 異常處理。在編程過程中&#xff0c;錯誤和異常是不可避免的。一個健壯的程序必須能夠妥善處理各種異常情況。本章將詳細介紹 Java 中的異常處理機制&#xff0c;幫助大家編寫出更穩定、更可靠的 Java 程序…