Home » Создание VGG с нуля в PyTorch — пошаговое руководство
Создание VGG с нуля в PyTorch — пошаговое руководство

Создание VGG с нуля в PyTorch — пошаговое руководство

Если ты решил погрузиться в глубокое изучение computer vision и устал от готовых решений, то создание VGG с нуля в PyTorch — это то, что тебе нужно. Эта статья покажет тебе, как имплементировать знаменитую архитектуру VGG-16 самостоятельно, не полагаясь на готовые модели из torchvision. Мы разберём внутренности этой нейросети, научимся правильно настраивать её для работы на собственном VPS-сервере и оптимизировать под конкретные задачи. Особенно полезно будет для тех, кто хочет понять, как работают свёрточные нейросети изнутри и получить максимальный контроль над процессом обучения.

Как устроена архитектура VGG и почему она всё ещё актуальна

VGG (Visual Geometry Group) — это одна из самых элегантных и понятных архитектур CNN, которая доказала, что глубина сети напрямую влияет на её производительность. Основная идея VGG заключается в использовании только свёрточных слоёв 3×3 с stride=1 и max-pooling слоёв 2×2 с stride=2. Простота и систематичность — вот что делает VGG идеальной для изучения.

Архитектура VGG-16 состоит из:

  • 13 свёрточных слоёв, разделённых на 5 блоков
  • 5 max-pooling слоёв
  • 3 полносвязных слоя в конце
  • Функции активации ReLU после каждого свёрточного слоя

Вот базовая структура VGG-16:

Block 1: Conv(3→64) → Conv(64→64) → MaxPool
Block 2: Conv(64→128) → Conv(128→128) → MaxPool
Block 3: Conv(128→256) → Conv(256→256) → Conv(256→256) → MaxPool
Block 4: Conv(256→512) → Conv(256→512) → Conv(512→512) → MaxPool
Block 5: Conv(512→512) → Conv(512→512) → Conv(512→512) → MaxPool
Classifier: FC(25088→4096) → FC(4096→4096) → FC(4096→1000)

Пошаговая реализация VGG-16 в PyTorch

Теперь перейдём к практике. Для начала нужно развернуть окружение на сервере. Если у тебя нет подходящего сервера с GPU, можно взять VPS с видеокартой для экспериментов или выделенный сервер для серьёзных задач.

Первым делом установим зависимости:

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
pip install numpy matplotlib pillow

Теперь создадим класс VGG с нуля:

import torch
import torch.nn as nn
import torch.nn.functional as F

class VGG16(nn.Module):
    def __init__(self, num_classes=1000):
        super(VGG16, self).__init__()
        
        # Определяем конфигурацию слоёв
        self.features = nn.Sequential(
            # Block 1
            nn.Conv2d(3, 64, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 64, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            
            # Block 2
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(128, 128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            
            # Block 3
            nn.Conv2d(128, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            
            # Block 4
            nn.Conv2d(256, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            
            # Block 5
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=1, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        
        # Classifier
        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes),
        )
        
        # Инициализация весов
        self._initialize_weights()
    
    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)  # Flatten
        x = self.classifier(x)
        return x
    
    def _initialize_weights(self):
        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.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)

Более элегантная реализация через конфигурацию

Жёсткое кодирование слоёв — не самый гибкий подход. Лучше использовать конфигурационный подход, который позволит легко создавать разные варианты VGG:

class VGG(nn.Module):
    def __init__(self, features, num_classes=1000, init_weights=True):
        super(VGG, self).__init__()
        self.features = features
        self.avgpool = nn.AdaptiveAvgPool2d((7, 7))
        self.classifier = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(True),
            nn.Dropout(),
            nn.Linear(4096, num_classes),
        )
        if init_weights:
            self._initialize_weights()

    def forward(self, x):
        x = self.features(x)
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x

    def _initialize_weights(self):
        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.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)


def make_layers(cfg, batch_norm=False):
    layers = []
    in_channels = 3
    for v in cfg:
        if v == 'M':
            layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
        else:
            conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
            if batch_norm:
                layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]
            else:
                layers += [conv2d, nn.ReLU(inplace=True)]
            in_channels = v
    return nn.Sequential(*layers)

# Конфигурации для разных версий VGG
cfgs = {
    'VGG11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'VGG13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'VGG16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
    'VGG19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
}

def vgg16(num_classes=1000, batch_norm=False):
    return VGG(make_layers(cfgs['VGG16'], batch_norm=batch_norm), num_classes=num_classes)

Тестирование и валидация модели

Теперь давайте протестируем нашу реализацию и убедимся, что она работает корректно:

import torch
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
from torchvision.datasets import CIFAR10

# Создаём модель
model = vgg16(num_classes=10)  # Для CIFAR-10
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)

# Проверяем архитектуру
print(f"Модель имеет {sum(p.numel() for p in model.parameters())} параметров")
print(f"Обучаемых параметров: {sum(p.numel() for p in model.parameters() if p.requires_grad)}")

# Тест с рандомным тензором
test_input = torch.randn(1, 3, 224, 224).to(device)
with torch.no_grad():
    output = model(test_input)
    print(f"Выходной тензор: {output.shape}")

# Настройка для обучения
transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

# Загружаем данные
train_dataset = CIFAR10(root='./data', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)

# Настраиваем оптимизатор и функцию потерь
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=5e-4)

Оптимизация и практические советы

При работе с VGG есть несколько важных нюансов, которые стоит учесть:

Проблема Решение Результат
Большое количество параметров Использовать Dropout, BatchNorm, Weight Decay Уменьшение переобучения
Медленное обучение Learning Rate Scheduling, Adam вместо SGD Быстрая сходимость
Нехватка GPU памяти Градиентное накопление, Mixed Precision Обучение на слабом железе

Вот улучшенная версия с BatchNorm и дополнительными оптимизациями:

class ImprovedVGG16(nn.Module):
    def __init__(self, num_classes=1000, dropout_rate=0.5):
        super(ImprovedVGG16, self).__init__()
        
        self.features = nn.Sequential(
            # Block 1
            nn.Conv2d(3, 64, 3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 64, 3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2),
            
            # Block 2
            nn.Conv2d(64, 128, 3, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(inplace=True),
            nn.Conv2d(128, 128, 3, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2),
            
            # Block 3
            nn.Conv2d(128, 256, 3, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, 3, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, 3, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2),
            
            # Block 4
            nn.Conv2d(256, 512, 3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, 3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, 3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2),
            
            # Block 5
            nn.Conv2d(512, 512, 3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, 3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, 3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2),
        )
        
        self.classifier = nn.Sequential(
            nn.Dropout(dropout_rate),
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(dropout_rate),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes),
        )
        
        self._initialize_weights()
    
    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x
    
    def _initialize_weights(self):
        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.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)

Продвинутые техники и автоматизация

Для автоматизации процесса обучения можно создать конфигурационный файл и скрипт запуска:

# train_config.py
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import StepLR
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import argparse
import json
from pathlib import Path

class VGGTrainer:
    def __init__(self, config_path):
        with open(config_path, 'r') as f:
            self.config = json.load(f)
        
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
        self.model = self._build_model()
        self.criterion = nn.CrossEntropyLoss()
        self.optimizer = self._build_optimizer()
        self.scheduler = StepLR(self.optimizer, step_size=30, gamma=0.1)
        
    def _build_model(self):
        model = ImprovedVGG16(
            num_classes=self.config['num_classes'],
            dropout_rate=self.config['dropout_rate']
        )
        return model.to(self.device)
    
    def _build_optimizer(self):
        if self.config['optimizer'] == 'SGD':
            return optim.SGD(
                self.model.parameters(),
                lr=self.config['learning_rate'],
                momentum=self.config['momentum'],
                weight_decay=self.config['weight_decay']
            )
        elif self.config['optimizer'] == 'Adam':
            return optim.Adam(
                self.model.parameters(),
                lr=self.config['learning_rate'],
                weight_decay=self.config['weight_decay']
            )
    
    def train_epoch(self, train_loader):
        self.model.train()
        running_loss = 0.0
        correct = 0
        total = 0
        
        for batch_idx, (data, target) in enumerate(train_loader):
            data, target = data.to(self.device), target.to(self.device)
            
            self.optimizer.zero_grad()
            output = self.model(data)
            loss = self.criterion(output, target)
            loss.backward()
            self.optimizer.step()
            
            running_loss += loss.item()
            _, predicted = output.max(1)
            total += target.size(0)
            correct += predicted.eq(target).sum().item()
            
            if batch_idx % 100 == 0:
                print(f'Batch: {batch_idx}, Loss: {loss.item():.4f}, '
                      f'Acc: {100.*correct/total:.2f}%')
        
        return running_loss / len(train_loader), 100. * correct / total
    
    def validate(self, val_loader):
        self.model.eval()
        val_loss = 0
        correct = 0
        total = 0
        
        with torch.no_grad():
            for data, target in val_loader:
                data, target = data.to(self.device), target.to(self.device)
                output = self.model(data)
                val_loss += self.criterion(output, target).item()
                _, predicted = output.max(1)
                total += target.size(0)
                correct += predicted.eq(target).sum().item()
        
        val_loss /= len(val_loader)
        val_acc = 100. * correct / total
        
        return val_loss, val_acc
    
    def save_checkpoint(self, epoch, val_acc, save_path):
        torch.save({
            'epoch': epoch,
            'model_state_dict': self.model.state_dict(),
            'optimizer_state_dict': self.optimizer.state_dict(),
            'val_acc': val_acc,
            'config': self.config
        }, save_path)

# Пример конфигурации (config.json)
config_example = {
    "num_classes": 10,
    "dropout_rate": 0.5,
    "learning_rate": 0.001,
    "momentum": 0.9,
    "weight_decay": 5e-4,
    "optimizer": "SGD",
    "batch_size": 32,
    "epochs": 100,
    "save_dir": "./checkpoints/"
}

Интеграция с другими инструментами

VGG можно эффективно интегрировать с популярными ML-инструментами:

  • TensorBoard — для визуализации метрик обучения
  • Weights & Biases — для отслеживания экспериментов
  • MLflow — для управления жизненным циклом модели
  • ONNX — для экспорта модели в разные фреймворки

Пример интеграции с TensorBoard:

from torch.utils.tensorboard import SummaryWriter
import torchvision.utils as vutils

class VGGWithTensorBoard(VGGTrainer):
    def __init__(self, config_path, log_dir='./logs'):
        super().__init__(config_path)
        self.writer = SummaryWriter(log_dir)
        
    def train_epoch(self, train_loader, epoch):
        self.model.train()
        running_loss = 0.0
        correct = 0
        total = 0
        
        for batch_idx, (data, target) in enumerate(train_loader):
            data, target = data.to(self.device), target.to(self.device)
            
            self.optimizer.zero_grad()
            output = self.model(data)
            loss = self.criterion(output, target)
            loss.backward()
            self.optimizer.step()
            
            running_loss += loss.item()
            _, predicted = output.max(1)
            total += target.size(0)
            correct += predicted.eq(target).sum().item()
            
            # Логируем в TensorBoard
            global_step = epoch * len(train_loader) + batch_idx
            self.writer.add_scalar('Loss/Train', loss.item(), global_step)
            
            if batch_idx % 100 == 0:
                acc = 100. * correct / total
                self.writer.add_scalar('Accuracy/Train', acc, global_step)
                
                # Логируем изображения
                if batch_idx == 0:
                    img_grid = vutils.make_grid(data[:8], normalize=True)
                    self.writer.add_image('Training Images', img_grid, epoch)
        
        return running_loss / len(train_loader), 100. * correct / total

Сравнение с другими архитектурами

Давайте сравним VGG с другими популярными архитектурами CNN:

Архитектура Параметры (млн) Top-1 Accuracy Особенности
VGG-16 138 71.5% Простая, понятная архитектура
ResNet-50 25.6 76.1% Residual connections, глубже
EfficientNet-B0 5.3 77.1% Оптимизированная архитектура
MobileNet-V2 3.4 72.0% Мобильные устройства

Деплой и продакшн

Для развёртывания VGG-модели в продакшн можно использовать следующий подход:

# serve_model.py
import torch
import torch.nn as nn
from flask import Flask, request, jsonify
from PIL import Image
import torchvision.transforms as transforms
import io
import base64
import json

app = Flask(__name__)

# Загружаем модель
class ModelServer:
    def __init__(self, model_path):
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
        self.model = self._load_model(model_path)
        self.transform = transforms.Compose([
            transforms.Resize((224, 224)),
            transforms.ToTensor(),
            transforms.Normalize(mean=[0.485, 0.456, 0.406], 
                               std=[0.229, 0.224, 0.225])
        ])
        
    def _load_model(self, model_path):
        checkpoint = torch.load(model_path, map_location=self.device)
        model = ImprovedVGG16(num_classes=checkpoint['config']['num_classes'])
        model.load_state_dict(checkpoint['model_state_dict'])
        model.to(self.device)
        model.eval()
        return model
    
    def predict(self, image):
        with torch.no_grad():
            tensor = self.transform(image).unsqueeze(0).to(self.device)
            output = self.model(tensor)
            probabilities = torch.nn.functional.softmax(output[0], dim=0)
            return probabilities.cpu().numpy()

# Инициализация сервера
model_server = ModelServer('./checkpoints/best_model.pth')

@app.route('/predict', methods=['POST'])
def predict():
    try:
        # Получаем изображение
        image_data = request.json['image']
        image = Image.open(io.BytesIO(base64.b64decode(image_data)))
        
        # Предсказание
        predictions = model_server.predict(image)
        
        # Возвращаем результат
        return jsonify({
            'predictions': predictions.tolist(),
            'predicted_class': int(predictions.argmax())
        })
    
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/health', methods=['GET'])
def health():
    return jsonify({'status': 'healthy'})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Интересные факты и нестандартные применения

VGG можно использовать не только для классификации изображений:

  • Feature Extraction — использование промежуточных слоёв VGG для извлечения признаков
  • Style Transfer — VGG часто используется как perceptual loss в задачах переноса стиля
  • Object Detection — VGG может служить backbone для детекторов объектов
  • Medical Imaging — адаптация VGG для анализа медицинских изображений

Пример использования VGG для feature extraction:

class VGGFeatureExtractor(nn.Module):
    def __init__(self, model_path, layer_name='features.30'):
        super().__init__()
        checkpoint = torch.load(model_path)
        self.model = ImprovedVGG16(num_classes=checkpoint['config']['num_classes'])
        self.model.load_state_dict(checkpoint['model_state_dict'])
        self.model.eval()
        
        # Регистрируем хук для извлечения признаков
        self.features = {}
        self.model._modules[layer_name.split('.')[0]][int(layer_name.split('.')[1])].register_forward_hook(
            self._get_features(layer_name)
        )
    
    def _get_features(self, name):
        def hook(model, input, output):
            self.features[name] = output.detach()
        return hook
    
    def forward(self, x):
        with torch.no_grad():
            _ = self.model(x)
            return self.features

# Использование
feature_extractor = VGGFeatureExtractor('./checkpoints/best_model.pth')
features = feature_extractor(input_tensor)
print(f"Размер признаков: {features['features.30'].shape}")

Автоматизация и скрипты

Создадим bash-скрипт для автоматизации всего процесса:

#!/bin/bash
# train_vgg.sh

set -e

echo "🚀 Запуск обучения VGG-16"

# Проверяем наличие GPU
if command -v nvidia-smi &> /dev/null; then
    echo "✅ GPU найден: $(nvidia-smi --query-gpu=name --format=csv,noheader,nounits)"
else
    echo "⚠️ GPU не найден, используем CPU"
fi

# Создаём директории
mkdir -p checkpoints logs data

# Устанавливаем зависимости
echo "📦 Установка зависимостей..."
pip install torch torchvision torchaudio tensorboard pillow

# Запускаем обучение
echo "🎯 Начинаем обучение..."
python train_vgg.py --config config.json --resume checkpoints/last.pth

# Тестируем модель
echo "🧪 Тестирование модели..."
python test_vgg.py --model checkpoints/best_model.pth

# Запускаем TensorBoard
echo "📊 Запуск TensorBoard..."
tensorboard --logdir=logs --port=6006 &

echo "✅ Обучение завершено! TensorBoard доступен на http://localhost:6006"

Мониторинг и производительность

Для мониторинга производительности VGG создадим систему метрик:

import time
import psutil
import GPUtil
from collections import defaultdict

class PerformanceMonitor:
    def __init__(self):
        self.metrics = defaultdict(list)
        
    def start_timer(self, name):
        self.start_time = time.time()
        
    def end_timer(self, name):
        elapsed = time.time() - self.start_time
        self.metrics[name].append(elapsed)
        
    def log_system_metrics(self):
        # CPU и RAM
        cpu_percent = psutil.cpu_percent()
        memory = psutil.virtual_memory()
        
        # GPU
        try:
            gpus = GPUtil.getGPUs()
            if gpus:
                gpu = gpus[0]
                gpu_util = gpu.load * 100
                gpu_memory = gpu.memoryUtil * 100
            else:
                gpu_util = gpu_memory = 0
        except:
            gpu_util = gpu_memory = 0
            
        self.metrics['cpu_percent'].append(cpu_percent)
        self.metrics['memory_percent'].append(memory.percent)
        self.metrics['gpu_util'].append(gpu_util)
        self.metrics['gpu_memory'].append(gpu_memory)
        
    def get_average_metrics(self):
        return {name: sum(values) / len(values) 
                for name, values in self.metrics.items()}
    
    def print_summary(self):
        avg_metrics = self.get_average_metrics()
        print("\n📊 Сводка производительности:")
        print(f"CPU: {avg_metrics.get('cpu_percent', 0):.1f}%")
        print(f"RAM: {avg_metrics.get('memory_percent', 0):.1f}%")
        print(f"GPU Util: {avg_metrics.get('gpu_util', 0):.1f}%")
        print(f"GPU Memory: {avg_metrics.get('gpu_memory', 0):.1f}%")
        print(f"Время обучения эпохи: {avg_metrics.get('epoch_time', 0):.2f}s")

# Использование в тренировочном цикле
monitor = PerformanceMonitor()

for epoch in range(num_epochs):
    monitor.start_timer('epoch_time')
    monitor.log_system_metrics()
    
    # Обучение...
    train_loss, train_acc = trainer.train_epoch(train_loader, epoch)
    
    monitor.end_timer('epoch_time')
    
    if epoch % 10 == 0:
        monitor.print_summary()

Вывод и рекомендации

Создание VGG с нуля в PyTorch — это отличный способ глубоко понять принципы работы свёрточных нейронных сетей. Хотя VGG уже не является state-of-the-art архитектурой, она остаётся фундаментальной для понимания computer vision.

Когда использовать VGG:

  • Для обучения и понимания CNN
  • Как feature extractor в transfer learning
  • Для задач, где важна интерпретируемость
  • В качестве baseline для новых архитектур

Когда лучше выбрать альтернативы:

  • Для production-систем с ограниченными ресурсами — MobileNet
  • Для максимальной точности — EfficientNet или современные Transformer-based модели
  • Для быстрого обучения — ResNet с предобученными весами

Помни, что для серьёзных экспериментов с deep learning лучше использовать мощные серверы с GPU. Если твоя домашняя машина не справляется, рассмотри аренду специализированного сервера — это окупится временем и нервами.

Полезные ссылки для дальнейшего изучения:


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked