From 41c9010db2ae839261c4484ed48a6689fce33557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=93=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=B0=D1=80=D1=8C?= Date: Mon, 14 Sep 2026 11:05:56 +1000 Subject: [PATCH] first commit --- .gitignore | 4 + MyDataExamplePL.py | 91 ++++++++++ MyNNExamplePL.py | 229 ++++++++++++++++++++++++ README.md | 19 ++ SimpleMLP.py | 37 ++++ environment.yml | 427 ++++++++++++++++++++++++++++++++++++++++++++ environment_win.yml | 13 ++ ffn.py | 189 ++++++++++++++++++++ 8 files changed, 1009 insertions(+) create mode 100644 .gitignore create mode 100644 MyDataExamplePL.py create mode 100644 MyNNExamplePL.py create mode 100644 README.md create mode 100644 SimpleMLP.py create mode 100644 environment.yml create mode 100644 environment_win.yml create mode 100644 ffn.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0bb651d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +models/ +Datasets/ +test_images/ +train_logs/ \ No newline at end of file diff --git a/MyDataExamplePL.py b/MyDataExamplePL.py new file mode 100644 index 0000000..16d8389 --- /dev/null +++ b/MyDataExamplePL.py @@ -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)}) diff --git a/MyNNExamplePL.py b/MyNNExamplePL.py new file mode 100644 index 0000000..3d2920f --- /dev/null +++ b/MyNNExamplePL.py @@ -0,0 +1,229 @@ +# %% Импорт библиотек + +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()) +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/6_0.9460_0.1643.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%}") \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b1d9924 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +Установка с созданием новой среды: +```bash +conda env create -f environment.yml +``` + +Установка в уже созданную среду: +```bash +conda env update --file environment.yml --prune +``` + +Для переноса в другую ОС: +```bash +conda env export --from-history > environment.yml +``` + +Просто сохранить зависимости: +```bash +conda env export > environment.yml +``` \ No newline at end of file diff --git a/SimpleMLP.py b/SimpleMLP.py new file mode 100644 index 0000000..f8c62e6 --- /dev/null +++ b/SimpleMLP.py @@ -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) diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..c9487ab --- /dev/null +++ b/environment.yml @@ -0,0 +1,427 @@ +name: CV +channels: + - defaults + - conda-forge +dependencies: + - _libgcc_mutex=0.1=main + - _openmp_mutex=5.1=52_gnu + - aiodns=3.6.1=py312h06a4308_0 + - aiohappyeyeballs=2.6.2=py312h06a4308_0 + - aiohttp=3.14.3=py312h47b2149_0 + - aiosignal=1.4.0=py312h06a4308_0 + - alabaster=1.0.0=py312h06a4308_0 + - alsa-lib=1.2.15.3=h47b2149_0 + - anyio=4.12.1=py312h06a4308_0 + - aom=3.13.2=h664349e_0 + - argon2-cffi=25.1.0=py312h06a4308_0 + - argon2-cffi-bindings=25.1.0=py312hee96239_0 + - arrow=1.4.0=py312h06a4308_0 + - astroid=4.0.3=py312h06a4308_0 + - asttokens=3.0.1=py312h06a4308_0 + - async-lru=2.3.0=py312h06a4308_0 + - asyncssh=2.24.0=py312h06a4308_0 + - attrs=26.1.0=py312h0c820a0_0 + - autopep8=2.0.4=pyhd3eb1b0_0 + - babel=2.18.0=py312h06a4308_0 + - bcrypt=5.0.0=py312h498d7c9_0 + - beartype=0.22.9=py312h06a4308_0 + - beautifulsoup4=4.15.0=py312h06a4308_0 + - binaryornot=0.6.0=py312h06a4308_0 + - black=26.5.1=py312h06a4308_0 + - blas=1.0=openblas + - bleach=6.4.0=py312h06a4308_0 + - brotlicffi=1.2.0.1=py312h7354ed3_0 + - bzip2=1.0.8=h5eee18b_6 + - c-ares=1.34.7=hd44998d_0 + - ca-certificates=2026.8.13=h06a4308_0 + - cairo=1.18.4=h44eff21_0 + - cattrs=26.1.0=py312h06a4308_0 + - certifi=2026.7.22=py312h06a4308_0 + - cffi=2.1.1=py312h3b52fac_0 + - chardet=5.2.0=py312h06a4308_1 + - charset-normalizer=3.4.7=py312h06a4308_0 + - click=8.5.0=py312h06a4308_0 + - cloudpickle=3.1.2=py312h06a4308_0 + - comm=0.2.3=py312h06a4308_0 + - cookiecutter=2.7.1=py312h06a4308_0 + - cpython=3.12.14=py312hd8ed1ab_3 + - cryptography=50.0.0=py312hef7b95b_0 + - cuda-crt-tools=12.9.86=ha770c72_2 + - cuda-cudart=12.9.79=h5888daf_0 + - cuda-cudart_linux-64=12.9.79=h3f2d84a_0 + - cuda-cuobjdump=12.9.82=hffce074_1 + - cuda-cupti=12.9.79=h676940d_2 + - cuda-nvcc-tools=12.9.86=he02047a_2 + - cuda-nvdisasm=12.9.88=hffce074_1 + - cuda-nvrtc=12.9.86=hecca717_1 + - cuda-nvtx=12.9.79=hecca717_1 + - cuda-nvvm-tools=12.9.86=h4bc722e_2 + - cuda-version=12.9=h4f385c5_3 + - cudnn=9.25.1.1=ha9dd1bd_0 + - cusparselt=0.7.0.0=hcd2ec93_0 + - cyrus-sasl=2.1.28=h2f687bf_5 + - dav1d=1.5.3=h12c9f22_1 + - dbus=1.16.2=h5bd4931_0 + - debugpy=1.8.21=py312h7354ed3_0 + - decorator=5.3.1=py312h06a4308_0 + - defusedxml=0.7.1=pyhd3eb1b0_0 + - diff-match-patch=20200713=pyhd3eb1b0_0 + - dill=0.4.1=py312h06a4308_0 + - docstring-to-markdown=0.17=py312h06a4308_0 + - docutils=0.21.2=py312h06a4308_1 + - executing=2.2.1=py312h06a4308_1 + - expat=2.8.4=h7354ed3_0 + - filelock=3.32.5=pyhd8ed1ab_0 + - flake8=7.1.1=py312h06a4308_0 + - fmt=12.1.0=h44f220b_0 + - fontconfig=2.15.0=h2c49b7f_0 + - freetype=2.14.1=hf5b9546_0 + - fribidi=1.0.16=h10f3c28_1 + - frozenlist=1.8.0=py312hbdd6827_0 + - fsspec=2026.7.0=pyhd8ed1ab_0 + - gettext=0.25.1=h92eb808_0 + - gettext-tools=0.25.1=h6a67909_0 + - giflib=6.1.3=h621bd71_1 + - gmp=6.3.0=hfd2156b_3 + - gmpy2=2.3.1=py312h04c1168_0 + - graphite2=1.3.15=h9ba177b_0 + - h11=0.16.0=py312h06a4308_1 + - harfbuzz=12.3.0=h79d275a_1 + - httpcore=1.0.9=py312h06a4308_0 + - httpx=0.28.1=py312h06a4308_1 + - icu=73.1=h6a678d5_0 + - idna=3.18=py312h06a4308_0 + - imagesize=1.4.1=py312h06a4308_0 + - importlib-metadata=9.0.0=py312h06a4308_0 + - inflection=0.5.1=py312h06a4308_1 + - intervaltree=3.2.1=pyhd3eb1b0_0 + - ipykernel=6.31.0=py312h7040dfc_0 + - ipython=9.15.0=py312h06a4308_0 + - ipython_pygments_lexers=1.1.1=py312h06a4308_0 + - isort=8.0.1=py312h06a4308_1 + - jansson=2.15.0=hbcba0ee_0 + - jaraco.classes=3.4.0=py312h06a4308_0 + - jaraco.context=6.1.0=py312h06a4308_0 + - jaraco.functools=4.4.0=py312h06a4308_0 + - jedi=0.19.2=py312h06a4308_0 + - jeepney=0.7.1=pyhd3eb1b0_0 + - jellyfish=1.2.1=py312h498d7c9_0 + - jinja2=3.1.6=pyhcf101f3_1 + - jpeg=9f=he7e78df_1 + - json5=0.15.0=py312h06a4308_0 + - jsonschema=4.26.0=py312h06a4308_0 + - jsonschema-specifications=2025.9.1=py312h06a4308_0 + - jupyter-builder=1.2.0=py312h06a4308_0 + - jupyter-lsp=2.3.0=py312h06a4308_0 + - jupyter_client=8.10.0=py312h06a4308_0 + - jupyter_core=5.9.1=py312h06a4308_0 + - jupyter_events=0.12.1=py312h06a4308_0 + - jupyter_server=2.20.0=py312h06a4308_0 + - jupyter_server_terminals=0.5.4=py312h06a4308_0 + - jupyterlab=4.6.2=py312h06a4308_1 + - jupyterlab_pygments=0.3.0=py312h06a4308_0 + - jupyterlab_server=2.28.0=py312h06a4308_1 + - kagglehub=1.0.2=py312h06a4308_0 + - kagglesdk=0.1.35=py312h06a4308_0 + - keyring=25.7.0=py312h06a4308_0 + - krb5=1.20.1=h143b758_1 + - lcms2=2.19=he283960_0 + - ld_impl_linux-64=2.44=h9e0c5a2_3 + - lerc=4.1.0=h7354ed3_2 + - libabseil=20260107.0=cxx17_h6199ee8_0 + - libasprintf=0.25.1=hf2ab22a_0 + - libasprintf-devel=0.25.1=hf2ab22a_0 + - libavif=1.3.0=h2b90b00_1 + - libbrotlicommon=1.2.0=h32cd6e7_0 + - libbrotlidec=1.2.0=ha2c5f68_0 + - libbrotlienc=1.2.0=h2e96acb_0 + - libcap=2.78=h084b8d7_1 + - libcublas=12.9.2.10=h676940d_0 + - libcudnn=9.25.1.1=hfd19c1e_0 + - libcudnn-dev=9.25.1.1=ha9dd1bd_0 + - libcudss0=0.4.0.2=he55f5cd_2 + - libcufft=11.4.1.4=hecca717_1 + - libcufile=1.14.1.1=hbc026e6_1 + - libcups=2.4.19=h23114ae_0 + - libcurand=10.3.10.19=h676940d_1 + - libcurl=8.19.0=ha05a353_0 + - libcusolver=11.7.5.82=h676940d_2 + - libcusparse=12.5.10.65=hecca717_2 + - libdeflate=1.22=h5eee18b_0 + - libdrm=2.4.134=h9c74679_0 + - libedit=3.1.20230828=h5eee18b_0 + - libegl=1.7.0=h5eee18b_2 + - libev=4.33=h7f8727e_1 + - libevent=2.1.12=hdbd6064_1 + - libexpat=2.8.4=h7354ed3_0 + - libffi=3.4.8=h06d3fd0_3 + - libgcc=15.2.0=h69a1729_8 + - libgcc-ng=15.2.0=h166f726_8 + - libgettextpo=0.25.1=hf2ab22a_0 + - libgettextpo-devel=0.25.1=hf2ab22a_0 + - libgfortran=15.2.0=h69a702a_20 + - libgfortran5=15.2.0=h68bc16d_20 + - libgl=1.7.0=h5eee18b_2 + - libglib=2.88.3=ha16e27a_0 + - libglvnd=1.7.0=h5eee18b_2 + - libglx=1.7.0=h5eee18b_2 + - libgomp=15.2.0=h4751f2c_8 + - libiconv=1.18=h75a1612_0 + - libidn2=2.3.8=hf80d704_0 + - libkrb5=1.22.1=h6d2bf13_0 + - libllvm20=20.1.8=h465586c_0 + - libllvm22=22.1.2=hfb1d434_0 + - liblzma=5.8.2=hb03c661_0 + - libnghttp2=1.67.1=h697f920_0 + - libnl=3.11.0=hb03c661_1 + - libnvjitlink=12.9.86=hecca717_2 + - libnvjpeg=12.4.0.76=h7354ed3_0 + - libopenblas=0.3.34=pthreads_h94d23a6_0 + - libopengl=1.7.0=h5eee18b_2 + - libopenjpeg=2.5.4=hee96239_1 + - libopus=1.6.1=h9f10d21_1 + - libpciaccess=0.18=h5eee18b_0 + - libpng=1.6.56=h22898a0_0 + - libpq=17.9=h0cb448f_1 + - libprotobuf=6.33.5=h4435b4a_0 + - libre2-11=2025.11.05=h3356fce_1 + - libsodium=1.0.21=h83fc4cd_1 + - libspatialindex=2.1.0=hf7d4471_0 + - libssh2=1.11.1=hfbabe93_1 + - libstdcxx=15.2.0=h39759b7_8 + - libstdcxx-ng=15.2.0=hc03a8fd_8 + - libsystemd0=261.2=h6f4a2f1_0 + - libtiff=4.7.1=h029b1ac_0 + - libtirpc=1.3.7=h47b2149_0 + - libtorch=2.12.0=gpu_cuda129_h0198de7_200 + - libudev1=261.2=h6f4a2f1_0 + - libunistring=1.4.2=h34b0ebb_0 + - libuuid=1.41.5=h5eee18b_0 + - libuv=1.52.1=h280c20c_1 + - libvpx=1.16.0=h4b463fa_0 + - libwebp-base=1.6.0=hb7bb969_0 + - libxcb=1.17.0=h9b100fa_0 + - libxkbcommon=1.9.1=h69220b7_0 + - libxkbfile=1.1.0=hf54ef8a_2 + - libxml2=2.13.9=h2c43086_0 + - libxslt=1.1.43=h28b3bda_0 + - libzlib=1.3.2=h47b2149_0 + - lightning-utilities=0.15.3=pyhd8ed1ab_0 + - lmdb=1.0.0=hfe55579_0 + - lsprotocol=2025.0.0=py312h06a4308_1 + - lz4-c=1.9.4=h7354ed3_5 + - magma=2.10.0=hfa452ef_1 + - markdown-it-py=4.2.0=py312h06a4308_0 + - markupsafe=3.0.3=py312h47b2149_0 + - matplotlib-inline=0.2.2=py312h06a4308_0 + - mccabe=0.7.0=pyhd3eb1b0_0 + - mdurl=0.1.2=py312h06a4308_0 + - mesalib=25.1.5=h3583ad3_5 + - minizip=4.2.1=h7e49174_0 + - mistune=3.3.3=py312h06a4308_0 + - more-itertools=11.0.2=py312h06a4308_0 + - mpc=1.4.0=ha2cb11d_1 + - mpfr=4.2.2=ha2cb11d_1 + - mpmath=1.4.1=pyhd8ed1ab_0 + - multidict=6.7.1=py312h47b2149_0 + - mypy_extensions=1.1.0=py312h06a4308_0 + - mysql=9.3.0=hef88997_5 + - mysql-client=9.3.0=hf2c64df_5 + - mysql-common=9.3.0=h9e076cb_5 + - mysql-devel=9.3.0=h9e076cb_5 + - mysql-libs=9.3.0=h6ecde68_5 + - mysql-server=9.3.0=h284711e_5 + - nbclient=0.11.0=py312h06a4308_0 + - nbconvert=7.17.1=py312h06a4308_0 + - nbconvert-core=7.17.1=py312h06a4308_0 + - nbconvert-pandoc=7.17.1=py312h06a4308_0 + - nbformat=5.11.0=py312h06a4308_0 + - nccl=2.30.7.1=h4d09622_0 + - ncurses=6.6=hfaaeb4e_0 + - nest-asyncio=1.6.0=py312h06a4308_1 + - nest-asyncio2=1.7.2=py312h06a4308_0 + - networkx=3.6.1=pyhcf101f3_0 + - nomkl=1.0=h5ca1d4c_0 + - notebook-shim=0.2.4=py312h06a4308_1 + - nspr=4.39=h6e5897e_0 + - nss=3.125=hcf7f90d_0 + - numpy=2.5.2=py312h35deafb_0 + - numpy-base=2.5.2=py312h4bc27c9_0 + - numpydoc=1.10.0=py312h06a4308_0 + - openjpeg=2.5.4=h4e0627c_1 + - openldap=2.6.12=h007892f_1 + - openssl=3.6.4=h781a0a9_0 + - optree=0.20.0=py312ha5105d2_1 + - packaging=26.3=py312h06a4308_0 + - pandoc=3.8=h06a4308_0 + - pandocfilters=1.5.1=py312h06a4308_0 + - parso=0.8.7=py312h06a4308_0 + - pathspec=1.1.1=py312h06a4308_0 + - pcre2=10.46=hf426167_0 + - pexpect=4.9.0=py312h06a4308_1 + - pickleshare=0.7.5=pyhd3eb1b0_1003 + - pillow=12.2.0=py312haa37d31_0 + - pip=26.2.1=pyhc872135_0 + - pixman=0.46.4=h86ba9f7_1 + - platformdirs=4.11.0=py312h06a4308_0 + - pluggy=1.6.0=py312h06a4308_0 + - prometheus_client=0.24.1=py312h06a4308_0 + - prompt-toolkit=3.0.53=py312h06a4308_0 + - prompt_toolkit=3.0.53=hd3eb1b0_0 + - propcache=0.5.2=py312h47b2149_0 + - protobuf=6.33.5=py312h7354ed3_0 + - psutil=7.2.2=py312h47b2149_0 + - pthread-stubs=0.3=h47b2149_2 + - ptyprocess=0.7.0=pyhd3eb1b0_3 + - pure_eval=0.2.3=py312h06a4308_0 + - pybind11=3.0.4=py312h24d9097_0 + - pybind11-abi=11=hd3eb1b0_0 + - pybind11-global=3.0.4=py312h24d9097_0 + - pycares=4.10.0=py312ha137689_0 + - pycodestyle=2.12.1=py312h06a4308_0 + - pycparser=3.0=py312h06a4308_0 + - pydocstyle=6.3.0=py312h06a4308_1 + - pyflakes=3.2.0=py312h06a4308_0 + - pygithub=2.9.1=py312h06a4308_0 + - pygments=2.20.0=py312h06a4308_0 + - pyjwt=2.13.0=py312h06a4308_0 + - pylint=4.0.6=py312h06a4308_0 + - pylint-venv=3.0.3=py312h06a4308_0 + - pyls-spyder=0.4.0=pyhd3eb1b0_0 + - pynacl=1.6.2=py312h83fc4cd_1 + - pyqt=6.11.0=py312h0eb9b55_0 + - pyqt6-sip=13.11.1=py312h47b2149_0 + - pyqtwebengine=6.11.0=py312hdc71590_0 + - pysocks=1.7.1=py312h06a4308_1 + - python=3.12.14=hc5f7cf0_0 + - python-dateutil=2.9.0post0=py312h06a4308_2 + - python-fastjsonschema=2.22.1=py312h06a4308_0 + - python-json-logger=4.1.0=py312h06a4308_0 + - python-lsp-black=2.0.0=py312h06a4308_1 + - python-lsp-jsonrpc=1.1.2=pyhd3eb1b0_0 + - python-lsp-ruff=2.3.0=py312h06a4308_0 + - python-lsp-server=1.14.0=py312h7040dfc_1 + - python-slugify=8.0.4=py312h06a4308_0 + - python-tzdata=2026.3=pyhd3eb1b0_0 + - python_abi=3.12=4_cp312 + - pytokens=0.4.1=py312h47b2149_0 + - pytoolconfig=1.3.1=py312h06a4308_0 + - pytorch=2.12.0=gpu_cuda129_py312h95261ae_200 + - pytorch-lightning=2.6.5=pyhcf101f3_0 + - pyuca=1.2=py312h06a4308_1 + - pyxdg=0.28=py312h06a4308_0 + - pyyaml=6.0.3=py312h8a5da7c_1 + - pyzmq=27.1.0=py312hcf8288c_1 + - qdarkstyle=3.2.3=pyhd3eb1b0_0 + - qstylizer=0.2.4=py312h06a4308_0 + - qtawesome=1.4.2=py312h4333747_0 + - qtbase=6.11.0=h9201cad_0 + - qtconsole=5.7.2=py312h06a4308_0 + - qtdeclarative=6.11.0=h89ee561_1 + - qtpy=2.4.3=py312h06a4308_0 + - qtsvg=6.11.0=hbecefc2_1 + - qttools=6.11.0=h0af0a4d_1 + - qtwayland=6.11.0=h9a62c7d_0 + - qtwebchannel=6.11.0=h7ebffc9_1 + - qtwebengine=6.11.0=hb54882b_0 + - qtwebsockets=6.11.0=h03d4248_1 + - rdma-core=65.0=h7b4f2e6_0 + - re2=2025.11.05=h71b5c5c_1 + - readline=8.3=hc2a1206_0 + - referencing=0.37.0=py312h06a4308_0 + - requests=2.34.2=py312h06a4308_0 + - rfc3339-validator=0.1.4=py312h06a4308_0 + - rfc3986-validator=0.1.1=py312h06a4308_0 + - rich=15.0.0=py312h06a4308_0 + - roman-numerals=3.1.0=py312h06a4308_0 + - rope=1.14.0=py312h06a4308_0 + - rpds-py=0.28.0=py312h498d7c9_0 + - rtree=1.4.1=py312hd966e25_1 + - ruff=0.15.1=py312h6e1b9ff_0 + - secretstorage=3.5.0=py312h3e8c6aa_0 + - send2trash=1.8.3=py312h06a4308_0 + - setuptools=80.10.2=py312h06a4308_0 + - sip=6.15.1=py312he6c97cb_0 + - six=1.17.0=py312h06a4308_0 + - sleef=3.9.0=ha0421bc_0 + - snappy=1.2.2=h4bcf44c_1 + - snowballstemmer=3.1.1=py312h06a4308_0 + - sortedcontainers=2.4.0=pyhd3eb1b0_0 + - soupsieve=2.8.4=py312h06a4308_0 + - sphinx=9.1.0=py312h893fd2c_0 + - sphinxcontrib-applehelp=2.0.0=pyhd3eb1b0_1 + - sphinxcontrib-devhelp=2.0.0=pyhd3eb1b0_0 + - sphinxcontrib-htmlhelp=2.1.0=pyhd3eb1b0_0 + - sphinxcontrib-jsmath=1.0.1=pyhd3eb1b0_0 + - sphinxcontrib-qthelp=2.0.0=pyhd3eb1b0_1 + - sphinxcontrib-serializinghtml=2.0.0=pyhd3eb1b0_0 + - spirv-tools=2026.1=h24d9097_0 + - spyder=6.1.5=py312h06a4308_0 + - spyder-kernels=3.1.6=py312h7040dfc_0 + - sqlite=3.53.2=h795bf6d_0 + - stack_data=0.6.3=py312h06a4308_0 + - superqt=0.8.1=py312ha9b9ae4_0 + - sympy=1.14.0=pyh2585a3b_106 + - terminado=0.18.1=py312h06a4308_1 + - text-unidecode=1.3=pyhd3eb1b0_0 + - textdistance=4.6.3=py312he3bba80_1 + - three-merge=0.1.1=pyhd3eb1b0_0 + - tinycss2=1.5.1=py312h06a4308_0 + - tk=8.6.15=h54e0aa7_0 + - tomlkit=0.15.1=py312h06a4308_0 + - torchao=0.17.0=cuda129_py312hd0f1f67_2 + - torchmetrics=1.9.0=pyhd8ed1ab_0 + - torchvision=0.27.0=cuda129py312h054a6d6_100 + - tornado=6.5.8=py312h47b2149_0 + - tqdm=4.70.0=pyh8f84b5b_0 + - traitlets=5.15.0=py312h06a4308_0 + - triton=3.7.0=cuda129py312h338dc61_1 + - typing-extensions=4.16.0=py312h06a4308_0 + - typing_extensions=4.16.0=py312h06a4308_0 + - tzdata=2026c=he532380_0 + - ujson=5.13.0=py312h7354ed3_0 + - urllib3=2.7.0=py312h06a4308_0 + - uvloop=0.22.1=py312h939ff40_1 + - watchdog=6.0.0=py312h06a4308_0 + - wayland=1.24.0=hdac8c69_0 + - wcwidth=0.8.2=py312h06a4308_0 + - webencodings=0.6.1=py312h06a4308_0 + - websocket-client=1.8.0=py312h06a4308_0 + - whatthepatch=1.0.7=py312h06a4308_0 + - wheel=0.47.0=py312h06a4308_0 + - wurlitzer=3.1.1=py312h06a4308_0 + - xcb-util=0.4.1=h5eee18b_2 + - xcb-util-cursor=0.1.5=h5eee18b_0 + - xcb-util-image=0.4.0=h5eee18b_2 + - xcb-util-keysyms=0.4.1=h5eee18b_0 + - xcb-util-renderutil=0.3.10=h5eee18b_0 + - xcb-util-wm=0.4.2=h5eee18b_0 + - xkeyboard-config=2.48=h382ed1a_0 + - xorg-libice=1.1.2=h9b100fa_0 + - xorg-libsm=1.2.6=h9b100fa_0 + - xorg-libx11=1.8.12=h9b100fa_1 + - xorg-libxau=1.0.12=h9b100fa_0 + - xorg-libxcomposite=0.4.6=h9b100fa_2 + - xorg-libxdamage=1.1.6=h9b100fa_0 + - xorg-libxdmcp=1.1.5=h9b100fa_0 + - xorg-libxext=1.3.7=h1ce37a7_0 + - xorg-libxfixes=6.0.1=h9b100fa_0 + - xorg-libxi=1.8.2=h9b100fa_0 + - xorg-libxrandr=1.5.4=h9b100fa_0 + - xorg-libxrender=0.9.12=h9b100fa_0 + - xorg-libxshmfence=1.3.3=h9b100fa_0 + - xorg-libxtst=1.2.5=h9b100fa_3 + - xorg-libxxf86vm=1.1.6=h9b100fa_0 + - xorg-xorgproto=2024.1=h47b2149_2 + - xz=5.8.2=h448239c_0 + - yaml=0.2.5=h591646f_1 + - yapf=0.43.0=py312h06a4308_0 + - yarl=1.24.2=py312h47b2149_0 + - zeromq=4.3.5=hf801bfb_2 + - zipp=4.1.0=py312h06a4308_0 + - zlib=1.3.2=h47b2149_0 + - zstd=1.5.7=h11fc155_0 + - pip: + - pyqt6-qt6==6.11.2 + - split-folders==0.6.1 +prefix: /home/daniel/anaconda3/envs/CV diff --git a/environment_win.yml b/environment_win.yml new file mode 100644 index 0000000..573374b --- /dev/null +++ b/environment_win.yml @@ -0,0 +1,13 @@ +name: CV +channels: + - defaults +dependencies: + - python=3.12 + - jupyterlab + - spyder + - pytorch-lightning + - torchao + - beartype + - torchvision + - kagglehub +prefix: /home/daniel/anaconda3/envs/CV diff --git a/ffn.py b/ffn.py new file mode 100644 index 0000000..ef36b5b --- /dev/null +++ b/ffn.py @@ -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')}") \ No newline at end of file