Mi informacion de contacto
Correo[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Aquí implementamos una red VGG-16 para clasificar el conjunto de datos CIFAR.
Prefacio
《Redes convolucionales muy profundas para el reconocimiento de imágenes a gran escala》
ICLR 2015
VGGes de oxfordVvisualGRAMORAMOGeometríaGRAMORAMO Propuesto por el grupo de grupo (debería poder ver el origen del nombre VGG). Esta red es un trabajo relacionado en ILSVRC 2014. El trabajo principal es demostrar que aumentar la profundidad de la red puede afectar el rendimiento final de la red hasta cierto punto. VGG tiene dos estructuras, a saber, VGG16 y VGG19. No existe una diferencia esencial entre los dos, pero la profundidad de la red es diferente.
principio VGG
Una mejora de VGG16 en comparación con AlexNet es queUtilice varios núcleos de convolución de 3x3 consecutivos para reemplazar los núcleos de convolución más grandes en AlexNet (11x11, 7x7, 5x5) . Para un campo receptivo determinado (el tamaño local de la imagen de entrada en relación con la salida), usar núcleos de convolución pequeños apilados es mejor que usar núcleos de convolución grandes, porque múltiples capas no lineales pueden aumentar la profundidad de la red para garantizar un modo de aprendizaje más complejo. el costo es relativamente pequeño (menos parámetros).
En pocas palabras, en VGG, se usan tres núcleos de convolución de 3x3 para reemplazar el núcleo de convolución de 7x7, y dos núcleos de convolución de 3x3 para reemplazar el núcleo de convolución de 5 * 5. El objetivo principal de esto es garantizar lo mismo. del campo receptivo, se mejora la profundidad de la red y el efecto de la red neuronal se mejora hasta cierto punto.
Por ejemplo, la superposición capa por capa de tres núcleos de convolución de 3x3 con un paso de 1 puede considerarse como un campo receptivo de tamaño 7 (en realidad, significa que tres convoluciones continuas de 3x3 equivalen a una convolución de 7x7), y su los parámetros totales son La cantidad es 3x (9xC^2). Si se usa directamente el núcleo de convolución 7x7, el número total de parámetros es 49xC^2, donde C se refiere al número de canales de entrada y salida.Obviamente, 27xC2 menos que 49xC2, es decir, los parámetros se reducen; el núcleo de convolución 3x3 favorece un mejor mantenimiento de las propiedades de la imagen.
A continuación se explica por qué se pueden utilizar dos núcleos de convolución de 3x3 en lugar de núcleos de convolución de 5*5:
La convolución 5x5 se considera como una pequeña red completamente conectada que se desliza en el área de 5x5. Primero podemos convolucionar con un filtro de convolución 3x3 y luego usar una capa completamente conectada para conectar la salida de convolución 3x3. verse como una capa convolucional de 3x3. De esta manera, podemos conectar en cascada (superponer) dos convoluciones de 3x3 en lugar de una convolución de 5x5.
Los detalles se muestran en la siguiente figura:
Estructura de red VGG
La siguiente es la estructura de la red VGG (tanto VGG16 como VGG19 están presentes):
Estructura de la red GG
VGG16 contiene 16 capas ocultas (13 capas convolucionales y 3 capas completamente conectadas), como se muestra en la columna D en la figura anterior.
VGG19 contiene 19 capas ocultas (16 capas convolucionales y 3 capas completamente conectadas), como se muestra en la columna E en la figura anterior.
La estructura de la red VGG es muy consistente: utiliza convolución 3x3 y agrupación máxima de 2x2 de principio a fin.
Ventajas de VGG
La estructura de VGGNet es muy simple. Toda la red utiliza el mismo tamaño de núcleo de convolución (3x3) y tamaño máximo de agrupación (2x2).
La combinación de varias capas convolucionales de filtro pequeño (3x3) es mejor que una capa convolucional de filtro grande (5x5 o 7x7):
Se verifica que el rendimiento se puede mejorar profundizando continuamente la estructura de la red.
Desventajas de VGG
VGG consume más recursos informáticos y utiliza más parámetros (este no es el recipiente de convolución 3x3), lo que resulta en un mayor uso de memoria (140 M).
El conjunto de datos CIFAR (Instituto Canadiense de Investigación Avanzada) es un pequeño conjunto de datos de imágenes ampliamente utilizado en el campo de la visión por computadora. Se utiliza principalmente para entrenar algoritmos de aprendizaje automático y visión por computadora, especialmente en tareas como el reconocimiento y clasificación de imágenes. El conjunto de datos CIFAR consta de dos partes principales: CIFAR-10 y CIFAR-100.
CIFAR-10 es un conjunto de datos que contiene 60.000 imágenes en color de 32x32, que se dividen en 10 categorías, cada categoría contiene 6.000 imágenes. Las 10 categorías son: aviones, automóviles, pájaros, gatos, venados, perros, ranas, caballos, barcos y camiones. En el conjunto de datos, se utilizan 50.000 imágenes para entrenamiento y 10.000 imágenes para pruebas. El conjunto de datos CIFAR-10 se ha convertido en uno de los conjuntos de datos más populares en la investigación y la enseñanza en el campo de la visión por computadora debido a su tamaño moderado y su rica información de clase.
El conjunto de datos CIFAR se utiliza comúnmente para tareas como clasificación de imágenes, reconocimiento de objetos y entrenamiento y prueba de redes neuronales convolucionales (CNN). Debido a su tamaño moderado y su rica información de categorías, es ideal para principiantes e investigadores que exploran algoritmos de reconocimiento de imágenes. Además, muchas competiciones de visión por computadora y aprendizaje automático también utilizan el conjunto de datos CIFAR como punto de referencia para evaluar el rendimiento de los algoritmos de los concursantes.
Para preparar el conjunto de datos, ya lo descargué. Si no funciona, descárgalo del sitio web oficial o te lo daré directamente.
Si necesita el conjunto de datos, comuníquese con el correo electrónico: [email protected]
Mi conjunto de datos se generó originalmente a través de los datos descargados en torchvision. Realmente no quiero hacer eso ahora. Quiero implementar la definición del conjunto de datos y la carga del DataLoader paso a paso, comprender este proceso y comprender. El proceso de procesamiento de conjuntos de datos puede profundizar en el aprendizaje profundo.
El estilo del conjunto de datos es el siguiente:
La categoría de etiqueta del conjunto de datos utiliza un.meta
El archivo está almacenado, por lo que debemos analizarlo. .meta
archivo para leer todos los datos de la etiqueta. El código de análisis es el siguiente:
# 首先了解所有的标签,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)
Los resultados del análisis son los siguientes:
['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
Nuestro conjunto de datos se ha descargado, por lo que debemos leer el contenido del archivo. Dado que el archivo es un archivo binario, debemos usar el modo de lectura binaria para leerlo.
El código de lectura es el siguiente:
# 载入单个批次的数据
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}')
resultado:
数据形状: (10000, 32, 32, 3), 标签形状: (10000,)
Después de la prueba anterior, sabemos cómo cargar datos. Ahora carguemos todos los datos.
Cargando el conjunto de entrenamiento:
# 整合所有批次的数据
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)
Producción:
训练数据形状: (50000, 32, 32, 3), 训练标签形状: (50000,)
Cargando el equipo de prueba:
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}')
Producción:
测试数据形状: (10000, 32, 32, 3), 测试标签形状: (10000,)
Definir una subclase de la clase Dataset es para facilitar la carga posterior del Cargador de datos para el entrenamiento por lotes.
Hay tres métodos que las subclases de Dataset deben implementar.
__init__()
constructor de clase__len__()
Devuelve la longitud del conjunto de datos.__getitem__()
Obtener un dato del conjunto de datosAquí mi implementación es la siguiente:
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)
Implementamos el marco Pytorch basado en la red VGG16 mencionada anteriormente.
dividido principalmente:
La implementación es la siguiente:
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
Para el entrenamiento y las pruebas, solo necesita crear una instancia del modelo, luego definir la función de optimización, la función de pérdida y la tasa de pérdida, y luego realizar el entrenamiento y las pruebas.
El código se muestra a continuación:
Definición de hiperparámetro:
# 定义模型进行训练
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)
Función de prueba:
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))
Épocas de formación:
# 定义训练步骤
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()
Guarde el modelo entrenado:
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)
Producción:
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
verificar caballo
perro de verificación