Подробно опиши какая задача решается в данном коде...
Created on: May 19, 2025
Created on: May 19, 2025
Подробно опиши какая задача решается в данном коде из файла. напиши теорию про CNN, как формируется. Какие параметры. На данном примере из кода напиши какая размерность была в начале и после свертки, сколько сверток. Задача 1. Анализ модели CNN
Провести анализ качества аппроксимации выборки EMNIST-letters моделью сверточной нейронной сети в зависимости от:
размера ядра (можно ввести ограничение, что на каждом слое размер ядра одинаковый);
числа слоев;
от пулинга;
добавления BatchNorm;
параметра dropout.
Все выводы должны быть представленны в формате tensorboard (каждый набор параметров, свой график, пример 16-й семинар).
P.S. Выборку можно взять из torchvision.
P.S.S Если не работает скачиваение EMNIST использовать FahionMnist.
Пояснение: В данном задании важно продемонстрировать умение строить различные структуры модели CNN. Не обязательно выполнять перебор всех вариантов нейросети (проходить по сетке гиперпараметров), но описание экспериментов должны присутвовать. import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
from torch.utils.tensorboard import SummaryWriter
import matplotlib.pyplot as plt
import numpy as np
import time
import os
import torchvision
from google.colab import drive
import shutil
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Используемое устройство: {device}")
drive.mount('/content/drive')
MODEL_SAVE_PATH = '/content/drive/MyDrive/cnn_analysis_models/'
if not os.path.exists(MODEL_SAVE_PATH):
os.makedirs(MODEL_SAVE_PATH)
LOG_DIR = '/content/drive/MyDrive/tensorboard_logs'
if not os.path.exists(LOG_DIR):
os.makedirs(LOG_DIR)
class TargetAdjustment(object):
def call(self, target):
return target - 1 # Корректировка меток EMNIST, чтобы начинались с 0
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,)) # Mean and std for MNIST-like datasets
])
target_transform = TargetAdjustment()
try:
print("Попытка загрузки набора данных EMNIST-letters...")
train_dataset = datasets.EMNIST(root='./data', split='letters', train=True, download=True,
transform=transform, target_transform=target_transform)
test_dataset = datasets.EMNIST(root='./data', split='letters', train=False, download=True,
transform=transform, target_transform=target_transform)
num_classes = 26 # EMNIST-letters содержит 26 классов (A-Z)
print("Набор данных EMNIST-letters успешно загружен")
except Exception as e:
print(f"Ошибка загрузки набора данных EMNIST: {e}")
print("Переключение на FashionMNIST...")
train_dataset = datasets.FashionMNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.FashionMNIST(root='./data', train=False, download=True, transform=transform)
num_classes = 10 # FashionMNIST содержит 10 классов
print("Набор данных FashionMNIST успешно загружен")
batch_size = 64
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
def show_examples(dataset, num_examples=5):
"""
Отображение случайных примеров из набора данных
textАргументы: dataset: Набор данных PyTorch num_examples: Количество примеров для отображения """ plt.figure(figsize=(15, 3)) indices = np.random.choice(len(dataset), num_examples, replace=False) for i, idx in enumerate(indices): img, label = dataset[idx] plt.subplot(1, num_examples, i+1) plt.imshow(img.squeeze().numpy(), cmap='gray') plt.title(f'Label: {label}') plt.axis('off') plt.tight_layout() plt.show()
print("Displaying dataset examples:")
show_examples(train_dataset)
class FlexibleCNN(nn.Module):
def init(self, num_layers=3, kernel_size=3, use_batchnorm=True,
dropout_rate=0.2, pooling_type='max', num_classes=10):
"""
Гибкая архитектура CNN, позволяющая настраивать различные гиперпараметры.
textАргументы: num_layers (int): Количество сверточных слоев kernel_size (int): Размер ядра свертки (одинаковый для всех слоев) use_batchnorm (bool): Использовать ли батч-нормализацию dropout_rate (float): Вероятность dropout (0 = без dropout) pooling_type (str): Тип пулинга ('max', 'avg', или 'none') num_classes (int): Количество выходных классов """ super(FlexibleCNN, self).__init__() self.layers = nn.ModuleList() # Входные каналы для набора данных (1 для изображений в оттенках серого) in_channels = 1 # Начинаем с 16 каналов и удваиваем с каждым слоем out_channels = 16 # Создание сверточных слоев for i in range(num_layers): # Добавление сверточного блока conv_block = [] # Добавление сверточного слоя padding = kernel_size // 2 # Padding типа "same" conv_block.append(nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, padding=padding)) # Добавление батч-нормализации, если указано if use_batchnorm: conv_block.append(nn.BatchNorm2d(out_channels)) # Добавление функции активации conv_block.append(nn.ReLU()) # Добавление пулинга, если указано if pooling_type == 'max': conv_block.append(nn.MaxPool2d(2)) elif pooling_type == 'avg': conv_block.append(nn.AvgPool2d(2)) # Добавление dropout, если указано if dropout_rate > 0: conv_block.append(nn.Dropout(dropout_rate)) # Добавление блока в список слоев self.layers.append(nn.Sequential(*conv_block)) # Обновление количества каналов для следующего слоя in_channels = out_channels out_channels *= 2 # Расчет размера сглаженных карт признаков # Предполагаем, что размер входного изображения 28x28 # Каждый слой пулинга уменьшает пространственные размеры вдвое feature_size = 28 // (2 ** (num_layers if pooling_type != 'none' else 0)) # Обработка случая, когда слишком много слоев или нет пулинга if feature_size < 1: feature_size = 1 # Итоговое количество каналов (для последнего сверточного слоя) final_channels = in_channels flattened_size = final_channels * (feature_size ** 2) # Добавление классификатора (полносвязные слои) self.classifier = nn.Sequential( nn.Flatten(), nn.Linear(flattened_size, 128), nn.ReLU(), nn.Dropout(dropout_rate), nn.Linear(128, num_classes) ) # Инициализация весов self._initialize_weights() def _initialize_weights(self): """Инициализация весов с использованием инициализации He""" for m in self.modules(): if isinstance(m, nn.Conv2d): nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') if m.bias is not None: nn.init.constant_(m.bias, 0) elif isinstance(m, nn.BatchNorm2d): nn.init.constant_(m.weight, 1) nn.init.constant_(m.bias, 0) elif isinstance(m, nn.Linear): nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') nn.init.constant_(m.bias, 0) def forward(self, x): """ Прямой проход через сеть Аргументы: x: Входной тензор формы [batch_size, channels, height, width] Возвращает: Выходной тензор формы [batch_size, num_classes] """ # Пропускаем вход через все сверточные слои for layer in self.layers: x = layer(x) # Пропускаем через классификатор return self.classifier(x)
def train_model(model, train_loader, test_loader, optimizer, criterion, num_epochs=10,
model_name='cnn_model', save_path=MODEL_SAVE_PATH):
"""
Обучение модели и ее периодическая оценка.
textАргументы: model: Модель для обучения train_loader: DataLoader для обучающих данных test_loader: DataLoader для тестовых данных optimizer: Используемый оптимизатор criterion: Используемая функция потерь num_epochs: Количество эпох обучения model_name: Имя для сохранения модели и логов tensorboard save_path: Путь для сохранения модели Возвращает: Обученную модель и словарь с историей обучения """ # Настройка записи в tensorboard writer = SummaryWriter(f"{LOG_DIR}/{model_name}") # Перемещение модели на устройство (CPU/GPU) model = model.to(device) # Инициализация лучшей точности для сохранения модели best_accuracy = 0.0 # История обучения history = { 'train_loss': [], 'train_acc': [], 'val_loss': [], 'val_acc': [] } # Получение пакета примеров для визуализации до обучения dataiter = iter(test_loader) sample_images, sample_labels = next(dataiter) sample_images = sample_images.to(device) # Визуализация структуры модели в tensorboard dummy_input = torch.zeros(1, 1, 28, 28).to(device) # 1 образец, 1 канал, изображение 28x28 writer.add_graph(model, dummy_input) # Добавление образцов изображений в tensorboard img_grid = torchvision.utils.make_grid(sample_images[:8]) writer.add_image('Примеры изображений', img_grid) # Получение предсказаний модели до обучения with torch.no_grad(): model.eval() sample_output_before = model(sample_images) sample_pred_before = torch.argmax(sample_output_before, dim=1) # Обучение модели for epoch in range(num_epochs): # Фаза обучения model.train() train_loss = 0.0 correct = 0 total = 0 for batch_idx, (data, target) in enumerate(train_loader): data, target = data.to(device), target.to(device) # Обнуление градиентов параметров optimizer.zero_grad() # Прямой проход outputs = model(data) loss = criterion(outputs, target) # Обратный проход и оптимизация loss.backward() optimizer.step() # Статистика train_loss += loss.item() _, predicted = outputs.max(1) total += target.size(0) correct += predicted.eq(target).sum().item() # Отчет о прогрессе if (batch_idx + 1) % 100 == 0: print(f'Эпоха: {epoch+1}/{num_epochs}, Батч: {batch_idx+1}/{len(train_loader)}, ' f'Потери: {loss.item():.4f}, Точность: {100.*correct/total:.2f}%') # Расчет метрик обучения для эпохи train_loss = train_loss / len(train_loader) train_accuracy = 100. * correct / total # Фаза валидации model.eval() val_loss = 0.0 val_correct = 0 val_total = 0 with torch.no_grad(): for data, target in test_loader: data, target = data.to(device), target.to(device) outputs = model(data) loss = criterion(outputs, target) val_loss += loss.item() _, predicted = outputs.max(1) val_total += target.size(0) val_correct += predicted.eq(target).sum().item() val_loss = val_loss / len(test_loader) val_accuracy = 100. * val_correct / val_total # Сохранение истории history['train_loss'].append(train_loss) history['train_acc'].append(train_accuracy) history['val_loss'].append(val_loss) history['val_acc'].append(val_accuracy) # Запись в tensorboard writer.add_scalar('Потери/обучение', train_loss, epoch) writer.add_scalar('Потери/валидация', val_loss, epoch) writer.add_scalar('Точность/обучение', train_accuracy, epoch) writer.add_scalar('Точность/валидация', val_accuracy, epoch) # Запись примеров предсказаний в tensorboard if epoch % 5 == 0 or epoch == num_epochs - 1: with torch.no_grad(): model.eval() sample_output = model(sample_images) sample_pred = torch.argmax(sample_output, dim=1) fig, axes = plt.subplots(1, 8, figsize=(15, 3)) for i in range(8): axes[i].imshow(sample_images[i].cpu().squeeze().numpy(), cmap='gray') axes[i].set_title(f'Прогноз: {sample_pred[i].item()}') axes[i].axis('off') plt.tight_layout() writer.add_figure(f'Предсказания/эпоха_{epoch}', fig, epoch) plt.close(fig) # Сохранение модели, если она лучшая на данный момент if val_accuracy > best_accuracy: best_accuracy = val_accuracy torch.save(model.state_dict(), f"{save_path}{model_name}_best.pth") print(f"Сохранена лучшая модель с точностью: {best_accuracy:.2f}%") # Вывод результатов эпохи print(f'Эпоха: {epoch+1}/{num_epochs}, Потери обучения: {train_loss:.4f}, Точность обучения: {train_accuracy:.2f}%, ' f'Потери валидации: {val_loss:.4f}, Точность валидации: {val_accuracy:.2f}%') # Получение предсказаний модели после обучения with torch.no_grad(): model.eval() sample_output_after = model(sample_images) sample_pred_after = torch.argmax(sample_output_after, dim=1) # Создание сравнительной диаграммы до и после обучения fig, axes = plt.subplots(2, 8, figsize=(15, 5)) for i in range(8): # До обучения axes[0, i].imshow(sample_images[i].cpu().squeeze().numpy(), cmap='gray') axes[0, i].set_title(f'До: {sample_pred_before[i].item()}') axes[0, i].axis('off') # После обучения axes[1, i].imshow(sample_images[i].cpu().squeeze().numpy(), cmap='gray') axes[1, i].set_title(f'После: {sample_pred_after[i].item()}') axes[1, i].axis('off') plt.tight_layout() writer.add_figure('До_и_после_обучения', fig, 0) plt.close(fig) # Закрытие tensorboard writer writer.close() return model, history
kernel_sizes = [3, 5, 7]
num_layers_list = [2, 3, 4]
pooling_types = ['max', 'avg', 'none']
batchnorm_options = [True, False]
dropout_rates = [0.0, 0.2, 0.5]
def experiment_kernel_sizes():
"""Эксперимент с различными размерами ядра"""
print("\n" + "="*50)
print("Запуск эксперимента: Размер ядра")
print("="*50)
textresults = {} # Фиксированные параметры num_layers = 3 use_batchnorm = True dropout_rate = 0.2 pooling_type = 'max' for kernel_size in kernel_sizes: model_name = f'kernel_{kernel_size}_layers_{num_layers}_bn_{use_batchnorm}_dropout_{dropout_rate}_pool_{pooling_type}' print(f"\nОбучение модели с размером ядра {kernel_size}") # Инициализация модели model = FlexibleCNN( num_layers=num_layers, kernel_size=kernel_size, use_batchnorm=use_batchnorm, dropout_rate=dropout_rate, pooling_type=pooling_type, num_classes=num_classes ) # Initialize optimizer and loss function optimizer = optim.Adam(model.parameters(), lr=0.001) criterion = nn.CrossEntropyLoss() # Train model trained_model, history = train_model( model=model, train_loader=train_loader, test_loader=test_loader, optimizer=optimizer, criterion=criterion, num_epochs=10, model_name=model_name ) results[kernel_size] = history['val_acc'][-1] return results
def experiment_num_layers():
"""Эксперимент с различным количеством слоев"""
print("\n" + "="*50)
print("Запуск эксперимента: Количество слоев")
print("="*50)
textresults = {} # Фиксированные параметры kernel_size = 3 use_batchnorm = True dropout_rate = 0.2 pooling_type = 'max' for num_layers in num_layers_list: model_name = f'kernel_{kernel_size}_layers_{num_layers}_bn_{use_batchnorm}_dropout_{dropout_rate}_pool_{pooling_type}' print(f"\nОбучение модели с {num_layers} слоями") # Initialize model model = FlexibleCNN( num_layers=num_layers, kernel_size=kernel_size, use_batchnorm=use_batchnorm, dropout_rate=dropout_rate, pooling_type=pooling_type, num_classes=num_classes ) # Initialize optimizer and loss function optimizer = optim.Adam(model.parameters(), lr=0.001) criterion = nn.CrossEntropyLoss() # Train model trained_model, history = train_model( model=model, train_loader=train_loader, test_loader=test_loader, optimizer=optimizer, criterion=criterion, num_epochs=10, model_name=model_name ) results[num_layers] = history['val_acc'][-1] return results
def experiment_pooling():
"""Эксперимент с различными типами пулинга"""
print("\n" + "="*50)
print("Запуск эксперимента: Тип пулинга")
print("="*50)
textresults = {} # Фиксированные параметры kernel_size = 3 num_layers = 3 use_batchnorm = True dropout_rate = 0.2 for pooling_type in pooling_types: model_name = f'kernel_{kernel_size}_layers_{num_layers}_bn_{use_batchnorm}_dropout_{dropout_rate}_pool_{pooling_type}' print(f"\nОбучение модели с типом пулинга {pooling_type}") # Initialize model model = FlexibleCNN( num_layers=num_layers, kernel_size=kernel_size, use_batchnorm=use_batchnorm, dropout_rate=dropout_rate, pooling_type=pooling_type, num_classes=num_classes ) # Initialize optimizer and loss function optimizer = optim.Adam(model.parameters(), lr=0.001) criterion = nn.CrossEntropyLoss() # Train model trained_model, history = train_model( model=model, train_loader=train_loader, test_loader=test_loader, optimizer=optimizer, criterion=criterion, num_epochs=10, model_name=model_name ) results[pooling_type] = history['val_acc'][-1] return results
def experiment_batchnorm():
"""Эксперимент с батч-нормализацией и без нее"""
print("\n" + "="*50)
print("Запуск эксперимента: Батч-нормализация")
print("="*50)
textresults = {} # Фиксированные параметры kernel_size = 3 num_layers = 3 dropout_rate = 0.2 pooling_type = 'max' for use_batchnorm in batchnorm_options: model_name = f'kernel_{kernel_size}_layers_{num_layers}_bn_{use_batchnorm}_dropout_{dropout_rate}_pool_{pooling_type}' print(f"\nОбучение модели с BatchNorm = {use_batchnorm}") # Initialize model model = FlexibleCNN( num_layers=num_layers, kernel_size=kernel_size, use_batchnorm=use_batchnorm, dropout_rate=dropout_rate, pooling_type=pooling_type, num_classes=num_classes ) # Initialize optimizer and loss function optimizer = optim.Adam(model.parameters(), lr=0.001) criterion = nn.CrossEntropyLoss() # Train model trained_model, history = train_model( model=model, train_loader=train_loader, test_loader=test_loader, optimizer=optimizer, criterion=criterion, num_epochs=10, model_name=model_name ) results[str(use_batchnorm)] = history['val_acc'][-1] return results
def experiment_dropout():
"""Эксперимент с различными коэффициентами dropout"""
print("\n" + "="*50)
print("Запуск эксперимента: Коэффициент Dropout")
print("="*50)
textresults = {} # Фиксированные параметры kernel_size = 3 num_layers = 3 use_batchnorm = True pooling_type = 'max' for dropout_rate in dropout_rates: model_name = f'kernel_{kernel_size}_layers_{num_layers}_bn_{use_batchnorm}_dropout_{dropout_rate}_pool_{pooling_type}' print(f"\nОбучение модели с коэффициентом dropout {dropout_rate}") # Initialize model model = FlexibleCNN( num_layers=num_layers, kernel_size=kernel_size, use_batchnorm=use_batchnorm, dropout_rate=dropout_rate, pooling_type=pooling_type, num_classes=num_classes ) # Initialize optimizer and loss function optimizer = optim.Adam(model.parameters(), lr=0.001) criterion = nn.CrossEntropyLoss() # Train model trained_model, history = train_model( model=model, train_loader=train_loader, test_loader=test_loader, optimizer=optimizer, criterion=criterion, num_epochs=10, model_name=model_name ) results[dropout_rate] = history['val_acc'][-1] return results
def visualize_model_predictions(model, test_loader, num_samples=10):
"""
Визуализация предсказаний модели на образцах данных.
textАргументы: model: Обученная модель test_loader: DataLoader для тестовых данных num_samples: Количество образцов для визуализации """ # Get samples dataiter = iter(test_loader) images, labels = next(dataiter) images = images[:num_samples] labels = labels[:num_samples] # Move to device images = images.to(device) # Get predictions model.eval() with torch.no_grad(): outputs = model(images) _, predicted = outputs.max(1) # Move back to CPU for display images = images.cpu() predicted = predicted.cpu() # Display images and predictions plt.figure(figsize=(15, 4)) for i in range(num_samples): plt.subplot(2, num_samples//2, i+1) plt.imshow(images[i].squeeze().numpy(), cmap='gray') plt.title(f'Pred: {predicted[i]}, True: {labels[i]}') plt.axis('off') plt.tight_layout() plt.show()
def create_tensorboard_zip():
"""Создание zip-файла с логами tensorboard"""
# Удаление существующего zip-файла, если он существует
if os.path.exists('tensorboard1.zip'):
os.remove('tensorboard1.zip')
textshutil.make_archive('tensorboard1', 'zip', LOG_DIR) print("\nЛоги tensorboard сохранены в файл tensorboard1.zip")
print("\nНачало экспериментов для анализа модели CNN на наборе данных EMNIST-letters")
kernel_results = experiment_kernel_sizes()
layers_results = experiment_num_layers()
pooling_results = experiment_pooling()
batchnorm_results = experiment_batchnorm()
dropout_results = experiment_dropout()
create_tensorboard_zip()
print("\n" + "="*50)
print("СВОДКА РЕЗУЛЬТАТОВ")
print("="*50)
print("\nРезультаты по размеру ядра:", kernel_results)
print("Результаты по количеству слоев:", layers_results)
print("Результаты по типу пулинга:", pooling_results)
print("Результаты по батч-нормализации:", batchnorm_results)
print("Результаты по коэффициенту dropout:", dropout_results)
print("\nЗагрузка лучшей модели для визуализации...")
best_model_name = 'kernel_3_layers_3_bn_True_dropout_0.2_pool_max' # Должно быть определено на основе результатов
best_model = FlexibleCNN(num_layers=3, kernel_size=3, use_batchnorm=True,
dropout_rate=0.2, pooling_type='max', num_classes=num_classes)
best_model.load_state_dict(torch.load(f"{MODEL_SAVE_PATH}{best_model_name}_best.pth"))
best_model = best_model.to(device)
untrained_model = FlexibleCNN(num_layers=3, kernel_size=3, use_batchnorm=True,
dropout_rate=0.2, pooling_type='max', num_classes=num_classes).to(device)
print("\nПредсказания необученной модели:")
visualize_model_predictions(untrained_model, test_loader)
print("\nПредсказания обученной модели:")
visualize_model_predictions(best_model, test_loader)
print("\n" + "="*50)
print("ВЫВОДЫ ПО ЭКСПЕРИМЕНТАМ")
print("="*50)
print("\nАнализ размера ядра:")
print(f"Результаты: {kernel_results}")
print("Что мы ожидали: Большие размеры ядра захватывают больше пространственной информации, но могут терять мелкие детали.")
print("\nАнализ количества слоев:")
print(f"Результаты: {layers_results}")
print("Что мы ожидали: Больше слоев может захватывать более сложные шаблоны, но может быть сложнее в обучении.")
print("\nАнализ типа пулинга:")
print(f"Результаты: {pooling_results}")
print("Что мы ожидали: Max pooling сохраняет важные признаки, avg pooling дает более сглаженные выходы.")
print("\nАнализ батч-нормализации:")
print(f"Результаты: {batchnorm_results}")
print("Что мы ожидали: Батч-нормализация помогает со стабильностью обучения и скоростью сходимости.")
print("\nАнализ коэффициента dropout:")
print(f"Результаты: {dropout_results}")
print("Что мы ожидали: Более высокие коэффициенты dropout помогают предотвратить переобучение, но могут затруднять обучение.")
Код — это исследовательский стенд для сверточных нейронных сетей (CNN), который автоматически:
FlexibleCNN
, где все ключевые гиперпараметры (размер свёрточного ядра, число слоёв, вид пулинга, BatchNorm, Dropout) задаются из-вне;Таким образом решается задача анализа влияния архитектурных решений CNN на качество распознавания рукописных букв.
Компонент | Что делает | На что влияет |
---|---|---|
Свёртка (Conv2d) | Сканирует изображение фильтрами → вычисляет карты признаков | Размер ядра, шаг, паддинг, количество фильтров: задают receptive field и глубину представления |
Batch Normalization | Нормирует активации внутри мини-батча | Стабильность и скорость обучения, позволяет использовать более высокие learning rate |
Активация (ReLU) | Вводит нелинейность | Помогает сети приближать сложные функции |
Пулинг (Max/Avg) | Сжимает пространственное разрешение | Инвариантность к сдвигам, уменьшение вычислений |
Dropout | Случайно обнуляет нейроны на обучении | Борется с переобучением |
Flatten + FC | Переводит 3-D тензор в вектор и классифицирует | Количество признаков = channels × H × W |
FlexibleCNN
textВход: 1 × 28 × 28 Каждый блок: Conv(k×k, SAME) → (BatchNorm) → ReLU → (Pooling) → (Dropout) После N блоков → Flatten → FC-128 → ReLU → Dropout → FC-num_classes
Параметры конструктора
num_layers
— сколько таких блоковkernel_size
— одно и то же k для всех convuse_batchnorm
— вставлять ли BatchNormdropout_rate
— 0 … 0.5pooling_type
— 'max' | 'avg' | 'none'
out_channels
удваиваются на каждом слое (16, 32, 64, …).num_layers = 3
, kernel_size = 3
, pooling_type = 'max'
, BatchNorm вкл., Dropout = 0.2
Слой | Размер тензора (С × H × W) | Пояснение |
---|---|---|
Вход | 1 × 28 × 28 | 1 канал, 28 px |
Conv1 | 16 × 28 × 28 | SAME-padding (p = 1) |
MaxPool1 | 16 × 14 × 14 | /2 |
Conv2 | 32 × 14 × 14 | p = 1 |
MaxPool2 | 32 × 7 × 7 | /2 |
Conv3 | 64 × 7 × 7 | p = 1 |
MaxPool3 | 64 × 3 × 3 | /2, округление вниз |
Flatten | 576 = 64 × 3 × 3 | вход в FC |
FC-128 → FC-num_classes | — | классификатор |
Всего свёрток = num_layers
(здесь 3).
Если убрать пулинг ('none'
), пространственная размерность остаётся 28 × 28, а число признаков сильно растёт (опасность-–-память).
№ | Что меняем | Значения | Фиксируем |
---|---|---|---|
1 | Размер ядра | 3 / 5 / 7 | 3 слоя, MaxPool, BN on, Dropout 0.2 |
2 | Число слоёв | 2 / 3 / 4 | k = 3, MaxPool, BN on, Dropout 0.2 |
3 | Тип пулинга | max / avg / none | 3 слоя, k = 3, BN on, Dropout 0.2 |
4 | BatchNorm | on / off | 3 слоя, k = 3, MaxPool, Dropout 0.2 |
5 | Dropout | 0 / 0.2 / 0.5 | 3 слоя, k = 3, MaxPool, BN on |
Каждая комбинация → своё имя эксперимента, собственная подпапка логов TensorBoard.
В лог пишутся: граф сети, лоссы/точности train-&-val, примеры прогнозов каждые 5 эпох.
Потери/…
– сходится ли модель, есть ли переобучение.Точность/…
– ищем пик вал-точности для подбора гиперпараметров.Совет: запускать TensorBoard прямо в Colab:
bash%load_ext tensorboard %tensorboard --logdir /content/drive/MyDrive/tensorboard_logs
torchinfo
), чтобы показать цену каждой конфигурации.С таким стендом вы свободно расширите эксперименты — например, добавите сгущение каналов («bottleneck»), замену ReLU на GELU, scheduler LR — достаточно изменить конструктор FlexibleCNN
и список гиперпараметров.
опиши подробнее как происходит преобразование размерностей при переходе через conv слои
Conv2d → (Pooling)
в FlexibleCNN
Обозначение | Формула/значение в коде | Комментарий |
---|---|---|
C_in | число входных каналов | 1 для серого изображения, дальше удваивается |
C_out | out_channels | начинается с 16 и умножается ×2 после каждого свёрточного блока |
stride (s) | 1 (по умолчанию) | сдвиг фильтра |
kernel (k) | kernel_size (3 / 5 / 7) | один и тот же во всех слоях |
padding (p) | k//2 | даёт «same»-свёртку (H_out = H_in) |
dilation (d) | 1 | увеличение receptive field не используется |
pool-stride | 2, если pooling_type!='none' | делит H и W пополам |
Для одного измерения (H или W):
Подставляя s=1
, d=1
, p=k//2
:
— поэтому изменение пространственного размера происходит только в Max/Average Pool.
textConv1: C 1 → 16 Conv2: C 16 → 32 Conv3: C 32 → 64 ⋯ C_L = 16 · 2^(L-1)
Удвоение заложено в цикле:
pythonout_channels = 16 for i in range(num_layers): Conv(in_channels, out_channels) … in_channels = out_channels out_channels *= 2
textБлок i: Conv(k,s=1,p=k//2) → (BN) → ReLU → (Pool-2) # если выбран max/avg → (Dropout)
Если pooling есть:
Если pooling = 'none':
Этап | C × H × W | Вычисление |
---|---|---|
Вход | 1 × 28 × 28 | — |
Conv1 | 16 × 28 × 28 | same-padding |
Pool1 | 16 × 14 × 14 | /2 |
Conv2 | 32 × 14 × 14 | — |
Pool2 | 32 × 7 × 7 | /2 |
Conv3 | 64 × 7 × 7 | — |
Pool3 | 64 × 3 × 3 | ⌊7/2⌋ = 3 |
Flatten | 576 | 64 × 3 × 3 |
FC-128 → FC-N | — | классификация |
Важно: если поставить
num_layers=4
с пулингом, последняя размерность станет 1 × 1.
Если отключить пулинг (pooling_type='none'
), то после 3 свёрток остаётся 28 × 28 и Flatten получает 64 × 28 × 28 = 50 176 признаков — модель становится тяжелее и требует больше памяти.
Пусть P = 1
, если в блоке есть пулинг, иначе 0. Тогда после L
слоёв:
Этой же формулой можно быстро проверить, не «съедут» ли размеры до 0 при большом L
.
Так шаг за шагом вычисляется полная форма тензора, а архитектура остаётся легко настраиваемой через пять гиперпараметров, которые вы варьируете в экспериментах.