# 研究ノート(朱浩辰)
## 12/18
### 研究方向の調整
アニメキャラクターまたは作品ラベルを最適な評価基準とする前提のもと、他のラベル分類方法でのモデル評価データとの違いを探ることに重点を置く研究を進めています。
### Randomラベルによる訓練結果の更新
作品名の順序をラベル順序として使用せず、各キャラクターフォルダ内の画像をランダムに0~9のラベルに分配して訓練を行いました。1000エポックの訓練を2回実施しました。


生成された画像では、キャラクターが基本的な外見特徴しか持たず、目や口などの部分が混在した状態であることが明らかです。
以前のRandomが成功しなかった結果と比べ、今回のランダム訓練の効果はより明確です。
- **評価データ**


評価データは以前と大きな差はなく、FIDおよびLPIPSデータの最低点はMFデータセットの訓練結果に近いですが、アニメおよびキャラクターデータセットの評価データとの間には明確な差があります。
### 論文に向けた準備
- **訓練損失の推移グラフ**
バッチ単位で一定数のエポックのGlossおよびDlossの平均値を表示し、100エポックごとにデータを上部にマークしました。 

- FIDおよびLPIPS数値グラフは今後作成を続けます。
### TO DO
来週は以下のラベルを用いてそれぞれ訓練および評価を行う予定です:
- **髪の色**
- **肌の色**
## 12/15
# トレーニングプロセス記録と改良
## 1. トレーニングコードの改良
- トレーニングコードにおいて、**50エポックごと**にモデルを保存。
- **1000エポック**までに合計**20回**のモデル保存を実施し、段階的な評価を可能に。


---
## 2. データセットの追加
- 新たに **男性**・**女性**の **老人**、**子供**のデータセットを導入し、学習サンプルを多様化。(**12月8日~11日**)
---
## 3. Animeデータセットのトレーニング
### **問題と解決**
1. **12月12日**
- 問題:他のアニメキャラクターのデータセットを追加した後、生成器の出力が**カラフルなノイズ**となり、人型が認識できなくなる問題が発生。
- データセットを何度も調整するも原因が特定できず。

2. **12月13日**
- **クレヨンしんちゃん**のデータセットを削除後、正常にトレーニングが進行。
- 原因分析:クレヨンしんちゃんのデータセットは、キャラクターの顔が抽象的すぎるため、モデルが学習できなかった可能性。
- **トレーニング成果と評価結果**:


3. **12月15日**
- 後続の評価ではクレヨンしんちゃんのデータセットで問題が再現されず。
- トレーニングコードを更新後、クレヨンしんちゃんを含むデータセットで再度 **1000エポック**を実施。しかし、**カラフルなノイズの問題**が再発。
---
## 4. 性別・年齢データセットのトレーニング
### **分類方法**
- データを6つのカテゴリに分ける:
1. 男性の子供
2. 男性の成人
3. 男性の老人
4. 女性の子供
5. 女性の成人
6. 女性の老人
### **問題と解決**
1. **12月13日~14日**
- 初期トレーニングで、生成器の出力が**カラフルなノイズ**となる問題が発生。
- データセットのサンプル数を何度も調整し、**クレヨンしんちゃんデータセット**を削除しても問題が解消せず。
- ChatGPTに相談後、**ラベル分類の戦略**を更新することで問題を解決し、トレーニングが正常に進行。
### **トレーニング成果と評価結果**
<details>
<summary>コード</summary>
```python=
from __future__ import print_function
import os
from glob import glob
import random
import pickle
import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data
import torchvision.transforms as transforms
import torchvision.utils as vutils
from torchvision.datasets.folder import default_loader
# 设置随机种子
manualSeed = 999
print("Random Seed: ", manualSeed)
random.seed(manualSeed)
torch.manual_seed(manualSeed)
# 配置参数
batch_size = 128
image_size = 64
nc = 3
nz = 100
ngf = 64
ndf = 64
num_epochs = 1000
lr = 0.0001
beta1 = 0.5
ngpu = 1
# 数据路径和标签处理
data_roots = "/home/chen/DCGAN_anime/data_MF"
age_gender_to_idx = {
"male_kid": 0, "male_man": 1, "male_old": 2,
"female_kid": 3, "female_woman": 4, "female_old": 5
}
print("Age and Gender Labels:", age_gender_to_idx)
# 动态生成 anime_to_idx
anime_classes = set()
for age_gender, _ in age_gender_to_idx.items():
age_gender_path = os.path.join(data_roots, age_gender)
if os.path.exists(age_gender_path):
for character_folder in os.listdir(age_gender_path):
if os.path.isdir(os.path.join(age_gender_path, character_folder)):
anime_classes.add(character_folder)
anime_classes = sorted(list(anime_classes))
anime_to_idx = {name: idx for idx, name in enumerate(anime_classes)}
print("Generated Anime Classes:", anime_to_idx)
n_age_gender_classes = len(age_gender_to_idx)
n_anime_classes = len(anime_to_idx)
# 数据集定义
class CustomDataset(torch.utils.data.Dataset):
def __init__(self, roots, transform=None):
self.samples = []
for age_gender, age_gender_idx in age_gender_to_idx.items():
age_gender_path = os.path.join(roots, age_gender)
if not os.path.exists(age_gender_path):
print(f"Warning: Path {age_gender_path} does not exist.")
continue
for character_folder in os.listdir(age_gender_path): # 遍历角色子文件夹
character_path = os.path.join(age_gender_path, character_folder)
if os.path.isdir(character_path): # 检查是否为目录
anime_idx = anime_to_idx.get(character_folder) # 获取角色对应的 anime_idx
if anime_idx is None:
print(f"Warning: {character_folder} not found in anime_to_idx. Skipping.")
continue
for img_path in glob(os.path.join(character_path, "*")):
if img_path.lower().endswith(('.jpg', '.png', '.jpeg')): # 仅匹配图片文件
self.samples.append((img_path, age_gender_idx, anime_idx)) # 保存路径和标签
print(f"Loaded {len(self.samples)} samples.") # 打印加载的样本数量
self.transform = transform
def __len__(self):
return len(self.samples)
def __getitem__(self, index):
path, age_gender_label, anime_label = self.samples[index]
sample = default_loader(path)
if self.transform is not None:
sample = self.transform(sample)
return sample, age_gender_label, anime_label
# 数据预处理
transform = transforms.Compose([
transforms.Resize(image_size),
transforms.CenterCrop(image_size),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])
dataset = CustomDataset(data_roots, transform=transform)
dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=2)
# 检查 GPU 可用性
device = torch.device("cuda:0" if (torch.cuda.is_available() and ngpu > 0) else "cpu")
# 权重初始化函数
def weights_init(m):
classname = m.__class__.__name__
if classname.find('Conv') != -1:
nn.init.normal_(m.weight.data, 0.0, 0.02)
elif classname.find('BatchNorm') != -1:
nn.init.normal_(m.weight.data, 1.0, 0.02)
nn.init.constant_(m.bias.data, 0)
# 定义生成器
class Generator(nn.Module):
def __init__(self, ngpu, nz, n_anime_classes, n_age_gender_classes, ngf, nc):
super(Generator, self).__init__()
self.ngpu = ngpu
self.anime_emb = nn.Embedding(n_anime_classes, n_anime_classes)
self.age_gender_emb = nn.Embedding(n_age_gender_classes, n_age_gender_classes)
self.main = nn.Sequential(
nn.ConvTranspose2d(nz + n_anime_classes + n_age_gender_classes, ngf * 8, 4, 1, 0, bias=False),
nn.BatchNorm2d(ngf * 8),
nn.ReLU(True),
nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf * 4),
nn.ReLU(True),
nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf * 2),
nn.ReLU(True),
nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf),
nn.ReLU(True),
nn.ConvTranspose2d(ngf, nc, 4, 2, 1, bias=False),
nn.Tanh()
)
def forward(self, noise, anime_labels, age_gender_labels):
anime_emb = self.anime_emb(anime_labels).view(anime_labels.size(0), -1, 1, 1)
age_gender_emb = self.age_gender_emb(age_gender_labels).view(age_gender_labels.size(0), -1, 1, 1)
input = torch.cat([noise, anime_emb, age_gender_emb], dim=1)
return self.main(input)
# 定义判别器
class Discriminator(nn.Module):
def __init__(self, ngpu, nc, ndf, n_anime_classes, n_age_gender_classes):
super(Discriminator, self).__init__()
self.ngpu = ngpu
self.main = nn.Sequential(
nn.Conv2d(nc, ndf, 4, 2, 1, bias=False),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 2),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 4),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 8),
nn.LeakyReLU(0.2, inplace=True)
)
self.validity_layer = nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False)
self.anime_classifier = nn.Linear(ndf * 8 * 4 * 4, n_anime_classes)
self.age_gender_classifier = nn.Linear(ndf * 8 * 4 * 4, n_age_gender_classes)
def forward(self, input):
features = self.main(input) # (batch_size, ndf * 8, 4, 4)
validity = torch.sigmoid(self.validity_layer(features).view(-1, 1))
# (batch_size, 1)
features_flattened = features.view(features.size(0), -1) # (batch_size, ndf * 8 * 4 * 4)
anime_pred = self.anime_classifier(features_flattened) # (batch_size, n_anime_classes)
age_gender_pred = self.age_gender_classifier(features_flattened) # (batch_size, n_age_gender_classes)
return validity, anime_pred, age_gender_pred
# 初始化模型
netG = Generator(ngpu, nz, n_anime_classes, n_age_gender_classes, ngf, nc).to(device)
netG.apply(weights_init)
netD = Discriminator(ngpu, nc, ndf, n_anime_classes, n_age_gender_classes).to(device)
netD.apply(weights_init)
# 损失函数和优化器
criterion = nn.BCELoss()
classification_loss = nn.CrossEntropyLoss()
optimizerD = optim.Adam(netD.parameters(), lr=lr, betas=(beta1, 0.999))
optimizerG = optim.Adam(netG.parameters(), lr=lr, betas=(beta1, 0.999))
# 创建用于可视化生成器进展的固定潜在向量和标签
fixed_noise = torch.randn(64, nz, 1, 1, device=device)
fixed_anime_labels = torch.randint(0, n_anime_classes, (64,), device=device)
fixed_age_gender_labels = torch.randint(0, n_age_gender_classes, (64,), device=device)
# 定义真实和假的标签
real_label = 1.
fake_label = 0.
save_path = "/home/chen/DCGAN_anime/models"
# 训练循环
print("Starting Training Loop...")
img_list = []
G_losses = []
D_losses = []
iters = 0
for epoch in range(num_epochs):
for i, data in enumerate(dataloader, 0):
############################
# (1) 更新判别器:最大化 log(D(x)) + log(1 - D(G(z)))
###########################
netD.zero_grad()
real_images, age_gender_labels, anime_labels = data[0].to(device), data[1].to(device), data[2].to(device)
b_size = real_images.size(0)
label = torch.full((b_size,), real_label, dtype=torch.float, device=device)
output, anime_pred, age_gender_pred = netD(real_images)
output = output.view(-1)
errD_real = criterion(output, label) + classification_loss(anime_pred, anime_labels) + classification_loss(age_gender_pred, age_gender_labels)
errD_real.backward()
noise = torch.randn(b_size, nz, 1, 1, device=device)
fake_images = netG(noise, anime_labels, age_gender_labels)
label.fill_(fake_label)
output, anime_pred, age_gender_pred = netD(fake_images.detach())
output = output.view(-1)
errD_fake = criterion(output, label)
errD_fake.backward()
optimizerD.step()
############################
# (2) 更新生成器:最大化 log(D(G(z)))
###########################
netG.zero_grad()
label.fill_(real_label)
output, anime_pred, age_gender_pred = netD(fake_images)
output = output.view(-1)
errG = criterion(output, label)
errG.backward()
optimizerG.step()
# 保存损失
G_losses.append(errG.item())
D_losses.append(errD_real.item() + errD_fake.item())
# 输出训练状态
if i % 50 == 0:
print(f"[{epoch + 1}/{num_epochs}][{i}/{len(dataloader)}] Loss_D: {errD_real.item() + errD_fake.item():.4f} Loss_G: {errG.item():.4f}")
# 检查生成器的进展
if iters % 500 == 0:
with torch.no_grad():
fake = netG(fixed_noise, fixed_anime_labels, fixed_age_gender_labels).detach().cpu()
img_list.append(vutils.make_grid(fake, padding=2, normalize=True))
iters += 1
# 每50个epoch保存数据
if (epoch + 1) % 50 == 0:
folder_path = os.path.join(save_path, f"epoch_{epoch + 1}")
os.makedirs(folder_path, exist_ok=True)
torch.save(netG.state_dict(), os.path.join(folder_path, "netG.pth"))
torch.save(netD.state_dict(), os.path.join(folder_path, "netD.pth"))
with open(os.path.join(folder_path, "G_losses.pkl"), "wb") as f:
pickle.dump(G_losses, f)
with open(os.path.join(folder_path, "D_losses.pkl"), "wb") as f:
pickle.dump(D_losses, f)
with open(os.path.join(folder_path, "img_list.pkl"), "wb") as f:
pickle.dump(img_list, f)
with open(os.path.join(folder_path, "fixed_noise.pkl"), "wb") as f:
pickle.dump(fixed_noise, f)
with open(os.path.join(folder_path, "fixed_labels.pkl"), "wb") as f:
pickle.dump((fixed_anime_labels, fixed_age_gender_labels), f)
real_batch = next(iter(dataloader))
with open(os.path.join(folder_path, "real_batch.pkl"), 'wb') as f:
pickle.dump(real_batch, f)
# 保存训练配置
training_config = {
"batch_size": batch_size,
"image_size": image_size,
"nz": nz,
"ngf": ngf,
"ndf": ndf,
"num_epochs": num_epochs,
"lr": lr,
"beta1": beta1,
"n_anime_classes": n_anime_classes,
"n_age_gender_classes": n_age_gender_classes
}
with open(os.path.join(folder_path, "training_config.pkl"), "wb") as f:
pickle.dump(training_config, f)
print(f"Saved models and configurations for epoch {epoch + 1} in {folder_path}")
# 保存路径中全局的文件
torch.save(netG, os.path.join(save_path, "netG_full.pth"))
torch.save(netD, os.path.join(save_path, "netD_full.pth"))
print("Training complete.")
```
</details>
- **評価結果**:



効果としてはあまり良くなく、画面がぼやけており、スコアも非常に低いです。
## 5. RANDOMデータセットのトレーニング
### **実験設定**
- **12月15日**
- ランダムに生成した**偽ラベル**を用いてキャラクターデータセットを学習。
- 今回の学習には**18キャラクター**のデータセットを使用。
### **トレーニングと成果**
<details>
<summary>コード</summary>
```python=
from __future__ import print_function
import os
from glob import glob
import random
import pickle
import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data
import torchvision.transforms as transforms
import torchvision.utils as vutils
from torchvision.datasets.folder import default_loader
# 设置随机种子
manualSeed = 999
print("Random Seed: ", manualSeed)
random.seed(manualSeed)
torch.manual_seed(manualSeed)
# 配置参数
batch_size = 64
image_size = 64
nc = 3
nz = 100
ngf = 64
ndf = 64
num_epochs = 1000
lr = 0.0001
beta1 = 0.5
ngpu = 1
# 数据路径
data_roots = "/home/chen/DCGAN_anime/data"
# 动态生成角色标签
anime_classes = sorted(os.listdir(data_roots)) # 按角色文件夹生成标签
anime_to_idx = {name: idx for idx, name in enumerate(anime_classes)}
print("Generated Anime Classes:", anime_to_idx)
n_anime_classes = len(anime_to_idx)
# 数据集定义
class CustomDataset(torch.utils.data.Dataset):
def __init__(self, root, transform=None):
self.samples = []
for character_folder in os.listdir(root): # 遍历角色文件夹
character_path = os.path.join(root, character_folder)
if os.path.isdir(character_path): # 检查是否为目录
anime_idx = anime_to_idx.get(character_folder) # 获取角色对应的 anime_idx
for img_path in glob(os.path.join(character_path, "*")):
if img_path.lower().endswith(('.jpg', '.png', '.jpeg')): # 仅匹配图片文件
self.samples.append((img_path, anime_idx)) # 保存路径和标签
print(f"Loaded {len(self.samples)} samples.") # 打印加载的样本数量
self.transform = transform
def __len__(self):
return len(self.samples)
def __getitem__(self, index):
path, anime_label = self.samples[index]
sample = default_loader(path)
if self.transform is not None:
sample = self.transform(sample)
return sample, anime_label
# 数据预处理
transform = transforms.Compose([
transforms.Resize(image_size),
transforms.CenterCrop(image_size),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])
dataset = CustomDataset(data_roots, transform=transform)
dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=2)
# 检查 GPU 可用性
device = torch.device("cuda:0" if (torch.cuda.is_available() and ngpu > 0) else "cpu")
# 权重初始化函数
def weights_init(m):
classname = m.__class__.__name__
if classname.find('Conv') != -1:
nn.init.normal_(m.weight.data, 0.0, 0.02)
elif classname.find('BatchNorm') != -1:
nn.init.normal_(m.weight.data, 1.0, 0.02)
nn.init.constant_(m.bias.data, 0)
# 定义生成器
class Generator(nn.Module):
def __init__(self, ngpu, nz, n_anime_classes, ngf, nc):
super(Generator, self).__init__()
self.ngpu = ngpu
self.anime_emb = nn.Embedding(n_anime_classes, n_anime_classes)
self.main = nn.Sequential(
nn.ConvTranspose2d(nz + n_anime_classes, ngf * 8, 4, 1, 0, bias=False),
nn.BatchNorm2d(ngf * 8),
nn.ReLU(True),
nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf * 4),
nn.ReLU(True),
nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf * 2),
nn.ReLU(True),
nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf),
nn.ReLU(True),
nn.ConvTranspose2d(ngf, nc, 4, 2, 1, bias=False),
nn.Tanh()
)
def forward(self, noise, anime_labels):
anime_emb = self.anime_emb(anime_labels).view(anime_labels.size(0), -1, 1, 1)
input = torch.cat([noise, anime_emb], dim=1)
return self.main(input)
# 定义判别器
class Discriminator(nn.Module):
def __init__(self, ngpu, nc, ndf, n_anime_classes):
super(Discriminator, self).__init__()
self.ngpu = ngpu
self.main = nn.Sequential(
nn.Conv2d(nc, ndf, 4, 2, 1, bias=False),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 2),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 4),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 8),
nn.LeakyReLU(0.2, inplace=True),
)
self.validity_layer = nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False)
self.anime_classifier = nn.Linear(ndf * 8 * 4 * 4, n_anime_classes)
def forward(self, input):
features = self.main(input)
validity = torch.sigmoid(self.validity_layer(features).view(-1, 1))
features_flattened = features.view(features.size(0), -1)
anime_pred = self.anime_classifier(features_flattened)
return validity, anime_pred
# 初始化模型
netG = Generator(ngpu, nz, n_anime_classes, ngf, nc).to(device)
netG.apply(weights_init)
netD = Discriminator(ngpu, nc, ndf, n_anime_classes).to(device)
netD.apply(weights_init)
# 损失函数和优化器
criterion = nn.BCELoss()
classification_loss = nn.CrossEntropyLoss()
optimizerD = optim.Adam(netD.parameters(), lr=lr, betas=(beta1, 0.999))
optimizerG = optim.Adam(netG.parameters(), lr=lr, betas=(beta1, 0.999))
# 开始训练
print("Starting Training Loop...")
# 训练循环代码保持不变
# 创建用于可视化生成器进展的固定潜在向量和标签
fixed_noise = torch.randn(64, nz, 1, 1, device=device)
fixed_anime_labels = torch.randint(0, n_anime_classes, (64,), device=device)
# 定义真实和假的标签
real_label = 1.0
fake_label = 0.0
# 定义保存路径
save_path = "/home/chen/DCGAN_anime/models"
os.makedirs(save_path, exist_ok=True)
# 训练循环
print("Starting Training Loop...")
img_list = []
G_losses = []
D_losses = []
iters = 0
for epoch in range(num_epochs):
for i, data in enumerate(dataloader, 0):
############################
# (1) 更新判别器:最大化 log(D(x)) + log(1 - D(G(z)))
###########################
netD.zero_grad()
real_images, anime_labels = data[0].to(device), data[1].to(device)
b_size = real_images.size(0)
label = torch.full((b_size,), real_label, dtype=torch.float, device=device)
output, anime_pred = netD(real_images)
output = output.view(-1)
errD_real = criterion(output, label) + classification_loss(anime_pred, anime_labels)
errD_real.backward()
noise = torch.randn(b_size, nz, 1, 1, device=device)
fake_anime_labels = torch.randint(0, n_anime_classes, (b_size,), device=device) # 随机生成假标签
fake_images = netG(noise, fake_anime_labels)
label.fill_(fake_label)
output, anime_pred = netD(fake_images.detach())
output = output.view(-1)
errD_fake = criterion(output, label)
errD_fake.backward()
optimizerD.step()
############################
# (2) 更新生成器:最大化 log(D(G(z)))
###########################
netG.zero_grad()
label.fill_(real_label)
output, anime_pred = netD(fake_images)
output = output.view(-1)
errG = criterion(output, label)
errG.backward()
optimizerG.step()
# 保存损失
G_losses.append(errG.item())
D_losses.append(errD_real.item() + errD_fake.item())
# 输出训练状态
if i % 50 == 0:
print(f"[{epoch + 1}/{num_epochs}][{i}/{len(dataloader)}] Loss_D: {errD_real.item() + errD_fake.item():.4f} Loss_G: {errG.item():.4f}")
# 检查生成器的进展
if iters % 500 == 0:
with torch.no_grad():
fake = netG(fixed_noise, fixed_anime_labels).detach().cpu()
img_list.append(vutils.make_grid(fake, padding=2, normalize=True))
iters += 1
# 每50个epoch保存数据
if (epoch + 1) % 50 == 0:
folder_path = os.path.join(save_path, f"epoch_{epoch + 1}")
os.makedirs(folder_path, exist_ok=True)
torch.save(netG.state_dict(), os.path.join(folder_path, "netG.pth"))
torch.save(netD.state_dict(), os.path.join(folder_path, "netD.pth"))
with open(os.path.join(folder_path, "G_losses.pkl"), "wb") as f:
pickle.dump(G_losses, f)
with open(os.path.join(folder_path, "D_losses.pkl"), "wb") as f:
pickle.dump(D_losses, f)
with open(os.path.join(folder_path, "img_list.pkl"), "wb") as f:
pickle.dump(img_list, f)
with open(os.path.join(folder_path, "fixed_noise.pkl"), "wb") as f:
pickle.dump(fixed_noise, f)
with open(os.path.join(folder_path, "fixed_labels.pkl"), "wb") as f:
pickle.dump(fixed_anime_labels, f)
real_batch = next(iter(dataloader))
with open(os.path.join(folder_path, "real_batch.pkl"), 'wb') as f:
pickle.dump(real_batch, f)
# 保存训练配置
training_config = {
"batch_size": batch_size,
"image_size": image_size,
"nz": nz,
"ngf": ngf,
"ndf": ndf,
"num_epochs": num_epochs,
"lr": lr,
"beta1": beta1,
"n_anime_classes": n_anime_classes,
}
with open(os.path.join(folder_path, "training_config.pkl"), "wb") as f:
pickle.dump(training_config, f)
print(f"Saved models and configurations for epoch {epoch + 1} in {folder_path}")
# 保存路径中全局的文件
torch.save(netG, os.path.join(save_path, "netG_full.pth"))
torch.save(netD, os.path.join(save_path, "netD_full.pth"))
print("Training complete.")
```
</details>
- **評価結果**:



MFよりはかなり良い結果ですが、前回のanimeモデルの成果と同程度であり、スコアはMFと変わらず、むしろ以前の数値より悪い結果となっています。原因については現在調査中です。
### 12/05
#### 1. FID、IS、LPIPSの評価基準を追加
以下の3つの評価基準を新たに導入し、実際に適用しました:
- **FID(Frechet Inception Distance)**
- 評価基準:生成画像と実際の画像分布の距離を測定し、値が小さいほど良い。
- 数値の参考:
- **優秀**:≤ 10
- **普通**:10 ~ 50
- **悪い**:> 50
- **IS(Inception Score)**
- 評価基準:生成画像の多様性と鮮明さを測定し、値が大きいほど良い。
- 数値の参考:
- **優秀**:≥ 7.0
- **普通**:3.5 ~ 7.0
- **悪い**:< 3.5
- **LPIPS(Learned Perceptual Image Patch Similarity)**
- 評価基準:生成画像と実際の画像の知覚的な類似性を測定し、値が小さいほど良い。
- 数値の参考:
- **優秀**:≤ 0.15
- **普通**:0.15 ~ 0.40
- **悪い**:> 0.40
以下は先週キャラクターをラベルとして分類した **CDCGAN** の生成器が生成した画像の評価結果。

---
#### 2. データ収集と処理
- 今後のラベル分類研究の準備として、**Naruto**、**Onepiece**、**Conan**、**DragonBall** の合計 **7キャラクター** の各 **25枚の画像** を追加収集し、**データ拡張(DA)** 処理を実施しました。
- 性別分布:男性4名、女性3名。

- 目的:今後の性別ラベル分類訓練に向けた準備。
---
#### 3. アニメラベル分類訓練
- 先週と同じラベル分類方式で、以下のラベルを使用して **500エポック** の訓練を実施:
{'Conan': 0, 'DragonBall': 1, 'Naruto': 2, 'Onepiece': 3}



- **TODO**
引き続き進める
### 11/28
#### DCGANについて
- **トレーニング詳細:**
- 以前の **ナルト** データセットに加えて、今回は **コナン**、**ルフィ** など合計 **5キャラクター** を対象に **1000エポック** の訓練を実施しました。
- **コナンのデータセット** の最後の **5エポック** で保存された生成器の出力結果です。

見た目としてはかなり良く、コナンの特徴をよく捉えています。
#### CDCGANについて
- **トレーニング詳細:**
- キャラクターごとにラベル分けして訓練を行いました。
- ラベルの割り当ては、フォルダ構造に基づいて自動的に行われます。手動でラベルを設定する必要はなく、**ImageFolder** のサブフォルダ構造に従ってデータを整理するだけで済みます。

- DCGANと比較して訓練時間が長く、本実験では **500エポック** を行いました。
- 結果:
- 以下は最後の **5エポック** で保存された生成器の出力結果です。



- **TODO**
- 意見を聞いてから今後の方針を決定します。
### 11/21
#### AnimeGAN: 1000エポックのトレーニング
- **トレーニング詳細:**
- 以前300エポックで行ったAnimeGANのトレーニングを、今回は1000エポックに増やして実施(約3日間)。
- **結果:**
- 生成器の出力は、1000エポック付近でも300エポック時と大きな差は見られませんでした。
- 黄色の特徴がより顕著になったほか、肌が滑らかになりました。
- 画像が以前よりも鮮明になったものの、さらにエポックを増やしても期待通りの効果は得られない可能性があります。
- 
- 

#### アニメデータセットのトレーニング
- **トレーニング詳細:**
- データセットを自分で収集したアニメキャラクターの画像に変更し、解像度を調整した後にトレーニングを実施。
- 300エポックで得られた結果は以下の通り。
- **結果:**
- 実写人物画像に比べ、アニメ画像を用いる場合、高完成度の結果を得るにはより長いトレーニング時間が必要であることが分かりました。
- アニメキャラクターごとに目の大きさなどの特徴が異なるため、推論結果が期待外れになる場合もあります。
- 
- 
- **TODO**
- 意見を聞いてから今後の方針を決定します。
### 11/14
- **DCGANについて**
- 参考リンク:[DCGAN PyTorch チュートリアル](https://pytorch.org/tutorials/beginner/dcgan_faces_tutorial.html)
<details>
<summary>コード</summary>
```python=
from __future__ import print_function
import pickle
import random
import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data
import torchvision.datasets as dset
import torchvision.transforms as transforms
import torchvision.utils as vutils
import numpy as np
import matplotlib.pyplot as plt
# ランダムシードを設定して結果の再現性を確保
manualSeed = 999
print("Random Seed: ", manualSeed)
random.seed(manualSeed)
torch.manual_seed(manualSeed)
# パラメータの設定
dataroot = "/home/chen/DCGAN_anime/data/naruto" # データセットのパス
workers = 2 # データローダーのスレッド数
batch_size = 128 # バッチサイズ
image_size = 64 # 画像サイズ
nc = 3 # 入力画像のチャンネル数
nz = 100 # 潜在空間ベクトルの長さ
ngf = 64 # ジェネレーターの特徴マップ深さ
ndf = 64 # ディスクリミネーターの特徴マップ深さ
num_epochs = 2000 # 訓練エポック数
lr = 0.0001 # 学習率
beta1 = 0.5 # Adamオプティマイザのbeta1パラメータ
ngpu = 1 # GPU数
# データセットのロード
transform = transforms.Compose([
transforms.Resize(image_size), # 画像サイズを64x64に調整
transforms.CenterCrop(image_size),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])
dataset = dset.ImageFolder(root=dataroot, transform=transform)
dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=workers)
# デバイスの選択
device = torch.device("cuda:0" if (torch.cuda.is_available() and ngpu > 0) else "cpu")
# 重み初期化関数
def weights_init(m):
classname = m.__class__.__name__
if classname.find('Conv') != -1:
nn.init.normal_(m.weight.data, 0.0, 0.02)
elif classname.find('BatchNorm') != -1:
nn.init.normal_(m.weight.data, 1.0, 0.02)
nn.init.constant_(m.bias.data, 0)
# ジェネレーターの定義
class Generator(nn.Module):
def __init__(self, ngpu):
super(Generator, self).__init__()
self.ngpu = ngpu
self.main = nn.Sequential(
nn.ConvTranspose2d(nz, ngf * 8, 4, 1, 0, bias=False),
nn.BatchNorm2d(ngf * 8),
nn.ReLU(True),
nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf * 4),
nn.ReLU(True),
nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf * 2),
nn.ReLU(True),
nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf),
nn.ReLU(True),
nn.ConvTranspose2d(ngf, nc, 4, 2, 1, bias=False),
nn.Tanh()
)
def forward(self, input):
return self.main(input)
# ジェネレーターの作成と重みの初期化
netG = Generator(ngpu).to(device)
if (device.type == 'cuda') and (ngpu > 1):
netG = nn.DataParallel(netG, list(range(ngpu)))
netG.apply(weights_init)
print("Generator:")
print(netG)
# ディスクリミネーターの定義
class Discriminator(nn.Module):
def __init__(self, ngpu):
super(Discriminator, self).__init__()
self.ngpu = ngpu
self.main = nn.Sequential(
nn.Conv2d(nc, ndf, 4, 2, 1, bias=False),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 2),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 4),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 8),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),
nn.Sigmoid()
)
def forward(self, input):
return self.main(input)
# ディスクリミネーターの作成と重みの初期化
netD = Discriminator(ngpu).to(device)
if (device.type == 'cuda') and (ngpu > 1):
netD = nn.DataParallel(netD, list(range(ngpu)))
netD.apply(weights_init)
print("Discriminator:")
print(netD)
# バイナリクロスエントロピー損失関数の初期化
criterion = nn.BCELoss()
# ジェネレーターの進捗を可視化するための固定潜在ベクトルの作成
fixed_noise = torch.randn(64, nz, 1, 1, device=device)
# 訓練中の実際と偽ラベルの習慣
real_label = 1.
fake_label = 0.
# ディスクリミネーターとジェネレーターのAdamオプティマイザの設定
optimizerD = optim.Adam(netD.parameters(), lr=lr, betas=(beta1, 0.999))
optimizerG = optim.Adam(netG.parameters(), lr=lr, betas=(beta1, 0.999))
# 訓練ループの開始
img_list = []
G_losses = []
D_losses = []
iters = 0
print("Training開始...")
# 各エポックについて
for epoch in range(num_epochs):
# dataloaderの各バッチについて
for i, data in enumerate(dataloader, 0):
############################
# (1) ディスクリミネーターネットワークの更新:log(D(x)) + log(1 - D(G(z)))を最大化
###########################
netD.zero_grad()
real_cpu = data[0].to(device)
b_size = real_cpu.size(0)
label = torch.full((b_size,), real_label, dtype=torch.float, device=device)
output = netD(real_cpu).view(-1)
errD_real = criterion(output, label)
errD_real.backward()
D_x = output.mean().item()
noise = torch.randn(b_size, nz, 1, 1, device=device)
fake = netG(noise)
label.fill_(fake_label)
output = netD(fake.detach()).view(-1)
errD_fake = criterion(output, label)
errD_fake.backward()
D_G_z1 = output.mean().item()
errD = errD_real + errD_fake
optimizerD.step()
############################
# (2) ジェネレーターネットワークの更新:log(D(G(z)))を最大化
###########################
netG.zero_grad()
label.fill_(real_label) # ジェネレーターにとって偽画像のラベルは真である
output = netD(fake).view(-1)
errG = criterion(output, label)
errG.backward()
D_G_z2 = output.mean().item()
optimizerG.step()
# 訓練状況の出力
if i % 50 == 0:
print('[%d/%d][%d/%d]\tLoss_D: %.4f\tLoss_G: %.4f\tD(x): %.4f\tD(G(z)): %.4f / %.4f'
% (epoch, num_epochs, i, len(dataloader),
errD.item(), errG.item(), D_x, D_G_z1, D_G_z2))
G_losses.append(errG.item())
D_losses.append(errD.item())
# ジェネレーターの進捗を確認
if (iters % 100 == 0) or ((epoch == num_epochs-1) and (i == len(dataloader)-1)):
with torch.no_grad():
fake = netG(fixed_noise).detach().cpu()
img_list.append(vutils.make_grid(fake, padding=2, normalize=True))
iters += 1
# 損失値とジェネレーターの出力画像リストの保存
with open('/home/chen/DCGAN_anime/G_losses.pkl', 'wb') as f:
pickle.dump(G_losses, f)
with open('/home/chen/DCGAN_anime/D_losses.pkl', 'wb') as f:
pickle.dump(D_losses, f)
with open('/home/chen/DCGAN_anime/img_list.pkl', 'wb') as f:
pickle.dump(img_list, f)
with open('/home/chen/DCGAN_anime/real_batch.pkl', 'wb') as f:
pickle.dump(real_cpu.cpu(), f)
# 実際の画像のバッチを保存
real_batch = next(iter(dataloader))
with open('/home/chen/DCGAN_anime/real_batch.pkl', 'wb') as f:
pickle.dump(real_batch, f)
print("Training完了。損失、画像、実際のバッチが保存されました。")
```
</details>
- 元のCeleb-A Facesデータセットで正常にトレーニング後、ナルトのデータセットに置き換え、以前のデータ増強済みのデータセットを使用しました。
- 画像サイズを64x64に調整し、1000エポックのトレーニングを実行しました。


- エポック数を増やすと(1000→2000)結果が崩れた。
- 
- データ増強を増やし、データセットを拡充し、学習率を調整する予定です。
- **AnimeGANについて**
- 最初のタグがないコードを使用し、PyTorch形式に調整してトレーニングを行いました。


- 今回は300エポックのトレーニングで、画像の解像度は前より向上しましたが、ナルトの特徴はあまり現れていません。
- **TODO**
- 意見を聞いてから今後の方針を決定します。
# 11/7
- **GPUについて**
- GPUでMindSporeを使用し、CUDA 11.6バージョンと対応するcuDNNのインストールを試みました。
- しかし、GPU搭載のCUDA 12.6バージョンと競合し、トレーニングコードを正常に実行できませんでした。
- 最終的にはPyTorchに切り替え、コードを調整してGPUでの正常な動作が確認されました。
- **トレーニング詳細**
- 『naruto』のキャラクター「ナルト」のデータセット(60枚の画像をデータ増強して約1200枚)を使用。
- エポック数を100回に増やしましたが、先週の30回とほぼ同じ結果で大きな変化は見られませんでした。
- **自己注意モジュールの強化**
- 特徴捕捉能力を高めるため、自己注意モジュールを追加しました。
- GPUの消費が増加し、現在はバッチサイズ2でトレーニングを実行しています。
- トレーニングと調整は現在も進行中です。
<details>
<summary>変更生成器コード</summary>
```python=
import torch
import torch.nn as nn
import torch.nn.functional as F
from .conv2d_block import ConvBlock
from .inverted_residual_block import InvertedResBlock
from .upsample import UpSample
class SelfAttention(nn.Module):
"""
Self-Attention Layer for enhancing global feature understanding.
"""
def __init__(self, in_dim):
super(SelfAttention, self).__init__()
self.query_conv = nn.Conv2d(in_channels=in_dim, out_channels=in_dim // 8, kernel_size=1)
self.key_conv = nn.Conv2d(in_channels=in_dim, out_channels=in_dim // 8, kernel_size=1)
self.value_conv = nn.Conv2d(in_channels=in_dim, out_channels=in_dim, kernel_size=1)
self.gamma = nn.Parameter(torch.zeros(1))
self.softmax = nn.Softmax(dim=-1)
def forward(self, x):
batch_size, C, width, height = x.size()
proj_query = self.query_conv(x).view(batch_size, -1, width * height).permute(0, 2, 1)
proj_key = self.key_conv(x).view(batch_size, -1, width * height)
energy = torch.bmm(proj_query, proj_key)
attention = self.softmax(energy)
proj_value = self.value_conv(x).view(batch_size, -1, width * height)
out = torch.bmm(proj_value, attention.permute(0, 2, 1))
out = out.view(batch_size, C, width, height)
out = self.gamma * out + x
return out
class Generator(nn.Module):
"""
Generator network with conditional input.
Args:
channels (int): Base channel number per layer.
num_classes (int): Number of classes (i.e., different anime styles).
"""
def __init__(self, channels=32, num_classes=15):
super(Generator, self).__init__()
# Embedding layer to convert labels into feature vectors
self.embedding = nn.Embedding(num_classes, channels * 8 * 8) # Update embedding size to match feature map size
self.generator = nn.Sequential(
ConvBlock(3 + channels, channels, kernel_size=7), # Concatenate input channels (3 + channels)
ConvBlock(channels, channels * 2, stride=2),
ConvBlock(channels * 2, channels * 4, stride=2),
ConvBlock(channels * 4, channels * 4),
SelfAttention(channels * 4), # Adding self-attention after basic convolution
ConvBlock(channels * 4, channels * 4),
InvertedResBlock(channels * 4, channels * 8),
SelfAttention(channels * 8), # Adding self-attention in the deeper layer for enhanced feature capture
InvertedResBlock(channels * 8, channels * 8),
InvertedResBlock(channels * 8, channels * 8),
InvertedResBlock(channels * 8, channels * 8),
ConvBlock(channels * 8, channels * 4),
UpSample(channels * 4, channels * 4),
ConvBlock(channels * 4, channels * 4),
UpSample(channels * 4, channels * 2),
ConvBlock(channels * 2, channels * 2),
ConvBlock(channels * 2, channels, kernel_size=7),
nn.Conv2d(channels, 3, kernel_size=1, stride=1, padding=0),
nn.Tanh()
)
def forward(self, x, label):
batch_size, _, height, width = x.shape
label_embed = self.embedding(label) # Shape: [batch_size, channels * 8 * 8]
# Reshape label embedding to match feature map dimensions
label_embed = label_embed.view(batch_size, -1, 8, 8) # Adjust shape to [batch_size, channels, 8, 8]
label_embed = F.interpolate(label_embed, size=(height, width), mode='bilinear', align_corners=False)
# Concatenate input image and label features
x = torch.cat((x, label_embed), dim=1)
return self.generator(x)
```
</details>
<details>
<summary>変更識別器コード</summary>
```python=
import torch
import torch.nn as nn
import torch.nn.functional as F
class SelfAttention(nn.Module):
def __init__(self, in_channels):
super(SelfAttention, self).__init__()
self.query_conv = nn.Conv2d(in_channels, in_channels // 8, 1)
self.key_conv = nn.Conv2d(in_channels, in_channels // 8, 1)
self.value_conv = nn.Conv2d(in_channels, in_channels, 1)
self.gamma = nn.Parameter(torch.zeros(1))
def forward(self, x):
batch_size, C, width, height = x.size()
proj_query = self.query_conv(x).view(batch_size, -1, width * height).permute(0, 2, 1)
proj_key = self.key_conv(x).view(batch_size, -1, width * height)
energy = torch.bmm(proj_query, proj_key)
attention = torch.softmax(energy, dim=-1)
proj_value = self.value_conv(x).view(batch_size, -1, width * height)
out = torch.bmm(proj_value, attention.permute(0, 2, 1))
out = out.view(batch_size, C, width, height)
out = self.gamma * out + x
return out
class Discriminator(nn.Module):
def __init__(self, channels, n_dis, num_classes=15):
super(Discriminator, self).__init__()
self.has_bias = False
# Embedding layer to convert label to feature vector
self.embedding = nn.Embedding(num_classes, channels * 4 * 4)
layers = [
nn.Conv2d(channels + 3, channels, kernel_size=3, stride=1, padding=1, bias=self.has_bias), # 修改通道数为 channels + 3
nn.LeakyReLU(0.2, inplace=True)
]
for _ in range(1, n_dis):
layers += [
nn.Conv2d(channels, channels * 2, kernel_size=3, stride=2, padding=1, bias=self.has_bias),
nn.LeakyReLU(0.2, inplace=True),
SelfAttention(channels * 2), # 加入注意力机制
nn.Conv2d(channels * 2, channels * 4, kernel_size=3, stride=1, padding=1, bias=self.has_bias),
nn.InstanceNorm2d(channels * 4, affine=False),
nn.LeakyReLU(0.2, inplace=True),
]
channels *= 4
layers += [
nn.Conv2d(channels, channels, kernel_size=3, stride=1, padding=1, bias=self.has_bias),
nn.InstanceNorm2d(channels, affine=False),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(channels, 1, kernel_size=3, stride=1, padding=1, bias=self.has_bias),
]
self.discriminator = nn.Sequential(*layers)
def forward(self, x, label):
batch_size, _, height, width = x.shape
# Convert label to embedding and expand dimensions
label_embed = self.embedding(label) # Output shape is [batch_size, channels * 4 * 4]
label_embed = label_embed.view(batch_size, -1, 4, 4) # Reshape to match a reasonable size for further interpolation
label_embed = F.interpolate(label_embed, size=(height, width), mode='bilinear', align_corners=False)
# Concatenate label embedding with image tensor
x = torch.cat((x, label_embed), dim=1)
return self.discriminator(x)
class Generator(nn.Module):
def __init__(self, num_classes):
super(Generator, self).__init__()
self.label_embedding = nn.Embedding(num_classes, 50) # 假设50维度的嵌入
self.generator = nn.Sequential(
nn.Conv2d(3 + 50, 32, kernel_size=7, padding=3), # 输入图像加上条件信息
nn.ReLU(inplace=True),
nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1),
nn.ReLU(inplace=True),
SelfAttention(64), # 加入注意力机制
nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1),
nn.ReLU(inplace=True),
SelfAttention(128), # 加入注意力机制
nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True),
nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1),
nn.ReLU(inplace=True),
nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(64, 3, kernel_size=7, padding=3),
nn.Tanh()
)
def forward(self, x, labels):
label_emb = self.label_embedding(labels)
label_emb = label_emb.unsqueeze(2).unsqueeze(3)
label_emb = label_emb.expand(x.size(0), 50, x.size(2), x.size(3))
x = torch.cat((x, label_emb), dim=1)
return self.generator(x)
```
</details>
# 10/31
## octa実験の進捗
**MindSpore方面**:現在、GPUバージョンが互換性を持たず、octaのCUDAバージョンは12.6であり、MindSporeは11.6までしか対応していません。何度か試みましたが解決せず、サポートをいただければと思っています。
**PyTorch方面**:金光さんのリンクを使って該当バージョンのtorchとtorchvisionをインストールし、正常に使用できていますが、animeganのコードの多くはMindSpore形式で書かれているため、PyTorchのサブクラスモジュールに順次置き換えている最中です。
- 第5エポック
- 第15エポック
- 第25エポック
- 第30エポック
### **推論 (netG_29.ckpt)**

## TO DO
MindSporeのGPUが使えるようサポートを希望します。難しい場合は、全てPyTorch対応に調整する予定です。
# 10/24
## octa実験の進捗
先週のジェネレータが生成した画像と入力した実際の画像のサイズが一致しない問題を解決しました。
## 実験結果と損失関数グラフ
下の図は、第3、4、5エポックのトレーニング段階で、実際の画像をアニメ風に変換した結果です。
- 第3エポック
- 第4エポック
- 第5エポック

<details>
<summary>画像を生成するコード</summary>
```python=
if (epoch % args.save_interval) == 0 and (iters == size - 1):
stylized = denormalize_input(generator(img, label)).asnumpy()
no_stylized = denormalize_input(img).asnumpy()
h, w = no_stylized.shape[2], no_stylized.shape[3]
stylized_resized = np.array([cv2.resize(stylized[i].transpose(1, 2, 0), (w, h)).transpose(2, 0, 1) for i in range(stylized.shape[0])])
imgs = cv2.cvtColor(stylized_resized[0, :, :, :].transpose(1, 2, 0), cv2.COLOR_RGB2BGR)
imgs1 = cv2.cvtColor(no_stylized[0, :, :, :].transpose(1, 2, 0), cv2.COLOR_RGB2BGR)
for i in range(1, args.batch_size):
imgs = np.concatenate(
(imgs, cv2.cvtColor(stylized_resized[i, :, :, :].transpose(1, 2, 0), cv2.COLOR_RGB2BGR)), axis=1)
imgs1 = np.concatenate(
(imgs1, cv2.cvtColor(no_stylized[i, :, :, :].transpose(1, 2, 0), cv2.COLOR_RGB2BGR)), axis=1)
cv2.imwrite(
os.path.join(args.save_image_dir, args.dataset, 'epoch_' + str(epoch) + '.jpg'),
np.concatenate((imgs1, imgs), axis=0))
```
</details>
### **推論 (netG_3.ckpt)**

<details>
<summary>推論コード</summary>
```python=
import argparse
import os
import mindspore
import cv2
from tqdm import tqdm
from mindspore import Tensor
from mindspore import context
from mindspore import float32 as dtype
from mindspore import load_checkpoint, load_param_into_net
from mindspore.train.model import Model
from models.generator import Generator
from animeganv2_utils.pre_process import transform, inverse_transform_infer
def parse_args():
"""Argument parsing."""
parser = argparse.ArgumentParser(description='infer')
parser.add_argument('--device_target', default='CPU', choices=['CPU', 'GPU', 'Ascend'], type=str)
parser.add_argument('--device_id', default=0, type=int)
parser.add_argument('--infer_dir', default='../dataset/test', type=str)
parser.add_argument('--infer_output', default='../dataset/output', type=str)
parser.add_argument('--ckpt_file_name', default='/home/chen/animeganv2/checkpoints/conan/netG_4.ckpt', type=str)
return parser.parse_args()
def main():
"""Convert real image to anime image."""
net = Generator()
param_dict = load_checkpoint(args.ckpt_file_name)
load_param_into_net(net, param_dict)
data = os.listdir(args.infer_dir)
bar = tqdm(data)
model = Model(net)
# 设置标签(例如,假设风格标签为 0)
label = Tensor([1], dtype=mindspore.int32)
if not os.path.exists(args.infer_output):
os.makedirs(args.infer_output)
for img_path in bar:
img = transform(os.path.join(args.infer_dir, img_path))
img = Tensor(img, dtype=dtype)
# 将图像和标签一起传入推理函数
output = model.predict(img, label)
img = inverse_transform_infer(img)
output = inverse_transform_infer(output)
output = cv2.resize(output, (img.shape[1], img.shape[0]))
cv2.imwrite(os.path.join(args.infer_output, img_path), output)
print('Successfully output images in ' + args.infer_output)
if __name__ == '__main__':
args = parse_args()
context.set_context(mode=context.GRAPH_MODE, device_target=args.device_target, device_id=args.device_id)
main()
```
</details>
結果原因の推測:生成器はすべてのアニメデータセットの特徴を**総合的に学習**した可能性があり、各スタイルの特性を個別に学習していない可能性があります。そのため、生成された画像は、特定のスタイルに固有の特徴ではなく、すべてのアニメスタイルに共通する特徴を含む**混合スタイル**のように見えます。
## TO DO
生成器や識別器、損失関数の改良を行い、ラベルが実際に使用されるようにする。
# 10/17
## octa実験の進捗
- **小池さんと新垣さんに連絡してアカウントを作成しました。**
- **RSA鍵の公開鍵を提供後、pentaを踏み台としてoctaサーバに接続しました。**
- **先生に相談し、configファイルのportとproxycommandを修正してもらい、vscodeでoctaに直接アクセスできるように問題を解決しました。**
- **現在、octaはGPUを使用できないため、一時的にCPUでトレーニングを行っています。**
- **fsmのデータセットを直接octaに転送できなかったため、DAを使用してデータセットを処理しました。現在、合計9936枚のアニメ画像があります。**
- 
- **トレーニングが進行中で、batchsizeは8に設定されており、1エポックに約6時間かかります。ckptファイルは各エポック終了時に保存されます。**
---
### **第一次トレーニング:**

- **ckptファイルの保存時にエラーが発生しました。generatorの呼び出し時に、imgとlabelのパラメータが一致していなかったためです。**
- **修正後、トレーニングを続けており、GPUが使用可能になったら30エポックの完全なトレーニングを行う予定です。**
---
### **第二次トレーニング:**

- **generatorネットワーク内でimgsとimg1のサイズが一致せず、GPTに相談して問題を解決する予定です。**
- **現在、少量のデータセットを使用してトレーニングを進めています。**
# 10/10
## DAの実装とエフェクト表示
<details>
<summary>DAコード</summary>
```python=
import os
import cv2
from PIL import Image
import numpy as np
# 回転して透明背景で空白部分を埋める
def rotate_image_with_transparent_fill(image, angle):
# 画像を RGBA モードに変換し、Alpha チャンネルを追加
pil_img = Image.fromarray(image).convert("RGBA")
# 画像を回転し、サイズを拡張
rotated_img = pil_img.rotate(angle, resample=Image.BICUBIC, expand=True)
# numpy 配列に変換
rotated_array = np.array(rotated_img)
# 空白部分(全黒の部分)を見つけ、Alpha チャンネルを 0 (完全透明)に設定
empty_mask = (rotated_array[:, :, :3] == 0).all(axis=-1)
rotated_array[empty_mask, 3] = 0 # Alpha チャンネルを透明に設定
return rotated_array
# Step 1: データ増強、透明背景を追加
def process_all_images_in_folder(input_dir, output_dir, num_augmentations=5):
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# フォルダ内のすべての画像ファイルを取得
image_files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
for img_name in image_files:
img_path = os.path.join(input_dir, img_name)
print(f"Processing image: {img_path}")
# 画像の読み込み
image = cv2.imread(img_path)
if image is None:
print(f"Warning: Failed to load image {img_name}. Skipping.")
continue
image = image[:, :, ::-1] # BGR to RGB
# 原始の画像を保存(回転していないことを確保)
original_output_path = os.path.join(output_dir, f"DA_original_{os.path.splitext(img_name)[0]}.png")
Image.fromarray(image).save(original_output_path) # 原始の画像を PNG として保存
# 指定された数量の DA 画像を生成
for i in range(num_augmentations):
angle = np.random.uniform(-30, 30) # 隣回旋角度、最大 30 度
augmented_img = rotate_image_with_transparent_fill(image, angle) # 透明背景で処理
# 処理後の画像を保存
output_path = os.path.join(output_dir, f"DA_{i}_{os.path.splitext(img_name)[0]}.png")
Image.fromarray(augmented_img).save(output_path) # 透明背景を持つ PNG ファイルとして保存
print(f"Saved processed image {i+1}/{num_augmentations} to {output_path}")
# Step 2: 画像サイズを調整
def resize_images(src_folder, dest_folder, size=(256, 256)):
if not os.path.exists(dest_folder):
os.makedirs(dest_folder)
for filename in os.listdir(src_folder):
if filename.lower().endswith((".png", ".jpg", ".jpeg")):
file_path = os.path.join(src_folder, filename)
img = Image.open(file_path)
img_resized = img.resize(size, Image.LANCZOS) # 高質質フィルタを使用
dest_file_path = os.path.join(dest_folder, filename)
img_resized.save(dest_file_path)
print(f"Resized and saved {filename} to {dest_folder}")
# 例示用法
input_dir = r"D:\animeganv2\dataset\onepiece\style1" # 入力画像フォルダ
output_dir_da = r"D:\animeganv2\dataset\onepiece\temp_da" # DA後保存の両存フォルダ
output_dir_resize = r"D:\animeganv2\dataset\onepiece\style" # 最終的に保存される調整されたフォルダ
# まずデータ増強を行い、各画像の変換後の画像を5枚生成し、原始の画像を保存
process_all_images_in_folder(input_dir, output_dir_da, num_augmentations=5)
# そして、増強後の画像のサイズ調整を行う
resize_images(output_dir_da, output_dir_resize)
```
</details>


## GeneratorとDiscriminatorを実装
<details>
<summary>変更生成器コード</summary>
```diff
import mindspore.nn as nn
from mindspore.common.initializer import Normal
from .conv2d_block import ConvBlock
+import mindspore.ops as ops
from .inverted_residual_block import InvertedResBlock
from .upsample import UpSample
class Generator(nn.Cell):
+ def __init__(self, channels=32, num_classes=15):
+ super(Generator, self).__init__()
+ has_bias = False
+
+ # +埋め込み層でラベルを特徴ベクトルに変換
+ self.embedding = nn.Embedding(num_classes, channels)
+
self.generator = nn.SequentialCell()
- self.generator.append(ConvBlock(3, channels, kernel_size=7))
+ # +結合後のチャンネル数は 3 + channels
+ self.generator.append(ConvBlock(3 + channels, channels, kernel_size=7))
self.generator.append(ConvBlock(channels, channels * 2, stride=2))
self.generator.append(ConvBlock(channels * 2, channels * 4, stride=2))
self.generator.append(ConvBlock(channels * 4, channels * 4))
self.generator.append(ConvBlock(channels * 4, channels * 4))
self.generator.append(InvertedResBlock(channels * 4, channels * 8))
self.generator.append(InvertedResBlock(channels * 8, channels * 8))
self.generator.append(InvertedResBlock(channels * 8, channels * 8))
self.generator.append(InvertedResBlock(channels * 8, channels * 8))
self.generator.append(ConvBlock(channels * 8, channels * 4))
self.generator.append(UpSample(channels * 4, channels * 4))
self.generator.append(ConvBlock(channels * 4, channels * 4))
self.generator.append(UpSample(channels * 4, channels * 2))
self.generator.append(ConvBlock(channels * 2, channels * 2))
self.generator.append(ConvBlock(channels * 2, channels, kernel_size=7))
self.generator.append(
nn.Conv2d(channels, 3, kernel_size=1, stride=1, pad_mode='same', padding=0,
weight_init=Normal(mean=0, sigma=0.02), has_bias=has_bias))
self.generator.append(nn.Tanh())
- def construct(self, x):
+ def construct(self, x, label):
+ """ build network """
+ # +ラベルを埋め込み層で特徴に変換
+ batch_size, _, height, width = x.shape
+ label_embed = self.embedding(label) # +出力形状は [batch_size, channels]
+
+ # +ラベル特徴を入力画像と同じ高さと幅に拡張
+ label_embed = label_embed.view(batch_size, -1, 1, 1) # +調整後の形状は [batch_size, channels, 1, 1]
+ label_embed = ops.BroadcastTo((batch_size, label_embed.shape[1], height, width))(label_embed) # +ブロードキャストして [batch_size, channels, height, width] にする
+
+ # +画像とラベル特徴を結合
+ x = ops.Concat(axis=1)((x, label_embed))
+
out = self.generator(x)
return out
```
</details>
<details>
<summary>変更識別器コード</summary>
```diff
import mindspore.nn as nn
from mindspore.common.initializer import Normal
from .instance_norm_2d import InstanceNorm2d
+import mindspore.ops as ops
+import mindspore
class Discriminator(nn.Cell):
- def __init__(self, channels, n_dis):
+ def __init__(self, channels, n_dis, num_classes=15):
super(Discriminator, self).__init__()
self.has_bias = False
+
+ # +埋め込み層でラベルを特徴ベクトルに変換
+ self.embedding = nn.Embedding(num_classes, channels * 4 * 4)
+
layers = [
- nn.Conv2d(3, channels, kernel_size=3, stride=1, pad_mode='same', padding=0,
+ nn.Conv2d(4, channels, kernel_size=3, stride=1, pad_mode='same', padding=0, # +チャンネル数を 4 に増加
weight_init=Normal(mean=0, sigma=0.02), has_bias=self.has_bias),
nn.LeakyReLU(alpha=0.2)
]
for _ in range(1, n_dis):
layers += [
nn.Conv2d(channels, channels * 2, kernel_size=3, stride=2, pad_mode='same', padding=0,
weight_init=Normal(mean=0, sigma=0.02), has_bias=self.has_bias),
nn.LeakyReLU(alpha=0.2),
nn.Conv2d(channels * 2, channels * 4, kernel_size=3, stride=1, pad_mode='same', padding=0,
weight_init=Normal(mean=0, sigma=0.02), has_bias=self.has_bias),
InstanceNorm2d(channels * 4, affine=False),
nn.LeakyReLU(alpha=0.2),
]
channels *= 4
layers += [
nn.Conv2d(channels, channels, kernel_size=3, stride=1, pad_mode='same', padding=0,
weight_init=Normal(mean=0, sigma=0.02), has_bias=self.has_bias),
InstanceNorm2d(channels, affine=False),
nn.LeakyReLU(alpha=0.2),
nn.Conv2d(channels, 1, kernel_size=3, stride=1, pad_mode='same', padding=0,
weight_init=Normal(mean=0, sigma=0.02), has_bias=self.has_bias),
]
self.discriminator = nn.SequentialCell(layers)
- def construct(self, x):
- out = self.discriminator(x)
- return out
+ def construct(self, x, label):
+ """ build network """
+ batch_size, _, height, width = x.shape
+
+ # +ラベルを (batch_size, 1, 1, 1) に変換
+ label_embed = ops.ExpandDims()(label, -1) # +ラベルの次元を [batch_size, 1] に拡張
+ label_embed = ops.ExpandDims()(label_embed, -1) # +さらに [batch_size, 1, 1] に拡張
+ label_embed = ops.ExpandDims()(label_embed, -1) # +さらに [batch_size, 1, 1, 1] に拡張
+ label_embed = ops.BroadcastTo((batch_size, 1, height, width))(label_embed) # +ブロードキャストして [batch_size, 1, height, width] にする
+
+ # +label_embed を入力と同じデータ型に変換
+ label_embed = ops.Cast()(label_embed, mindspore.float32)
+
+ # +label_embed と x を結合
+ x = ops.Concat(axis=1)((x, label_embed))
+ return self.discriminator(x)
```
</details>
<details>
<summary>lossコード</summary>
```python=
import mindspore
import mindspore.ops as ops
from mindspore import nn
from mindspore import load_checkpoint, load_param_into_net
from .color_loss import ColorLoss
from .gram_loss import GramLoss
from .vgg19 import Vgg
def vgg19(vgg19_path, num_classes=1000):
net = Vgg([64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512], num_classes=num_classes,
batch_norm=True)
param_dict = load_checkpoint(vgg19_path)
load_param_into_net(net, param_dict)
net.requires_grad = False
return net
class GeneratorLoss(nn.Cell):
def __init__(self, discriminator, generator, args):
super(GeneratorLoss, self).__init__(auto_prefix=True)
self.discriminator = discriminator
self.generator = generator
self.content_loss = nn.L1Loss()
self.gram_loss = GramLoss()
self.color_loss = ColorLoss()
self.wadvg = args.wadvg
self.wadvd = args.wadvd
self.wcon = args.wcon
self.wgra = args.wgra
self.wcol = args.wcol
self.vgg19 = vgg19(args.vgg19_path)
self.adv_type = args.gan_loss
self.bce_loss = nn.BCELoss()
self.relu = nn.ReLU()
def construct(self, img, anime_gray, label):
""" build network """
# 生成器に入力する画像とラベル
fake_img = self.generator(img, label)
fake_d = self.discriminator(fake_img, label) # 判別器もスタイルラベルを受け取る
fake_feat = self.vgg19(fake_img)
anime_feat = self.vgg19(anime_gray)
img_feat = self.vgg19(img)
# コンテンツ損失計算のために特徴マップの次元を揃える
if img_feat.shape != fake_feat.shape:
fake_feat = ops.ResizeBilinear((img_feat.shape[2], img_feat.shape[3]))(fake_feat)
# カラー損失計算のために次元を揃える
if img.shape != fake_img.shape:
fake_img = ops.ResizeBilinear((img.shape[2], img.shape[3]))(fake_img)
result = self.wadvg * self.adv_loss_g(fake_d) + \
self.wcon * self.content_loss(img_feat, fake_feat) + \
self.wgra * self.gram_loss(anime_feat, fake_feat) + \
self.wcol * self.color_loss(img, fake_img)
return result
def adv_loss_g(self, pred):
if self.adv_type == 'hinge':
return -mindspore.numpy.mean(pred)
if self.adv_type == 'lsgan':
return mindspore.numpy.mean(mindspore.numpy.square(pred - 1.0))
if self.adv_type == 'normal':
return self.bce_loss(pred, mindspore.numpy.zeros_like(pred))
return mindspore.numpy.mean(mindspore.numpy.square(pred - 1.0))
class DiscriminatorLoss(nn.Cell):
def __init__(self, discriminator, generator, args):
nn.Cell.__init__(self, auto_prefix=True)
self.discriminator = discriminator
self.generator = generator
self.wadvg = args.wadvg
self.wadvd = args.wadvd
self.bce_loss = nn.BCELoss()
self.relu = nn.ReLU()
self.adv_type = args.gan_loss
def construct(self, img, anime, anime_gray, anime_smt_gray, label):
""" build network """
# 生成された画像と実画像にはラベルが含まれる
fake_img = self.generator(img, label)
fake_d = self.discriminator(fake_img, label)
real_anime_d = self.discriminator(anime, label)
real_anime_gray_d = self.discriminator(anime_gray, label)
real_anime_smt_gray_d = self.discriminator(anime_smt_gray, label)
# 対抗的損失計算のために次元を揃える
if fake_img.shape != anime.shape:
fake_img = ops.ResizeBilinear((anime.shape[2], anime.shape[3]))(fake_img)
result = self.wadvd * (
1.7 * self.adv_loss_d_real(real_anime_d) +
1.7 * self.adv_loss_d_fake(fake_d) +
1.7 * self.adv_loss_d_fake(real_anime_gray_d) +
1.0 * self.adv_loss_d_fake(real_anime_smt_gray_d)
)
return result
def adv_loss_d_real(self, pred):
if self.adv_type == 'hinge':
return mindspore.numpy.mean(self.relu(1.0 - pred))
if self.adv_type == 'lsgan':
return mindspore.numpy.mean(mindspore.numpy.square(pred - 1.0))
if self.adv_type == 'normal':
return self.bce_loss(pred, mindspore.numpy.ones_like(pred))
return mindspore.numpy.mean(mindspore.numpy.square(pred - 1.0))
def adv_loss_d_fake(self, pred):
if self.adv_type == 'hinge':
return mindspore.numpy.mean(self.relu(1.0 + pred))
if self.adv_type == 'lsgan':
return mindspore.numpy.mean(mindspore.numpy.square(pred))
if self.adv_type == 'normal':
return self.bce_loss(pred, mindspore.numpy.zeros_like(pred))
return mindspore.numpy.mean(mindspore.numpy.square(pred))
```
</details>
<details>
<summary>trainコード</summary>
```python=
import argparse
import os
import cv2
from tqdm import tqdm
import numpy as np
import mindspore
import mindspore.nn as nn
from mindspore import Tensor
from mindspore import context
from mindspore import float32 as dtype
from losses.loss import GeneratorLoss, DiscriminatorLoss
from models.animegan import AnimeGAN
from models.discriminator import Discriminator
from models.generator import Generator
from process_datasets.animeganv2_dataset import AnimeGANDataset
from animeganv2_utils.pre_process import denormalize_input, check_params
def parse_args():
"""Argument parsing"""
parser = argparse.ArgumentParser(description='train')
parser.add_argument('--device_target', default='CPU', choices=['CPU', 'GPU', 'Ascend'], type=str)
parser.add_argument('--device_id', default=0, type=int)
parser.add_argument('--dataset', default='onepiece', type=str)
parser.add_argument('--data_dir', default='D:/animeganv2/dataset', type=str)
parser.add_argument('--checkpoint_dir', default='../checkpoints', type=str)
parser.add_argument('--vgg19_path', default='D:/animeganv2/vgg.ckpt', type=str)
parser.add_argument('--save_image_dir', default='../images', type=str)
parser.add_argument('--resume', default=False, type=bool)
parser.add_argument('--phase', default='train', type=str)
parser.add_argument('--epochs', default=30, type=int)
parser.add_argument('--init_epochs', default=5, type=int)
parser.add_argument('--batch_size', default=4, type=int)
parser.add_argument('--num_parallel_workers', default=1, type=int)
parser.add_argument('--save_interval', default=1, type=int)
parser.add_argument('--debug_samples', default=0, type=int)
parser.add_argument('--lr_g', default=2.0e-4, type=float)
parser.add_argument('--lr_d', default=4.0e-4, type=float)
parser.add_argument('--init_lr', default=1.0e-3, type=float)
parser.add_argument('--gan_loss', default='lsgan', choices=['lsgan', 'hinge', 'bce'], type=str)
parser.add_argument('--wadvg', default=0.9, type=float, help='Adversarial loss weight for G')
parser.add_argument('--wadvd', default=300, type=float, help='Adversarial loss weight for D')
parser.add_argument('--wcon', default=1.8, type=float, help='Content loss weight')
parser.add_argument('--wgra', default=2.0, type=float, help='Gram loss weight')
parser.add_argument('--wcol', default=10.0, type=float, help='Color loss weight')
parser.add_argument('--img_ch', default=3, type=int, help='The size of image channel')
parser.add_argument('--ch', default=64, type=int, help='Base channel number per layer')
parser.add_argument('--n_dis', default=3, type=int, help='The number of discriminator layer')
return parser.parse_args()
def main():
"""Build and train model."""
check_params(args)
print("Init models...")
generator = Generator()
discriminator = Discriminator(args.ch, args.n_dis)
optimizer_g = nn.Adam(generator.trainable_params(), learning_rate=args.lr_g, beta1=0.5, beta2=0.999)
optimizer_d = nn.Adam(discriminator.trainable_params(), learning_rate=args.lr_d, beta1=0.5, beta2=0.999)
net_d_with_criterion = DiscriminatorLoss(discriminator, generator, args)
net_g_with_criterion = GeneratorLoss(discriminator, generator, args)
my_train_one_step_cell_for_d = nn.TrainOneStepCell(net_d_with_criterion, optimizer_d)
my_train_one_step_cell_for_g = nn.TrainOneStepCell(net_g_with_criterion, optimizer_g)
animegan = AnimeGAN(my_train_one_step_cell_for_d, my_train_one_step_cell_for_g)
animegan.set_train()
# 保持 data_dir 不变,指向根目录
data = AnimeGANDataset(args)
data = data.run()
size = data.get_dataset_size()
for epoch in range(args.epochs):
iters = 0
for img, anime, anime_gray, anime_smt_gray, label in tqdm(data.create_tuple_iterator()):
img = Tensor(img, dtype=dtype)
anime = Tensor(anime, dtype=dtype)
anime_gray = Tensor(anime_gray, dtype=dtype)
anime_smt_gray = Tensor(anime_smt_gray, dtype=dtype)
label = Tensor(label, dtype=mindspore.int32)
net_d_loss, net_g_loss = animegan(img, anime, anime_gray, anime_smt_gray, label)
if iters % 50 == 0:
print('[%d/%d][%d/%d]\tLoss_D: %.4f\tLoss_G: %.4f' % (
epoch + 1, args.epochs, iters, size, net_d_loss.asnumpy().min(), net_g_loss.asnumpy().min()))
if (epoch % args.save_interval) == 0 and (iters == size - 1):
stylized = denormalize_input(generator(img)).asnumpy()
no_stylized = denormalize_input(img).asnumpy()
imgs = cv2.cvtColor(stylized[0, :, :, :].transpose(1, 2, 0), cv2.COLOR_RGB2BGR)
imgs1 = cv2.cvtColor(no_stylized[0, :, :, :].transpose(1, 2, 0), cv2.COLOR_RGB2BGR)
for i in range(1, args.batch_size):
imgs = np.concatenate(
(imgs, cv2.cvtColor(stylized[i, :, :, :].transpose(1, 2, 0), cv2.COLOR_RGB2BGR)), axis=1)
imgs1 = np.concatenate(
(imgs1, cv2.cvtColor(no_stylized[i, :, :, :].transpose(1, 2, 0), cv2.COLOR_RGB2BGR)), axis=1)
cv2.imwrite(
os.path.join(args.save_image_dir, args.dataset, 'epoch_' + str(epoch) + '.jpg'),
np.concatenate((imgs1, imgs), axis=0))
mindspore.save_checkpoint(generator, os.path.join(args.checkpoint_dir, args.dataset,
'netG_' + str(epoch) + '.ckpt'))
iters += 1
if __name__ == '__main__':
args = parse_args()
context.set_context(mode=context.GRAPH_MODE, device_target=args.device_target, device_id=args.device_id)
main()
```
</details>
現状:多くのエラーを解消した後、ローカルでの実行は成功し、トレーニングが可能ですが、学校のリモートサーバーを使用する際にはCUDAツールキットのインストールが必要です。管理者権限が必要なため、関連する問題を解決してからトレーニングを実行する必要があります。

## TODO
- **リモートサーバーの関連問題を解決してトレーニングを実行する。**
- **問題を特定した後、生成器と判別器を再度改良する。**
# 10/3
## データセット収集の進捗: 14/15
onepieceのデータセットを除き、他のアニメスタイルのデータセットはすべて収集完了しています。onepieceのデータセットには300枚以上の画像が含まれているため、処理により多くの時間が必要です。
## AnimeGANへのConditional導入の進捗
**ソースコード**: [Mindspore AnimeGANv2](https://link.zhihu.com/?target=https%3A//gitee.com/mindspore/course/tree/master/application_example/animeganv2)
データセットの処理: すべてのアニメデータセットを統合し、それぞれの画像にスタイルラベルを追加しました(15種類のアニメスタイル、それぞれのスタイルにラベルを割り当て済み)。 animeganv2_dataset.pyを調整し、すべてのスタイル画像の読み込みとラベルの割り当てを実現しました。
<details>
<summary>コード</summary>
```python=
class DatasetGenerator:
def __init__(self, args, transform=None):
data_dir = args.data_dir
style_to_label = {
'onepiece': 0,
'naruto': 1,
'jojo': 2,
'Shingeki no Kyojin': 3,
'Nanatsu no Taizai': 4,
'Kimetsu no Yaiba': 5,
'Jujutsu Kaisen': 6,
'Gintama': 7,
'Fullmetal Alchemist': 8,
'frieren': 9,
'Dragon Ball': 10,
'conan': 11,
'Bleach': 12,
'Code Geass': 13,
'Puella Magi Madoka Magica': 14,
}
self.image_files = []
self.labels = []
for style, label in style_to_label.items():
style_dir = os.path.join(data_dir, style, 'style')
if not os.path.exists(style_dir):
print(f"Warning: {style} does not contain expected directories.")
continue
style_files = os.listdir(style_dir)
self.image_files.extend([os.path.join(style_dir, fi) for fi in style_files])
self.labels.extend([label] * len(style_files))
self.transform = transform
print(f"Total anime images: {len(self.image_files)}")
def __getitem__(self, index):
# 通常の処理に加えて、ラベルを返す
image_path = self.image_files[index]
image = cv2.imread(image_path)[:, :, ::-1]
label = self.labels[index]
if self.transform:
image = self.transform(image)
return image, label
```
</details>
## TO DO
生成器と判别器の調整を完了させ、AnimeGANがスタイルラベルに基づいて異なるスタイルの画像を生成できるようにし、結果を生成する予定です
# 9/26
## 質問内容
訓練および推論結果が良くない理由は何ですか?
当時の回答:
データセットが少ない、画像のピクセルの問題、生成器と識別器の計算の最適化が必要。
後で考えた回答:
それに加えて、画像自体が良くない。多くの画像には顔の特徴以外にも体や背景の余分な部分が含まれており、特徴抽出に影響が出ている。データセットとしては完全に不適格で、データセット自体の最適化が必要です。
## やったこと
- **データセットの大幅な修正**
元のデータセットの画像のほとんどが上半身画像であり、128x128のサイズでは顔の部分が小さくなるため、すべての画像から頭部(頭、目、輪郭)を切り取り、新しいデータセットを作成。
- **Dlibを使用した顔検出**
Dlibのコードを使って自動処理を試みたが、アニメキャラクターに適用できたのは15-35%程度であり、残りの画像は手動で処理。
<details>
<summary>コード</summary>
```python=
import dlib
import cv2
import os
# 入力フォルダと出力フォルダのパスを定義
input_folder = r'C:\Users\haoch\Desktop\conan'
output_folder = r'C:\Users\haoch\Desktop\conan_faces'
# 出力フォルダが存在しない場合、新しく作成
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# dlibの顔検出器をロード
detector = dlib.get_frontal_face_detector()
# 入力フォルダ内のすべての画像ファイルをループ処理
for filename in os.listdir(input_folder):
if filename.endswith(".jpg") or filename.endswith(".png"):
img_path = os.path.join(input_folder, filename)
img = cv2.imread(img_path)
if img is None:
print(f"画像を読み込めませんでした {filename}")
continue
# 画像をグレースケールに変換
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Dlibを使って顔を検出
faces = detector(gray)
# 検出された各顔をトリミングして保存
for i, face in enumerate(faces):
x, y, width, height = face.left(), face.top(), face.width(), face.height()
# トリミング領域が画像の範囲内にあることを確認
if x >= 0 and y >= 0 and x + width <= img.shape[1] and y + height <= img.shape[0]:
face_crop = img[y:y+height, x:x+width]
output_path = os.path.join(output_folder, f"face_{i}_{filename}")
cv2.imwrite(output_path, face_crop)
else:
print(f"検出された顔の領域が画像範囲外のため、スキップします {filename}")
print("すべての顔画像が出力フォルダに保存されました。")
```
</details>
- **主人公の正面顔を追加**
データセットの範囲を広げ、各データセットに主人公の正面顔を30〜50枚追加し、データ数を増加。

データセットの更新がまだ完了していないため、トレーニングを行っていない。
# 7/10
# Animeデータセットのトレーニング
<details>
<summary>1から第400エポック</summary>



</details>
<details>
<summary>第2000エポックまで</summary>



</details>

# vanilla GANでも生成してみる
- **GitHubリンク**:[vanilla GAN](https://github.com/safwankdb/Vanilla-GAN)
<details>
<summary>vanilla_gan.py</summary>
```python=
import os
import torch
import torch.nn as nn
import torch.optim as optim
from torch.autograd.variable import Variable
from torchvision import transforms, datasets
from torchvision.utils import make_grid
from torch.utils.data import DataLoader
import imageio
import numpy as np
from matplotlib import pyplot as plt
# データ変換を定義
transform = transforms.Compose([
transforms.Resize((64, 64)), # 画像サイズを調整
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
to_image = transforms.ToPILImage()
# onepiece データセットをロード
dataset_path = 'C:/Users/haoch/Desktop/onepiece'
dataset = datasets.ImageFolder(root=dataset_path, transform=transform)
dataloader = DataLoader(dataset, batch_size=64, shuffle=True)
device = 'cpu' # CPUを使用、CUDAサポートがある場合は 'cuda' に変更
class Generator(nn.Module):
def __init__(self):
super(Generator, self).__init__()
self.main = nn.Sequential(
nn.Linear(100, 256),
nn.ReLU(True),
nn.Linear(256, 512),
nn.ReLU(True),
nn.Linear(512, 1024),
nn.ReLU(True),
nn.Linear(1024, 64*64*3),
nn.Tanh()
)
def forward(self, input):
return self.main(input).view(-1, 3, 64, 64)
class Discriminator(nn.Module):
def __init__(self):
super(Discriminator, self).__init__()
self.main = nn.Sequential(
nn.Linear(64*64*3, 1024),
nn.LeakyReLU(0.2, inplace=True),
nn.Dropout(0.3),
nn.Linear(1024, 512),
nn.LeakyReLU(0.2, inplace=True),
nn.Dropout(0.3),
nn.Linear(512, 256),
nn.LeakyReLU(0.2, inplace=True),
nn.Dropout(0.3),
nn.Linear(256, 1),
nn.Sigmoid()
)
def forward(self, input):
return self.main(input.view(-1, 64*64*3))
generator = Generator()
discriminator = Discriminator()
generator.to(device)
discriminator.to(device)
# 学習率を下げる
g_optim = optim.Adam(generator.parameters(), lr=1e-4)
d_optim = optim.Adam(discriminator.parameters(), lr=1e-4)
g_losses = []
d_losses = []
images = []
criterion = nn.BCELoss()
def noise(n, n_features=100):
return Variable(torch.randn(n, n_features)).to(device)
def make_ones(size):
data = Variable(torch.ones(size, 1))
return data.to(device)
def make_zeros(size):
data = Variable(torch.zeros(size, 1))
return data.to(device)
def train_discriminator(optimizer, real_data, fake_data):
n = real_data.size(0)
optimizer.zero_grad()
prediction_real = discriminator(real_data)
error_real = criterion(prediction_real, make_ones(n))
error_real.backward()
prediction_fake = discriminator(fake_data)
error_fake = criterion(prediction_fake, make_zeros(n))
error_fake.backward()
optimizer.step()
return error_real + error_fake
def train_generator(optimizer, fake_data):
n = fake_data.size(0)
optimizer.zero_grad()
prediction = discriminator(fake_data)
error = criterion(prediction, make_ones(n))
error.backward()
optimizer.step()
return error
num_epochs = 500
k = 1
test_noise = noise(64)
generator.train()
discriminator.train()
for epoch in range(num_epochs):
g_error = 0.0
d_error = 0.0
for i, data in enumerate(dataloader):
imgs, _ = data
n = len(imgs)
real_data = imgs.to(device)
# 判別器を訓練
fake_data = generator(noise(n)).detach()
d_error += train_discriminator(d_optim, real_data, fake_data)
# 生成器を訓練
fake_data = generator(noise(n))
g_error += train_generator(g_optim, fake_data)
# 損失と生成画像を記録
img = generator(test_noise).cpu().detach()
img = make_grid(img)
images.append(img)
g_losses.append(g_error.item() / i)
d_losses.append(d_error.item() / i)
print('エポック {}: g_loss: {:.8f} d_loss: {:.8f}\r'.format(epoch, g_error.item() / i, d_error.item() / i))
print('トレーニング終了')
torch.save(generator.state_dict(), 'onepiece_generator.pth')
imgs = [np.array(to_image(i)) for i in images]
imageio.mimsave('progress.gif', imgs)
# 曲線をスムージング
def smooth_curve(points, factor=0.9):
smoothed_points = []
for point in points:
if smoothed_points:
previous = smoothed_points[-1]
smoothed_points.append(previous * factor + point * (1 - factor))
else:
smoothed_points.append(point)
return smoothed_points
plt.plot(smooth_curve(g_losses), label='Generator_Losses')
plt.plot(smooth_curve(d_losses), label='Discriminator Losses')
plt.legend()
plt.savefig('loss.png')
```
</details>

## TO DO
- **CNNのCGANで生成されたアニメ画像の改良**
- **DCGANを使用して異なるアニメの画像を生成する試み**