Skip to content

Commit 0305d46

Browse files
authored
Merge branch 'main' into usps-inputshape
2 parents 3254c29 + 725cb0b commit 0305d46

33 files changed

+3628
-543
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@ jobs:
2525

2626
- name: Run tests
2727
run: |
28-
PYTHONPATH=. pytest tests
28+
PYTHONPATH=. pytest 4
2929
shell: bash -el {0}

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ wandb/*
99
wandb_api.py
1010

1111
#Magnus specific
12-
docker/*
1312
job*
13+
env2/*
14+
ruffian.sh
15+
localtest.sh
1416

1517
# Byte-compiled / optimized / DLL files
1618
__pycache__/
@@ -150,6 +152,7 @@ ENV/
150152
env.bak/
151153
venv.bak/
152154

155+
153156
# Spyder project settings
154157
.spyderproject
155158
.spyproject

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.12

doc/Magnus_page.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
Magnus Individual Task
2+
======================
3+
4+
# Magnus Størdal Individual Task
5+
6+
## Task overview
7+
In addition to the overall task, I was tasked to implement a three layer linear network, a dataset loader for the SVHN dataset, and a entropy metric.
8+
9+
## Network Implementation In-Depth
10+
For the network part I was tasked with making a three-layer linear network where each layer conists of 133 neurons. This is a fairly straightforward implementation where we make a custom class which inherits from the PyTorch Module class. This allows for our class to have two methods. The __init__ method and a forward method. When we make an instance of the class we'll be able to call the instance like we would call a function, and have it run the forward method.
11+
12+
The network is initialized with the following metrics:
13+
* image_shape
14+
* num_classes
15+
* nr_channels
16+
17+
The num_classes argument is used to define the number of output neurons. Each dataset has somewhere between 5 and 10 classes, and as such there isn't a single output size works well.
18+
19+
As each layer is a linear layer we need to initialize the network with respect to the image size. We are working with datasets which are either greyscale or color images, and can be any height and width. Therefore we have the image_shape argument, which provides the information on the image height and width, and the nr_channels argument which states the number of channels we use. With these values we initialize the first layer accordingly, that is: height * width * channels inputsize.
20+
21+
The forward method in this class has an assertion making sure the input has four channels, they being batch size, channels, height and width.
22+
Each input is flattened over the channel, height and width channels. Then they are passed through each layer and the resulting logits are returned.
23+
24+
25+
## SVHN Dataset In-Depth
26+
27+
28+
29+
30+
## Entropy Metric In-Depth
31+
32+
The EntropyPrediction class' main job is to take some inputs and return the Shannon Entropy metric of those inputs. The class has four methods with the following jobs:
33+
* __init__ : Initialize the class.
34+
* __call__ : Main method which is used to calculate and store the batchwise shannon entropy.
35+
* __returnmetric__ : Returns the collected metric.
36+
* __reset__ : Removes all the stored values up until that point. Readies the instance for storing values from a new epoch.
37+
38+
The class is initialized with a single parameter called "averages". This is inspired from other PyTorch and NumPy implementations and controlls how values from different batches or within batches will be combined. The __init__ method checks the value of this argument with an assertion, which must be one of three string. We only allow "mean", "sum" and "none" as methods of combining the different entropy values. We'll come back to the specifics here.
39+
Furthermore, this method will also store the different Shannon Entropy values as we pass values into the __call__ method.
40+
41+
In __call__ we get both true labels and model logit scores for each sample in the batch as input. We're calculating Shannon Entropy, not KL-divergence, so the true labels aren't needed.
42+
With permission I've used the scipy implementation to calculate entropy here. We apply a softmax over the logit values, then calculate the Shannon Entropy, and make sure to remove any NaN or Inf values which might arise from a perfect guess/distribution.
43+
44+
Next we have the __returnmetric__ method which is used to retrive the stored metric. Here the averages argument comes into play.
45+
Depending on what has been chosen as the averaging metric when initializing the class, one of the following operations will be applied to the stored values:
46+
* Mean: Calculate the mean of the stored entropy values.
47+
* Sum: Sum the stored entropy values.
48+
* None: Do nothing with the stored entropy values.
49+
Then the value(s) are returned.
50+
51+
Lastly we have the __reset__ method which simply emptied the variable which stores the entropy values to prepare it for the next epoch.

doc/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@ culpa qui officia deserunt mollit anim id est laborum.
1212
:caption: Some caption
1313

1414
about.md
15+
Magnus_page.md
1516
:::
17+
18+
Individual Sections
19+
===================

docker/Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM pytorch/pytorch:2.4.1-cuda11.8-cudnn9-runtime
2+
WORKDIR /tmp/
3+
COPY requirements.txt .
4+
RUN apt-get update
5+
RUN pip install -r requirements.txt
6+
RUN apt-get install ffmpeg libsm6 libxext6 -y git
7+
RUN pip install ftfy regex tqdm

docker/createdocker.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/sh
2+
3+
sudo chmod 666 /var/run/docker.sock
4+
5+
docker build docker -t seilmast/colabexam:latest
6+
docker push seilmast/colabexam:latest

docker/requirements.txt

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
annotated-types==0.7.0
2+
asttokens==3.0.0
3+
certifi==2024.12.14
4+
charset-normalizer==3.4.1
5+
click==8.1.8
6+
comm==0.2.2
7+
debugpy==1.8.12
8+
decorator==5.1.1
9+
docker-pycreds==0.4.0
10+
executing==2.2.0
11+
filelock==3.13.1
12+
fsspec==2024.6.1
13+
gitdb==4.0.12
14+
GitPython==3.1.44
15+
h5py==3.12.1
16+
idna==3.10
17+
iniconfig==2.0.0
18+
ipykernel==6.29.5
19+
ipython==8.31.0
20+
jedi==0.19.2
21+
Jinja2==3.1.4
22+
jupyter_client==8.6.3
23+
jupyter_core==5.7.2
24+
MarkupSafe==2.1.5
25+
matplotlib-inline==0.1.7
26+
mpmath==1.3.0
27+
nest-asyncio==1.6.0
28+
networkx==3.3
29+
numpy==2.1.2
30+
packaging==24.2
31+
parso==0.8.4
32+
pexpect==4.9.0
33+
pillow==11.0.0
34+
platformdirs==4.3.6
35+
pluggy==1.5.0
36+
prompt_toolkit==3.0.50
37+
protobuf==5.29.3
38+
psutil==6.1.1
39+
ptyprocess==0.7.0
40+
pure_eval==0.2.3
41+
pydantic==2.10.6
42+
pydantic_core==2.27.2
43+
Pygments==2.19.1
44+
pytest==8.3.4
45+
python-dateutil==2.9.0.post0
46+
PyYAML==6.0.2
47+
pyzmq==26.2.1
48+
requests==2.32.3
49+
scipy==1.15.1
50+
sentry-sdk==2.20.0
51+
setproctitle==1.3.4
52+
six==1.17.0
53+
smmap==5.0.2
54+
stack-data==0.6.3
55+
sympy==1.13.1
56+
tornado==6.4.2
57+
traitlets==5.14.3
58+
typing_extensions==4.12.2
59+
urllib3==2.3.0
60+
wandb==0.19.5
61+
wcwidth==0.2.13

environment.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ dependencies:
99
- sphinx-autobuild
1010
- sphinx-rtd-theme
1111
- pip
12-
- h5py
12+
- h5py==3.12.1
13+
- hdf5==1.14.4
1314
- black
1415
- isort
1516
- jupyterlab
@@ -20,6 +21,8 @@ dependencies:
2021
- scalene
2122
- tqdm
2223
- scipy
24+
- wandb
25+
- scikit-learn
2326
- pip:
2427
- torch
2528
- torchvision

main.py

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from torch.utils.data import DataLoader
66
from torchvision import transforms
77
from tqdm import tqdm
8+
from wandb_api import WANDB_API
89

910
from utils import MetricWrapper, createfolders, get_args, load_data, load_model
1011

@@ -29,7 +30,9 @@ def main():
2930

3031
device = args.device
3132

33+
3234
if "usps" in args.dataset.lower():
35+
3336
transform = transforms.Compose(
3437
[
3538
transforms.Resize((28, 28)),
@@ -39,23 +42,29 @@ def main():
3942
else:
4043
transform = transforms.Compose([transforms.ToTensor()])
4144

42-
# Dataset
43-
traindata = load_data(
44-
args.dataset,
45-
train=True,
46-
data_path=args.datafolder,
47-
download=args.download_data,
48-
transform=transform,
49-
)
50-
validata = load_data(
45+
traindata, validata, testdata = load_data(
5146
args.dataset,
52-
train=False,
53-
data_path=args.datafolder,
54-
download=args.download_data,
47+
data_dir=args.datafolder,
5548
transform=transform,
49+
val_size=args.val_size,
50+
5651
)
5752

58-
metrics = MetricWrapper(*args.metric, num_classes=traindata.num_classes)
53+
train_metrics = MetricWrapper(
54+
*args.metric,
55+
num_classes=traindata.num_classes,
56+
macro_averaging=args.macro_averaging,
57+
)
58+
val_metrics = MetricWrapper(
59+
*args.metric,
60+
num_classes=traindata.num_classes,
61+
macro_averaging=args.macro_averaging,
62+
)
63+
test_metrics = MetricWrapper(
64+
*args.metric,
65+
num_classes=traindata.num_classes,
66+
macro_averaging=args.macro_averaging,
67+
)
5968

6069
# Find the shape of the data, if is 2D, add a channel dimension
6170
data_shape = traindata[0][0].shape
@@ -80,6 +89,9 @@ def main():
8089
valiloader = DataLoader(
8190
validata, batch_size=args.batchsize, shuffle=False, pin_memory=True
8291
)
92+
testloader = DataLoader(
93+
testdata, batch_size=args.batchsize, shuffle=False, pin_memory=True
94+
)
8395

8496
criterion = nn.CrossEntropyLoss()
8597
optimizer = th.optim.Adam(model.parameters(), lr=args.learning_rate)
@@ -104,22 +116,23 @@ def main():
104116
optimizer.step()
105117
optimizer.zero_grad(set_to_none=True)
106118

107-
metrics(y, logits)
119+
train_metrics(y, logits)
108120

109121
break
110-
print(metrics.accumulate())
122+
print(train_metrics.accumulate())
111123
print("Dry run completed successfully.")
112124
exit()
113125

114126
# wandb.login(key=WANDB_API)
115127
wandb.init(
116-
entity="ColabCode-org",
117-
# entity="FYS-8805 Exam",
118-
project="Test",
128+
entity="ColabCode",
129+
project=args.run_name,
119130
tags=[args.modelname, args.dataset],
131+
config=args,
132+
120133
)
121134
wandb.watch(model)
122-
exit()
135+
123136
for epoch in range(args.epoch):
124137
# Training loop start
125138
trainingloss = []
@@ -135,33 +148,49 @@ def main():
135148
optimizer.zero_grad(set_to_none=True)
136149
trainingloss.append(loss.item())
137150

138-
metrics(y, logits)
151+
train_metrics(y, logits)
139152

140-
wandb.log(metrics.accumulate(str_prefix="Train "))
141-
metrics.reset()
142-
143-
evalloss = []
144-
# Eval loop start
153+
valloss = []
154+
# Validation loop start
145155
model.eval()
146156
with th.no_grad():
147157
for x, y in tqdm(valiloader, desc="Validation"):
148158
x, y = x.to(device), y.to(device)
149159
logits = model.forward(x)
150160
loss = criterion(logits, y)
151-
evalloss.append(loss.item())
152-
153-
metrics(y, logits)
161+
valloss.append(loss.item())
154162

155-
wandb.log(metrics.accumulate(str_prefix="Evaluation "))
156-
metrics.reset()
163+
val_metrics(y, logits)
157164

158165
wandb.log(
159166
{
160167
"Epoch": epoch,
161168
"Train loss": np.mean(trainingloss),
162-
"Evaluation Loss": np.mean(evalloss),
169+
"Validation loss": np.mean(valloss),
163170
}
171+
| train_metrics.__getmetrics__(str_prefix="Train ")
172+
| val_metrics.__getmetrics__(str_prefix="Validation ")
164173
)
174+
train_metrics.__resetmetrics__()
175+
val_metrics.__resetmetrics__()
176+
177+
testloss = []
178+
model.eval()
179+
with th.no_grad():
180+
for x, y in tqdm(testloader, desc="Testing"):
181+
x, y = x.to(device), y.to(device)
182+
logits = model.forward(x)
183+
loss = criterion(logits, y)
184+
testloss.append(loss.item())
185+
186+
preds = th.argmax(logits, dim=1)
187+
test_metrics(y, preds)
188+
189+
wandb.log(
190+
{"Epoch": 1, "Test loss": np.mean(testloss)}
191+
| test_metrics.__getmetrics__(str_prefix="Test ")
192+
)
193+
test_metrics.__resetmetrics__()
165194

166195

167196
if __name__ == "__main__":

0 commit comments

Comments
 (0)