le mie informazioni di contatto
Posta[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Qui implementiamo una rete VGG-16 per classificare il set di dati CIFAR
Prefazione
《Reti convoluzionali molto profonde per il riconoscimento di immagini su larga scala》
2015 ICLR
VGGViene da OxfordEisualeGeometriaG Proposto dal gruppo roup (dovresti essere in grado di vedere l'origine del nome VGG). Questa rete è un lavoro correlato all'ILSVRC 2014. Il lavoro principale è dimostrare che l'aumento della profondità della rete può influenzare in una certa misura le prestazioni finali della rete. VGG ha due strutture, vale a dire VGG16 e VGG19. Non esiste alcuna differenza essenziale tra i due, ma la profondità della rete è diversa.
Principio VGG
Un miglioramento di VGG16 rispetto ad AlexNet è questoUtilizza diversi kernel di convoluzione 3x3 consecutivi per sostituire i kernel di convoluzione più grandi in AlexNet (11x11, 7x7, 5x5) . Per un dato campo ricettivo (la dimensione locale dell'immagine di input rispetto all'output), l'utilizzo di kernel a convoluzione piccola impilati è migliore rispetto all'utilizzo di kernel a convoluzione di grandi dimensioni, poiché più livelli non lineari possono aumentare la profondità della rete per garantire modalità di apprendimento più complesse il costo è relativamente piccolo (meno parametri).
Per dirla semplicemente, in VGG, tre kernel di convoluzione 3x3 vengono utilizzati per sostituire il kernel di convoluzione 7x7 e due kernel di convoluzione 3x3 vengono utilizzati per sostituire il kernel di convoluzione 5*5. Lo scopo principale di ciò è garantire la stessa condizione del campo recettivo, la profondità della rete viene migliorata e l'effetto della rete neurale viene migliorato in una certa misura.
Ad esempio, la sovrapposizione strato per strato di tre nuclei di convoluzione 3x3 con passo pari a 1 può essere considerato come un campo recettivo di dimensione 7 (in realtà, significa che tre convoluzioni continue 3x3 equivalgono a una convoluzione 7x7), e la sua i parametri totali sono La quantità è 3x(9xC^2). Se viene utilizzato direttamente il kernel di convoluzione 7x7, il numero totale di parametri è 49xC^2, dove C si riferisce al numero di canali di ingresso e uscita.Ovviamente, 27xC2 inferiore a 49xC2, ovvero i parametri sono ridotti; e il kernel di convoluzione 3x3 favorisce un migliore mantenimento delle proprietà dell'immagine.
Ecco una spiegazione del motivo per cui è possibile utilizzare due kernel di convoluzione 3x3 invece dei kernel di convoluzione 5*5:
La convoluzione 5x5 è considerata come una piccola rete completamente connessa che scorre nell'area 5x5. Possiamo prima convoluzione con un filtro di convoluzione 3x3, quindi utilizzare un livello completamente connesso per collegare l'output della convoluzione 3x3. È possibile essere visto come uno strato convoluzionale 3x3. In questo modo, possiamo mettere in cascata (sovrapporre) due convoluzioni 3x3 invece di una convoluzione 5x5.
I dettagli sono mostrati nella figura seguente:
Struttura della rete VGG
Quella che segue è la struttura della rete VGG (sono presenti sia VGG16 che VGG19):
Struttura della rete GG
VGG16 contiene 16 strati nascosti (13 strati convoluzionali e 3 strati completamente connessi), come mostrato nella colonna D nella figura sopra
VGG19 contiene 19 strati nascosti (16 strati convoluzionali e 3 strati completamente connessi), come mostrato nella colonna E nella figura sopra
La struttura della rete VGG è molto coerente, utilizzando la convoluzione 3x3 e il pooling massimo 2x2 dall'inizio alla fine.
Vantaggi del VGG
La struttura di VGGNet è molto semplice L'intera rete utilizza la stessa dimensione del kernel di convoluzione (3x3) e la dimensione massima del pooling (2x2).
La combinazione di diversi strati convoluzionali con filtro piccolo (3x3) è migliore di uno strato convoluzionale con filtro grande (5x5 o 7x7):
È verificato che le prestazioni possono essere migliorate approfondendo continuamente la struttura della rete.
Svantaggi del VGG
VGG consuma più risorse di elaborazione e utilizza più parametri (questo non è il piatto della convoluzione 3x3), con conseguente maggiore utilizzo della memoria (140 M).
Il set di dati CIFAR (Canadian Institute For Advanced Research) è un piccolo set di dati di immagini ampiamente utilizzato nel campo della visione artificiale. Viene utilizzato principalmente per addestrare algoritmi di apprendimento automatico e visione artificiale, in particolare in attività come il riconoscimento e la classificazione delle immagini. Il set di dati CIFAR è costituito da due parti principali: CIFAR-10 e CIFAR-100.
CIFAR-10 è un set di dati contenente 60.000 immagini a colori 32x32, suddivise in 10 categorie, ciascuna categoria contenente 6.000 immagini. Le 10 categorie sono: aeroplani, automobili, uccelli, gatti, cervi, cani, rane, cavalli, barche e camion. Nel set di dati vengono utilizzate 50.000 immagini per l'addestramento e 10.000 immagini per i test. Il set di dati CIFAR-10 è diventato uno dei set di dati più popolari nella ricerca e nell'insegnamento nel campo della visione artificiale grazie alle sue dimensioni moderate e alla ricchezza di informazioni sulle classi.
Il set di dati CIFAR viene comunemente utilizzato per attività quali la classificazione delle immagini, il riconoscimento degli oggetti e l'addestramento e il test delle reti neurali convoluzionali (CNN). Grazie alle sue dimensioni moderate e alle ricche informazioni sulle categorie, è ideale per principianti e ricercatori che esplorano algoritmi di riconoscimento delle immagini. Inoltre, molte competizioni di visione artificiale e apprendimento automatico utilizzano anche il set di dati CIFAR come punto di riferimento per valutare le prestazioni degli algoritmi dei concorrenti.
Per preparare il set di dati l'ho già scaricato. Se non funziona basta scaricarlo dal sito ufficiale, oppure te lo fornirò direttamente.
Se hai bisogno del set di dati, contatta l'e-mail: [email protected]
Il mio set di dati è stato originariamente generato tramite i dati scaricati in torchvision. Non voglio farlo ora, voglio implementare la definizione del set di dati e il caricamento del DataLoader passo dopo passo, comprendere questo processo e comprendere. il processo di elaborazione del set di dati può renderti più approfondito nell'apprendimento profondo.
Lo stile del set di dati è il seguente:
La categoria di etichetta del set di dati utilizza a.meta
Il file è archiviato, quindi è necessario analizzarlo .meta
file per leggere tutti i dati dei tag. Il codice di analisi è il seguente:
# 首先了解所有的标签,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)
I risultati dell’analisi sono i seguenti:
['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
Il nostro set di dati è stato scaricato, quindi dobbiamo leggere il contenuto del file Poiché il file è un file binario, dobbiamo utilizzare la modalità di lettura binaria per leggerlo.
Il codice di lettura è il seguente:
# 载入单个批次的数据
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}')
risultato:
数据形状: (10000, 32, 32, 3), 标签形状: (10000,)
Dopo il test precedente, sappiamo come caricare i dati. Ora carichiamo tutti i dati.
Caricamento del set di allenamento:
# 整合所有批次的数据
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)
Produzione:
训练数据形状: (50000, 32, 32, 3), 训练标签形状: (50000,)
Caricamento del set di prova:
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}')
Produzione:
测试数据形状: (10000, 32, 32, 3), 测试标签形状: (10000,)
La definizione di una sottoclasse della classe Dataset serve a facilitare il successivo caricamento del Dataloader per l'addestramento batch.
Esistono tre metodi che le sottoclassi di Dataset devono implementare.
__init__()
costruttore di classi__len__()
Restituisce la lunghezza del set di dati__getitem__()
Ottieni un dato dal set di datiQui la mia implementazione è la seguente:
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)
Implementiamo il framework Pytorch basato sulla rete VGG16 sopra menzionata.
principalmente suddivisi:
L'implementazione è la seguente:
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
Per l'addestramento e il test, è sufficiente creare un'istanza del modello, quindi definire la funzione di ottimizzazione, la funzione di perdita e il tasso di perdita, quindi eseguire l'addestramento e il test.
codice mostrato come di seguito:
Definizione dell'iperparametro:
# 定义模型进行训练
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)
Funzione di prova:
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))
Epoche di allenamento:
# 定义训练步骤
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()
Salvare il modello addestrato:
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)
Produzione:
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
Verifica cavallo
cane da verifica