Vggnet前身是Alexnet,从Alexnet上获得启发,通过加深网络结构进行改进,加入3FC全连接层,增加了参数个数,提升了模型的实用性以及准确率,当然也提升了内存消耗。
VGG Net由牛津大学的视觉几何组(Visual Geometry Group)和 Google DeepMind公司的研究员一起研发的的深度卷积神经网络,在 ILSVRC 2014 上取得了第二名的成绩,将 Top-5错误率降到7.3%。当时的第一名是googlenet。
Vgg网络到现在仍在图像处理领域有较高的借鉴价值。
(1)提升网络层数。
(2)3:1配比conv+ReLU和pooling提取图像特征。
(3)通过反向传播进行函数的修正和拟合。
(4)全连接层(FC层)加入dropout层,一可以防止过拟合,而可以丢掉无用的连接。
(5)1x1卷积核看似没什么用,但配合ReLU()函数可以增加更多非线性因素,其实就是更容易逼近函数。
(6)2个3x3卷积堆叠等于1个5x5卷积,3个3x3堆叠等于1个7x7卷积,采用更多次更小的卷积核可以带来更多非线性因素,利于函数的拟合。
提升网络深度有助于训练更优质的模型。
更小的卷积核,更多的卷积次数有利于添加更多线性因素,训练更精确的模型。
FC层要耗费大量的内存,对内存的消耗远超过conv,之后的研究表明,Vgg模型中去掉FC层不仅可以减少参数的个数,减少尝试次数,且训练精度不会由较大影响(毕竟3FC层那么多参数,没人能保证所有参数已经达到了最好,所以是否会影响最高精度只能是凭借经验而言)。
Vgg网络有上面5个版本,其中Vgg16与Vgg19最为著名。(注意计算层数只计算conv与FC,不算maxpool和soft-max)
Vgg网络由{[conv+ReLU()]xN+maxpool}x5+3xFC组成。
N的个数在每一轮maxpool之前都有所不同。取值为2、3、4。
经典的Vgg16由13个conv层与3个FC层组成。Vgg19由16个conv层与3个FC层组成。
import torch.nn as nn
import math
class VGG(nn.Module):
def __init__(self,num_classes=1000):
super(VGG,self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3,64,kernel_size=3,padding=1),
nn.ReLU(True),
nn.Conv2d(64,64,kernel_size=3,padding=1),
nn.ReLU(True),
nn.MaxPool2d(kernel_size=2,stride=2),
nn.Conv2d(64,128,kernel_size=3,padding=1),
nn.ReLU(True),
nn.Conv2d(128,128,kernel_size=3,padding=1),
nn.ReLU(True),
nn.MaxPool2d(kernel_size=2,stride=2),
nn.Conv2d(128,256,kernel_size=3,padding=1),
nn.ReLU(True),
nn.Conv2d(256,256,kernel_size=3,padding=1),
nn.ReLU(True),
nn.Conv2d(256,256,kernel_size=3,padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2,stride=2),
nn.Conv2d(256,512,kernel_size=3,padding=1),
nn.ReLU(True),
nn.Conv2d(512,512,kernel_size=3,padding=1),
nn.ReLU(True),
nn.Conv2d(512,512,kernel_size=3,padding=1),
nn.ReLU(True),
nn.MaxPool2d(kernel_size=2,stride=2),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(True),
nn.MaxPool2d(kernel_size=2, stride=2),
)
self.classifier = nn.Sequential(
nn.Linear(512*7*7,4096),
nn.ReLU(True),
nn.Linear(4096,4096),
nn.ReLU(True),
nn.Dropout(),
nn.Linear(4096,num_classes),
)
def forward(self,x):
x = self.features(x)
x = x.view(x.size(0),-1)
x = self.classifier(x)
return x
VggNet=VGG(10)
数据集结构是data/test和data/learn其中test、learn文件夹下面是标签作为文件夹名称,图片作为文件夹内容的若干文件夹。
这里因为内存问题,batchsize比较小,且没有跑完,但代码是可以正常运行的。
# coding=gbk
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import argparse
from VGG import VggNet
import torch
import os, glob
import random, csv
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from PIL import Image
# 定义是否使用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 参数设置,使得我们能够手动输入命令行参数,就是让风格变得和Linux命令行差不多
parser = argparse.ArgumentParser(description='PyTorch CIFAR10 Training')
parser.add_argument('--outf', default='./model/', help='folder to output images and model checkpoints') #输出结果保存路径
parser.add_argument('--net', default='./model/VGG16.pth', help="path to net (to continue training)") #恢复训练时的模型路径
args = parser.parse_args()
# 超参数设置
EPOCH = 135 #遍历数据集次数
pre_epoch = 0 # 定义已经遍历数据集的次数
BATCH_SIZE = 32 #批处理尺寸(batch_size)
LR = 0.1 #学习率
class FireAndSmoke(Dataset):
def __init__(self, root, resize):
super(FireAndSmoke, self).__init__()
self.root = root
self.resize = resize
self.name2label = {
}
# 返回指定目录下的文件列表,并对文件列表进行排序,
# os.listdir每次返回目录下的文件列表顺序会不一致,
# 排序是为了每次返回文件列表顺序一致
for name in sorted(os.listdir(os.path.join(root))):
# 过滤掉非目录文件
if not os.path.isdir(os.path.join(root, name)):
continue
# 构建字典,名字:0~4数字
self.name2label[name] = len(self.name2label.keys())
# eg: {'squirtle': 4, 'bulbasaur': 0, 'pikachu': 3, 'mewtwo': 2, 'charmander': 1}
# print(self.name2label)
# image, label
self.images, self.labels = self.load_csv("images.csv")
# 将目录下的图片路径与其对应的标签写入csv文件,
# 并将csv文件写入的内容读出,返回图片名与其标签
def load_csv(self, filename):
"""
:param filename:
:return:
"""
# 是否已经存在了cvs文件
if not os.path.exists(os.path.join(self.root, filename)):
images = []
for name in self.name2label.keys():
# 获取指定目录下所有的满足后缀的图像名
# pokemon/mewtwo/00001.png
images += glob.glob(os.path.join(self.root, name, "*.png"))
images += glob.glob(os.path.join(self.root, name, "*.jpg"))
images += glob.glob(os.path.join(self.root, name, "*.jpeg"))
# 1165 'pokemon/pikachu/00000058.png'
print(len(images), images)
# 将元素打乱
random.shuffle(images)
with open(os.path.join(self.root, filename), mode="w", newline="") as f:
writer = csv.writer(f)
for img in images: # 'pokemon/pikachu/00000058.png'
name = img.split(os.sep)[-2]
label = self.name2label[name]
# 将图片路径以及对应的标签写入到csv文件中
# 'pokemon/pikachu/00000058.png', 0
writer.writerow([img, label])
print("writen into csv file: ", filename)
# 如果已经存在了csv文件,则读取csv文件
images, labels = [], []
with open(os.path.join(self.root, filename)) as f:
reader = csv.reader(f)
for row in reader:
# 'pokemon/pikachu/00000058.png', 0
img, label = row
label = int(label)
images.append(img)
labels.append(label)
assert len(images) == len(labels)
return images, labels
def __len__(self):
return len(self.images)
def denormalize(self, x_hat):
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
# x_hat = (x-mean)/std
# x = x_hat*std = mean
# x: [c, h, w]
# mean: [3] => [3, 1, 1]
mean = torch.tensor(mean).unsqueeze(1).unsqueeze(1)
std = torch.tensor(std).unsqueeze(1).unsqueeze(1)
# print(mean.shape, std.shape)
x = x_hat * std + mean
return x
def __getitem__(self, idx):
# idx~[0~len(images)]
# self.images, self.labels
# img: 'pokemon/bulbasaur/00000000.png'
# label: 0
img, label = self.images[idx], self.labels[idx]
tf = transforms.Compose([
lambda x: Image.open(x).convert("RGB"), # string path => image data
transforms.Resize((int(self.resize * 1.25), int(self.resize * 1.25))),
transforms.RandomRotation(15),
transforms.CenterCrop(self.resize),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
img = tf(img)
label = torch.tensor(label)
return img, label
train_dataset = FireAndSmoke("data/train",224)#训练数据集
trainloader = torch.utils.data.DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2) #生成一个个batch进行批训练,组成batch的时候顺序打乱取
test_dataset = FireAndSmoke("data/test",224)
testloader = torch.utils.data.DataLoader(test_dataset, batch_size=256, shuffle=False, num_workers=2) # 多线程来读数据
# # Cifar-10的标签
# classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# 模型定义-VggNet
net = VggNet
# 定义损失函数和优化方式
criterion = nn.CrossEntropyLoss() #损失函数为交叉熵,多用于多分类问题
optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9, weight_decay=5e-4) #优化方式为mini-batch momentum-SGD,并采用L2正则化(权重衰减)
# 训练
if __name__ == "__main__":
best_acc = 85 #2 初始化best test accuracy
print("Start Training, VggNet!") # 定义遍历数据集的次数
with open("acc.txt", "w") as f:
with open("log.txt", "w")as f2:
for epoch in range(pre_epoch, EPOCH):
print('\nEpoch: %d' % (epoch + 1))
net.train()
# model.train() :启用 BatchNormalization 和 Dropout
sum_loss = 0.0
correct = 0.0
total = 0.0
for i, data in enumerate(trainloader, 0):
# 准备数据
length = len(trainloader)
inputs, labels = data
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
# forward + backward
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# 每训练1个batch打印一次loss和准确率
sum_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += predicted.eq(labels.data).cpu().sum()
print('[epoch:%d, iter:%d] Loss: %.03f | Acc: %.3f%% '
% (epoch + 1, (i + 1 + epoch * length), sum_loss / (i + 1), 100. * correct / total))
f2.write('%03d %05d |Loss: %.03f | Acc: %.3f%% '
% (epoch + 1, (i + 1 + epoch * length), sum_loss / (i + 1), 100. * correct / total))
f2.write('\n')
f2.flush()
# 每训练完一个epoch测试一下准确率
print("Waiting Test!")
with torch.no_grad():
# with torch.no_grad()或者@torch.no_grad()中的数据不需要计算梯度,也不会进行反向传播
correct = 0
total = 0
for data in testloader:
net.eval()
# model.eval() :不启用 BatchNormalization 和 Dropout
images, labels = data
images, labels = images.to(device), labels.to(device)
outputs = net(images)
# 取得分最高的那个类 (outputs.data的索引号)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum()
print('测试分类准确率为:%.3f%%' % (100 * correct / total))
acc = 100. * correct / total
# 将每次测试结果实时写入acc.txt文件中
print('Saving model......')
torch.save(net.state_dict(), '%s/net_%03d.pth' % (args.outf, epoch + 1)) #torch.save报错,原因是没有新建model文件夹。
f.write("EPOCH=%03d,Accuracy= %.3f%%" % (epoch + 1, acc))
f.write('\n')
f.flush()
# 记录最佳测试分类准确率并写入best_acc.txt文件中
if acc > best_acc:
f3 = open("best_acc.txt", "w")
f3.write("EPOCH=%d,best_acc= %.3f%%" % (epoch + 1, acc))
f3.close()
best_acc = acc
print("Training Finished, TotalEPOCH=%d" % EPOCH)