Compare commits

...
25 Commits
Author SHA1 Message Date
Brian Bjarke Jensen 493da70ba4 updated to do prediction and print result 2024-07-28 23:22:40 +02:00
Brian Bjarke Jensen b415137e25 added method to instantiate from prediction dict 2024-07-28 23:19:51 +02:00
Brian Bjarke Jensen cead3d24c8 added from_tensor method 2024-07-28 23:13:15 +02:00
Brian Bjarke Jensen 0bfbb1920c fixed conversion of classes 2024-07-28 23:12:06 +02:00
Brian Bjarke Jensen 164102557e removed unused import 2024-07-28 23:10:15 +02:00
Brian Bjarke Jensen cf923a228d updated model name reference 2024-07-28 21:37:28 +02:00
Brian Bjarke Jensen 9f4a09a18c fixed definition of layers 2024-07-28 21:36:21 +02:00
Brian Bjarke Jensen 849b59e575 removed double connection to minio 2024-07-28 21:34:56 +02:00
Brian Bjarke Jensen 4a7e33b527 updated referenced model name 2024-07-28 21:32:16 +02:00
Brian Bjarke Jensen e5fad6d765 added function to generate and save new model 2024-07-28 21:31:48 +02:00
Brian Bjarke Jensen be6833ac6a added option to download weights 2024-07-28 21:25:24 +02:00
Brian Bjarke Jensen 6541a47668 added option to download resnet18 weights 2024-07-28 21:19:47 +02:00
Brian Bjarke Jensen 96ccdb5c90 moved file for clarity 2024-07-28 21:16:32 +02:00
Brian Bjarke Jensen 6e9c0a2626 fixed number of inputs 2024-07-28 21:13:51 +02:00
Brian Bjarke Jensen 396bf29f32 updated function name 2024-07-28 21:08:51 +02:00
Brian Bjarke Jensen 05774364fc renamed function for clarity 2024-07-28 21:08:27 +02:00
Brian Bjarke Jensen 74a9538feb renamed function for clarity 2024-07-28 21:05:15 +02:00
Brian Bjarke Jensen 9423c3378a fixed dockerfile syntax 2024-07-28 21:02:59 +02:00
Brian Bjarke Jensen e6fdbfe0b0 changed to not download pretrained weights for ResNet18 2024-07-28 21:01:19 +02:00
Brian Bjarke Jensen 6c354b23bd test run prediction on image 2024-07-27 21:41:29 +02:00
Brian Bjarke Jensen 3ad6c161b1 added new modules 2024-07-27 21:40:38 +02:00
Brian Bjarke Jensen a1ff6a6f33 moved for clarity 2024-07-27 21:40:17 +02:00
Brian Bjarke Jensen cf4f67d7a5 installed tqdm 2024-07-27 21:38:18 +02:00
Brian Bjarke Jensen 7c968db8ac renamed file for clarity 2024-07-27 21:18:39 +02:00
Brian Bjarke Jensen e656cd4e12 remove unneeded code for running in docker container 2024-07-27 21:08:47 +02:00
30 changed files with 217 additions and 190 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# build stage
FROM python:3.12-slim-bookworm as BUILDER
FROM python:3.12-slim-bookworm AS builder
# set environment variables
ENV PYTHONUNBUFFERED=1 \
+1 -1
View File
@@ -1,5 +1,5 @@
# build stage
FROM python:3.12-slim-bookworm as BUILDER
FROM python:3.12-slim-bookworm AS builder
# set environment variables
ENV PYTHONUNBUFFERED=1 \
+35 -12
View File
@@ -1,25 +1,48 @@
"""Main script to be run by service."""
from pathlib import Path
import json
import logging
from traceback import print_exc
from dotenv import load_dotenv
import torch
from models import VisualCommunicationModel
from torchinfo import summary
from utils import load_model
from tqdm import tqdm
from utils import DEVICE, VCDADataset, load_model
from shared.data_store import connect
from shared.data_store import connect_minio
from shared.dto import ModelData
from shared.utils import setup_logging
if __name__ == '__main__':
# load in env file
env_path = Path(__file__).parent.parent.parent / 'server.env'
load_dotenv(env_path)
# setup logging
setup_logging()
# connect to minio
minio_client = connect()
minio_client = connect_minio()
# instantiate model
model: VisualCommunicationModel = load_model(client=minio_client)
# show model weights
summary(model)
print('loaded model')
model.eval()
# setup dataset
dataset = VCDADataset(
minio_client=minio_client,
data_name_list=[
'02dbaf48d713e4e6d3a6b98fd2dc866e',
],
do_augment=False,
)
# make prediction
with torch.no_grad():
for i in tqdm(range(len(dataset))):
try:
# get image
image = dataset[i]
image = torch.unsqueeze(image, 0) # add artificial batch dimension
image = image.to(DEVICE)
# make prediction
pred: ModelData = model(image)
except Exception:
print_exc()
continue
else:
print(json.dumps(pred.model_dump(), indent=4))
logging.debug('finished')
+1 -1
View File
@@ -1 +1 @@
fb75db772ba8b280bdaf1774d5f73f58
2bbe2be17a663f4299657378ac5e993e
-2
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
-2
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
-2
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
-2
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
+8 -10
View File
@@ -1,26 +1,24 @@
from __future__ import annotations
import torch.nn as nn
from torch import nn
class FullyConnectedModel(nn.Module):
"""Fully connected layers model template for intrepreting feature space-
output from ResNet18 head."""
def __init__(self, num_out_features: int):
super().__init__()
# define layers
self.fc1 = nn.Linear(in_features=16*16*512, out_features=512)
self.fc1 = nn.Linear(in_features=512, out_features=128)
self.af1 = nn.ReLU()
self.fc2 = nn.Linear(in_features=512, out_features=128)
self.fc2 = nn.Linear(in_features=128, out_features=32)
self.af2 = nn.ReLU()
self.fc3 = nn.Linear(in_features=128, out_features=32)
self.af3 = nn.ReLU()
self.fc4 = nn.Linear(in_features=32, out_features=num_out_features)
self.fc3 = nn.Linear(in_features=32, out_features=num_out_features)
def forward(self, x):
"""Pass input through model."""
x = self.fc1(x)
x = self.af1(x)
x = self.fc2(x)
x = self.af2(x)
x = self.fc3(x)
x = self.af3(x)
x = self.fc4(x)
return x
-2
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
-2
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
-2
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
-2
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
-2
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
+7 -3
View File
@@ -5,11 +5,15 @@ import torchvision
class ResNet18Head(nn.Module):
def __init__(self):
def __init__(self, download_resnet_weights: bool = False):
super().__init__()
# copy out parts from ResNet18 with weights
if download_resnet_weights:
weights = torchvision.models.ResNet18_Weights.IMAGENET1K_V1
else:
weights = None
resnet18 = torchvision.models.resnet18(
weights=torchvision.models.ResNet18_Weights.IMAGENET1K_V1,
weights=weights,
)
# save relevant layers
self.conv1 = resnet18.conv1
@@ -21,7 +25,7 @@ class ResNet18Head(nn.Module):
self.layer3 = resnet18.layer3
self.layer4 = resnet18.layer4
self.avgpool = resnet18.avgpool
self.flat = nn.Flatten() # size 512
self.flat = nn.Flatten() # size 512
def forward(self, x):
x = self.conv1(x)
-2
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
+22 -86
View File
@@ -4,19 +4,7 @@ from __future__ import annotations
from torch import nn
from shared.dto import (
AngleData,
ContactData,
DistanceData,
FramingData,
InformationValueData,
ModalityColorData,
ModalityDepthData,
ModalityLightingData,
PointOfViewData,
SalienceData,
VisualSyntaxData,
)
from shared.dto import ModelData
from .angle import AngleTail
from .contact import ContactTail
@@ -35,10 +23,10 @@ from .visual_syntax import VisualSyntaxTail
class VisualCommunicationModel(nn.Module):
"""Visual communication model."""
def __init__(self):
def __init__(self, download_resnet_weights: bool = False):
super().__init__()
# store other models
self.resnet_head = ResNet18Head()
self.resnet_head = ResNet18Head(download_resnet_weights)
self.visual_syntax_tail = VisualSyntaxTail()
self.contact_tail = ContactTail()
self.angle_tail = AngleTail()
@@ -51,76 +39,24 @@ class VisualCommunicationModel(nn.Module):
self.framing_tail = FramingTail()
self.salience_tail = SalienceTail()
def forward(self, x):
def forward(self, x) -> ModelData:
"""Calculate model output on data."""
# generate visual representation
vis_rep = self.resnet_head(x)
# prepare result map
results = {}
# predict visual syntax
results['visual_syntax'] = VisualSyntaxData.from_list(
self.visual_syntax_tail(
vis_rep,
).cpu(),
)
# predict contact
results['contact'] = ContactData.from_list(
self.contact_tail(
vis_rep,
).cpu(),
)
# predict angle
results['angle'] = AngleData.from_list(
self.angle_tail(
vis_rep,
).cpu(),
)
# predict point of view
results['point_of_view'] = PointOfViewData.from_list(
self.point_of_view_tail(
vis_rep,
).cpu(),
)
# predict distance
results['distance'] = DistanceData.from_list(
self.distance_tail(
vis_rep,
).cpu(),
)
# predict modality lighting
results['modality_lighting'] = ModalityLightingData.from_list(
self.modality_lighting_tail(
vis_rep,
).cpu(),
)
# predict modality color
results['modality_color'] = ModalityColorData.from_list(
self.modality_color_tail(
vis_rep,
).cpu(),
)
# predict modality depth
results['modality_depth'] = ModalityDepthData.from_list(
self.modality_depth_tail(
vis_rep,
).cpu(),
)
# predict information value
results['information_value'] = InformationValueData.from_list(
self.information_value_tail(
vis_rep,
).cpu(),
)
# predict framing
results['framing'] = FramingData.from_list(
self.framing_tail(
vis_rep,
).cpu(),
)
# predict salience
results['salience'] = SalienceData.from_list(
self.salience_tail(
vis_rep,
).cpu(),
)
return results
features = self.resnet_head(x)
# make predictions
prediction_dict = {
'visual_syntax': self.visual_syntax_tail(features),
'contact': self.contact_tail(features),
'angle': self.angle_tail(features),
'point_of_view': self.point_of_view_tail(features),
'distance': self.distance_tail(features),
'modality_lighting': self.modality_lighting_tail(features),
'modality_color': self.modality_color_tail(features),
'modality_depth': self.modality_depth_tail(features),
'information_value': self.information_value_tail(features),
'framing': self.framing_tail(features),
'salience': self.salience_tail(features),
}
# convert to respective classes
data = ModelData.from_prediction_dict(prediction_dict)
return data
-2
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
+2 -1
View File
@@ -1 +1,2 @@
from .load_model import load_model
from .load_model import DEVICE, load_model
from .vcda_dataset import VCDADataset
@@ -6,7 +6,7 @@ import torch
import torch.nn as nn
from dataloader import VCDADataset # noqa: F401
from .models import VisualCommunicationModel
from ..models import VisualCommunicationModel
PRE_WARMUP_LR = 1e-10
POST_WARMUP_LR = 1e-5
@@ -2,6 +2,7 @@
import random
from minio import Minio
from PIL import Image
from torch import Tensor
from torch.utils.data import Dataset
@@ -15,7 +16,7 @@ from torchvision.transforms.functional import (
to_tensor,
)
from shared.data_store import connect, get_image
from shared.data_store import get_image
# resnet18 original normalization values
RESNET_NORMALIZE_MEAN = [0.485, 0.456, 0.406]
@@ -27,11 +28,13 @@ class VCDADataset(Dataset):
def __init__(
self,
minio_client: Minio,
data_name_list: list[str],
do_augment: bool = False,
random_annotations: bool = False,
):
super().__init__()
self.minio_client = minio_client
self.data_name_list = data_name_list
self.do_augment = do_augment
self.random_annotations = random_annotations
@@ -47,9 +50,6 @@ class VCDADataset(Dataset):
contrast=8e-2,
saturation=8e-2,
)
# connect to minio
minio_client = connect()
self.minio_client = minio_client
def __len__(self):
return len(self.data_name_list)
Generated
+26 -1
View File
@@ -2228,6 +2228,31 @@ type = "legacy"
url = "http://192.168.1.2:5001/index"
reference = "threadripper"
[[package]]
name = "tqdm"
version = "4.66.4"
description = "Fast, Extensible Progress Meter"
optional = false
python-versions = ">=3.7"
files = [
{file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"},
{file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[package.extras]
dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"]
notebook = ["ipywidgets (>=6)"]
slack = ["slack-sdk"]
telegram = ["requests"]
[package.source]
type = "legacy"
url = "http://192.168.1.2:5001/index"
reference = "threadripper"
[[package]]
name = "trio"
version = "0.25.1"
@@ -2481,4 +2506,4 @@ reference = "threadripper"
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "adcc6b2cdc33ee2aa56b419d6123e3d1553b08641cd81c22b187ecd0096812b1"
content-hash = "8caa4d24aec4c8cd5b1e726f2e00bc0cf8d865350aa5dc4282fc88d53d1b944c"
+1
View File
@@ -34,6 +34,7 @@ torch = "^2.2.1"
torchvision = "^0.17.1"
torchinfo = "^1.8.0"
minio = "^7.2.7"
tqdm = "^4.66.4"
[tool.poetry.group.shared.dependencies]
+1 -1
View File
@@ -1,4 +1,4 @@
from .connect import connect
from .connect_minio import connect_minio
from .delete import delete
from .get import get
from .get_image import get_image
@@ -6,7 +6,7 @@ import os
from minio import Minio
def connect() -> Minio:
def connect_minio() -> Minio:
"""Connect to MinIO server."""
# ensure necessary env vars available
env_var_list = [
+1 -2
View File
@@ -1,4 +1,3 @@
"""Database utils module content."""
from __future__ import annotations
from .connect import connect
from .connect_mongodb import connect_mongodb
@@ -1,8 +1,5 @@
"""
Definition of function to connect to database
using environment variables.
"""
from __future__ import annotations
"""Definition of function to connect to database using environment
variables."""
import logging
import os
@@ -11,7 +8,7 @@ from dotenv import load_dotenv
from pymongo import MongoClient
def connect():
def connect_mongodb():
"""Connect to MongoDB using env vars."""
# load env vars
load_dotenv()
+13 -13
View File
@@ -1,10 +1,9 @@
"""Definition of DataModel base class."""
from __future__ import annotations
import random
from pydantic import BaseModel
from pydantic import ValidationError
from pydantic import BaseModel, ValidationError
from torch import Tensor
class DataModel(BaseModel):
@@ -33,26 +32,27 @@ class DataModel(BaseModel):
raise ValidationError()
assert isinstance(option, str), 'option is not a string'
allowed_options_list = cls.list_fields()
assert option in allowed_options_list, \
f"{option} is not among allowed fields {allowed_options_list}"
assert (
option in allowed_options_list
), f"{option} is not among allowed fields {allowed_options_list}"
kwargs = {field: 0 for field in cls.list_fields()}
kwargs[option] = 1
return cls(**kwargs)
@classmethod
def from_list(cls, data_list: list[float]):
def from_tensor(cls, tensor: Tensor):
"""Instantiate from list of values."""
kwargs = {key: val for key, val in zip(cls.list_fields(), data_list)}
assert tensor.size(dim=0) == 1, f'tensor batch larger than 1: {tensor}'
data_list = [float(t.item()) for t in tensor[0]]
kwargs = dict(zip(cls.list_fields(), data_list))
return cls(**kwargs)
def __repr__(self) -> str:
model_dict = self.model_dump()
model_repr_str = f"{self.classname()}("
model_repr_str += ', '.join([
f"{field}={value:.3f}"
for field, value
in model_dict.items()
])
model_repr_str = f'{self.classname()}('
model_repr_str += ', '.join(
[f'{field}={value:.3f}' for field, value in model_dict.items()],
)
model_repr_str += ')'
return model_repr_str
+57 -24
View File
@@ -1,4 +1,5 @@
"""Definition of ModelData data model."""
from __future__ import annotations
from .angle import AngleData
@@ -17,6 +18,7 @@ from .visual_syntax import VisualSyntaxData
class ModelData(DataModel):
"""ModelData model for data IO with combined ML model."""
visual_syntax: VisualSyntaxData
contact: ContactData
angle: AngleData
@@ -34,8 +36,50 @@ class ModelData(DataModel):
"""Instantiate with random numbers."""
kwargs = {
field: field_info.annotation.from_random() # type: ignore
for field, field_info
in cls.model_fields.items()
for field, field_info in cls.model_fields.items()
}
return cls(**kwargs)
@classmethod
def from_prediction_dict(
cls,
prediction_dict: dict,
) -> ModelData:
"""Instantiate from prediction dictionary."""
kwargs = {
'visual_syntax': VisualSyntaxData.from_tensor(
prediction_dict['visual_syntax'],
),
'contact': ContactData.from_tensor(
prediction_dict['contact'],
),
'angle': AngleData.from_tensor(
prediction_dict['angle'],
),
'point_of_view': PointOfViewData.from_tensor(
prediction_dict['point_of_view'],
),
'distance': DistanceData.from_tensor(
prediction_dict['distance'],
),
'modality_lighting': ModalityLightingData.from_tensor(
prediction_dict['modality_lighting'],
),
'modality_color': ModalityColorData.from_tensor(
prediction_dict['modality_color'],
),
'modality_depth': ModalityDepthData.from_tensor(
prediction_dict['modality_depth'],
),
'information_value': InformationValueData.from_tensor(
prediction_dict['information_value'],
),
'framing': FramingData.from_tensor(
prediction_dict['framing'],
),
'salience': SalienceData.from_tensor(
prediction_dict['salience'],
),
}
return cls(**kwargs)
@@ -56,27 +100,16 @@ class ModelData(DataModel):
) -> ModelData:
"""Instantiate from annotation."""
kwargs = {
'visual_syntax': VisualSyntaxData
.from_choice(visual_syntax),
'contact': ContactData
.from_choice(contact),
'angle': AngleData
.from_choice(angle),
'point_of_view': PointOfViewData
.from_choice(point_of_view),
'distance': DistanceData
.from_choice(distance),
'modality_lighting': ModalityLightingData
.from_choice(modality_lighting),
'modality_color': ModalityColorData
.from_choice(modality_color),
'modality_depth': ModalityDepthData
.from_choice(modality_depth),
'information_value': InformationValueData
.from_choice(information_value),
'framing': FramingData
.from_choice(framing),
'salience': SalienceData
.from_choice(salience),
'visual_syntax': VisualSyntaxData.from_choice(visual_syntax),
'contact': ContactData.from_choice(contact),
'angle': AngleData.from_choice(angle),
'point_of_view': PointOfViewData.from_choice(point_of_view),
'distance': DistanceData.from_choice(distance),
'modality_lighting': ModalityLightingData.from_choice(modality_lighting),
'modality_color': ModalityColorData.from_choice(modality_color),
'modality_depth': ModalityDepthData.from_choice(modality_depth),
'information_value': InformationValueData.from_choice(information_value),
'framing': FramingData.from_choice(framing),
'salience': SalienceData.from_choice(salience),
}
return cls(**kwargs)
+32
View File
@@ -0,0 +1,32 @@
"""Definition of function to generate a new randomly initialized model and save
it in Minio datastore."""
from pathlib import Path
from dotenv import load_dotenv
from torchinfo import summary
from model.src.models import VisualCommunicationModel
from shared.data_store import connect_minio, put_model
from shared.utils import setup_logging
if __name__ == '__main__':
# load in env file
env_path = Path(__file__).parent.parent / 'server.env'
assert env_path.exists()
load_dotenv(env_path)
# setup logging
setup_logging()
# connect to minio
client = connect_minio()
# instantiate model
model = VisualCommunicationModel(download_resnet_weights=True)
# show model weights
summary(model)
# put buffer in minio bucket
hash_str = put_model(
client=client,
model=model,
)
print(f"hash string: {hash_str}")
print('saved to Minio')