第十课.图片风格迁移和GAN

2023-11-17

Neural Style Transfer

Neural Style Transfer原理

图片风格迁移,结合一张图片的内容和另一张图片的风格,生成一张新风格的图片(内容与第一张图片接近,风格与第二张图片接近);
fig1

在早期有一篇文章:A Neural Algorithm of Artistic Style实现了图片风格迁移;通过VGG作为特征提取工具,使用特征分别重新组合去表达图像的内容和风格,假设两张输入图像为content和style,输出图像为target,则目的是使target的内容与content接近,风格与style接近。
特征提取器使用训练自ImageNet的VGG19,网络结构如下:

VGG(
  (features): Sequential(
    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU(inplace)
    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU(inplace)
    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (6): ReLU(inplace)
    (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (8): ReLU(inplace)
    (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace)
    (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (13): ReLU(inplace)
    (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (15): ReLU(inplace)
    (16): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (17): ReLU(inplace)
    (18): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (19): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (20): ReLU(inplace)
    (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (22): ReLU(inplace)
    (23): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (24): ReLU(inplace)
    (25): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (26): ReLU(inplace)
    (27): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (29): ReLU(inplace)
    (30): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (31): ReLU(inplace)
    (32): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (33): ReLU(inplace)
    (34): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (35): ReLU(inplace)
    (36): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
  (classifier): Sequential(
    (0): Linear(in_features=25088, out_features=4096, bias=True)
    (1): ReLU(inplace)
    (2): Dropout(p=0.5)
    (3): Linear(in_features=4096, out_features=4096, bias=True)
    (4): ReLU(inplace)
    (5): Dropout(p=0.5)
    (6): Linear(in_features=4096, out_features=1000, bias=True)
  )
)

在论文中,vgg19.features.0,5,10,19,28层的输出张量是有用的,即一共包含5层特征,已知两个输入图像content: ( c , h , w ) (c,h,w) (c,h,w)和style: ( c , h , w ) (c,h,w) (c,h,w),以及隐式输入图像target: ( c , h , w ) (c,h,w) (c,h,w)target一般直接从content复制,三个图像的size均一致,然后并行输入VGG,分别返回0,5,10,19,28层的输出张量:

  • f c o n t e n t f_{content} fcontent:vgg(content),content features,共5层特征;
  • f s t y l e f_{style} fstyle:vgg(style),style features,共5层特征;
  • f t a r g e t f_{target} ftarget:vgg(target),target features,共5层特征;

使用这5层特征计算图像内容损失content loss:
c o n t e n t l o s s = ∑ l = 1 5 m e a n [ ( f c o n t e n t , l − f t a r g e t , l ) 2 ] contentloss=\sum_{l=1}^{5}mean[(f_{content,l}-f_{target,l})^{2}] contentloss=l=15mean[(fcontent,lftarget,l)2]
对于风格损失style loss,需要先设计一种可以表达风格的方式,在论文中,使用格拉姆矩阵Gram matrix表达风格,例如,对于 f s t y l e f_{style} fstyle,取出第 l l l层的特征 f s t y l e , l f_{style,l} fstyle,l,形状为 ( 1 , c h a n n e l , h e i g h t , w i d t h ) (1,channel,height,width) (1,channel,height,width),reshape到 ( c h a n n e l , h e i g h t × w i d t h ) (channel,height\times width) (channel,height×width),则该层特征 f s t y l e , l f_{style,l} fstyle,l对应的风格即Gram matrix为:
g r a m s t y l e , l = f s t y l e , l ⋅ f s t y l e , l T gram_{style,l}=f_{style,l}\cdot f_{style,l}^{T} gramstyle,l=fstyle,lfstyle,lT
显然, g r a m s t y l e , l gram_{style,l} gramstyle,l的形状为 ( c h a n n e l , c h a n n e l ) (channel,channel) (channel,channel),基于格拉姆矩阵表达的风格,可以计算风格的损失style loss为:
s t y l e l o s s = ∑ l = 1 5 m e a n [ ( g r a m s t y l e , l − g r a m t a r g e t , l ) 2 ] c h a n n e l l ⋅ h e i g h t l ⋅ w i d t h l styleloss=\sum_{l=1}^{5}\frac{mean[(gram_{style,l}-gram_{target,l})^{2}]}{channel_{l}\cdot height_{l}\cdot width_{l}} styleloss=l=15channellheightlwidthlmean[(gramstyle,lgramtarget,l)2]
因此,整体损失为内容损失和风格损失之和,为了平衡数量级,为各项损失增加权重:
l o s s = c o n t e n t l o s s + 100 s t y l e l o s s loss=contentloss+100styleloss loss=contentloss+100styleloss


Gram matrix
向量内积反映相似程度,而格拉姆矩阵由各个向量内积组成,反映了各个向量的内在联系:
fig2某一层特征reshape为 ( c h a n n e l , h e i g h t × w i d t h ) (channel,height\times width) (channel,height×width),即上图左部的张量,在第七课提到,卷积网络是一种局部特征提取器,输出张量的通道数等于该层CNN的滤波器个数,输出的张量的一个通道实际上代表一类特征,因此,格拉姆矩阵其实反映了各个特征之间的联系,从风格角度看,各个特征之间正是通过风格关联起来的;
因此,格拉姆矩阵表达出了特征中间隐藏的风格信息


准备工作

首先导入必要的包和模块,from __future__ import division是精确除法(python2中/代表整除,导入division才能操作精确除法),在python3中其实可以省略,相关说明见python记事本

from __future__ import division
from torchvision import models
from torchvision import transforms
from PIL import Image
import argparse
import torch
import torchvision
import torch.nn as nn

import numpy as np
import matplotlib.pyplot as plt

device=torch.device("cuda" if torch.cuda.is_available() else "cpu")

定义读取图片的函数load_image

# 读取图片
def load_image(image_path,transform=None,max_size=None,shape=None):
    # 读入图片并转为3通道
    image=Image.open(image_path).convert('RGB')
    if max_size:
        scale=max_size/max(image.size)
        size=np.array(image.size)*scale
        # ndarray.astype(T):复制ndarray,再转换到类型T
        # Image.ANTIALIAS:平滑
        image=image.resize(size.astype(int),Image.ANTIALIAS)
        
    if shape:
        # Image.LANCZOS:一种插值方法
        image=image.resize(shape,Image.LANCZOS)
        
    if transform:
        # 进行transform
        image=transform(image)
        # 增加batch维度
        image=image.unsqueeze(dim=0)
        
    return image.to(device)

transform=transforms.Compose([
    transforms.ToTensor(),
    # 由于VGG在ImageNet上预训练,所以使用ImageNet的标准化参数去标准化
    # 有利于模型学习的稳定性
    transforms.Normalize(mean=[0.485,0.456,0.406],
                         std=[0.229,0.224,0.225])
])


content=load_image("content.png",
                   transform=transform,
                   max_size=400)
print(content.size())

# 注意image.resize中参数shape与张量size的对应关系
# 张量size为(c,h,w)
style=load_image("style.png",
                 transform=transform,
                 shape=[content.size(3),content.size(2)])
print(style.size())

"""
torch.Size([1, 3, 301, 400])
torch.Size([1, 3, 301, 400])
"""

另外,定义张量可视化函数imshow

unloader=transforms.ToPILImage()

def imshow(tensor:"(N,C,H,W)",title=None):
    image=tensor.cpu().clone()
    # 去除batch维度
    image=image.squeeze(dim=0)
    image=unloader(image)
    
    plt.figure()
    plt.imshow(image)
    if title is not None:
        plt.title(title)
    plt.show()

imshow(content,title="content")
imshow(style,title="style")

fig3

定义模型并加载预训练的模型参数

在Neural Style Transfer的任务中,并不需要训练网络,网络仅仅是特征提取工具,所以直接加载ImageNet预训练的vgg19参数即可,模型定义如下,所做工作仅仅是获取指定的5层输出特征:

class VGGnet(nn.Module):
    def __init__(self,model_state_path=None):
        super().__init__()
        
        vgg19=models.vgg19(pretrained=False)
        # 模型及对应参数文件在文档里找:
        # https://github.com/pytorch/vision/tree/master/torchvision/models
        if model_state_path:
            vgg19.load_state_dict(torch.load(model_state_path))
            
        # 论文中,vgg19.features.0,5,10,19,28层是有用的
        self.select=['0','5','10','19','28']
        self.vgg=vgg19.features
        
    def forward(self,x):
        features=[]
        
        # _modules返回元素有序的字典
        for name,layer in self.vgg._modules.items():
            x=layer(x)
            if name in self.select:
                features.append(x)
            
        return features

来自torchvision的预训练模型及对应参数可以在torchvision/models里查找:

model_urls = {
    'vgg11': 'https://download.pytorch.org/models/vgg11-bbd30ac9.pth',
    'vgg13': 'https://download.pytorch.org/models/vgg13-c768596a.pth',
    'vgg16': 'https://download.pytorch.org/models/vgg16-397923af.pth',
    'vgg19': 'https://download.pytorch.org/models/vgg19-dcbb9e9d.pth',
    'vgg11_bn': 'https://download.pytorch.org/models/vgg11_bn-6002323d.pth',
    'vgg13_bn': 'https://download.pytorch.org/models/vgg13_bn-abd245e5.pth',
    'vgg16_bn': 'https://download.pytorch.org/models/vgg16_bn-6c64b313.pth',
    'vgg19_bn': 'https://download.pytorch.org/models/vgg19_bn-c79401a0.pth',
}

实例化模型并加载预训练参数:

model=VGGnet(
    model_state_path="./vgg19-dcbb9e9d.pth"
    ).to(device)

对图像content进行特征提取如下:

model.eval()
features=model.forward(content)

可以打印查看收集的5层特征:

for feat in features:
    print(feat.size())

"""
torch.Size([1, 64, 301, 400])
torch.Size([1, 128, 150, 200])
torch.Size([1, 256, 75, 100])
torch.Size([1, 512, 37, 50])
torch.Size([1, 512, 18, 25])
"""

训练target以及结果可视化

target为风格迁移后的图像,注意,论文的做法不同于以往的模型训练,使用ImageNet训练的VGG作为特征提取器,真正优化的是target张量,所以要设置target可计算梯度:

# clone()返回的tensor是非叶子节点(有计算图连接)
target=content.clone().requires_grad_(True)

选择优化方法为Adam,梯度更新对象为target:

optimizer=torch.optim.Adam([target],
                           lr=0.003,
                           betas=[0.5,0.999])

优化target:

# 优化target
num_step=2000
for step in range(num_step):
    """
    每张图像前向计算后得到特征列表
    torch.Size([1, 64, 301, 400])
    torch.Size([1, 128, 150, 200])
    torch.Size([1, 256, 75, 100])
    torch.Size([1, 512, 37, 50])
    torch.Size([1, 512, 18, 25])
    """
    target_features=model.forward(target)
    content_features=model.forward(content)
    style_features=model.forward(style)
    
    # loss = content loss + style loss
    style_loss=0
    content_loss=0
    
    for f1,f2,f3 in zip(target_features,content_features,style_features):
        # 每层的f1,f2,f3形状均为(n,c,h,w), n=1
        content_loss+=torch.mean((f1-f2)**2) # a number
             
        # 很明显本实验batch_size=1,n为1
        n,c,h,w = f1.size()
        f1 = f1.view(c, h*w)
        f3 = f3.view(c, h*w)
        
        # 计算gram matrix
        # torch.mm 是严格的矩阵乘法
        f1 = torch.mm(f1, f1.t()) # (c,c)
        f3 = torch.mm(f3, f3.t()) # (c,c)

        # 由target的gram matrix和style的gram matrix计算style loss
        style_loss+=torch.mean((f1-f3)**2)/(c*h*w)
        
    # 加上权重平衡数量级
    loss=content_loss+style_loss*100.
    
    # 梯度清零
    optimizer.zero_grad()
    
    # 反向传播计算梯度
    loss.backward()
    
    # 更新张量target
    optimizer.step()
    
    if step % 10 == 0:
        print("Step [{}/{}], Content Loss: {:.4f}, Style Loss: {:.4f}".format(step,
                                                                              num_step,
                                                                              content_loss.item(),
                                                                              style_loss.item())) 

优化后的target本身还处在标准化的分布上(注意,content与style采用的是相同参数进行标准化),所以务必要采取反标准化(标准化还原),标准化还原的参数计算回顾第九课中的标准化与反标准化部分,计算出反标准化参数为:均值[-2.12, -2.04, -1.80],标准差[4.37, 4.46, 4.44]
分别可视化反标准化前后的target为:

# 还原Normalize
denorm=transforms.Normalize([-2.12, -2.04, -1.80],
                            [4.37, 4.46, 4.44])

# 去除batch维度
img=target.clone().squeeze()
# 可视化反标准化前的target
imshow(img)

# ToPILImage接收的tensor值在0到1
img=denorm(img).clamp_(0,1)

imshow(img,title="target")

fig4

生成对抗网络GAN

GAN原理

GAN即Generative Adversarial Network,GAN由两部分组成,一个是生成器Generator,另一个是判别器Discriminator:

  • Generator:从隐式空间latent space映射出生成数据,并让生成数据的分布接近真实数据;
  • Discriminator:分类器,鉴别真实数据与Generator生成的伪造数据;

GAN的训练不需要在训练前给数据集进行标注,属于无监督学习:
fig5


使用二元交叉熵 B C E L o s s ( x , y ) BCELoss(x,y) BCELoss(x,y)计算损失时,对于某个样本 i i i,损失为:
J i = − [ y i l o g x i + ( 1 − y i ) l o g ( 1 − x i ) ] J_{i}=-[y_{i}logx_{i}+(1-y_{i})log(1-x_{i})] Ji=[yilogxi+(1yi)log(1xi)]
其中, y i y_{i} yi为样本的真实标签(0或1), x i x_{i} xi为网络输出的概率值;


GAN的训练过程如下,假设使用二元交叉熵计算损失:

  • 首先可以随机生成latent space,使用生成器 G G G生成伪造数据 f a k e i m a g e s fakeimages fakeimages,设置标签 f a k e l a b e l s fakelabels fakelabels为0,代表伪造数据;判别器 D D D从真实数据 i m a g e s images images中采样,真实数据的标签 r e a l l a b e l s reallabels reallabels设置为1,代表真实数据,判别器(二分类器)通过数据进行前向计算,即得到:
    f a k e o u t p u t s = D ( f a k e i m a g e s ) fakeoutputs=D(fakeimages) fakeoutputs=D(fakeimages)
    r e a l o u t p u t s = D ( i m a g e s ) realoutputs=D(images) realoutputs=D(images)
    损失计算为:
    l o s s d = B C E L o s s ( f a k e o u t p u t s , f a k e l a b e l s ) + B C E L o s s ( r e a l o u t p u t s , r e a l l a b e l s ) loss_{d}=BCELoss(fakeoutputs,fakelabels)+BCELoss(realoutputs,reallabels) lossd=BCELoss(fakeoutputs,fakelabels)+BCELoss(realoutputs,reallabels)
    基于 l o s s d loss_{d} lossd计算梯度,并更新判别器 D D D
  • 在训练完判别器后,继续使用之前生成器生成的数据 f a k e i m a g e s fakeimages fakeimages,传给判别器进行前向计算:
    g o u t p u t s = D ( f a k e i m a g e s ) goutputs=D(fakeimages) goutputs=D(fakeimages)
    与训练判别器不同,目的是使生成器学会生成逼真的数据从而欺骗判别器,所以损失计算为:
    l o s s g = B C E L o s s ( g o u t p u t s , r e a l l a b e l s ) loss_{g}=BCELoss(goutputs,reallabels) lossg=BCELoss(goutputs,reallabels)
    基于 l o s s g loss_{g} lossg计算梯度,并更新生成器 G G G
  • 重复上述步骤;

GAN生成Mnist

准备工作

导入必要的包和模块:

import torch
import torch.nn as nn

import torchvision
from torchvision import transforms

import matplotlib.pyplot as plt
import numpy as np

device=torch.device("cuda" if torch.cuda.is_available() else "cpu")

加载mnist数据集:

# 加载mnist
batch_size=32
transform=transforms.Compose([
    transforms.ToTensor(),
])

# mnistdata[i]有两个对象,第一个代表图片本身,第二个代表图片所属类别
mnistdata=torchvision.datasets.MNIST("./DataSet",train=True,transform=transform)

使用dataloader:

dataloader=torch.utils.data.DataLoader(dataset=mnistdata,
                                      batch_size=batch_size,
                                      shuffle=True)

使用dataloader获取一个batch,从batch选一张图像可视化:

batch=next(iter(dataloader)) # batch[0]保存images,batch[1]保存labels

batch_images=batch[0] # [batch_size,1,28,28]
print(batch_images.size())
# torch.Size([32, 1, 28, 28])

image=batch_images[6]

unloader=transforms.ToPILImage()
image=unloader(image)    
plt.figure()
# imshow接收数组或PIL图像,tensor是(c,h,w),PIL是(h,w,c)
# 当imshow(X)中X为(M,N)时,以热力图(彩色)形式绘制
plt.imshow(image)    
plt.show()

注意plt.inshow(),接收数组或PIL图像,当imshow(X)X ( M , N ) (M,N) (M,N)时,以热力图(彩色)形式绘制:
fig6


tensor与PILImage的形状区别
tensor是 ( c , h , w ) (c,h,w) (c,h,w),PIL是 ( h , w , c ) (h,w,c) (h,w,c)


模型定义

对于mnist这样的简单数据,使用简单的全连接网络即可达到效果,判别器定义如下:

image_size=28*28
hidden_size=256

D=nn.Sequential(
    nn.Linear(image_size,hidden_size),
    nn.LeakyReLU(0.2),
    nn.Linear(hidden_size,hidden_size),
    nn.LeakyReLU(0.2),
    # 二分类
    nn.Linear(hidden_size,1),
    nn.Sigmoid()
).to(device)

注意到判别器使用了LeakyReLU进行非线性变换:
L e a k R e L U ( x ) = m a x ( 0 , x ) + s l o p e × m i n ( 0 , x ) LeakReLU(x)=max(0,x)+slope\times min(0,x) LeakReLU(x)=max(0,x)+slope×min(0,x)
pytorch中,函数为:

torch.nn.LeakyReLU(negative_slope=0.01, inplace=False)

如果inplace=True,则对于张量XLeakyReLU(X)会同步改变X,即inplace操作;

对于生成器定义,注意,由于真实样本ToTensor后的值在0到1之间,所以生成器产生的样本也需要用sigmoid映射到0到1之间:

# 生成器:从LatentSpace开始
latent_size=64

G=nn.Sequential(
    nn.Linear(latent_size,hidden_size),
    nn.ReLU(),
    nn.Linear(hidden_size,hidden_size),
    nn.ReLU(),
    nn.Linear(hidden_size,image_size),
    # 由于真实样本ToTensor后的值在0到1之间,所以生成器产生的样本也需要用sigmoid映射到0到1之间
    nn.Sigmoid()
).to(device)

训练与可视化

使用二元交叉熵计算损失,并选择Adam为优化方法:

# Binary Cross Entropy Loss
loss_fn=nn.BCELoss()

d_optimizer=torch.optim.Adam(D.parameters(),lr=0.0002)
g_optimizer=torch.optim.Adam(G.parameters(),lr=0.0002)

定义可视化函数,用于可视化生成器生成的数据,隐式空间为 z z z,则生成器生成的数据为 G ( z ) G(z) G(z)

# G(z)的可视化
def gzimshow(Gen:"Generator model",title=None):
    latent_space = torch.randn(1, latent_size).to(device)
    image = Gen(latent_space).view(1, 28, 28).cpu()
    image=unloader(image)
    
    plt.figure()
    plt.imshow(image)
    if title is not None:
        plt.title(title)
    plt.show()

训练判别器与生成器:

# 训练
total_steps=len(dataloader)
num_epochs=100

for epoch in range(num_epochs):
    for i,( images, _ ) in enumerate(dataloader):
        images=images.view(batch_size,image_size).to(device)
        
        real_labels=torch.ones(batch_size,1).to(device)
        fake_labels=torch.zeros(batch_size,1).to(device)
        
        real_outputs=D(images)
        
        # 真实图片的损失
        d_loss_real=loss_fn(real_outputs,real_labels)
        
        # 生成 fake images
        # latent space
        z=torch.randn(batch_size,latent_size).to(device)
        fake_images=G(z)
        
        # 将G的输出从计算图中剥离,避免在训练D时涉及到G的梯度
        fake_outputs=D(fake_images.detach())
        
        d_loss_fake=loss_fn(fake_outputs,fake_labels)
        
        d_loss=d_loss_real+d_loss_fake
        
        # 更新D
        d_optimizer.zero_grad()
        d_loss.backward()
        d_optimizer.step()
        
        #更新G
        # 不能detach,因为要追踪G的梯度
        g_outputs=D(fake_images)
        g_loss=loss_fn(g_outputs,real_labels)
        
        # 由于D还在计算图中,所以需要将D的梯度置0
        d_optimizer.zero_grad()
        g_optimizer.zero_grad()
        g_loss.backward()
        g_optimizer.step()
        
        # 一般来说 G loss 越小越好
        if i%200==0:
            print("Epoch[{}/{}],Step[{}/{}],D_loss:{},G_loss:{},D(x):{},D(G(z)):{}".format(
                epoch,num_epochs,i,total_steps,
                d_loss.item(),
                g_loss.item(),
                real_outputs.mean().item(),
                fake_outputs.mean().item()
            ))    
            
        if epoch%20==0 and i%total_steps==0:
            gzimshow(G,"Epoch[{}/{}],Step[{}/{}]".format(epoch,num_epochs,i,total_steps))

gzimshow直观反映了生成器生成的图像越来越真实:
fig7在训练结束后,使用生成器生成的mnsit图像如下:

gzimshow(G)

fig8

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

第十课.图片风格迁移和GAN 的相关文章

  • SpringToolSuite4中maven不能创建项目,创建后没有maven dependence,以及gradle创建后不能使用,更改阿里云仓库

    SpringToolSuite4中maven不能创建项目 创建后没有maven dependence gradle创建后不能使用 更改阿里云仓库 感慨一下 maven 配置指南 gradle 配置指南 感慨一下 其实这个问题很无语 搞了好几
  • 匈牙利匹配

    文章目录 不带权重的二分图匹配 算法核心 代码示例 带权重的二分图匹配 算法步骤 算法核心 如何用最少的直线覆盖矩阵中的全部0元素 如何调整矩阵 代码实例 参考 不带权重的二分图匹配 算法核心 把冲突节点的原先匹配的左节点重新连接到它的未被
  • ceph分布式存储集群部署及应用

    分布式文件系统 Distributed File System 分布式文件系统是指文件系统管理的物理存储资源不一定直接连接在本地节点上 而是通过计算机网络与节点相连 分布式文件系统的设计基于C S模式 常用分布式文件系统 Lustre Ha

随机推荐

  • Linux shell 环境变量注意事项

    在涉及环境变量名时 什么时候该用 符号 什么时候不该用 符号记住一点 如果要用到变量就使用 符号 如果要操作变量就不使用 符号 这条规则的一个例外就是printenv显示某个变量的值
  • 01 LNK2038:检测到“RuntimeLibrary”的不匹配项

    LNK2038 检测到 RuntimeLibrary 的不匹配项 问题描述 error LNK2038 检测到 RuntimeLibrary 的不匹配项 解决方法 qtmian lib报错采用方法3解决 方法1 修改VS项目运行库配置 方法
  • 基于Dijkstra、A*和动态规划的移动机器人路径规划(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 目录 1 概述 2 运行结果 2 1 Dijkstra算法 2 2 A 算法 2 3 动态规划 3 Matlab代码实现 1 概述 在基
  • JS 判断正负数

    需求 在文本框中输入一些内容 点击按钮判断文本框中内容是正数 负数 还是0 或者为非数字 实现
  • 软件质量测试雨课堂习题

    目录 第一章 软件测试基础 第二章 软件测试策略 第五章 软件测试的过程管理 第六章 软件测试的度量 第七章 软件测试技术 第九章 第三方测试 第一章 软件测试基础 1 软件测试目的是什么 ABC A 修正软件错误和缺陷提高软件质量 B 发
  • 从零开始的自动化测试框架——Web篇01

    Selenium 谈到web自动化 逃不开的一定会是Selenium 这是最为主流 也是最广为人知的一项web自动化产物 但目前业内web自动化其实主要分为以下方向 Selenium 核心主流自动化技术 功能齐全 一般是搭配webdrive
  • vue3中引入省市区地区选择element-china-area-data及页面回显

    安装element china area data npm i element china area data 引入 import regionData CodeToText TextToCode from element china ar
  • 【echarts报错】: ‘normal‘ hierarchy in itemStyle has been removed since 4.0.

    文章目录 报错 分析 解决 报错 charts5 js 7169 ECharts DEPRECATED normal hierarchy in itemStyle has been removed since 4 0 All style p
  • Mac安装sshpass同时解决Calling Non-checksummed download of sshpass formula file from an arbitrary URL报错

    可以直接使用 brew install https raw githubusercontent com kadwanev bigboybrew master Library Formula sshpass rb 但是会报错 Error Ca
  • 软件测试员一定需要懂编程代码吗?

    软件测试人员需要懂代码吗 如果软件测试人员会代码 那还有软件开发人员的事吗 既能测试又能敲代码的人是不是很牛 不管是外行人还是内行人 对一份与自己无关的职业的认识往往缺乏基本的认知 比如今天要说的软件测试 很多人都会对软件测试这个岗位存在一
  • 回顾:网络编程(待排版,知识点看情况补充

    一 CS模型 整个流程 服务器启动后 首先创建一个 或多个 监听socket 并调用bind函数将其绑定到服务器感兴趣的端口上 然后调用listen函数等待客户连接 服务器稳定运行之后 客户端就可以调用connect函数向服务器发起连接 由
  • Java String类型数据转为Byte数据

    方式一 我们可以直接通过Byte decode 方式直接转化内 String str 1 Byte decode Byte decode str System out println decode 方式二 首先我们可以先将String转为i
  • 区块链优秀gitbook资料

    docker 从入门到实践 https yeasy gitbooks io docker practice content image list html go 语言圣经 https docs hacknode org gopl zh ch
  • 双向链表(数据结构)(C语言)

    目录 概念 带头双向循环链表的实现 前情提示 双向链表的结构体定义 双向链表的初始化 关于无头单向非循环链表无需初始化函数 顺序表 带头双向循环链表需要的思考 双向链表在pos位置之前插入x 双向链表的打印 双链表删除pos位置的结点 双向
  • 学习总结4.1 Linux文件权限修改

    Linux系统中的每个文件都有访问许可权限 文件的访问权限分为只读 只写和可执行三种 只读权限表示只允许读其内容 而禁止对其做任何的更改操作 只写权限表示允许修改文件的内容 可执行权限表示允许将该文件作为一个程序执行 每一文件的访问权限都有
  • c/c++笔试面试题_6

    几个简单的c 面试题 2006 10 14 14 50 今天偶然看见这几个面试题 很有感触 想起一年前自己的求职经历 1 引言 本文的写作目的并不在于提供C C 程序员求职面试指导 而旨在从技术上分析面试题的内涵 文中的大多数面试题来自各大
  • 谷歌浏览器Google Chrome离线版(持续更新中)

    谷歌浏览器官方正式版采用自主研发Chromium内核 它是全球受欢迎的谷歌浏览器电脑版 追求速度 隐私安全的网络浏览器 而Google Chrome浏览器离线版更可以在无网络的情况下安装 一 在线版和离线版区别 在线版 即下载官方下载的一个
  • 服务链路追踪(Spring Cloud Sleuth)

    sleuth 英 slu 美 slu n 足迹 警犬 侦探vi 做侦探 微服务架构是一个分布式架构 它按业务划分服务单元 一个分布式系统往往有很多个服务单元 由于服务单元数量众多 业务的复杂性 如果出现了错误和异常 很难去定位 主要体现在
  • java中使用spark如何将column多列合为一列

    接下来介绍几种使用spark将DataFrame中一行的多列合并到一列中 并且该列以不同的类型展示保存 1 建立dataset 自己需要连接的mongo库 private static String datasource 自己需要连接的mo
  • 第十课.图片风格迁移和GAN

    目录 Neural Style Transfer Neural Style Transfer原理 准备工作 定义模型并加载预训练的模型参数 训练target以及结果可视化 生成对抗网络GAN GAN原理 GAN生成Mnist 准备工作 模型