meine Kontaktdaten
Postmesophia@protonmail.com
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Hier implementieren wir ein VGG-16-Netzwerk zur Klassifizierung des CIFAR-Datensatzes
Vorwort
《Sehr tiefe Faltungsnetzwerke für die großflächige Bilderkennung》
ICLR 2015
VGGEs ist aus OxfordVbildlichGEometrieG Vorgeschlagen von der Gruppe der Gruppe (Sie sollten den Ursprung des Namens VGG erkennen können). Dieses Netzwerk ist eine verwandte Arbeit auf der ILSVRC 2014. Die Hauptarbeit besteht darin, zu beweisen, dass eine Erhöhung der Netzwerktiefe die endgültige Leistung des Netzwerks bis zu einem gewissen Grad beeinflussen kann. VGG hat zwei Strukturen, nämlich VGG16 und VGG19. Es gibt keinen wesentlichen Unterschied zwischen den beiden, aber die Netzwerktiefe ist unterschiedlich.
VGG-Prinzip
Darin liegt eine Verbesserung von VGG16 im Vergleich zu AlexNetVerwenden Sie mehrere aufeinanderfolgende 3x3-Faltungskerne, um die größeren Faltungskerne in AlexNet (11x11, 7x7, 5x5) zu ersetzen. . Für ein bestimmtes Empfangsfeld (die lokale Größe des Eingabebildes im Verhältnis zur Ausgabe) ist die Verwendung gestapelter kleiner Faltungskerne besser als die Verwendung großer Faltungskerne, da mehrere nichtlineare Schichten die Netzwerktiefe erhöhen können, um einen komplexeren Lernmodus sicherzustellen Die Kosten sind relativ gering (weniger Parameter).
Vereinfacht ausgedrückt werden in VGG drei 3x3-Faltungskerne verwendet, um den 7x7-Faltungskern zu ersetzen, und zwei 3x3-Faltungskerne werden verwendet, um den 5*5-Faltungskern zu ersetzen. Der Hauptzweck besteht darin, das Gleiche sicherzustellen Durch das Empfangsfeld wird die Tiefe des Netzwerks verbessert und die Wirkung des neuronalen Netzwerks wird bis zu einem gewissen Grad verbessert.
Beispielsweise kann die schichtweise Überlagerung von drei 3x3-Faltungskernen mit einem Schritt von 1 als Empfangsfeld der Größe 7 betrachtet werden (eigentlich bedeutet dies, dass drei kontinuierliche 3x3-Faltungen einer 7x7-Faltung entsprechen). Die Gesamtzahl der Parameter beträgt 3x(9xC^2). Wenn der 7x7-Faltungskern direkt verwendet wird, beträgt die Gesamtzahl der Parameter 49xC^2, wobei sich C auf die Anzahl der Eingangs- und Ausgangskanäle bezieht.Offensichtlich 27xC2 weniger als 49xC2, das heißt, die Parameter werden reduziert und der 3x3-Faltungskern trägt dazu bei, die Bildeigenschaften besser aufrechtzuerhalten.
Hier ist eine Erklärung, warum zwei 3x3-Faltungskerne anstelle von 5*5-Faltungskernen verwendet werden können:
Die 5x5-Faltung wird als kleines, vollständig verbundenes Netzwerk betrachtet, das im 5x5-Bereich gleitet. Wir können dann eine vollständig verbundene Schicht verwenden, um den 3x3-Faltungsausgang zu verbinden als 3x3-Faltungsschicht angesehen werden. Auf diese Weise können wir zwei 3x3-Faltungen anstelle einer 5x5-Faltung kaskadieren (überlagern).
Die Details sind in der folgenden Abbildung dargestellt:
VGG-Netzwerkstruktur
Das Folgende ist die Struktur des VGG-Netzwerks (sowohl VGG16 als auch VGG19 sind vorhanden):
GG-Netzwerkstruktur
VGG16 enthält 16 verborgene Schichten (13 Faltungsschichten und 3 vollständig verbundene Schichten), wie in Spalte D in der obigen Abbildung dargestellt
VGG19 enthält 19 verborgene Schichten (16 Faltungsschichten und 3 vollständig verbundene Schichten), wie in Spalte E in der obigen Abbildung dargestellt
Die Struktur des VGG-Netzwerks ist sehr konsistent und verwendet von Anfang bis Ende 3x3-Faltung und 2x2-Max-Pooling.
Vorteile von VGG
Die Struktur von VGGNet ist sehr einfach. Das gesamte Netzwerk verwendet die gleiche Faltungskerngröße (3x3) und die gleiche maximale Poolinggröße (2x2).
Die Kombination mehrerer Faltungsschichten mit kleinem Filter (3x3) ist besser als eine Faltungsschicht mit großem Filter (5x5 oder 7x7):
Es wird nachgewiesen, dass die Leistung durch eine kontinuierliche Vertiefung der Netzwerkstruktur verbessert werden kann.
Nachteile von VGG
VGG verbraucht mehr Rechenressourcen und verwendet mehr Parameter (dies ist nicht der Topf der 3x3-Faltung), was zu einer höheren Speichernutzung führt (140 MB).
Der CIFAR-Datensatz (Canadian Institute For Advanced Research) ist ein kleiner Bilddatensatz, der im Bereich Computer Vision weit verbreitet ist. Er wird hauptsächlich zum Trainieren von Algorithmen für maschinelles Lernen und Computer Vision verwendet, insbesondere für Aufgaben wie Bilderkennung und -klassifizierung. Der CIFAR-Datensatz besteht aus zwei Hauptteilen: CIFAR-10 und CIFAR-100.
CIFAR-10 ist ein Datensatz mit 60.000 32x32-Farbbildern, die in 10 Kategorien unterteilt sind, wobei jede Kategorie 6.000 Bilder enthält. Die 10 Kategorien sind: Flugzeuge, Autos, Vögel, Katzen, Hirsche, Hunde, Frösche, Pferde, Boote und Lastwagen. Im Datensatz werden 50.000 Bilder zum Training und 10.000 Bilder zum Testen verwendet. Der CIFAR-10-Datensatz hat sich aufgrund seiner moderaten Größe und reichhaltigen Klasseninformationen zu einem der sehr beliebten Datensätze in Forschung und Lehre im Bereich Computer Vision entwickelt.
Der CIFAR-Datensatz wird häufig für Aufgaben wie Bildklassifizierung, Objekterkennung sowie das Training und Testen von Faltungs-Neuronalen Netzen (CNN) verwendet. Aufgrund seiner moderaten Größe und umfangreichen Kategorieinformationen ist es ideal für Anfänger und Forscher, die sich mit Bilderkennungsalgorithmen beschäftigen. Darüber hinaus verwenden viele Wettbewerbe im Bereich Computer Vision und maschinelles Lernen den CIFAR-Datensatz als Benchmark, um die Leistung der Algorithmen der Teilnehmer zu bewerten.
Um den Datensatz vorzubereiten, habe ich ihn bereits heruntergeladen. Wenn es nicht funktioniert, laden Sie ihn einfach von der offiziellen Website herunter, oder ich gebe ihn Ihnen direkt.
Wenn Sie den Datensatz benötigen, wenden Sie sich bitte an die E-Mail-Adresse: 2837468248@qq.com
Mein Datensatz wurde ursprünglich durch die heruntergeladenen Daten in Torchvision generiert. Ich möchte die Definition des Datensatzes und das Laden des DataLoaders jetzt Schritt für Schritt umsetzen und verstehen Der Prozess der Datensatzverarbeitung kann Ihnen einen tieferen Einblick in das Deep Learning verschaffen.
Der Datensatzstil ist wie folgt:
Die Beschriftungskategorie des Datensatzes verwendet a.meta
Die Datei wird gespeichert, daher müssen wir sie analysieren .meta
Datei zum Lesen aller Tag-Daten. Der Parsing-Code lautet wie folgt:
# 首先了解所有的标签,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)
Die Ergebnisse der Analyse sind wie folgt:
['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
Unser Datensatz wurde heruntergeladen, daher müssen wir den Inhalt der Datei lesen. Da es sich bei der Datei um eine Binärdatei handelt, müssen wir zum Lesen den binären Lesemodus verwenden.
Der Lesecode lautet wie folgt:
# 载入单个批次的数据
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}')
Ergebnis:
数据形状: (10000, 32, 32, 3), 标签形状: (10000,)
Nach dem obigen Test wissen wir, wie man Daten lädt. Jetzt laden wir alle Daten.
Laden des Trainingssatzes:
# 整合所有批次的数据
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)
Ausgabe:
训练数据形状: (50000, 32, 32, 3), 训练标签形状: (50000,)
Laden des Testsatzes:
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}')
Ausgabe:
测试数据形状: (10000, 32, 32, 3), 测试标签形状: (10000,)
Durch Definieren einer Unterklasse der Dataset-Klasse soll das spätere Laden des Dataloaders für das Batch-Training erleichtert werden.
Es gibt drei Methoden, die Unterklassen von Dataset implementieren müssen.
__init__()
Klassenkonstruktor__len__()
Gibt die Länge des Datensatzes zurück__getitem__()
Holen Sie sich ein Datenelement aus dem DatensatzHier ist meine Implementierung wie folgt:
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)
Wir implementieren das Pytorch-Framework basierend auf dem oben erwähnten VGG16-Netzwerk.
hauptsächlich unterteilt:
Die Implementierung ist wie folgt:
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
Für Training und Tests müssen Sie lediglich das Modell instanziieren, dann die Optimierungsfunktion, die Verlustfunktion und die Verlustrate definieren und dann Training und Tests durchführen.
Code wie folgt anzeigen:
Hyperparameterdefinition:
# 定义模型进行训练
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)
Testfunktion:
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))
Ausbildungsepochen:
# 定义训练步骤
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()
Speichern Sie das trainierte Modell:
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)
Ausgabe:
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
Pferd überprüfen
Verifizierungshund