目录

在上篇博客(AN网络之入门教程(四)之基于DCGAN动漫头像生成)中,介绍了基于DCGAN的动漫头像生成,时隔几月,序属三秋,在这篇博客中,将介绍如何使用条件GAN网络(conditional GAN)生成符合需求的图片。

做成的效果图如下所示,“一键起飞”

项目地址:Github

在阅读这篇博客之前,首先得先对GAN和DCGAN有一部分的了解,如果对GAN不是很了解的话,建议先去了解GAN网络,或者也可以参考一下我之前的博客系列

相比较于普通的GAN网络,cgan在网络结构上发生了一些改变,与GAN网络相比,在Input layer添加了一个\(Y\)的标签,其代表图片的属性标签——在Minst数据集中,标签即代表着手写数字为几(如7,3),而在动漫头像数据集中,标签可以表示为头发的颜色,或者眼睛的颜色(当然为其他的属性特征也是的)。

在\(G\)网络中,Generator可以根据给的\(z\) (latent noise)和 \(y\) 生成相对应的图片,而\(D\)网络可以根据给的\(x\)(比如说图片)和 \(Y\) 进行评判。下图便是一个CGAN网络的简单示意图。

在这篇博客中,使用的框架:

  • Keras version:2.3.1

Prepare

首先的首先,我们需要数据集,里面既需要包括动漫头像的图片,也需要有每一张图片所对应的标签数据。这里我们使用Anime-Face-ACGAN中提供的图片数据集和标签数据集,当然,在我的Github中也提供了数据集的下载(其中,我的数据集对图片进行了清洗,将没有相对应标签的图片进行了删除)。

部分图片数据如下所示:

在tags_clean.csv 中,数据形式如下图所示,每一行代表的是相对应图片的标签数据。第一个数据为ID,同时也是图片的文件名字,后面的数据即为图片的特征数据

这里我们需要标签属性的仅仅为eyes的颜色数据和hair的颜色数据,应注意的是在csv中存在某一些图片没有这些数据(如第0个数据)。

以上便将这次所需要的数据集介绍完了,下面将简单的介绍一下数据集的加载。

加载数据集

首先我们先进行加载数据集,一共需要加载两个数据集,一个是图片数据集合,一个是标签数据集合。在标签数据集中,我们需要的是眼睛的颜色头发的颜色。在数据集中,一共分别有12种头发的颜色和11种眼睛的颜色。

# 头发的种类
HAIRS = ['orange hair', 'white hair', 'aqua hair', 'gray hair', 'green hair', 'red hair', 'purple hair', 'pink hair','blue hair', 'black hair', 'brown hair', 'blonde hair']
# 眼睛的种类
EYES = ['gray eyes', 'black eyes', 'orange eyes', 'pink eyes', 'yellow eyes', 'aqua eyes', 'purple eyes', 'green eyes','brown eyes', 'red eyes', 'blue eyes']

接下来加载数据集,在这个操作中,我们提取出csv中的hair和eye的颜色并得到相对应的id,然后将其保存到numpy数组中。

# 加载标签数据
import numpy as np
import csv
with open('tags_clean.csv', 'r') as file:
lines = csv.reader(file, delimiter=',')
y_hairs = []
y_eyes = []
y_index = []
for i, line in enumerate(lines):
# id 对应的是图片的名字
idx = line[0]
# tags 代表图片的所有特征(有hair,eyes,doll等等,当时我们只关注eye 和 hari)
tags = line[1]
tags = tags.split('\t')[:-1]
y_hair = []
y_eye = []
for tag in tags:
tag = tag[:tag.index(':')]
if (tag in HAIRS):
y_hair.append(HAIRS.index(tag))
if (tag in EYES):
y_eye.append(EYES.index(tag))
# 如果同时存在hair 和 eye标签就代表这个标签是有用标签。
if (len(y_hair) == 1 and len(y_eye) == 1):
y_hairs.append(y_hair)
y_eyes.append(y_eye)
y_index.append(idx)
y_eyes = np.array(y_eyes)
y_hairs = np.array(y_hairs)
y_index = np.array(y_index)
print("一种有{0}个有用的标签".format(len(y_index)))

通过上述的操作,我们就提取出了在csv文件中同时存在eye颜色hair颜色标签的数据了。并保存了所对应图片的id数据

接下来我们就是根据id数据去读取出相对应的图片了,其中,所有的图片均为(64,64,3)的RGB图片,并且图片的保存位置为/faces

import os
import cv2
# 创建数据集images_data
images_data = np.zeros((len(y_index), 64, 64, 3))
# 从本地文件读取图片加载到images_data中。
for index,file_index in enumerate (y_index):
images_data[index] = cv2.cvtColor(
cv2.resize(
cv2.imread(os.path.join("faces", str(file_index) + '.jpg'), cv2.IMREAD_COLOR),
(64, 64)),cv2.COLOR_BGR2RGB
)

接下来将图片进行归一化(一般来说都需要将图片进行归一化提高收敛的速度):

images_data = (images_data / 127.5) - 1

通过以上的操作,我们就将数据导入内存中了,因为这个数据集比较小,因此将其全部导入到内存中是完全的。

构建网络

first of all,我们将我们需要的库导入:

from keras.layers import Input, Dense, Reshape, Flatten, Dropout, multiply, Activation
from keras.layers import BatchNormalization, Activation, Embedding, ZeroPadding2D
from keras.layers import Conv2D, Conv2DTranspose, Dropout, UpSampling2D, MaxPooling2D,Concatenate
from keras.layers.advanced_activations import LeakyReLU
from keras.models import Sequential, Model, load_model
from keras.optimizers import SGD, Adam, RMSprop
from keras.utils import to_categorical,plot_model
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

构建Generator

关于G网络的模型图如下所示,而代码便是按照如下的模型图来构建网络模型:

  • Input:头发的颜色,眼睛的颜色,100维的高斯噪声。
  • Output:(64,64,3)的RGB图片。

构建模型图的代码:


def build_generator_model(noise_dim, hair_num_class, eye_num_class):
"""
定义generator的生成方法
:param noise_dim: 噪声的维度
:param hair_num_class: hair标签的种类个数
:param eye_num_class: eye标签的种类个数
:return: generator
"""
# kernel初始化模式
kernel_init = 'glorot_uniform' model = Sequential(name='generator') model.add(Reshape((1, 1, -1), input_shape=(noise_dim + 16,)))
model.add(Conv2DTranspose(filters=512, kernel_size=(4, 4), strides=(1, 1), padding="valid",
data_format="channels_last", kernel_initializer=kernel_init, ))
model.add(BatchNormalization(momentum=0.5))
model.add(LeakyReLU(0.2))
model.add(Conv2DTranspose(filters=256, kernel_size=(4, 4), strides=(2, 2), padding="same",
data_format="channels_last", kernel_initializer=kernel_init))
model.add(BatchNormalization(momentum=0.5))
model.add(LeakyReLU(0.2))
model.add(Conv2DTranspose(filters=128, kernel_size=(4, 4), strides=(2, 2), padding="same",
data_format="channels_last", kernel_initializer=kernel_init))
model.add(BatchNormalization(momentum=0.5))
model.add(LeakyReLU(0.2))
model.add(Conv2DTranspose(filters=64, kernel_size=(4, 4), strides=(2, 2), padding="same",
data_format="channels_last", kernel_initializer=kernel_init))
model.add(BatchNormalization(momentum=0.5))
model.add(LeakyReLU(0.2))
model.add(Conv2D(filters=64, kernel_size=(3, 3), strides=(1, 1), padding="same", data_format="channels_last",
kernel_initializer=kernel_init))
model.add(BatchNormalization(momentum=0.5))
model.add(LeakyReLU(0.2))
model.add(Conv2DTranspose(filters=3, kernel_size=(4, 4), strides=(2, 2), padding="same",
data_format="channels_last", kernel_initializer=kernel_init))
model.add(Activation('tanh')) latent = Input(shape=(noise_dim,))
eyes_class = Input(shape=(1,), dtype='int32')
hairs_class = Input(shape=(1,), dtype='int32') hairs = Flatten()(Embedding(hair_num_class, 8, init='glorot_normal')(hairs_class))
eyes = Flatten()(Embedding(eye_num_class, 8, init='glorot_normal')(eyes_class))
# 连接模型的输入
con = Concatenate()([latent, hairs, eyes])
# 模型的输出
fake_image = model(con)
# 创建模型
m = Model(input=[latent, hairs_class, eyes_class], output=fake_image)
return m

构建G网络:

# 生成网络
G = build_generator_model(100,len(HAIRS),len(EYES))
# 调用这个方法可以画出模型图
# plot_model(G, to_file='generator.png', show_shapes=True, expand_nested=True, dpi=500)

构建Discriminator

这里我们的discriminator的网络结构上文中的cgan网络结构稍有不同。在前文中,我们是在Discriminator的输入端的输入是图片标签,而在这里,我们的Discriminator的输入仅仅是图片,输出才是label 和 真假概率。

网络结构如下所示:

然后根据上述的网络结构来构建discriminator,代码如下:

def build_discriminator_model(hair_num_class, eye_num_class):
"""
定义生成 discriminator 的方法
:param hair_num_class: 头发颜色的种类
:param eye_num_class: 眼睛颜色的种类
:return: discriminator
"""
kernel_init = 'glorot_uniform'
discriminator_model = Sequential(name="discriminator_model")
discriminator_model.add(Conv2D(filters=64, kernel_size=(4, 4), strides=(2, 2), padding="same",
data_format="channels_last", kernel_initializer=kernel_init,
input_shape=(64, 64, 3)))
discriminator_model.add(LeakyReLU(0.2))
discriminator_model.add(Conv2D(filters=128, kernel_size=(4, 4), strides=(2, 2), padding="same",
data_format="channels_last", kernel_initializer=kernel_init))
discriminator_model.add(BatchNormalization(momentum=0.5))
discriminator_model.add(LeakyReLU(0.2))
discriminator_model.add(Conv2D(filters=256, kernel_size=(4, 4), strides=(2, 2), padding="same",
data_format="channels_last", kernel_initializer=kernel_init))
discriminator_model.add(BatchNormalization(momentum=0.5))
discriminator_model.add(LeakyReLU(0.2))
discriminator_model.add(Conv2D(filters=512, kernel_size=(4, 4), strides=(2, 2), padding="same",
data_format="channels_last", kernel_initializer=kernel_init))
discriminator_model.add(BatchNormalization(momentum=0.5))
discriminator_model.add(LeakyReLU(0.2))
discriminator_model.add(Flatten())
# 网络的输入
dis_input = Input(shape=(64, 64, 3)) features = discriminator_model(dis_input)
# 真/假概率的输出
validity = Dense(1, activation="sigmoid")(features)
# 头发颜色种类的输出
label_hair = Dense(hair_num_class, activation="softmax")(features)
# 眼睛颜色种类的输出
label_eyes = Dense(eye_num_class, activation="softmax")(features)
m = Model(dis_input, [validity, label_hair, label_eyes])
return m

然后调用方法创建discriminator。

D = build_discriminator_model(len(HAIRS),len(EYES))
# 画出模型图
# plot_model(D, to_file='discriminator.png', show_shapes=True, expand_nested=True, dpi=500)

构建cGAN网络

cgan网络的输入是generator的输入,cgan的输出是discriminator的输出,网络模型图如下所示:

模型图看起来很复杂,但是实际上代码却很简单,针对于GAN网络,我们只需要将GAN网络中的D网络进行冻结(将trainable变成False)即可。

def build_ACGAN(gen_lr=0.00015, dis_lr=0.0002, noise_size=100):
"""
生成
:param gen_lr: generator的学习率
:param dis_lr: discriminator的学习率
:param noise_size: 噪声维度size
:return:
"""
# D网络优化器
dis_opt = Adam(lr=dis_lr, beta_1=0.5)
# D网络loss
losses = ['binary_crossentropy', 'categorical_crossentropy', 'categorical_crossentropy']
# 配置D网络
D.compile(loss=losses, loss_weights=[1.4, 0.8, 0.8], optimizer=dis_opt, metrics=['accuracy']) # 在训练的generator时,冻结discriminator的权重
D.trainable = False opt = Adam(lr=gen_lr, beta_1=0.5)
gen_inp = Input(shape=(noise_size,))
hairs_inp = Input(shape=(1,), dtype='int32')
eyes_inp = Input(shape=(1,), dtype='int32')
GAN_inp = G([gen_inp, hairs_inp, eyes_inp])
GAN_opt = D(GAN_inp)
gan = Model(input=[gen_inp, hairs_inp, eyes_inp], output=GAN_opt)
gan.compile(loss=losses, optimizer=opt, metrics=['accuracy'])
return gan

然后调用方法构建GAN网络即可:

gan = build_ACGAN()
# plot_model(gan, to_file='gan.png', show_shapes=True, expand_nested=True, dpi=500)

工具方法

然后我们定义一些方法,有:

  • 产生噪声:gen_noise
  • G网络产生图片,并将生成的图片进行保存
  • 从数据集中随机获取动漫头像和标签数据

关于这些代码具体的说明,可以看一下注释。

def gen_noise(batch_size, noise_size=100):
"""
生成高斯噪声
:param batch_size: 生成噪声的数量
:param noise_size: 噪声的维度
:return: (batch_size,noise)的高斯噪声
"""
return np.random.normal(0, 1, size=(batch_size, noise_size)) def generate_images(generator,img_path):
"""
G网络生成图片
:param generator: 生成器
:return: (64,64,3)维度 16张图片
"""
noise = gen_noise(16, 100)
hairs = np.zeros(16)
eyes = np.zeros(16) # 指令生成头发,和眼睛的颜色
for h in range(len(HAIRS)):
hairs[h] = h for e in range(len(EYES)):
eyes[e] = e
# 生成图片
fake_data_X = generator.predict([noise, hairs, eyes])
plt.figure(figsize=(4, 4))
gs1 = gridspec.GridSpec(4, 4)
gs1.update(wspace=0, hspace=0)
for i in range(16):
ax1 = plt.subplot(gs1[i])
ax1.set_aspect('equal')
image = fake_data_X[i, :, :, :]
fig = plt.imshow(image)
plt.axis('off')
fig.axes.get_xaxis().set_visible(False)
fig.axes.get_yaxis().set_visible(False)
plt.tight_layout()
# 保存图片
plt.savefig(img_path, bbox_inches='tight', pad_inches=0) def sample_from_dataset(batch_size, images, hair_tags, eye_tags):
"""
从数据集中随机获取图片
:param batch_size: 批处理大小
:param images: 数据集
:param hair_tags: 头发颜色标签数据集
:param eye_tags: 眼睛颜色标签数据集
:return:
"""
choice_indices = np.random.choice(len(images), batch_size)
sample = images[choice_indices]
y_hair_label = hair_tags[choice_indices]
y_eyes_label = eye_tags[choice_indices]
return sample, y_hair_label, y_eyes_label

进行训练

然后定义训练方法, 在训练的过程中,我们一般来说会将10进行smooth,让它们在一定的范围内波动。同时我们在训练D网络的过程中,我们会这样做:

  1. 真实的图片,真实的标签进行训练 —— 训练判别器对真实图片的判别能力
  2. G网络产生的图片,虚假的标签进行训练 —— 训练判别器对fake 图片的判别能力

在训练G网路的时候我们会这样做:

  1. 产生噪声,虚假的标签(代码随机生成头发的颜色和眼睛的颜色),然后输入到GAN网络中
  2. 而针对于GAN网络的输出,我们将其定义为[1(认为其为真实图片)],[输入端的标签]。GAN网络的输出认为是1(实际上是虚假的图片),这样就能够产生一个loss,从而通过反向传播来更新G网络的权值(在这一个步骤中,D网络的权值并不会进行更新。)
def train(epochs, batch_size, noise_size, hair_num_class, eye_num_class):
"""
进行训练
:param epochs: 训练的步数
:param batch_size: 训练的批处理大小
:param noise_size: 噪声维度大小
:param hair_num_class: 头发颜色种类
:param eye_num_class: 眼睛颜色种类
:return:
"""
for step in range(0, epochs): # 每隔100轮保存数据
if (step % 100) == 0:
step_num = str(step).zfill(6)
generate_images(G, os.path.join("./generate_img", step_num + "_img.png")) # 随机产生数据并进行编码
sampled_label_hairs = np.random.randint(0, hair_num_class, batch_size).reshape(-1, 1)
sampled_label_eyes = np.random.randint(0, eye_num_class, batch_size).reshape(-1, 1)
sampled_label_hairs_cat = to_categorical(sampled_label_hairs, num_classes=hair_num_class)
sampled_label_eyes_cat = to_categorical(sampled_label_eyes, num_classes=eye_num_class)
noise = gen_noise(batch_size, noise_size)
# G网络生成图片
fake_data_X = G.predict([noise, sampled_label_hairs, sampled_label_eyes]) # 随机获得真实数据并进行编码
real_data_X, real_label_hairs, real_label_eyes = sample_from_dataset(
batch_size, images_data, y_hairs, y_eyes)
real_label_hairs_cat = to_categorical(real_label_hairs, num_classes=hair_num_class)
real_label_eyes_cat = to_categorical(real_label_eyes, num_classes=eye_num_class) # 产生0,1标签并进行smooth
real_data_Y = np.ones(batch_size) - np.random.random_sample(batch_size) * 0.2
fake_data_Y = np.random.random_sample(batch_size) * 0.2 # 训练D网络
dis_metrics_real = D.train_on_batch(real_data_X, [real_data_Y, real_label_hairs_cat,
real_label_eyes_cat])
dis_metrics_fake = D.train_on_batch(fake_data_X, [fake_data_Y, sampled_label_hairs_cat,
sampled_label_eyes_cat]) noise = gen_noise(batch_size, noise_size)
# 产生随机的hair 和 eyes标签
sampled_label_hairs = np.random.randint(0, hair_num_class, batch_size).reshape(-1, 1)
sampled_label_eyes = np.random.randint(0, eye_num_class, batch_size).reshape(-1, 1) # 将标签变成(,12)或者(,11)类型的
sampled_label_hairs_cat = to_categorical(sampled_label_hairs, num_classes=hair_num_class)
sampled_label_eyes_cat = to_categorical(sampled_label_eyes, num_classes=eye_num_class) real_data_Y = np.ones(batch_size) - np.random.random_sample(batch_size) * 0.2
# GAN网络的输入
GAN_X = [noise, sampled_label_hairs, sampled_label_eyes]
# GAN网络的输出
GAN_Y = [real_data_Y, sampled_label_hairs_cat, sampled_label_eyes_cat]
# 对GAN网络进行训练
gan_metrics = gan.train_on_batch(GAN_X, GAN_Y) # 保存生成器
if step % 100 == 0:
print("Step: ", step)
print("Discriminator: real/fake loss %f, %f" % (dis_metrics_real[0], dis_metrics_fake[0]))
print("GAN loss: %f" % (gan_metrics[0]))
G.save(os.path.join('./model', str(step) + "_GENERATOR.hdf5"))

一般来说,训练1w轮就可以得到一个比较好的结果了(博客的开头的那两张图片就是训练1w轮的模型生成的),不过值得注意的是,在训练轮数过多的情况下产生了过拟合(产生的图片逐渐一毛一样)。

train(1000000,64,100,len(HAIRS),len(EYES))

可视化界面

可视化界面的代码如下所示,也是我从Anime-Face-ACGAN里面copy的,没什么好说的,就是直接使用tk框架搭建了一个界面,一个按钮。

import tkinter as tk
from tkinter import ttk import imageio
import numpy as np
from PIL import Image, ImageTk
from keras.models import load_model num_class_hairs = 12
num_class_eyes = 11
def load_model():
# 这里使用的是1w轮的训练模型
g = load_model(str(10000) + '_GENERATOR.hdf5')
return g
# 加载模型
G = load_model()
# 创建窗体
win = tk.Tk()
win.title('可视化GUI')
win.geometry('400x200') def gen_noise(batch_size, latent_size):
return np.random.normal(0, 1, size=(batch_size, latent_size)) def generate_images(generator, latent_size, hair_color, eyes_color):
noise = gen_noise(1, latent_size)
return generator.predict([noise, hair_color, eyes_color]) def create():
hair_color = np.array(comboxlist1.current()).reshape(1, 1)
eye_color = np.array(comboxlist2.current()).reshape(1, 1) image = generate_images(G, 100, hair_color, eye_color)[0]
imageio.imwrite('anime.png', image)
img_open = Image.open('anime.png')
img = ImageTk.PhotoImage(img_open)
label.configure(image=img)
label.image = img comvalue1 = tk.StringVar() # 窗体自带的文本,新建一个值
comboxlist1 = ttk.Combobox(win, textvariable=comvalue1)
comboxlist1["values"] = (
'orange hair', 'white hair', 'aqua hair', 'gray hair', 'green hair', 'red hair', 'purple hair', 'pink hair',
'blue hair', 'black hair', 'brown hair', 'blonde hair')
# 默认选择第一个
comboxlist1.current(0)
comboxlist1.pack() comvalue2 = tk.StringVar()
comboxlist2 = ttk.Combobox(win, textvariable=comvalue2)
comboxlist2["values"] = (
'gray eyes', 'black eyes', 'orange eyes', 'pink eyes', 'yellow eyes', 'aqua eyes', 'purple eyes', 'green eyes',
'brown eyes', 'red eyes', 'blue eyes')
# 默认选择第一个
comboxlist2.current(0)
comboxlist2.pack() bm = tk.PhotoImage(file='anime.png')
label = tk.Label(win, image=bm)
label.pack() b = tk.Button(win,
text='一键起飞', # 显示在按钮上的文字
width=15, height=2,
command=create) # 点击按钮式执行的命令
b.pack()
win.mainloop()

界面如下所示

总结

cgan网相比较dcgan而言,差别不是很大,只不过是加了一个标签label而已。不过该篇博客的代码还是大量的借鉴了Anime-Face-ACGAN的代码,因为我也是一个新手,Just Study Together.

参考

Anime-Face-ACGAN

GAN — CGAN & InfoGAN (using labels to improve GAN)

A tutorial on Conditional Generative Adversarial Nets + Keras implementation

How to Develop a Conditional GAN (cGAN) From Scratch

GAN网络之入门教程(五)之基于条件cGAN动漫头像生成的更多相关文章

  1. GAN网络从入门教程(一)之GAN网络介绍

    GAN网络从入门教程(一)之GAN网络介绍 稍微的开一个新坑,同样也是入门教程(因此教程的内容不会是从入门到精通,而是从入门到入土).主要是为了完成数据挖掘的课程设计,然后就把挖掘榔头挖到了GAN网络 ...

  2. GAN网络从入门教程(二)之GAN原理

    在一篇博客GAN网络从入门教程(一)之GAN网络介绍中,简单的对GAN网络进行了一些介绍,介绍了其是什么,然后大概的流程是什么. 在这篇博客中,主要是介绍其数学公式,以及其算法流程.当然数学公式只是简 ...

  3. GAN网络从入门教程(三)之DCGAN原理

    目录 DCGAN简介 DCGAN的特点 几个重要概念 下采样(subsampled) 上采样(upsampling) 反卷积(Deconvolution) 批标准化(Batch Normalizati ...

  4. GAN网络之入门教程(四)之基于DCGAN动漫头像生成

    目录 使用前准备 数据集 定义参数 构建网络 构建G网络 构建D网络 构建GAN网络 关于GAN的小trick 训练 总结 参考 这一篇博客以代码为主,主要是来介绍如果使用keras构建一个DCGAN ...

  5. SpringBoot入门教程(五)Java基于MySQL实现附近的人

    “附近的人”这个功能估计都不陌生,与之类似的功能最开始是在各大地图应用上接触过,比如搜附近的电影院,附近的超市等等.然而真正让附近的人火遍大江南北的应该是微信"附近的人"这个功能, ...

  6. 【Zigbee技术入门教程-号外】基于Z-Stack协议栈的抢答系统

    [Zigbee技术入门教程-号外]基于Z-Stack协议栈的抢答系统 广东职业技术学院  欧浩源 一.引言    2017年全国职业院校技能大赛"物联网技术应用"赛项中任务三题2的 ...

  7. PySide——Python图形化界面入门教程(五)

    PySide——Python图形化界面入门教程(五) ——QListWidget 翻译自:http://pythoncentral.io/pyside-pyqt-tutorial-the-qlistw ...

  8. Elasticsearch入门教程(五):Elasticsearch查询(一)

    原文:Elasticsearch入门教程(五):Elasticsearch查询(一) 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:h ...

  9. 无废话ExtJs 入门教程五[文本框:TextField]

    无废话ExtJs 入门教程五[文本框:TextField] extjs技术交流,欢迎加群(201926085) 继上一节内容,我们在表单里加了个两个文本框.如下所示代码区的第42行位置,items: ...

随机推荐

  1. lombok配置

    lombok在springboot中无需配置即可在控制台输出. 一般使用backlog.xml作为配置文件. 在application中指定backlog位置 不做任何配置时候,logback.xml ...

  2. [易霖博YCTF]Web WriteUp

    中午队里师傅发到群里的比赛,借来队里师傅账号和队里其他师傅一起做了一下,ak了web,师傅们tql.学到挺多东西,总结一下. rce_nopar 进入题目给出源码: <?php if(isset ...

  3. 「查缺补漏」巩固你的Nginx知识体系

    Nginx篇 基本介绍 Nginx是一款轻量级的 Web服务器 / 反向代理服务器 / 电子邮件(IMAP/POP3)代理服务器,主要的优点是: 支持高并发连接,尤其是静态界面,官方测试Nginx能够 ...

  4. Activiti7 表介绍

    由于Activiti自生成的表较多,这里先对activiti自生成数据库表进行介绍. 数据库表的创建在后续的demo文章中进行介绍,并且后续会写一篇关于数据库详解的文章,这里先大概知道Activiti ...

  5. Mysql慢查询(分析工具)

    慢查询分析工具[mysqldumpslow] 常用的慢查询日志分析工具 汇总除查询条件外其他完全相同的SQL,并将分析结果按照参数中所指定的顺序输出 语法: mysqldumpslow -s r -t ...

  6. javascript面试题(一)

    1. var bar = null; console.log(typeof bar === 'object'); //logs true! 尽管 typeof bar === "object ...

  7. Agumaster 增加雪球网爬虫

  8. 关于pom.xml文件中配置jquery,以及如何在jsp中引入

    pom.xml <!-- 对jquery的支持 --> <dependency> <groupId>org.webjars.bower</groupId> ...

  9. python3 for

    当range中只有一个参数时,此参数表示终点,但不包括.(从0开始) 当range中有两个参数时,分别表示起点和终点.(左闭但不包括终点) 当range中有三个参数时,分别表示起点和终点,和步长,意思 ...

  10. linux如何把普通用户添加到sudo组

    sudo原理:运行命令时,系统检查/etc/sudoers 配置文件,看这个用户是否有执行sudo的权限,如果有权限,系统要求输入用户自己的密码,如果密码输入正确,系统会以root身份运行 passw ...