代码相关在后半部分
前端实现
页面使用纯html、css、js制作。包括主页、网址导航、头像生成、精选图片、联系作者等功能。
主页实现
主页图片可以自动循环播放。
网址导航实现
网址导航点击相应模块可以跳转到相应网站,所有网站均免费无广告。节省用户寻找合适的网站消耗的大量时间,总共包括三个类别用动漫、小说漫画、图片。
图像生成实现、包括生成路线一与二。一是调用训练好的DCGAN模型进行自动生成。线路二是获取本地头像资源进行显示。
生成线路一展示
生成线路二展示
精选图片实现。本功能是图片展示模仿pinterest网站设计
联系作者实现
代码实现
train()函数代码
# coding:utf8
import os
import ipdb
import torch as t
import torchvision as tv
import tqdm # 进度条库
from model import NetG, NetD
from torchnet.meter import AverageValueMeter # 用于跟踪平均值
class Config(object):
"""model: 定义判别器和生成器的初始化参数"""
nz = 100 # 判别器的输入,即噪声的维度 (1*1*nz)
ngf = 64 # 生成器feature map数
ndf = 64 # 判别器feature map数
"""加载数据"""
# 数据集存放路径(文件夹里只有图片)
data_path = './face/'
# 多进程加载数据所用的进程数('win'中,该值设置为0)
num_workers = 0
# 数据集中的图片尺寸,图片大小都是固定的,为3*96*96
image_size = 96
# 小批量值
batch_size = 256
"""训练所用到的超参数"""
# 是否使用GPU
gpu = True
# 训练迭代次数
max_epoch = 500
# 生成器和判别器的学习率
lr1 = 2e-4 # 生成器的学习率
lr2 = 2e-4 # 判别器的学习率
# 优化器参数: Adam优化器的beta1参数
beta1 = 0.5
# 每1个batch训练一次判别器
d_every = 1
# 每5个batch训练一次生成器
g_every = 5
"""可视化生成器训练过程"""
# 是否使用visdom可视化
vis = True
# visdom的env名字
env = 'GAN'
# 每间隔plot_every个batch,visdom画图一次
plot_every = 20
# 存在该文件则进入debug模式
debug_file = '/游戏/Anime-Avatar-Generating_With-DCGAN-main/tmp/debuggan'
"""保存训练过程中,判别器生成的图片 """
# 保存图片的路径
save_path = 'visdom'
# 每save_every个epoch保存一次模型权重和生成的图片,
# 权重文件默认保存在checkpoints, 生成图片默认保存在save_path
save_every = 20
"""预训练模型"""
netd_path = None
netg_path = None
# netd_path = 'checkpoints/netd_299.pth'
# netg_path = 'checkpoints/netg_299.pth'
"""使用训练好的生成器,生成图片"""
# 从512张生成的图片中保存最好的64张
gen_search_num = 512;
gen_num = 64
# 噪声均值和方差
gen_mean = 0 # 生成噪声的均值
gen_std = 1 # 生成噪声的标准差
# result
gen_img = 'result.png'
opt = Config()
def train(**kwargs):
for k_, v_ in kwargs.items(): # 覆盖配置,可以在调试中动态修改参数
setattr(opt, k_, v_)
device = t.device('cuda') if opt.gpu else t.device('cpu')
print("device: ", device)
if opt.vis:
from visualize import Visualizer
vis = Visualizer(opt.env) # 初始化可视化工具
# 设置数据转换和加载
transforms = tv.transforms.Compose([
tv.transforms.Resize(opt.image_size),
tv.transforms.CenterCrop(opt.image_size),
tv.transforms.ToTensor(),
tv.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) # 接受两个mean(均值)和 std(标准差)
])
dataset = tv.datasets.ImageFolder(opt.data_path, transform=transforms)
print("dataset loader")
dataloader = t.utils.data.DataLoader(dataset,
batch_size=opt.batch_size,
shuffle=True,
num_workers=opt.num_workers,
drop_last=True
)
# 网络
print("net")
netg, netd = NetG(opt), NetD(opt)
map_location = lambda storage, loc: storage
if opt.netd_path:
netd.load_state_dict(t.load(opt.netd_path, map_location=map_location)) # pytorch提供标准提取模型函数
if opt.netg_path:
netg.load_state_dict(t.load(opt.netg_path, map_location=map_location))
netd.to(device)
netg.to(device)
# 定义优化器和损失
optimizer_g = t.optim.Adam(netg.parameters(), opt.lr1, betas=(opt.beta1, 0.999))
optimizer_d = t.optim.Adam(netd.parameters(), opt.lr2, betas=(opt.beta1, 0.999))
criterion = t.nn.BCELoss().to(device) # 二元交叉熵损失函数,判断输入图像是真实的还是生成的
# 真图片label为1,假图片label为0
# noises为生成网络的输入
true_labels = t.ones(opt.batch_size).to(device) # 真实图片标签
fake_labels = t.zeros(opt.batch_size).to(device) # 伪造图片标签
fix_noises = t.randn(opt.batch_size, opt.nz, 1, 1).to(device) # 固定噪声,用于生成图片
noises = t.randn(opt.batch_size, opt.nz, 1, 1).to(device) # 噪声输入
errord_meter = AverageValueMeter() # 判别器误差
errorg_meter = AverageValueMeter() # 生成器误差
epochs = range(opt.max_epoch)
for epoch in iter(epochs):
print("epoch:", epoch)
fix_fake_imgs = None
for ii, (img, _) in tqdm.tqdm(enumerate(dataloader)):
real_img = img.to(device)
if ii % opt.d_every == 0:
# 训练判别器
optimizer_d.zero_grad() # 清除判别器梯度
## 尽可能的把真图片判别为正确
output = netd(real_img)
error_d_real = criterion(output, true_labels)
error_d_real.backward() # 反向传播真实图片的损失
## 尽可能把假图片判别为错误
noises.data.copy_(t.randn(opt.batch_size, opt.nz, 1, 1)) # 生成新的随机噪声
fake_img = netg(noises).detach() # 根据噪声生成假图
output = netd(fake_img)
error_d_fake = criterion(output, fake_labels) # 计算假图片产生的损失
error_d_fake.backward()
optimizer_d.step() # 更新判别器的权重
error_d = error_d_fake + error_d_real # 总的判别器损失
errord_meter.add(error_d.item()) # 更新判别器损失的计量器
if ii % opt.g_every == 0:
# 训练生成器
optimizer_g.zero_grad()
noises.data.copy_(t.randn(opt.batch_size, opt.nz, 1, 1))
fake_img = netg(noises)
output = netd(fake_img)
error_g = criterion(output, true_labels)
error_g.backward()
optimizer_g.step() # 更新生成器的权重
errorg_meter.add(error_g.item()) # 更新生成器损失的计量器
# if opt.vis and ii % opt.plot_every == opt.plot_every - 1:
if opt.vis:
## 可视化
# if os.path.exists(opt.debug_file):
# ipdb.set_trace()
fix_fake_imgs = netg(fix_noises)
vis.images(fix_fake_imgs.detach().cpu().numpy()[:64] * 0.5 + 0.5, win='fixfake')
vis.images(real_img.data.cpu().numpy()[:64] * 0.5 + 0.5, win='real')
vis.plot('errord', errord_meter.value()[0]) # 可视化判别器的平均损失
vis.plot('errorg', errorg_meter.value()[0]) # 可视化生成器的平均损失
if (epoch + 1) % opt.save_every == 0:
# 保存模型、图片
if fix_fake_imgs is not None:
tv.utils.save_image(fix_fake_imgs.data[:64], '%s/%s.png' % (opt.save_path, epoch), normalize=True,
range=(-1, 1))
t.save(netd.state_dict(), 'checkpoints/netd_%s.pth' % epoch)
t.save(netg.state_dict(), 'checkpoints/netg_%s.pth' % epoch)
# 持续监控和记录判别器和生成器的损失值
errord_meter.reset()
errorg_meter.reset()
if __name__ == '__main__':
# python -m visdom.server
train()
生成器与判别器
# coding:utf8
from torch import nn
class NetG(nn.Module):
"""
生成器定义
"""
def __init__(self, opt):
super(NetG, self).__init__()
ngf = opt.ngf # 生成器feature map数
self.main = nn.Sequential(
# 输入是一个nz维度的噪声,可以认为是一个1*1*nz的feature map
nn.ConvTranspose2d(opt.nz, ngf * 8, 4, 1, 0, bias=False),# 逆卷积层,用于从随机噪声生成feature map
nn.BatchNorm2d(ngf * 8), # 批量归一化,稳定训练
nn.ReLU(True),# ReLU激活函数,增加非线性
# 上一步的输出形状:(ngf*8) x 4 x 4
nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf * 4),
nn.ReLU(True),
# 上一步的输出形状: (ngf*4) x 8 x 8
nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf * 2),
nn.ReLU(True),
# 上一步的输出形状: (ngf*2) x 16 x 16
nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf),
nn.ReLU(True),
# 上一步的输出形状:(ngf) x 32 x 32
nn.ConvTranspose2d(ngf, 3, 5, 3, 1, bias=False),
nn.Tanh() # 输出范围 -1~1 采用Tanh
# 输出形状:3 x 96 x 96
)
def forward(self, input):
return self.main(input)
class NetD(nn.Module):
"""
判别器定义
"""
def __init__(self, opt):
super(NetD, self).__init__()
ndf = opt.ndf
self.main = nn.Sequential(
# 输入 3 x 96 x 96
nn.Conv2d(3, ndf, 5, 3, 1, bias=False),# 卷积层,从图像中提取特征
nn.LeakyReLU(0.2, inplace=True), # LeakyReLU激活函数,允许小的负值,增加非线性
# 输出 (ndf) x 32 x 32
nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 2),
nn.LeakyReLU(0.2, inplace=True),
# 输出 (ndf*2) x 16 x 16
nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 4),
nn.LeakyReLU(0.2, inplace=True),
# 输出 (ndf*4) x 8 x 8
nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 8),
nn.LeakyReLU(0.2, inplace=True),
# 输出 (ndf*8) x 4 x 4
nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),
nn.Sigmoid() # 输出一个概率
)
def forward(self, input):
return self.main(input).view(-1)
generate()函数
# coding:utf8
import os
import torch as t
import torchvision as tv
from model import NetG, NetD
class Config(object):
"""model: 定义判别器和生成器的初始化参数"""
nz = 100 # 判别器的输入,即噪声的维度 (1*1*nz)
ngf = 64 # 生成器feature map数
ndf = 64 # 判别器feature map数
"""加载数据"""
# 数据集存放路径(文件夹里只有图片)
data_path = './faces'
# 多进程加载数据所用的进程数('win'中,该值设置为0)
num_workers = 0
# 数据集中的图片尺寸,图片大小都是固定的,为3*96*96
image_size = 96
# 小批量值
batch_size = 256
"""训练所用到的超参数"""
# 是否使用GPU
gpu = True
# 训练迭代次数
max_epoch = 300
# 生成器和判别器的学习率
lr1 = 2e-4 # 生成器的学习率
lr2 = 2e-4 # 判别器的学习率
# 优化器参数: Adam优化器的beta1参数
beta1 = 0.5
# 每1个batch训练一次判别器
d_every = 1
# 每5个batch训练一次生成器
g_every = 5
"""可视化生成器训练过程"""
# 是否使用visdom可视化
vis = True
# visdom的env名字
env = 'GAN'
# 每间隔plot_every个batch,visdom画图一次
plot_every = 20
# 存在该文件则进入debug模式
debug_file = '/tmp/debuggan'
"""保存训练过程中,判别器生成的图片 """
# 保存图片的路径
save_path = 'visdom'
# 每save_every个epoch保存一次模型权重和生成的图片,
# 权重文件默认保存在checkpoints, 生成图片默认保存在save_path
save_every = 50
"""预训练模型"""
# netd_path = None
# netg_path = None
# 379不错
netd_path = 'checkpoints/netd_399.pth'
netg_path = 'checkpoints/netg_399.pth'
"""使用训练好的生成器,生成图片"""
# 从512张生成的图片中保存最好的64张
gen_search_num = 512;gen_num = 64
# 噪声均值和方差
gen_mean = 0
gen_std = 1
# result
# gen_img = './static/img-generate/result.png'
gen_img= './static/img-generate/'
opt = Config()
@t.no_grad()
def generate(**kwargs):
"""
随机生成动漫头像,并根据netd的分数选择较好的
"""
# 将参数kwargs中的参数赋值给opt对象,这里假设opt是一个命名空间对象
for k_, v_ in kwargs.items():
setattr(opt, k_, v_)
device=t.device('cuda') if opt.gpu else t.device('cpu')
print("device: ",device)
# 创建生成器和判别器的实例,并设置为评估模式
netg, netd = NetG(opt).eval(), NetD(opt).eval()
# 生成随机噪声向量
noises = t.randn(opt.gen_search_num, opt.nz, 1, 1).normal_(opt.gen_mean, opt.gen_std)
noises = noises.to(device)
# 加载判别器和生成器的参数,并将模型移动到对应的设备上
map_location = lambda storage, loc: storage
netd.load_state_dict(t.load(opt.netd_path, map_location=map_location))
netg.load_state_dict(t.load(opt.netg_path, map_location=map_location))
netd.to(device)
netg.to(device)
# 生成图片,并计算图片在判别器的分数
fake_img = netg(noises)
scores = netd(fake_img).detach()
# 挑选最好的某几张
print("fake img")
indexs = scores.topk(opt.gen_num)[1]
print("indexs: ",indexs)
result = []
for ii in indexs:
result.append(fake_img.data[ii])
# 保存图片
print("result")
# tv.utils.save_image(t.stack(result), opt.gen_img, normalize=True, value_range=(-1, 1))
if not os.path.exists(opt.gen_img):
os.makedirs(opt.gen_img)
for i, img in enumerate(result, start=1):
# 构造图片保存路径,如./static/img-generate/1.png, ./static/img-generate/2.png, ..., ./static/img-generate/64.png
img_path = os.path.join(opt.gen_img, f'{i}.png')
# 保存单张图片
tv.utils.save_image(img, img_path, normalize=True, range=(-1, 1))
if __name__ == '__main__':
generate()
可视化函数
# coding:utf8
from itertools import chain
import visdom
import torch
import time
import torchvision as tv
import numpy as np
class Visualizer():
"""
封装了visdom的基本操作,但是你仍然可以通过`self.vis.function`
调用原生的visdom接口
"""
def __init__(self, env='default', **kwargs):
import visdom
self.vis = visdom.Visdom(env=env, use_incoming_socket=False,**kwargs)
# 画的第几个数,相当于横座标
# 保存(’loss',23) 即loss的第23个点
self.index = {}
self.log_text = ''
def reinit(self, env='default', **kwargs):
"""
修改visdom的配置
"""
self.vis = visdom.Visdom(env=env,use_incoming_socket=False, **kwargs)
return self
def plot_many(self, d):
"""
一次plot多个
@params d: dict (name,value) i.e. ('loss',0.11)
"""
for k, v in d.items():
self.plot(k, v)
def img_many(self, d):
for k, v in d.items():
self.img(k, v)
def plot(self, name, y):
"""
self.plot('loss',1.00)
"""
x = self.index.get(name, 0)
self.vis.line(Y=np.array([y]), X=np.array([x]),
win=(name),
opts=dict(title=name),
update=None if x == 0 else 'append'
)
self.index[name] = x + 1
def img(self, name, img_):
"""
self.img('input_img',t.Tensor(64,64))
"""
if len(img_.size()) < 3:
img_ = img_.cpu().unsqueeze(0)
self.vis.image(img_.cpu(),
win=(name),
opts=dict(title=name)
)
def img_grid_many(self, d):
for k, v in d.items():
self.img_grid(k, v)
def img_grid(self, name, input_3d):
"""
一个batch的图片转成一个网格图,i.e. input(36,64,64)
会变成 6*6 的网格图,每个格子大小64*64
"""
self.img(name, tv.utils.make_grid(
input_3d.cpu()[0].unsqueeze(1).clamp(max=1, min=0)))
def log(self, info, win='log_text'):
"""
self.log({'loss':1,'lr':0.0001})
"""
self.log_text += ('[{time}] {info} <br>'.format(
time=time.strftime('%m%d_%H%M%S'),
info=info))
self.vis.text(self.log_text, win=win)
def __getattr__(self, name):
return getattr(self.vis, name)