MXNet实现图片的样式风格迁移(Style Transfer)

news2024/12/26 21:04:12

样式迁移就是将一个样式(风格)应用到一张主图上,改变这张图片的风格,比如说拍摄了一张夜晚的图片,我们可以拿梵高的"星月夜"图片做样式,应用到拍摄的图片上,两者合成后的新图片,风格就有了梵高的风格了

原理与操作都比较简单,准备两张图片,一张做内容,一张做样式,然后分别对它们进行特征的抽取,内容图片抽取内容特征,样式图片就抽取样式特征,进行合成。

其中抽取特征一般使用卷积神经网络,本节我们使用VGG-19预训练模型。

在模型中抽取不同层,分别抽取内容特征与样式特征,其中抽取内容特征,需靠近输出层,本节我们是抽取第25层(实质第26层),因为如果靠近输入层抽取特征的话,抽取的是一些细节,一些纹理之类,而这些细节我们就当成风格来抽取吧。抽取细节,本节共抽取5层,分别是第0层,5层,10层,19层,28层的特征作为样式特征。

上面是整个流程的大概介绍,下面我们来具体的实现它。

图片的标准化处理

首先就是对输入图像的处理,需要做一些标准化处理,以及形状的变换,让其适合这个模型的训练:

import d2lzh as d2l
from mxnet import autograd, gluon, image, init, nd
from mxnet.gluon import model_zoo, nn
import time
# 读取内容图像和样式图像
d2l.set_figsize()
c_img = image.imread('content.jpg')
# print(c_img.shape)#(501, 800, 3)高、宽、通道(H,W,C)
# d2l.plt.imshow(c_img.asnumpy())#mxnet.ndarray.ndarray.NDArray转numpy.ndarray

s_img = image.imread('style.jpg')
# print(s_img.shape)#(334, 800, 3)
# d2l.plt.imshow(s_img.asnumpy())
# 预处理函数和后处理函数
rgb_mean = nd.array([0.485, 0.456, 0.406])
rgb_std = nd.array([0.229, 0.224, 0.225])


def preprocess(img, img_shape):
    '''将图像做标准化处理(N,C,H,W)'''
    img = image.imresize(img, *img_shape)
    img = (img.astype('float32')/255 - rgb_mean) / rgb_std
    return img.transpose((2, 0, 1)).expand_dims(axis=0)

def postprocess(img):
    '''
    图像打印函数要求每个像素的浮点数值在0到1之间
    我们使用clip函数对小于0和大于1的值分别取0和1
    (H,W,C)
    '''
    img = img[0].as_in_context(rgb_std.context)
    return (img.transpose((1, 2, 0))*rgb_std+rgb_mean).clip(0, 1)

图像的处理比较简单,需要注意的是,读取出来的形状是高、宽、通道(H,W,C),如果显示的话,需要asnumpy())转换成numpy.ndarray类型。在神经网络中的适配形状(N,C,H,W),所以增加一个批处理维度之后,再transpose转置形状即可

VGG-19网络模型

然后通过预处理模型VGG-19来构建新的网络模型,在model_zoo模块中有很多可选的预处理的模型:pretrained_net=model_zoo.vision.vgg19(pretrained=True)

VGG-19模型参数文件vgg19-ad2f660d.params,压缩包507M,如果直接下载的时候比较慢,可以使用迅雷直接下载。

下载解压到这个目录即可:

Downloading C:\Users\Tony\AppData\Roaming\mxnet\models\vgg19-ad2f660d.zip from https://apache-mxnet.s3-accelerate.dualstack.amazonaws.com/gluon/models/vgg19-ad2f660d.zip

当然如果不需要预训练的话,也就是不需要参数文件,只加载网络模型的结构,可以让参数pretrained=False,这样就不会下载参数文件了。

我们打印VGG-19的网络的整个架构看下,可以看到是一个深度卷积神经网络:

VGG(
(features): HybridSequential(
(0): Conv2D(3 -> 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): Activation(relu)
(2): Conv2D(64 -> 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(3): Activation(relu)
(4): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False, global_pool=False, pool_type=max, layout=NCHW)
(5): Conv2D(64 -> 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(6): Activation(relu)
(7): Conv2D(128 -> 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(8): Activation(relu)
(9): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False, global_pool=False, pool_type=max, layout=NCHW)
(10): Conv2D(128 -> 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(11): Activation(relu)
(12): Conv2D(256 -> 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(13): Activation(relu)
(14): Conv2D(256 -> 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(15): Activation(relu)
(16): Conv2D(256 -> 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(17): Activation(relu)
(18): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False, global_pool=False, pool_type=max, layout=NCHW)
(19): Conv2D(256 -> 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(20): Activation(relu)
(21): Conv2D(512 -> 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(22): Activation(relu)
(23): Conv2D(512 -> 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(24): Activation(relu)
(25): Conv2D(512 -> 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(26): Activation(relu)
(27): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False, global_pool=False, pool_type=max, layout=NCHW)
(28): Conv2D(512 -> 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(29): Activation(relu)
(30): Conv2D(512 -> 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(31): Activation(relu)
(32): Conv2D(512 -> 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(33): Activation(relu)
(34): Conv2D(512 -> 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(35): Activation(relu)
(36): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False, global_pool=False, pool_type=max, layout=NCHW)
(37): Dense(25088 -> 4096, Activation(relu))
(38): Dropout(p = 0.5, axes=())
(39): Dense(4096 -> 4096, Activation(relu))
(40): Dropout(p = 0.5, axes=())
)
(output): Dense(4096 -> 1000, linear)
)

前面介绍说了,为了避免合成的图像过多的保留内容图像的细节,我们从VGG中选择靠近输出的层,来抽取内容特征,我们叫做内容层;从VGG中选择不同层的输出来匹配全局和全局的样式,这些层我们叫做样式层。

比如我们想要获取25层的特征值(这里我们选用为内容层),可以这样获取:

pretrained_net.features[25]

是一个卷积核是3x3,步幅为1,填充为1的二维卷积层

Conv2D(512 -> 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))

新建网络

接下来我们新建一个网络,抽取VGG里面的层来分别保存内容特征和样式特征。

pretrained_net=model_zoo.vision.vgg19(pretrained=True)
#选取内容层与样式层
content_layers,style_layers=[25],[0,5,10,19,28]
#构建一个新网络(29层)
net=nn.Sequential()
for i in range(max(content_layers+style_layers)+1):
    net.add(pretrained_net.features[i])

#net(X)的输出只有一个输出,而这里需要两个(内容与样式)
def extract_features(X,content_layers,style_layers):
    '''分别保存内容层与样式层的输出'''
    contents=[]
    styles=[]
    for i in range(len(net)):
        X=net[i](X)
        if i in content_layers:
            contents.append(X)
        if i in style_layers:
            styles.append(X)
    return contents,styles

#抽取内容图像的内容特征
def get_contents(img_shape,ctx):
    content_X=preprocess(c_img,img_shape).copyto(ctx)
    contents_Y,_=extract_features(content_X,content_layers,style_layers)
    return content_X,contents_Y

#抽取样式图像的样式特征
def get_styles(img_shape,ctx):
    style_X=preprocess(s_img,img_shape).copyto(ctx)
    _,styles_Y=extract_features(style_X,content_layers,style_layers)
    return style_X,styles_Y

这里使用vgg19网络模型,然后通过这个模型分别抽取内容和样式,内容是整体,是主题,所以不考虑细节,而样式就是细节,纹理等,所以抽取多个靠近输入层的输出来做样式特征。

损失函数

新模型构建好了之后,就开始定义损失函数,这里有三个损失函数:

内容损失:通过平方误差函数衡量合成图像与内容图像在内容特征上的差异,使得合成图像在内容特征上接近内容图像

def content_loss(Y_hat, Y):
    '''内容损失'''
    return (Y_hat-Y).square().mean()

样式损失:通过平方误差函数衡量合成图像与样式图像在样式上的差异,使得合成图像在样式特征上接近样式图像

def gram(X):
    """格拉姆矩阵"""
    num_channels, n = X.shape[1], X.size//X.shape[1]
    X = X.reshape((num_channels, n))
    return nd.dot(X, X.T)/(num_channels*n)
def style_loss(Y_hat, gram_Y):
    '''样式损失'''
    return ((gram(Y_hat)-gram_Y).square().mean())

总变差损失:主要是对合成图像里面大量高频噪点做降噪处理,常用的就是总变差降噪(total variation denoising),这样的处理有助于减少合成图像中的噪点。

def tv_loss(Y_hat):
    '''总变差损失'''
    return 0.5*((Y_hat[:, :, 1:, :]-Y_hat[:, :, :-1, :]).abs().mean()+
                (Y_hat[:, :, :, 1:]-Y_hat[:,:,:,:-1]).abs().mean())

损失函数就是三者损失函数的加权和,我们可以调节它们三者的权重值,来权衡合成图像在保留内容、迁移样式以及降噪三方面的相对重要性。

content_w, style_w, tv_w = 1, 1e3, 50
def compute_loss(X, contents_Y_hat, styles_Y_hat, contents_Y, styles_Y_gram):
    contents_l = [content_loss(Y_hat, Y)*content_w for Y_hat,Y in zip(contents_Y_hat, contents_Y)]
    styles_l = [style_loss(Y_hat, Y)*style_w for Y_hat,Y in zip(styles_Y_hat, styles_Y_gram)]
    tv_l = tv_loss(X)*tv_w
    # 所有损失求和
    l = nd.add_n(*styles_l)+nd.add_n(*contents_l)+tv_l
    return contents_l, styles_l, tv_l, l

让风格凸显点,就调大style_w样式权重值,三者权重值有兴趣的可以试着改变看看效果

训练模型

最后一步就做训练,在这个样式迁移中,合成图像是唯一需要更新的变量。

假如表示坐标(i,j)的像素值,其降低总变差损失的公式如下:

尽可能使得邻近的像素值相似。

在训练的时候,不断的抽取合成图像的内容特征和样式特征,并计算损失函数。由于每隔50个迭代周期才调用同步函数asscalar,很容易造成内存占用过高,因此我们在每个迭代周期都调用一次同步函数waitall

这里我使用的样式图片是目前很热门的《中国奇谭》中的《鹅鹅鹅》图片,很喜欢的水墨画风格,我们来看下合成之后的效果会是什么样的。

全部代码如下:

import d2lzh as d2l
from mxnet import autograd, gluon, image, init, nd
from mxnet.gluon import model_zoo, nn
import time

# 读取内容图像和样式图像
d2l.set_figsize()
c_img = image.imread('content.jpg')
# print(c_img.shape)#(501, 800, 3)高、宽、通道(H,W,C)
# d2l.plt.imshow(c_img.asnumpy())#mxnet.ndarray.ndarray.NDArray转numpy.ndarray

s_img = image.imread('style.jpg')
# print(s_img.shape)#(334, 800, 3)
# d2l.plt.imshow(s_img.asnumpy())

# 预处理函数和后处理函数
rgb_mean = nd.array([0.485, 0.456, 0.406])
rgb_std = nd.array([0.229, 0.224, 0.225])


def preprocess(img, img_shape):
    '''将图像做标准化处理(N,C,H,W)'''
    img = image.imresize(img, *img_shape)
    img = (img.astype('float32')/255 - rgb_mean) / rgb_std
    return img.transpose((2, 0, 1)).expand_dims(axis=0)

def postprocess(img):
    '''
    图像打印函数要求每个像素的浮点数值在0到1之间
    我们使用clip函数对小于0和大于1的值分别取0和1
    (H,W,C)
    '''
    img = img[0].as_in_context(rgb_std.context)
    return (img.transpose((1, 2, 0))*rgb_std+rgb_mean).clip(0, 1)


"""通过VGG-19模型构建新的网络"""
# 预训练模型VGG-19
pretrained_net = model_zoo.vision.vgg19(pretrained=True)
# print(pretrained_net)
# 抽取内容层与样式层
content_layers, style_layers = [25], [0, 5, 10, 19, 28]
# 构建一个新网络(29层)
net = nn.Sequential()
for i in range(max(content_layers+style_layers)+1):
    net.add(pretrained_net.features[i])

# 由于net(X)的输出只有一个输出,而这里需要两个(内容与样式)
def extract_features(X, content_layers, style_layers):
    '''分别抽取并保存内容层与样式层的输出'''
    contents = []
    styles = []
    for i in range(len(net)):
        X = net[i](X)
        if i in content_layers:
            contents.append(X)
        if i in style_layers:
            styles.append(X)
    return contents, styles

# 抽取内容图像的内容特征
def get_contents(img_shape, ctx):
    content_X = preprocess(c_img, img_shape).copyto(ctx)
    contents_Y, _ = extract_features(content_X, content_layers, style_layers)
    return content_X, contents_Y

# 抽取样式图像的样式特征
def get_styles(img_shape, ctx):
    style_X = preprocess(s_img, img_shape).copyto(ctx)
    _, styles_Y = extract_features(style_X, content_layers, style_layers)
    return style_X, styles_Y


"""定义损失函数"""
def content_loss(Y_hat, Y):
    '''内容损失'''
    return (Y_hat-Y).square().mean()

def gram(X):
    """格拉姆矩阵"""
    num_channels, n = X.shape[1], X.size//X.shape[1]
    X = X.reshape((num_channels, n))
    return nd.dot(X, X.T)/(num_channels*n)
def style_loss(Y_hat, gram_Y):
    '''样式损失'''
    return ((gram(Y_hat)-gram_Y).square().mean())

def tv_loss(Y_hat):
    '''总变差损失'''
    return 0.5*((Y_hat[:, :, 1:, :]-Y_hat[:, :, :-1, :]).abs().mean()+
                (Y_hat[:, :, :, 1:]-Y_hat[:,:,:,:-1]).abs().mean())


# 损失函数:上面三者损失的加权和
content_w, style_w, tv_w = 1, 1e3, 50
def compute_loss(X, contents_Y_hat, styles_Y_hat, contents_Y, styles_Y_gram):
    contents_l = [content_loss(Y_hat, Y)*content_w for Y_hat,Y in zip(contents_Y_hat, contents_Y)]
    styles_l = [style_loss(Y_hat, Y)*style_w for Y_hat,Y in zip(styles_Y_hat, styles_Y_gram)]
    tv_l = tv_loss(X)*tv_w
    # 所有损失求和
    l = nd.add_n(*styles_l)+nd.add_n(*contents_l)+tv_l
    return contents_l, styles_l, tv_l, l


"""合成图像"""
# 在样式迁移中,合成图像是唯一需要更新的变量
class GeneratedImage(nn.Block):
    """将合成图像视为模型参数"""
    def __init__(self, img_shape, **kwargs):
        super(GeneratedImage, self).__init__(**kwargs)
        self.weight = self.params.get('weight', shape=img_shape)

    def forward(self):
        return self.weight.data()

# 创建合成图像的模型实例,并将其初始化为图像X
# 样式图像在各个样式层的格拉姆矩阵styles_Y_gram将在训练前预先计算好
def get_inits(X, ctx, lr, styles_Y):
    gen_img = GeneratedImage(X.shape)
    gen_img.initialize(init.Constant(X), ctx=ctx, force_reinit=True)
    trainer = gluon.Trainer(gen_img.collect_params(),'adam', {'learning_rate': lr})
    styles_Y_gram = [gram(Y) for Y in styles_Y]
    return gen_img(), styles_Y_gram, trainer


"""训练模型"""
def train(X, contents_Y, styles_Y, ctx, lr, max_epochs, lr_decay_epoch):
    X, styles_Y_gram, trainer = get_inits(X, ctx, lr, styles_Y)
    for i in range(max_epochs):
        start = time.time()
        with autograd.record():
            contents_Y_hat, styles_Y_hat = extract_features(X, content_layers, style_layers)
            contents_l, styles_l, tv_l, l = compute_loss(X, contents_Y_hat, styles_Y_hat, contents_Y, styles_Y_gram)
        l.backward()
        trainer.step(1)
        nd.waitall()
        if i % 50 == 0 and i != 0:
            print('epoch %3d,内容损失 %.2f,样式损失 %.2f,总变差损失 %.2f, %.2f秒' % (i, nd.add_n(*contents_l).asscalar(),
                                                                       nd.add_n(*styles_l).asscalar(), tv_l.asscalar(), time.time()-start))
        if i % lr_decay_epoch == 0 and i != 0:
            trainer.set_learning_rate(trainer.learning_rate*0.1)
            print('衰减后的学习率:%.1e' % trainer.learning_rate)
    return X

# 图片宽,高(W,H)
ctx, img_shape = d2l.try_gpu(), (200, 100)
net.collect_params().reset_ctx(ctx)
content_X, contents_Y = get_contents(img_shape, ctx)
_, styles_Y = get_styles(img_shape, ctx)
output = train(content_X, contents_Y, styles_Y, ctx, 0.01, 500, 200)
d2l.plt.imsave('new-style.jpg',postprocess(output).asnumpy())

#尺寸调大
ctx,image_shape=d2l.try_gpu(), (560,320)
_,content_Y=get_contents(image_shape,ctx)
_,style_Y=get_styles(image_shape,ctx)
X=preprocess(postprocess(output)*255,image_shape)
output=train(X,content_Y,style_Y,ctx,0.01,300,100)
d2l.plt.imsave('big-new-style.jpg',postprocess(output).asnumpy())
epoch 50,内容损失 59.70,样式损失 77.01,总变差损失 14.96, 0.04秒
epoch 100,内容损失 55.44,样式损失 51.54,总变差损失 15.23, 0.04秒
epoch 150,内容损失 53.06,样式损失 41.99,总变差损失 15.33, 0.04秒
epoch 200,内容损失 51.30,样式损失 37.01,总变差损失 15.34, 0.04秒
衰减后的学习率:1.0e-03
epoch 250,内容损失 51.09,样式损失 36.52,总变差损失 15.32, 0.04秒
epoch 300,内容损失 50.92,样式损失 36.09,总变差损失 15.31, 0.04秒
epoch 350,内容损失 50.76,样式损失 35.66,总变差损失 15.30, 0.04秒
epoch 400,内容损失 50.61,样式损失 35.23,总变差损失 15.28, 0.05秒
衰减后的学习率:1.0e-04
epoch 450,内容损失 50.59,样式损失 35.19,总变差损失 15.28, 0.04秒
epoch 50,内容损失 9.28,样式损失 8.89,总变差损失 5.67, 0.28秒
epoch 100,内容损失 7.61,样式损失 7.23,总变差损失 5.09, 0.27秒
衰减后的学习率:1.0e-03
epoch 150,内容损失 7.51,样式损失 7.10,总变差损失 5.00, 0.27秒
epoch 200,内容损失 7.43,样式损失 7.01,总变差损失 4.94, 0.26秒
衰减后的学习率:1.0e-04
epoch 250,内容损失 7.42,样式损失 7.00,总变差损失 4.93, 0.27秒

保存的尺寸比较小,尝试调大点,当然这个取决于你的内存与显存的大小,我的配置比较低,已尽量调大到不让内存溢出的情况:

ctx,image_shape=d2l.try_gpu(), (560,320)
_,content_Y=get_contents(image_shape,ctx)
_,style_Y=get_styles(image_shape,ctx)
X=preprocess(postprocess(output)*255,image_shape)
output=train(X,content_Y,style_Y,ctx,0.01,300,100)
d2l.plt.imsave('big-new-style.jpg',postprocess(output).asnumpy())

两张原图如下,一张内容图,一张样式图:

然后贴出大的合成图片,可以看出,内容大体是没有多大变化,整张图片的风格带了点“鹅鹅鹅”的味道!

当然这些合成的图片,一般来说原图与样式图尽量保持一样,差异只在于风格上,这样出来的效果会更好,这里只是一个示例,有兴趣的伙伴可以多合成一些看看。

格拉姆矩阵(Gram matrix)

在样式损失函数,我们发现出现一个新的知识点:格拉姆矩阵

先来看下它的定义,n维欧式空间中任意k个向量之间两两的内积(dot)所组成的矩阵,称为这k个向量的格拉姆矩阵(Gram matrix),很明显,这是一个对称矩阵,回到前面的格拉姆函数

def gram(X):
    """格拉姆矩阵"""
    num_channels, n = X.shape[1], X.size//X.shape[1]
    X = X.reshape((num_channels, n))
    return nd.dot(X, X.T)/(num_channels*n)

这里也可以看到,返回的是(通道数,通道数)这样的形状,是对称的。

在这节的例子中,输入图像特征的形状是(N,C,H,W)。我们经过flatten(将H*W进行平铺成一维向量)和矩阵转置操作,可以变形为(C,N*H*W)(N*H*W, C)的矩阵,然后内积就得到格拉姆矩阵,得到的形状是(C,C),换句话说,在特征图上每个像素值都来自一个特定卷积核在特定位置的卷积,因此每个像素值表示一个特征的强度,而格拉姆矩阵计算的是两两特征之间的相关性。

题外话:这里既然是特定位置的卷积,也反映着特征之间的相关性,那感觉跟互相关的概念很像了,互相关的定义是两个函数分别做复数共轭和反向平移并使其相乘的无穷积分。当然个人的通俗理解其实很简单,就是加权和的意思,使用一小段代码来验证下,相乘以及互相关,而互相关的结果就是相乘之后的和:

import d2lzh as d2l

a=nd.array([[1,2],[3,4]])
b=nd.array([[1,2],[6,7]])
>>> a*b

[[ 1.  4.]
 [18. 28.]]
<NDArray 2x2 @cpu(0)>

>>> d2l.corr2d(a,b)

[[51.]]
<NDArray 1x1 @cpu(0)>

格拉姆矩阵的每个值可以说是代表i通道的特征图j通道的特征图的互相关程度。

假设样本数N=1的情况,这里的X可以看作有C个长度为H*W的向量组成,其中向量代表了通道i上的样式特征,这些向量的格拉姆矩阵(向量的内积),就表达了通道i和通道j上样式特征的相关性。

当然函数除以了(num_channels*n)这个是为了避免H*W出现较大的值,为了让样式损失不受这些值的大小的影响的一个做法。

可能初次接触格拉姆矩阵的伙伴们会有点不很明白,如果说熟悉协方差的话,那理解起来就更简单了,我们再看下它的样式损失:

def style_loss(Y_hat, gram_Y):
    '''样式损失'''
    return ((gram(Y_hat)-gram_Y).square().mean())

那么对这种格拉姆矩阵的差异,换种理解方式就是体现两者之间的方向,如果说大于0,说明总体来说是方向相同的,值越大说明越相似,反之小于0说明方向相反,它们之间的差异越大,这样应该明白了我们就是尽量让样式接近,方向肯定一样,这样就效果越好。

当然限于水平问题,不够严谨,只能说尽量帮助大家理解,对这些理解可能也不是很准,望留言指正!

最后附加一张内容抽取与样式抽取的损失函数的结构图

实线箭头是样式迁移的损失函数,虚线箭头是迭代模型参数,就是不断的更新合成的图像!

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

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

相关文章

linux基本功系列之uptime命令实战

文章目录一. uptime命令介绍二. 语法格式及常用选项三. 参考案例3.1 显示当前系统运行负载情况3.2 显示机器正常运行的时间3.3 显示机器启动时间3.4 关于平均负载的介绍总结前言&#x1f680;&#x1f680;&#x1f680; 想要学好Linux&#xff0c;命令是基本功&#xff0c;企业…

推荐 5 个实用 GitHub 项目

本期推荐开源项目目录&#xff1a;1. AI-For-Beginners2. 一个小巧轻便的 PDF 阅读器3. 开源的智能手表4. 开源内容管理系统5. 程序员海外工作/英文面试手册01AI-For-Beginners之前推荐过 Microsoft 出品的 Web 技术栈课程&#xff0c;本开源项目同样是 Microsoft 的 Azure Clo…

go runtime

go 运行时&#xff0c;也称为 go runtime&#xff0c;类似Java中的JVM虚拟机&#xff0c;不过runtime并非是虚拟机。其本身就是每个 go 程序的一部分&#xff0c;它会跟源码一起编译并链接到目标程序中&#xff0c;即便只写了一个 hello world 程序&#xff0c;这个程序中也包含…

day15 二叉树 | 104、二叉树的最大深度 111、二叉树的最小深度 222、完全二叉树的节点个数

题目 104、二叉树的最大深度 递归法&#xff08;后序&#xff09;&#xff08;必须会&#xff09; // 定义&#xff1a;输入根节点&#xff0c;返回这棵二叉树的最大深度 int maxDepth(TreeNode root) {if (root null) {return 0;}// 利用定义&#xff0c;计算左右子树的最大…

论文笔记:Graph WaveNet for Deep Spatial-Temporal Graph Modeling

IJCAI 2019 1 abstract & intro 时空数据挖掘问题大多数使用邻接矩阵来建模节点之间的属性关系&#xff0c;这种思路的一个基本假设是&#xff1a;节点信息取决于自身和邻居的历史信息。 但这类模型的假设存在着一些问题&#xff1a; 未能充分建模节点之间的依赖关…

宝塔部署springboot,vue,node.js项目

宝塔部署springboot项目&#xff1a; 先将命令转移到jar包所属文件夹中 分为短暂部署和永久部署 短暂部署&#xff1a;java -jar xxx.jar 永久部署&#xff1a;nohup java -jar demo-1.0.0.jar logs_mark.txt 2>&1 & nohup:linux的命令,代表关闭但持续运行 查看898…

52.Isaac教程--操纵杆

操纵杆 ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 文章目录操纵杆使用游戏机操纵杆使用其他操纵杆使用 Playstation 操纵杆很容易控制运行 Isaac SDK 的机器人&#xff0c;但也可以使用其他控制器。 使用游戏机操纵杆 按照以下步骤校准您的…

微信小程序018小说在线阅读系统书城

管理员的主要功能有&#xff1a; 1.管理员输入账户登陆后台 2.个人中心&#xff1a;管理员修改密码和账户信息 3.用户管理&#xff1a;对注册的用户信息进行删除&#xff0c;查询&#xff0c;添加&#xff0c;修改 4.小说信息管理&#xff1a;对小说信息进行添加&#xff0c;修…

TCP/IP第六章笔记ICMP协议

文章目录6.1 引言6.2 ICMP报文分组格式和类型6.3 ICMP地址掩码请求与应答6.4 ICMP时间戳请求与应答6.5 ICMP端口不可达差错6.6 ICMP的处理&#xff08;4.4BSD系统下&#xff09;6.1 引言 第三章在IP选择路由时&#xff0c;如果一个报文最后限制转发次数用完后还传输不到目的地…

【高并发】- 不可不知道的RPC框架服务通信

前言 前面章节讲解了高并发系统中相关指标、为什么要学习高并发设计思想、高并发系统中每个环节的流量处理等思想。本章节讲解服务通信&#xff0c;来帮助大家更好理解系统间通信过程。 1 RPC框架介绍 RPC&#xff08;Remote Procedure Call&#xff0c;远程过程调用&#xff0…

Win10家庭版和Win10专业版有什么区别?

win10操作系统拥有7个不同的版本&#xff0c;其中win10家庭版和专业版被人们广泛的应用&#xff0c;很多用户都不知道如何区分win10家庭版和专业版&#xff0c;接下来把win10家庭版和专业版有什么不同的地方告诉大家。我们一起来看看吧。 win10家庭版和专业版的区别&#xff1a…

java服务框架高级之微服务保护 Sentinel 限制规则,流控模式,流控效果

初识Sentinel 雪崩问题&#xff1a; 解决雪崩问题的常见方式有四种&#xff1a; 1.超时处理&#xff1a;设定超时时间&#xff0c;请求超过一定时间没有响应就返回错误信息&#xff0c;不会无休止等待 2.舱壁模式&#xff1a;限定每个业务能使用的线程数&#xff0c;避免耗尽…

2023外企还香吗?2022届计算机谈谈入职外企的感受和经历

互联网寒冬&#xff0c;大规模“毕业”的环境下&#xff0c;2023年外企又香了吗&#xff1f; 大家好&#xff0c;我是2022届计算机应届毕业生&#xff0c;毕业入职了一家大型非互联网外企&#xff0c;2022年由于只上了半年班&#xff0c;所以只有半年工资&#xff0c;总收入在1…

【JavaScript速成之路】一文带你初识JavaScript

&#x1f4c3;个人主页&#xff1a;「小杨」的csdn博客 &#x1f525;系列专栏&#xff1a;【JavaScript速成之路】 &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; 文章目录前言1&#xff0c;JavaScript1.1&#xff0c;JavaScript概述1.2&#xff0c;Java…

【人工智能学习笔记】人工智能里的数学1——概述

与软件开发相比&#xff0c;人工智能领域需要大量数学知识。主要涉及微积分、线性代数、概率论和最优化。本文主要介绍人工智能里用到了哪些数学知识&#xff0c;试图向您提供一个目录式的、导读式的概述。后期计划一一展开讲解。本文作为我学习人工智能的笔记&#xff0c;主要…

C++继承(上)

我们知道&#xff0c;面向对象有三大特性&#xff1a;封装&#xff0c;继承&#xff0c;多态。封装前面已经说过&#xff0c;这篇文章主要说说继承。 文章目录1.继承的概念及定义1.1继承的概念1.2 继承定义1.2.1定义格式1.2.2继承关系和访问限定符2. 基类和派生类对象赋值转换…

vue.js客服系统实时聊天项目开发(十四)点击加载展示历史消息列表

当访客一进去聊天界面以后&#xff0c;需要获取一下历史消息展示到界面&#xff0c;并且需要能分页的原理展示 在顶部有一个加载更多记录的按钮&#xff0c;点击就能按分页获取数据 //展示历史消息记录getHistoryList(){var _thisthis;let pagesize5;this.$axios.get(this.ApiH…

Python | 数据类型之列表

知识目录一、定义和遍历1.1 定义列表1.2 打印和截取列表1.3 遍历列表二、增删改查2.1 更新和删除列表2.3 脚本操作符 * in三、函数和方法3.1 函数3.2 方法列表是最常用的Python数据类型&#xff0c;它可以作为一个方括号内的逗号分隔值出现。列表的数据项不需要具有相同的类型…

springboot-rocketmq整合NOT_CONSUME_YET问题处理

一、测试流程1.添加POM<dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.2.1</version></dependency>2.配置属性参数# 默认的消息组 rocketmq.producer.…

微信小程序项目初试总结

微信小程序项目初试总结准备工作注册小程序账号获取AppID下载 微信开发者工具下载 HBuilderX安装 scss/sass 编译HBuilderX 的调试新建项目把项目运行到微信开发者工具使用 Git 管理项目把项目托管到码云模块制作通用tabBar首页分类搜索商品列表商品详情加入购物车购物车页面登…