目录

在上篇博客(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. .Net在Windows上使用Jenkins做CI/CD的那些事

    背景 最近入职了一家新公司,公司各个方面都让我非常的满意,我也怀着紧张与兴奋的心情入职后,在第一天接到了领导给我的第一个任务——把整个项目的依赖引用重新整理并实施项目的CI/CD. 本篇的重点主要分享 ...

  2. 剑指 Offer 19. 正则表达式匹配

    题目描述 请实现一个函数用来匹配包含'. '和'*'的正则表达式.模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次).在本题中,匹配是指字符串的所有字符匹配整个模式. ...

  3. Solon详解(三)- Solon的web开发

    Solon详解系列文章: Solon详解(一)- 快速入门 Solon详解(二)- Solon的核心 Solon详解(三)- Solon的web开发 Solon详解(四)- Solon的事务传播机制 ...

  4. 判断同名股票是否存在的MyBatis查询函数写法

    在A股中,除非股票退市,六位的股票代号是永不变化的,而名称则可能变化,比如更换主业,更换金主,因经营不善而戴帽等,这时名称都会改变. 因此,从网页上爬取的实时股票信息,需要常常与存在本地数据库里的信息 ...

  5. 支持MySQL数据库的agumaster版本

    下载地址:https://files.cnblogs.com/files/xiandedanteng/agumaster20200501.zip

  6. 20190923-01Linux帮助命令 000 009

    man 获得帮助信息 1. 基本语法 man [命令或配置文件] (功能描述:获得帮助信息) 2.显示说明 表1-6 信息 功能 NAME 命令的名称和单行描述 SYNOPSIS 怎样使用命令 DES ...

  7. Zookeeper协议篇-Paxos算法与ZAB协议

    前言 可以自行去学习一下Zookeeper中的系统模型,节点特性,权限认证以及事件通知Watcher机制相关知识,本篇主要学习Zookeeper一致性算法和满足分布式协调的Zab协议 Paxos算法 ...

  8. 让document.write的广告无阻塞的加载

    广告代码分析 很多第三方的广告系统都是使用document.write来加载广告,如下面的一个javascript的广告链接. 1 <script type="text/javascr ...

  9. 单元测试unittest(基于数据驱动的框架:unittest+HTMLTestRunner/BeautifulReport+yaml+ddt)

    一.定义 unittest单元测试框架不仅可以适用于单元测试,还可以适用WEB自动化测试用例的开发与执行,该测试框架可组织执行测试用例,并且提供了丰富的断言方法,判断测试用例是否通过,最终生成测试结果 ...

  10. python 3 字典

    字典,是可变的无序集合,同时是一种以键值对为基本元素的可以存储各种数据类型的集合,用大括号({})表示字典的开始和结束,元素之间用(,)分隔. 键值对,由键(Key)和值(Value)组成,中间用冒号 ...