Обнавлена структура проекта
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# %% Импорт библиотек
|
||||
|
||||
import kagglehub
|
||||
import splitfolders
|
||||
import pytorch_lightning as pl
|
||||
|
||||
from torch.utils.data import DataLoader
|
||||
from torchvision.datasets import ImageFolder
|
||||
from torchvision.transforms import v2 as transforms
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Optional, Callable, Any
|
||||
|
||||
# %% Скачивание набора данных
|
||||
|
||||
path = kagglehub.dataset_download("jehanbhathena/weather-dataset",
|
||||
output_dir="./Datasets",
|
||||
force_download=False)
|
||||
|
||||
# %% Разделение набора данных
|
||||
|
||||
splitfolders.ratio("./Datasets/weather-dataset",
|
||||
output="./Datasets/weather-dataset-split",
|
||||
ratio=(.8, .1, .1),
|
||||
group_prefix=None,
|
||||
move=False)
|
||||
|
||||
# %% Пример модуля для загрузки данных
|
||||
|
||||
TransformType = Optional[Callable[..., Any]]
|
||||
|
||||
class MyImageDataset(pl.LightningDataModule):
|
||||
def __init__(self,
|
||||
batch_size: int = 1,
|
||||
train_transform: TransformType = None,
|
||||
val_transform: TransformType = None,
|
||||
img_root: str = ""
|
||||
):
|
||||
|
||||
super().__init__()
|
||||
|
||||
self.batch_size = batch_size
|
||||
self.train_transform = train_transform
|
||||
self.val_transform = val_transform
|
||||
self.img_root = img_root
|
||||
|
||||
def setup(self, stage=None):
|
||||
self.train_set = ImageFolder(self.img_root / Path("train"),
|
||||
transform=self.train_transform)
|
||||
self.val_set = ImageFolder(self.img_root / Path("val"),
|
||||
transform=self.val_transform)
|
||||
self.test_set = ImageFolder(self.img_root / Path("test"),
|
||||
transform=self.val_transform)
|
||||
|
||||
def train_dataloader(self):
|
||||
return DataLoader(self.train_set, batch_size=self.batch_size,
|
||||
shuffle=True, num_workers=2)
|
||||
|
||||
def val_dataloader(self):
|
||||
return DataLoader(self.val_set, batch_size=self.batch_size,
|
||||
shuffle=False, num_workers=2)
|
||||
|
||||
def test_dataloader(self):
|
||||
return DataLoader(self.test_set, batch_size=self.batch_size,
|
||||
shuffle=False, num_workers=2)
|
||||
|
||||
# %% Создание трансформаций
|
||||
|
||||
transform_train = transforms.Compose([
|
||||
transforms.Resize((224, 224)),
|
||||
transforms.RandomHorizontalFlip(p=.5),
|
||||
transforms.ToTensor(),
|
||||
transforms.Normalize(mean=[0.485, 0.456, 0.406],
|
||||
std=[0.229, 0.224, 0.225])
|
||||
])
|
||||
|
||||
transform_test = transforms.Compose([
|
||||
transforms.Resize((224, 224)),
|
||||
transforms.ToTensor(),
|
||||
transforms.Normalize(mean=[0.485, 0.456, 0.406],
|
||||
std=[0.229, 0.224, 0.225])
|
||||
])
|
||||
|
||||
# %% Создание экземляра класса и вывод объектов класса
|
||||
|
||||
dm = MyImageDataset(train_transform=transform_train,
|
||||
val_transform=transform_test,
|
||||
img_root="./Datasets/weather-dataset-split")
|
||||
dm.setup()
|
||||
|
||||
print({k:v for k, v in enumerate(dm.test_set.classes)})
|
||||
@@ -0,0 +1,256 @@
|
||||
# %% Импорт библиотек
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import pytorch_lightning as pl
|
||||
|
||||
from torch.utils.data import DataLoader
|
||||
from torchmetrics import MetricCollection
|
||||
from torchvision import datasets, transforms
|
||||
from torchmetrics.classification import Accuracy, Precision, Recall, F1Score
|
||||
|
||||
# Автоматическое логирование результатов
|
||||
from pytorch_lightning.loggers import WandbLogger, TensorBoardLogger, CSVLogger
|
||||
# сохранение весов
|
||||
from pytorch_lightning.callbacks.model_checkpoint import ModelCheckpoint
|
||||
# автоматическое отслеживание lr
|
||||
from pytorch_lightning.callbacks import LearningRateMonitor
|
||||
# ранние остановки
|
||||
from pytorch_lightning.callbacks.early_stopping import EarlyStopping
|
||||
|
||||
# %% Гиперпараметры
|
||||
|
||||
num_epoch = 8
|
||||
batch_size = 64
|
||||
|
||||
# %% Создание модели PyTorch
|
||||
|
||||
class MyTorchCNNModel(nn.Module):
|
||||
def __init__(self,
|
||||
output_size: int = 10):
|
||||
super().__init__()
|
||||
|
||||
self.output_size = output_size
|
||||
|
||||
self.feature = nn.Sequential(
|
||||
nn.LazyConv2d(64, kernel_size=3, stride=1, padding=1),
|
||||
nn.BatchNorm2d(64),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),
|
||||
nn.BatchNorm2d(128),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1),
|
||||
nn.BatchNorm2d(256),
|
||||
nn.ReLU(),
|
||||
nn.MaxPool2d(kernel_size=2)
|
||||
)
|
||||
|
||||
self.head = nn.Conv2d(256, self.output_size, kernel_size=1)
|
||||
self.pool = nn.AdaptiveAvgPool2d((1, 1))
|
||||
|
||||
|
||||
def forward(self, image):
|
||||
x = self.feature(image)
|
||||
x = self.head(x)
|
||||
x = self.pool(x)
|
||||
|
||||
return x.flatten(1)
|
||||
|
||||
# %% Создание модели PyTorch Lightning
|
||||
|
||||
class MyNNExample(pl.LightningModule):
|
||||
def __init__(self,
|
||||
model: nn.Module,
|
||||
eta=3e-4):
|
||||
super().__init__()
|
||||
|
||||
self.save_hyperparameters(ignore=["model"])
|
||||
# device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
self.model = model
|
||||
|
||||
self.criterion = nn.CrossEntropyLoss()
|
||||
self.train_metrics = MetricCollection({
|
||||
"accuracy": Accuracy(task="multiclass",
|
||||
num_classes=model.output_size),
|
||||
"precision": Precision(task="multiclass",
|
||||
num_classes=model.output_size,
|
||||
average="macro"),
|
||||
"recall": Recall(task="multiclass",
|
||||
num_classes=model.output_size,
|
||||
average="macro"),
|
||||
"f1": F1Score(task="multiclass",
|
||||
num_classes=model.output_size,
|
||||
average="macro")
|
||||
})
|
||||
|
||||
self.val_metrics = self.train_metrics.clone()
|
||||
self.test_metrics = self.train_metrics.clone()
|
||||
|
||||
|
||||
def forward(self, image):
|
||||
return self.model(image)
|
||||
|
||||
|
||||
def _shared_step(self,
|
||||
sample,
|
||||
stage,
|
||||
metrics):
|
||||
x, y = sample
|
||||
logits = self.forward(x.to(torch.float32))
|
||||
preds = torch.argmax(logits, 1)
|
||||
loss = self.criterion(logits, y.to(torch.int64))
|
||||
|
||||
metrics.update(preds, y)
|
||||
self.log(f"{stage}_loss", loss,
|
||||
prog_bar=True, on_epoch=True, on_step=False)
|
||||
|
||||
return loss
|
||||
|
||||
def _log_metrics(self, stage, metrics):
|
||||
computed = metrics.compute()
|
||||
|
||||
for name, value in computed.items():
|
||||
self.log(f"{stage}_{name}", value, prog_bar=True)
|
||||
|
||||
metrics.reset()
|
||||
|
||||
def training_step(self, batch, batch_idx):
|
||||
return self._shared_step(batch, "train", self.train_metrics)
|
||||
|
||||
def on_train_epoch_end(self):
|
||||
self._log_metrics("train", self.train_metrics)
|
||||
|
||||
def validation_step(self, batch, batch_idx):
|
||||
return self._shared_step(batch, "val", self.val_metrics)
|
||||
|
||||
def on_validation_epoch_end(self):
|
||||
self._log_metrics("val", self.val_metrics)
|
||||
|
||||
def test_step(self, batch, batch_idx):
|
||||
return self._shared_step(batch, "test", self.test_metrics)
|
||||
|
||||
def on_test_epoch_end(self):
|
||||
self._log_metrics("test", self.test_metrics)
|
||||
|
||||
def configure_optimizers(self):
|
||||
optimizer = torch.optim.Adam(self.parameters(), lr=self.hparams.eta)
|
||||
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
|
||||
optimizer, patience=5, mode="min"
|
||||
)
|
||||
|
||||
return {
|
||||
"optimizer": optimizer,
|
||||
"lr_scheduler": {
|
||||
"scheduler": scheduler,
|
||||
"monitor": "val_loss",
|
||||
"interval": "epoch",
|
||||
}
|
||||
}
|
||||
|
||||
# %% Загрузка набора данных
|
||||
|
||||
image_transforms = transforms.Compose([
|
||||
transforms.ToTensor()
|
||||
])
|
||||
|
||||
train_dataset = datasets.MNIST(
|
||||
root="./Datasets",
|
||||
train=True,
|
||||
download=False, # Если скачен, то убрать
|
||||
transform=image_transforms
|
||||
)
|
||||
|
||||
test_dataset = datasets.MNIST(
|
||||
root="./Datasets",
|
||||
train=False,
|
||||
download=False, # Если скачен, то убрать
|
||||
transform=image_transforms
|
||||
)
|
||||
|
||||
train_loader = DataLoader(dataset=train_dataset,
|
||||
batch_size=batch_size,
|
||||
shuffle=True)
|
||||
|
||||
test_loader = DataLoader(dataset=test_dataset,
|
||||
batch_size=batch_size,
|
||||
shuffle=False)
|
||||
|
||||
# %% Запуск обучения
|
||||
|
||||
model = MyNNExample(model=MyTorchCNNModel())
|
||||
model.compile()
|
||||
|
||||
callbacks = [
|
||||
ModelCheckpoint(dirpath="models",
|
||||
filename="{epoch}_{val_accuracy:.4f}_{val_loss:.4f}",
|
||||
auto_insert_metric_name=False,
|
||||
save_last=True,
|
||||
save_top_k=1,
|
||||
monitor="val_loss",
|
||||
mode="min"),
|
||||
LearningRateMonitor(logging_interval="step"),
|
||||
EarlyStopping(monitor="val_loss",
|
||||
min_delta=2e-4,
|
||||
patience=10,
|
||||
verbose=False,
|
||||
mode="min")
|
||||
]
|
||||
|
||||
trainer = pl.Trainer(
|
||||
max_epochs=num_epoch,
|
||||
accelerator="gpu",
|
||||
devices=1,
|
||||
logger=CSVLogger("./train_logs", name="mnist_csv",
|
||||
version=None, flush_logs_every_n_steps=100),
|
||||
callbacks=callbacks
|
||||
)
|
||||
|
||||
trainer.fit(model=model,
|
||||
train_dataloaders=train_loader,
|
||||
val_dataloaders=test_loader)
|
||||
|
||||
# %% Инференс модели
|
||||
|
||||
checkpoint_path = "./models/7_0.9720_0.0958.ckpt"
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
model = MyNNExample.load_from_checkpoint(checkpoint_path,
|
||||
model=MyTorchCNNModel(output_size=10))
|
||||
model = model.to(device)
|
||||
model.eval()
|
||||
|
||||
image, true_label = test_dataset[42]
|
||||
image_batch = image.unsqueeze(0).to(device)
|
||||
|
||||
with torch.no_grad():
|
||||
logits = model(image_batch)
|
||||
probs = torch.softmax(logits, dim=1)
|
||||
pred_label = logits.argmax(dim=1).item()
|
||||
|
||||
print(f"Предсказание: {pred_label}")
|
||||
print(f"Истинная метка: {true_label}")
|
||||
print(f"Уверенность: {probs[0, pred_label].item():.2%}")
|
||||
|
||||
# %% Matlab export
|
||||
|
||||
pytorch_model = model.model
|
||||
pytorch_model.eval()
|
||||
pytorch_model = pytorch_model.cpu()
|
||||
|
||||
dummy_input = torch.randn(1, 1, 28, 28, dtype=torch.float32)
|
||||
|
||||
with torch.no_grad():
|
||||
_ = pytorch_model(dummy_input)
|
||||
|
||||
traced_model = torch.jit.trace(pytorch_model, dummy_input)
|
||||
|
||||
with torch.no_grad():
|
||||
y_original = pytorch_model(dummy_input)
|
||||
y_traced = traced_model(dummy_input)
|
||||
|
||||
print("Original:", y_original.shape)
|
||||
print("Traced:", y_traced.shape)
|
||||
print("Max difference:", torch.max(torch.abs(y_original - y_traced)).item())
|
||||
|
||||
traced_model.save("./models/my_model_matlab.pt")
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# %% Импорт библиотек
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
# %% Входные переменные
|
||||
input_ = 32*32
|
||||
hidden_1 = 5
|
||||
hidden_2 = 12
|
||||
output = 2
|
||||
learning_rate = 1e-3
|
||||
|
||||
weights_1 = torch.rand(size=(input_, hidden_1), requires_grad=True)
|
||||
weights_2 = torch.rand(size=(hidden_1, hidden_2), requires_grad=True)
|
||||
weights_3 = torch.rand(size=(hidden_2, output), requires_grad=True)
|
||||
|
||||
random_input = torch.rand(1, input_)
|
||||
|
||||
true_label = torch.zeros(1, output)
|
||||
true_label[0][0] = 1
|
||||
# %% Цикл обучения
|
||||
criterion = nn.CrossEntropyLoss()
|
||||
optimizer = torch.optim.SGD([weights_1, weights_2, weights_3], lr=learning_rate)
|
||||
|
||||
hidden_layer_1 = random_input @ weights_1
|
||||
hidden_layer_2 = hidden_layer_1 @ weights_2
|
||||
output_layer = hidden_layer_2 @ weights_3
|
||||
|
||||
loss = criterion(output_layer, true_label)
|
||||
print(loss)
|
||||
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
print(weights_1._grad)
|
||||
print(weights_2._grad)
|
||||
print(weights_3._grad)
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
# %% Импорт библиотек
|
||||
|
||||
import tqdm
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from torch.utils.data import DataLoader
|
||||
from torchmetrics import MetricCollection
|
||||
from torchvision import datasets, transforms
|
||||
from torchmetrics.classification import Accuracy, Precision, Recall, F1Score
|
||||
|
||||
from PIL import Image
|
||||
from beartype import beartype
|
||||
|
||||
# %% Гиперпараметры
|
||||
num_epoch = 8
|
||||
batch_size = 64
|
||||
learning_rate = 1e-3
|
||||
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
|
||||
# %% Трансформация данных
|
||||
|
||||
# Resize(size) — изменяет размер изображения.
|
||||
# Grayscale() — преобразование изображения в оттенки серого.
|
||||
# ToTensor() — преобразует изображение PIL или NumPy-массив в PyTorch-тензор.
|
||||
# Normalize(mean, std) — нормализация тензора изображения.
|
||||
# GaussianBlur(kernel_size) — размытие по Гауссу.
|
||||
|
||||
|
||||
image_transforms = transforms.Compose([
|
||||
transforms.ToTensor()
|
||||
])
|
||||
|
||||
# %% Загрузка набора данных
|
||||
|
||||
train_dataset = datasets.MNIST(
|
||||
root="./MNIST",
|
||||
train=True,
|
||||
download=False, # Если скачен, то убрать
|
||||
transform=image_transforms
|
||||
)
|
||||
|
||||
test_dataset = datasets.MNIST(
|
||||
root="./MNIST",
|
||||
train=False,
|
||||
download=False, # Если скачен, то убрать
|
||||
transform=image_transforms
|
||||
)
|
||||
|
||||
train_loader = DataLoader(dataset=train_dataset,
|
||||
batch_size=batch_size,
|
||||
shuffle=True)
|
||||
|
||||
test_loader = DataLoader(dataset=test_dataset,
|
||||
batch_size=batch_size,
|
||||
shuffle=False)
|
||||
# %% Структура модели
|
||||
|
||||
class FFN(nn.Module):
|
||||
|
||||
@beartype
|
||||
def __init__(self,
|
||||
input_size: int,
|
||||
output_size: int):
|
||||
super().__init__()
|
||||
|
||||
self.input_layer = nn.Linear(input_size, 32*32)
|
||||
self.hidden_layer_1 = nn.Linear(32*32, 32*32)
|
||||
self.hidden_layer_2 = nn.Linear(32*32, 32*32)
|
||||
self.output_layer = nn.Linear(32*32, output_size)
|
||||
|
||||
@beartype
|
||||
def forward(self, image: torch.Tensor) -> torch.Tensor:
|
||||
x = image.flatten(start_dim=1) # batch_size x 28*28
|
||||
x = torch.relu(self.input_layer(x))
|
||||
x = torch.relu(self.hidden_layer_1(x))
|
||||
x = torch.relu(self.hidden_layer_2(x))
|
||||
x = self.output_layer(x)
|
||||
|
||||
return x # batch_size x 10
|
||||
|
||||
model = FFN(input_size=28*28,
|
||||
output_size=10)
|
||||
model.to(device)
|
||||
|
||||
# %% Метрики
|
||||
|
||||
train_metrics = MetricCollection({
|
||||
"accuracy": Accuracy(task="multiclass", num_classes=10),
|
||||
"precision": Precision(task="multiclass", num_classes=10, average="macro"),
|
||||
"recall": Recall(task="multiclass", num_classes=10, average="macro"),
|
||||
"f1": F1Score(task="multiclass", num_classes=10, average="macro")
|
||||
}).to(device)
|
||||
|
||||
test_metrics = MetricCollection({
|
||||
"accuracy": Accuracy(task="multiclass", num_classes=10),
|
||||
"precision": Precision(task="multiclass", num_classes=10, average="macro"),
|
||||
"recall": Recall(task="multiclass", num_classes=10, average="macro"),
|
||||
"f1": F1Score(task="multiclass", num_classes=10, average="macro")
|
||||
}).to(device)
|
||||
|
||||
# %% Оптимизаторы
|
||||
|
||||
criterion = nn.CrossEntropyLoss()
|
||||
optim = torch.optim.Adam(model.parameters(), lr=learning_rate)
|
||||
# %% Тренировочный цикл
|
||||
|
||||
for epoch in tqdm.tqdm(range(num_epoch)):
|
||||
model.train()
|
||||
running_loss = 0
|
||||
|
||||
for data, target in train_loader:
|
||||
data, target = data.to(device), target.to(device)
|
||||
|
||||
optim.zero_grad()
|
||||
outputs = model(data)
|
||||
loss = criterion(outputs, target)
|
||||
loss.backward()
|
||||
running_loss += loss.item()
|
||||
optim.step()
|
||||
|
||||
preds = outputs.argmax(dim=1)
|
||||
train_metrics.update(preds, target)
|
||||
|
||||
results = train_metrics.compute()
|
||||
print(f"Epoch: {epoch+1}")
|
||||
print("=== TRAIN ===")
|
||||
print(f" Loss: {(running_loss/len(train_loader)):.4f}")
|
||||
print(f" Accuracy: {results['accuracy']:.4f}")
|
||||
print(f" Precision: {results['precision']:.4f}")
|
||||
print(f" Recall: {results['recall']:.4f}")
|
||||
print(f" F1: {results['f1']:.4f}")
|
||||
print()
|
||||
|
||||
train_metrics.reset()
|
||||
|
||||
model.eval()
|
||||
with torch.no_grad():
|
||||
test_loss = 0
|
||||
for data, target in test_loader:
|
||||
data, target = data.to(device), target.to(device)
|
||||
|
||||
outputs = model(data)
|
||||
loss = criterion(outputs, target)
|
||||
test_loss += loss.item()
|
||||
preds = outputs.argmax(dim=1)
|
||||
test_metrics.update(preds, target)
|
||||
|
||||
results = test_metrics.compute()
|
||||
|
||||
print("=== TEST ===")
|
||||
print(f" Loss: {(test_loss/len(train_loader)):.4f}")
|
||||
print(f" Accuracy: {results['accuracy']:.4f}")
|
||||
print(f" Precision: {results['precision']:.4f}")
|
||||
print(f" Recall: {results['recall']:.4f}")
|
||||
print(f" F1: {results['f1']:.4f}")
|
||||
print()
|
||||
|
||||
test_metrics.reset()
|
||||
|
||||
|
||||
torch.save(model.state_dict(), "./models/mnist_model.pth")
|
||||
# %% Инференс модели
|
||||
|
||||
image_transforms = transforms.Compose([
|
||||
transforms.Grayscale(),
|
||||
transforms.Resize((28,28)),
|
||||
transforms.ToTensor()
|
||||
])
|
||||
|
||||
model.eval()
|
||||
model.load_state_dict(torch.load("./models/mnist_model.pth"))
|
||||
|
||||
def predict(image_path):
|
||||
image = Image.open(image_path)
|
||||
image = image_transforms(image)
|
||||
image = image.unsqueeze(0)
|
||||
image = image.to(device)
|
||||
|
||||
with torch.no_grad():
|
||||
output = model(image)
|
||||
|
||||
pred = output.argmax(dim=1).item()
|
||||
|
||||
return pred
|
||||
|
||||
print(f"Predicted label: {predict('./test_images/mnist/3.png')}")
|
||||
print(f"Predicted label: {predict('./test_images/mnist/5.png')}")
|
||||
print(f"Predicted label: {predict('./test_images/mnist/6.png')}")
|
||||
Reference in New Issue
Block a user