Files
deep-learning-flower-classi…/DenseNet/train.py
T
2025-04-20 21:02:44 +08:00

287 lines
8.6 KiB
Python

import argparse
import math
import os
import urllib.request
import matplotlib.pyplot as plt
import numpy as np
import torch
import torch.optim as optim
import torch.optim.lr_scheduler as lr_scheduler
from model import densenet121, load_state_dict
from my_dataset import MyDataSet
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
from utils import evaluate, read_split_data, train_one_epoch
def download_weights(url, filename):
"""
Download weights file if it doesn't exist locally
"""
if not os.path.exists(filename):
print(f"Downloading weights file from {url}...")
try:
# Create directory if it doesn't exist
os.makedirs(
os.path.dirname(filename) if os.path.dirname(filename) else ".",
exist_ok=True,
)
# Download the file
urllib.request.urlretrieve(url, filename)
print(f"Downloaded weights file to {filename}")
return True
except Exception as e:
print(f"Error downloading weights file: {e}")
return False
return True
def main(args):
device = torch.device(args.device if torch.cuda.is_available() else "cpu")
print(args)
print(
'Start Tensorboard with "tensorboard --logdir=runs", view at http://localhost:6006/'
)
tb_writer = SummaryWriter()
if os.path.exists("./weights") is False:
os.makedirs("./weights")
train_images_path, train_images_label, val_images_path, val_images_label = (
read_split_data(args.data_path)
)
data_transform = {
"train": transforms.Compose(
[
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
]
),
"val": transforms.Compose(
[
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
]
),
}
# 实例化训练数据集
train_dataset = MyDataSet(
images_path=train_images_path,
images_class=train_images_label,
transform=data_transform["train"],
)
# 实例化验证数据集
val_dataset = MyDataSet(
images_path=val_images_path,
images_class=val_images_label,
transform=data_transform["val"],
)
batch_size = args.batch_size
nw = min(
[os.cpu_count(), batch_size if batch_size > 1 else 0, 8]
) # number of workers
print("Using {} dataloader workers every process".format(nw))
train_loader = torch.utils.data.DataLoader(
train_dataset,
batch_size=batch_size,
shuffle=True,
pin_memory=True,
num_workers=nw,
collate_fn=train_dataset.collate_fn,
)
val_loader = torch.utils.data.DataLoader(
val_dataset,
batch_size=batch_size,
shuffle=False,
pin_memory=True,
num_workers=nw,
collate_fn=val_dataset.collate_fn,
)
# 如果存在预训练权重则载入
model = densenet121(num_classes=args.num_classes).to(device)
if args.weights != "":
if os.path.exists(args.weights):
load_state_dict(model, args.weights)
else:
# Try to download the weights file
weights_url = "https://download.pytorch.org/models/densenet121-a639ec97.pth"
if download_weights(weights_url, args.weights):
load_state_dict(model, args.weights)
else:
print(
f"Warning: Could not download weights file. Training from scratch."
)
# 是否冻结权重
if args.freeze_layers:
for name, para in model.named_parameters():
# 除最后的全连接层外,其他权重全部冻结
if "classifier" not in name:
para.requires_grad_(False)
pg = [p for p in model.parameters() if p.requires_grad]
optimizer = optim.SGD(
pg, lr=args.lr, momentum=0.9, weight_decay=1e-4, nesterov=True
)
# Scheduler https://arxiv.org/pdf/1812.01187.pdf
lf = (
lambda x: ((1 + math.cos(x * math.pi / args.epochs)) / 2) * (1 - args.lrf)
+ args.lrf
) # cosine
scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)
# Lists to store metrics for plotting
train_losses = []
val_accuracies = []
learning_rates = []
best_acc = 0.0
for epoch in range(args.epochs):
# train
mean_loss = train_one_epoch(
model=model,
optimizer=optimizer,
data_loader=train_loader,
device=device,
epoch=epoch,
)
scheduler.step()
current_lr = optimizer.param_groups[0]["lr"]
learning_rates.append(current_lr)
# validate
acc = evaluate(model=model, data_loader=val_loader, device=device)
# Store metrics for plotting
train_losses.append(mean_loss)
val_accuracies.append(acc)
print(
"[epoch %d] train_loss: %.3f val_accuracy: %.3f lr: %.6f"
% (epoch + 1, mean_loss, acc, current_lr)
)
tags = ["loss", "accuracy", "learning_rate"]
tb_writer.add_scalar(tags[0], mean_loss, epoch)
tb_writer.add_scalar(tags[1], acc, epoch)
tb_writer.add_scalar(tags[2], current_lr, epoch)
torch.save(model.state_dict(), "./weights/model-{}.pth".format(epoch))
if acc > best_acc:
best_acc = acc
# Save the model with the best validation accuracy
torch.save(model.state_dict(), "./weights/best_model.pth")
# When training is complete, visualize the training process
visualize_training(args.epochs, train_losses, val_accuracies, learning_rates)
def visualize_training(epochs, train_losses, val_accuracies, learning_rates):
"""
Create and save visualizations of the training process
"""
plt.figure(figsize=(15, 10))
# Plot training loss
plt.subplot(2, 2, 1)
plt.plot(
range(1, epochs + 1), train_losses, "b-", marker="o", label="Training Loss"
)
plt.title("Training Loss vs. Epochs")
plt.xlabel("Epochs")
plt.ylabel("Loss")
plt.grid(True)
plt.legend()
# Plot validation accuracy
plt.subplot(2, 2, 2)
plt.plot(
range(1, epochs + 1),
val_accuracies,
"r-",
marker="o",
label="Validation Accuracy",
)
plt.title("Validation Accuracy vs. Epochs")
plt.xlabel("Epochs")
plt.ylabel("Accuracy")
plt.grid(True)
plt.legend()
# Plot learning rate
plt.subplot(2, 2, 3)
plt.plot(
range(1, epochs + 1), learning_rates, "m-", marker="o", label="Learning Rate"
)
plt.title("Learning Rate vs. Epochs")
plt.xlabel("Epochs")
plt.ylabel("Learning Rate")
plt.grid(True)
plt.legend()
# Plot loss vs. accuracy
plt.subplot(2, 2, 4)
plt.scatter(
train_losses,
val_accuracies,
c=range(epochs),
cmap="viridis",
s=50,
alpha=0.7,
edgecolors="k",
linewidths=0.5,
)
plt.colorbar(label="Epoch")
plt.title("Validation Accuracy vs. Training Loss")
plt.xlabel("Training Loss")
plt.ylabel("Validation Accuracy")
plt.grid(True)
plt.tight_layout()
plt.savefig("densenet_training_visualization.png")
plt.show()
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--num_classes", type=int, default=5)
parser.add_argument("--epochs", type=int, default=30)
parser.add_argument(
"--batch-size", type=int, default=4
) # 如果cuda报超出显存可以改小一点
parser.add_argument("--lr", type=float, default=0.001)
parser.add_argument("--lrf", type=float, default=0.1)
# 数据集所在根目录
# http://download.tensorflow.org/example_images/flower_photos.tgz
parser.add_argument("--data-path", type=str, default="../data/flower_photos")
# densenet121 官方权重下载地址
# https://download.pytorch.org/models/densenet121-a639ec97.pth
parser.add_argument(
"--weights",
type=str,
default="densenet121-a639ec97.pth",
help="initial weights path",
)
parser.add_argument("--freeze-layers", type=bool, default=False)
parser.add_argument(
"--device", default="cuda:0", help="device id (i.e. 0 or 0,1 or cpu)"
)
opt = parser.parse_args()
main(opt)