моя контактная информация
Почтамезофия@protonmail.com
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Здесь мы реализуем сеть VGG-16 для классификации набора данных CIFAR.
Предисловие
《Очень глубокие сверточные сети для крупномасштабного распознавания изображений》
МКЗР 2015
ВГГЭто из ОксфордаВвизуальныйггеометрияг Предложено группой roup (вы должны увидеть происхождение названия VGG). Эта сеть является связанной работой на ILSVRC 2014. Основная работа заключается в том, чтобы доказать, что увеличение глубины сети может в определенной степени повлиять на конечную производительность сети. VGG имеет две структуры, а именно VGG16 и VGG19. Между ними нет существенной разницы, но глубина сети различна.
Принцип ВГГ
Улучшение VGG16 по сравнению с AlexNet заключается в том, чтоИспользуйте несколько последовательных ядер свертки 3x3 для замены более крупных ядер свертки в AlexNet (11x11, 7x7, 5x5). . Для данного восприимчивого поля (локального размера входного изображения относительно выходного) использование сложенных небольших ядер свертки лучше, чем использование больших ядер свертки, поскольку несколько нелинейных слоев могут увеличить глубину сети, чтобы обеспечить более сложный режим обучения, и стоимость сравнительно небольшая (меньше параметров).
Проще говоря, в VGG три ядра свертки 3х3 используются для замены ядра свертки 7х7, а два ядра свертки 3х3 используются для замены ядра свертки 5*5. Основная цель этого — обеспечить то же самое при условии. Рецептивного поля глубина сети улучшается, а эффект нейронной сети в определенной степени улучшается.
Например, послойную суперпозицию трех ядер свертки 3х3 с шагом 1 можно рассматривать как рецептивное поле размера 7 (фактически это означает, что три непрерывные свертки 3х3 эквивалентны свертке 7х7), а ее общее количество параметров равно 3x(9xC^2). Если ядро свертки 7x7 используется напрямую, общее количество параметров равно 49xC^2, где C относится к количеству входных и выходных каналов.Очевидно, 27xC2 менее 49xC2, то есть параметры уменьшены, а ядро свертки 3х3 способствует лучшему сохранению свойств изображения;
Вот объяснение того, почему можно использовать два ядра свертки 3х3 вместо ядер свертки 5*5:
Свертка 5x5 рассматривается как небольшая полносвязная сеть, скользящая в области 5x5. Мы можем сначала выполнить свертку с помощью фильтра свертки 3x3, а затем использовать полносвязный слой для подключения выхода свертки 3x3. рассматриваться как сверточный слой 3x3. Таким образом, мы можем каскадировать (накладывать) две свертки 3x3 вместо одной свертки 5x5.
Подробности показаны на рисунке ниже:
Структура сети VGG
Ниже представлена структура сети VGG (присутствуют как VGG16, так и VGG19):
Структура сети ГГ
VGG16 содержит 16 скрытых слоев (13 сверточных слоев и 3 полностью связанных слоя), как показано в столбце D на рисунке выше.
VGG19 содержит 19 скрытых слоев (16 сверточных слоев и 3 полностью связанных слоя), как показано в столбце E на рисунке выше.
Структура сети VGG очень последовательная: от начала до конца используется свертка 3x3 и максимальный пул 2x2.
Преимущества ВГГ
Структура VGGNet очень проста. Вся сеть использует одинаковый размер ядра свертки (3x3) и максимальный размер пула (2x2).
Комбинация нескольких небольших сверточных слоев фильтра (3x3) лучше, чем один большой сверточный слой фильтра (5x5 или 7x7):
Подтверждено, что производительность можно повысить за счет постоянного углубления структуры сети.
Недостатки ВГГ
VGG потребляет больше вычислительных ресурсов и использует больше параметров (это не свертка 3x3), что приводит к большему использованию памяти (140 МБ).
Набор данных CIFAR (Канадский институт перспективных исследований) — это небольшой набор данных изображений, широко используемый в области компьютерного зрения. Он в основном используется для обучения алгоритмов машинного обучения и компьютерного зрения, особенно в таких задачах, как распознавание и классификация изображений. Набор данных CIFAR состоит из двух основных частей: CIFAR-10 и CIFAR-100.
CIFAR-10 — это набор данных, содержащий 60 000 цветных изображений размером 32x32, которые разделены на 10 категорий, каждая категория содержит 6000 изображений. 10 категорий: самолеты, автомобили, птицы, кошки, олени, собаки, лягушки, лошади, лодки и грузовики. В наборе данных 50 000 изображений используются для обучения и 10 000 изображений — для тестирования. Набор данных CIFAR-10 стал одним из очень популярных наборов данных в исследованиях и преподавании в области компьютерного зрения благодаря своему умеренному размеру и богатой информации о классах.
Набор данных CIFAR обычно используется для таких задач, как классификация изображений, распознавание объектов, а также обучение и тестирование сверточных нейронных сетей (CNN). Благодаря умеренному размеру и богатой информации о категориях он идеально подходит для новичков и исследователей, изучающих алгоритмы распознавания изображений. Кроме того, многие соревнования по компьютерному зрению и машинному обучению также используют набор данных CIFAR в качестве эталона для оценки эффективности алгоритмов участников.
Для подготовки набора данных я его уже скачал. Если не получится, просто скачайте с официального сайта, или я вам его дам напрямую.
Если вам нужен набор данных, свяжитесь по электронной почте: [email protected].
Мой набор данных изначально был сгенерирован посредством загруженных данных в torchvision. Мне сейчас не очень хочется это делать, я хочу поэтапно реализовать определение набора данных и загрузку DataLoader, разобраться в этом процессе и разобраться. процесс обработки набора данных может помочь вам глубже понять глубокое обучение.
Стиль набора данных следующий:
Категория меток набора данных использует.meta
Файл сохранен, поэтому нам нужно проанализировать .meta
файл для чтения всех данных тега. Код синтаксического анализа выглядит следующим образом:
# 首先了解所有的标签,TODO 可以详细了解一下这个解包的过程
import pickle
def unpickle(file):
with open(file, 'rb') as fo:
dict = pickle.load(fo, encoding='bytes')
return dict
meta_data = unpickle('./dataset_method_1/cifar-10-batches-py/batches.meta')
label_names = meta_data[b'label_names']
# 将字节标签转换为字符串
label_names = [label.decode('utf-8') for label in label_names]
print(label_names)
Результаты анализа следующие:
['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
Наш набор данных загружен, поэтому нам нужно прочитать содержимое файла. Поскольку файл является двоичным, нам нужно использовать двоичный режим чтения для его чтения.
Код чтения следующий:
# 载入单个批次的数据
import numpy as np
def load_data_batch(file):
with open(file, 'rb') as fo:
dict = pickle.load(fo, encoding='bytes')
X = dict[b'data']
Y = dict[b'labels']
X = X.reshape(10000, 3, 32, 32).transpose(0, 2, 3, 1) # reshape and transpose to (10000, 32, 32, 3)
Y = np.array(Y)
return X, Y
# 加载第一个数据批次
data_batch_1 = './dataset_method_1/cifar-10-batches-py/data_batch_1'
X1, Y1 = load_data_batch(data_batch_1)
print(f'数据形状: {X1.shape}, 标签形状: {Y1.shape}')
результат:
数据形状: (10000, 32, 32, 3), 标签形状: (10000,)
После приведенного выше теста мы знаем, как загружать данные. Теперь давайте загрузим все данные.
Загрузка обучающего набора:
# 整合所有批次的数据
def load_all_data_batches(batch_files):
X_list, Y_list = [], []
for file in batch_files:
X, Y = load_data_batch(file)
X_list.append(X)
Y_list.append(Y)
X_all = np.concatenate(X_list)
Y_all = np.concatenate(Y_list)
return X_all, Y_all
batch_files = [
'./dataset_method_1/cifar-10-batches-py/data_batch_1',
'./dataset_method_1/cifar-10-batches-py/data_batch_2',
'./dataset_method_1/cifar-10-batches-py/data_batch_3',
'./dataset_method_1/cifar-10-batches-py/data_batch_4',
'./dataset_method_1/cifar-10-batches-py/data_batch_5'
]
X_train, Y_train = load_all_data_batches(batch_files)
print(f'训练数据形状: {X_train.shape}, 训练标签形状: {Y_train.shape}')
Y_train = Y_train.astype(np.int64)
Выход:
训练数据形状: (50000, 32, 32, 3), 训练标签形状: (50000,)
Загрузка тестового набора:
test_batch = './dataset_method_1/cifar-10-batches-py/test_batch'
X_test, Y_test = load_data_batch(test_batch)
Y_test = Y_test.astype(np.int64)
print(f'测试数据形状: {X_test.shape}, 测试标签形状: {Y_test.shape}')
Выход:
测试数据形状: (10000, 32, 32, 3), 测试标签形状: (10000,)
Определение подкласса класса Dataset предназначено для облегчения последующей загрузки Dataloader для пакетного обучения.
Есть три метода, которые должны реализовать подклассы Dataset.
__init__()
конструктор класса__len__()
Возвращает длину набора данных__getitem__()
Получить часть данных из набора данныхЗдесь моя реализация выглядит следующим образом:
from torch.utils.data import DataLoader, Dataset
# 定义 Pytorch 的数据集
class CIFARDataset(Dataset):
def __init__(self, images, labels, transform=None):
self.images = images
self.labels = labels
self.transform = transform
def __len__(self):
return len(self.images)
def __getitem__(self, idx):
image = self.images[idx]
label = self.labels[idx]
if self.transform:
image = self.transform(image)
return image, label
transform_train = transforms.Compose(
[transforms.Pad(4),
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
transforms.RandomHorizontalFlip(),
transforms.RandomGrayscale(),
transforms.RandomCrop(32, padding=4),
])
# 把数据集变成 Image 的数组,不然好像不能进行数据的增强
# 改变训练数据
from PIL import Image
def get_PIL_Images(origin_data):
datas = []
for i in range(len(origin_data)):
data = Image.fromarray(origin_data[i])
datas.append(data)
return datas
train_data = get_PIL_Images(X_train)
train_loader = DataLoader(CIFARDataset(train_data, Y_train, transform_train), batch_size=24, shuffle=True)
# 测试集的预处理
transform_test = transforms.Compose(
[
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))]
)
test_loader = DataLoader(CIFARDataset(X_test, Y_test, transform_test), batch_size=24, shuffle=False)
Мы реализуем фреймворк Pytorch на основе упомянутой выше сети VGG16.
в основном разделены:
Реализация следующая:
class VGG16(nn.Module):
def __init__(self):
super(VGG16, self).__init__()
# 卷积层,这里进行卷积
self.convolusion = nn.Sequential(
nn.Conv2d(3, 96, kernel_size=3, padding=1), # 设置为padding=1 卷积完后,数据大小不会变
nn.BatchNorm2d(96),
nn.ReLU(inplace=True),
nn.Conv2d(96, 96, kernel_size=3, padding=1),
nn.BatchNorm2d(96),
nn.ReLU(inplace=True),
nn.Conv2d(96, 96, kernel_size=3, padding=1),
nn.BatchNorm2d(96),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(96, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.Conv2d(128, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.Conv2d(128, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(128, 256, kernel_size=3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(256, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.AvgPool2d(kernel_size=1, stride=1)
)
# 全连接层
self.dense = nn.Sequential(
nn.Linear(512, 4096), # 32*32 的图像大小经过 5 次最大化池化后就只有 1*1 了,所以就是 512 个通道的数据输入全连接层
nn.ReLU(inplace=True),
nn.Dropout(0.4),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Dropout(0.4),
)
# 输出层
self.classifier = nn.Linear(4096, 10)
def forward(self, x):
out = self.convolusion(x)
out = out.view(out.size(0), -1)
out = self.dense(out)
out = self.classifier(out)
return out
Для обучения и тестирования вам нужно только создать экземпляр модели, затем определить функцию оптимизации, функцию потерь и уровень потерь, а затем выполнить обучение и тестирование.
код показан ниже:
Определение гиперпараметра:
# 定义模型进行训练
model = VGG16()
# model.load_state_dict(torch.load('./my-VGG16.pth'))
optimizer = optim.SGD(model.parameters(), lr=0.01, weight_decay=5e-3)
loss_func = nn.CrossEntropyLoss()
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.4, last_epoch=-1)
Функция тестирования:
def test():
model.eval()
correct = 0 # 预测正确的图片数
total = 0 # 总共的图片数
with torch.no_grad():
for data in test_loader:
images, labels = data
images = images.to(device)
outputs = model(images).to(device)
outputs = outputs.cpu()
outputarr = outputs.numpy()
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum()
accuracy = 100 * correct / total
accuracy_rate.append(accuracy)
print(f'准确率为:{accuracy}%'.format(accuracy))
Эпохи обучения:
# 定义训练步骤
total_times = 40
total = 0
accuracy_rate = []
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
for epoch in range(total_times):
model.train()
model.to(device)
running_loss = 0.0
total_correct = 0
total_trainset = 0
print("epoch: ",epoch)
for i, (data,labels) in enumerate(train_loader):
data = data.to(device)
outputs = model(data).to(device)
labels = labels.to(device)
loss = loss_func(outputs,labels).to(device)
optimizer.zero_grad()
loss.backward()
optimizer.step()
running_loss += loss.item()
_,pred = outputs.max(1)
correct = (pred == labels).sum().item()
total_correct += correct
total_trainset += data.shape[0]
if i % 100 == 0 and i > 0:
print(f"正在进行第{i}次训练, running_loss={running_loss}".format(i, running_loss))
running_loss = 0.0
test()
scheduler.step()
Сохраните обученную модель:
torch.save(model.state_dict(), './my-VGG16.pth')
accuracy_rate = np.array(accuracy_rate)
times = np.linspace(1, total_times, total_times)
plt.xlabel('times')
plt.ylabel('accuracy rate')
plt.plot(times, accuracy_rate)
plt.show()
print(accuracy_rate)
model_my_vgg = VGG16()
model_my_vgg.load_state_dict(torch.load('./my-VGG16-best.pth',map_location='cpu'))
from torchvision import transforms
from PIL import Image
# 定义图像预处理步骤
preprocess = transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor(),
# transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225]),
])
def load_image(image_path):
image = Image.open(image_path)
image = preprocess(image)
image = image.unsqueeze(0) # 添加批次维度
return image
image_data = load_image('./plane2.jpg')
print(image_data.shape)
output = model_my_vgg(image_data)
verify_data = X1[9]
verify_label = Y1[9]
output_verify = model_my_vgg(transform_test(verify_data).unsqueeze(0))
print(output)
print(output_verify)
Выход:
torch.Size([1, 3, 32, 32])
tensor([[ 1.5990, -0.5269, 0.7254, 0.3432, -0.5036, -0.3267, -0.5302, -0.9417,
0.4186, -0.1213]], grad_fn=<AddmmBackward0>)
tensor([[-0.6541, -2.0759, 0.6308, 1.9791, 0.8525, 1.2313, 0.1856, 0.3243,
-1.3374, -1.0211]], grad_fn=<AddmmBackward0>)
print(label_names[torch.argmax(output,dim=1,keepdim=False)])
print(label_names[verify_label])
print("pred:",label_names[torch.argmax(output_verify,dim=1,keepdim=False)])
airplane
cat
pred: cat
Проверить лошадь
проверочная собака