目录

在上篇博客(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. Unity游戏资源反解工具

    https://github.com/ata4/disunity http://devxdevelopment.com/UnityUnpacker 链接:https://pan.baidu.com/s ...

  2. nginx的gzip压缩

    随着nginx的发展,越来越多的网站使用nginx,因此nginx的优化变得越来越重要,今天我们来看看nginx的gzip压缩到底是怎么压缩的呢? gzip(GNU-ZIP)是一种压缩技术.经过gzi ...

  3. Redis安装问题解决方案

    Redis部署采坑记   Redis部署采坑记 问题一: 问题描述: make 编译源码时报错 问题详解: 因为缺少gcc-c++编译器的原因 解决方案: 方案一: yum -y install gc ...

  4. Kubernetes笔记(五):了解Pod(容器组)

    Kubernetes 中, 容器总是以 Pod(容器组)的方式进行调度与运行.因此对 Pod 的理解与掌握是学习 Kubernetes 的基础. 理解 Pod Pod(容器组)是 Kubernetes ...

  5. Python3实现zip分卷压缩

    Python实现zip分卷压缩 使用 zipfile 库 查看 官方中文文档 利用 Python 压缩 ZIP 文件,我们第一反应是使用 zipfile 库,然而,它的官方文档中却明确标注" ...

  6. “未在本地计算机上注册“Microsoft.ACE.OLEDB.12.0”提供程序”的解决方案

    不论是连接Access数据库或是SQL Server数据库,"未在本地计算机上注册"Microsoft.ACE.OLEDB.12.0"提供程序."这个问题从Of ...

  7. Spring源码系列(三)--spring-aop的基础组件、架构和使用

    简介 前面已经讲完 spring-bean( 详见Spring ),这篇博客开始攻克 Spring 的另一个重要模块--spring-aop. spring-aop 可以实现动态代理(底层是使用 JD ...

  8. 云计算openstack核心组件——keystone身份认证服务(5)

    一.Keystone介绍:       keystone 是OpenStack的组件之一,用于为OpenStack家族中的其它组件成员提供统一的认证服务,包括身份验证.令牌的发放和校验.服务列表.用户 ...

  9. elasticsearch 索引清理脚本及常用命令

    elastic索引日志清理不及时,很容易产生磁盘紧张,官网给出curl -k -XDELETE可以清理不需要的索引日志. 清理脚本 #!/bin/bash #Author: 648403020@qq. ...

  10. 《Java核心技术卷一》之 泛型

    一.引言 在学习集合的时候我们会发现一个问题,将一个对象丢到集合中后,集合并不记住对象的类型,统统都当做Object处理,这样我们取出来再使用时就得强制转换类型,导致代码臃肿,而且加入集合时都是以Ob ...