# Todo:
# - generate transformed OSSL dataset from 650-4000
# - retrained model on OSSL dataset using this wavenumber range
# - generate transformed ringtrial dataset
# - generate transformed Fukushima dataset
Fastai BW data augmentation
Experimenting with Fastai and BW data augmentation.
Runpod setup
# setting up pod and pip install uhina
# accessing a pod terminal
# 1. To get access to the pod ip adress: runpodctl get pod -a
# 2. ssh into the pod: ssh root@<ip-address> -p 58871 -i ~/.ssh/id_ed25519
# git clone https://github.com/franckalbinet/uhina.git
# pip install uhina
# runpodctl send im-bw
# runpodctl send ossl-tfm.csv
Loading data
import pandas as pd
from pathlib import Path
import fastcore.all as fc
from fastai.data.all import *
from fastai.vision.all import *
from multiprocessing import cpu_count
import warnings
'ignore') warnings.filterwarnings(
= '../../_data/ossl-tfm/ossl-tfm.csv'
ossl_source = pd.read_csv(ossl_source); df.head() df
fname | kex | |
---|---|---|
0 | 3998362dd2659e2252cd7f38b43c9b1f.png | 0.182895 |
1 | 2bab4dbbac073b8648475ad50d40eb95.png | 0.082741 |
2 | 29213d2193232be8867d85dec463ec00.png | 0.089915 |
3 | 8b1ee9146c026faee20a40df86736864.png | 0.135030 |
4 | 6e8e9d1105e7da7055555cb5d310df5f.png | 0.270421 |
'kex'].min(), df['kex'].max() df[
(0.0, 3.6521352871126975)
# image size is 750x281
# start = np.random.uniform(1, 50); print(start)
# end = np.random.uniform(90.1, 99.5); print(end)
# steps = np.random.randint(5, 100); print(steps)
# percentiles = torch.linspace(start=start, end=end, steps=steps)
# percentiles
@delegates()
class Quantize(RandTransform):
# split_idx,mode,mode_mask,order = None,BILINEAR,NEAREST,1
"Quantize B&W image into `num_colors` colors."
= None
split_idx def __init__(self,
int=10,
num_colors:bool=False,
verbose:int=100, # how many different quantization to generate for valid set
n_percentiles_valid:int|None=41, # Seed for random number generator used to generate fixed augmentation for validation set
seed:**kwargs
):
store_attr()super().__init__(**kwargs)
self.counter_valid = 0
self.percentiles = None
self.percentiles_valid = self.generate_percentiles_valid(n=n_percentiles_valid, seed=self.seed)
def before_call(self,
b, int # Index of the train/valid dataset (0: train, 1: valid)
split_idx:
):self.idx = split_idx
def get_random_percentiles(self, seed:int|None=None):
if seed is not None:
np.random.seed(seed)= np.random.uniform(1, 50)
start
= np.random.uniform(90.1, 99.5)
end = np.random.randint(5, 100)
steps return torch.linspace(start=start, end=end, steps=steps)
def generate_percentiles_valid(self, n:int=100, seed:int|None=None):
return [self.get_random_percentiles(seed=self.seed) for i in range(n)]
def get_percentiles(self):
if self.idx == 1:
return self.percentiles_valid[self.counter_valid%len(self.percentiles_valid)]
else:
return self.get_random_percentiles()
def encodes(self, x:Image.Image):
= image2tensor(x)[0, :, :]
im_tensor
= self.get_percentiles()
percentiles = torch.quantile(im_tensor.float(), percentiles / 100)
levels = torch.bucketize(im_tensor.float(), levels)
im_quant
= plt.get_cmap('Spectral_r')
cmap = tensor(cmap(im_quant.float() / im_quant.max())[:,:,:3])
im_color = im_color.permute(2, 0, 1)
im_color return to_image(im_color)
# Image.Image
= '../../_data/all-grey-255.png'
im_path = PILImage.create(im_path)
im # type(im)
# im = Image.open(im_path)
# PILImageBW
# fastai_im = PILImageBW(im_path) # fastai.vision.core.PILImage
# fastai_im.show(figsize=(10,10))
= Quantize(verbose=False)(im)
im im
= '../../_data/all-grey-255.png'
im_path = PILImage.create(im_path)
im print(f'original shape: {im.shape}')
= image2tensor(im)
im_tensor print(f'tensor shape: {im_tensor.shape} which is simply each pixel value replicated 3 times (R, G, B)')
= im_tensor[0, :, :]
im_tensor print(f'tensor shape: {im_tensor.shape}')
= torch.arange(40, 99, 1, dtype=torch.float32)
percentiles print(f'percentiles: {percentiles}')
= torch.quantile(im_tensor.float(), percentiles / 100)
levels print(f'levels: {levels}')
= torch.bucketize(im_tensor.float(), levels)
im_quant print(f'im_quant: {im_quant}, # unique values: {im_quant.unique()}')
# Color map: takes values between 0 and 1 and returns a color (RGBA)
= plt.get_cmap('Spectral_r')
cmap
= tensor(cmap(im_quant.float() / im_quant.max())[:,:,:3])
im_color print(f'im_color shape: {im_color.shape}')
= im_color.permute(2, 0, 1)
im_color print(f'im_color permuted to (C, H, W): {im_color.shape}')
= to_image(im_color)
im_color_fastai print(f'im_color_fastai: {im_color_fastai}')
im_color_fastai
original shape: (221, 669)
tensor shape: torch.Size([3, 221, 669]) which is simply each pixel value replicated 3 times (R, G, B)
tensor shape: torch.Size([221, 669])
percentiles: tensor([40., 41., 42., 43., 44., 45., 46., 47., 48., 49., 50., 51., 52., 53.,
54., 55., 56., 57., 58., 59., 60., 61., 62., 63., 64., 65., 66., 67.,
68., 69., 70., 71., 72., 73., 74., 75., 76., 77., 78., 79., 80., 81.,
82., 83., 84., 85., 86., 87., 88., 89., 90., 91., 92., 93., 94., 95.,
96., 97., 98.])
levels: tensor([182., 183., 185., 187., 189., 191., 192., 194., 195., 198., 200., 202.,
204., 206., 207., 209., 210., 211., 213., 214., 215., 216., 217., 218.,
219., 220., 220., 221., 222., 223., 224., 225., 226., 227., 229., 230.,
231., 232., 232., 233., 234., 235., 236., 237., 238., 238., 239., 240.,
240., 241., 241., 242., 243., 243., 243., 245., 246., 248., 250.])
im_quant: tensor([[41, 41, 41, ..., 46, 46, 46],
[41, 41, 41, ..., 46, 46, 46],
[35, 36, 36, ..., 44, 44, 44],
...,
[10, 7, 0, ..., 0, 0, 0],
[10, 7, 0, ..., 0, 0, 0],
[10, 7, 0, ..., 0, 0, 0]]), # unique values: tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
37, 39, 40, 41, 42, 43, 44, 46, 47, 49, 51, 52, 55, 56, 57, 58, 59])
im_color shape: torch.Size([221, 669, 3])
im_color permuted to (C, H, W): torch.Size([3, 221, 669])
im_color_fastai: <PIL.Image.Image image mode=RGB size=669x221>
= DataBlock(blocks=(ImageBlock, RegressionBlock),
ossl =ColReader(0, pref='../../_data/ossl-tfm/im/'),
get_x=ColReader(1),
get_y# batch_tfms=Normalize.from_stats(*imagenet_stats),
=[RatioResize(224)],
batch_tfms=[Quantize()],
item_tfms=RandomSplitter(valid_pct=0.1, seed=41)
splitter# batch_tfms=aug_transforms()
)
# ossl.summary(df)
#cpu_count()
# dls = ossl.dataloaders(df, num_workers=cpu_count())
= ossl.dataloaders(df) dls
=5, ncols=1, figsize=(10, 15)) dls.show_batch(nrows
#learn = vision_learner(dls, resnet18, pretrained=False, metrics=R2Score()).to_fp16()
= vision_learner(dls, resnet18, pretrained=False, metrics=R2Score()) learn
#learn = load_learner('./models/bw-data-augment-0.pkl', cpu=True)
learn.lr_find()
SuggestedLRs(valley=0.0030199517495930195)
25, 3e-3) learn.fit_one_cycle(
epoch | train_loss | valid_loss | r2_score | time |
---|---|---|---|---|
0 | 0.965983 | 0.168971 | -0.212750 | 03:28 |
1 | 0.126813 | 1.278555 | -8.176533 | 03:28 |
2 | 0.092299 | 0.075169 | 0.460494 | 03:33 |
3 | 0.079262 | 0.058242 | 0.581981 | 03:41 |
4 | 0.073574 | 0.072033 | 0.482998 | 03:48 |
5 | 0.063900 | 0.046245 | 0.668084 | 03:37 |
6 | 0.051317 | 0.041811 | 0.699914 | 03:46 |
7 | 0.042400 | 0.035283 | 0.746765 | 03:34 |
8 | 0.041587 | 0.035850 | 0.742691 | 04:01 |
9 | 0.039711 | 0.034675 | 0.751131 | 04:08 |
10 | 0.036430 | 0.032221 | 0.768739 | 04:04 |
11 | 0.034432 | 0.031230 | 0.775856 | 03:49 |
12 | 0.029793 | 0.029620 | 0.787409 | 03:40 |
13 | 0.029803 | 0.028552 | 0.795076 | 03:47 |
14 | 0.027022 | 0.029088 | 0.791229 | 03:51 |
15 | 0.025353 | 0.030500 | 0.781095 | 03:53 |
16 | 0.023718 | 0.026129 | 0.812462 | 03:33 |
17 | 0.021063 | 0.024969 | 0.820793 | 03:33 |
18 | 0.019587 | 0.024076 | 0.827199 | 03:30 |
19 | 0.017989 | 0.023424 | 0.831881 | 03:33 |
20 | 0.016224 | 0.023345 | 0.832445 | 03:30 |
21 | 0.015382 | 0.022867 | 0.835880 | 03:28 |
22 | 0.015437 | 0.023114 | 0.834103 | 03:34 |
23 | 0.015099 | 0.022699 | 0.837085 | 03:31 |
24 | 0.013830 | 0.022753 | 0.836699 | 03:35 |
= Path('./models')
path_model / '650-4000-epoch-25-lr-3e-3.pkl') learn.export(path_model
Evaluation
= learn.get_preds(dl=dls.valid)
val_preds, val_targets assess_model(val_preds, val_targets)
Predicted Actual
0 0.277349 0.000000
1 0.207959 0.184960
2 0.495959 0.194201
3 0.266406 0.262364
4 0.373048 0.355799
R2 Score on validation set: 0.8367
= learn.tta(dl=dls.valid, n=20)
val_preds_tta, val_targets_tta assess_model(val_preds_tta, val_targets_tta)
Predicted Actual
0 0.231803 0.000000
1 0.205699 0.184960
2 0.468034 0.194201
3 0.263332 0.262364
4 0.381026 0.355799
R2 Score on validation set: 0.8418
# EXAMPLE of TTA on single item
# from fastai.vision.all import *
# # Define your TTA transforms
# tta_tfms = [
# RandomResizedCrop(224, min_scale=0.5),
# Flip(),
# Rotate(degrees=(-15, 15)),
# Brightness(max_lighting=0.2),
# Contrast(max_lighting=0.2)
# ]
# # Create a pipeline of TTA transformations
# tta_pipeline = Pipeline(tta_tfms)
# # Load your model
# learn = load_learner('path/to/your/model.pkl')
# # Define the input data (e.g., an image)
# input_data = PILImage.create('path/to/your/image.jpg')
# # Apply TTA transforms to the input data and make predictions
# predictions = []
# for _ in range(5): # Apply 5 different augmentations
# augmented_data = tta_pipeline(input_data)
# prediction = learn.predict(augmented_data)
# predictions.append(prediction)
# # Average the predictions
# average_prediction = sum(predictions) / len(predictions)
# print(average_prediction)
# Assuming you have a new CSV file for your test data
# test_source = '../../_data/ossl-tfm/ossl-tfm-test.csv'
# test_df = pd.read_csv(test_source)
# # Create a new DataLoader for the test data
# test_dl = learn.dls.test_dl(test_df)
# # Get predictions on the test set
# test_preds, test_targets = learn.get_preds(dl=test_dl)
# # Now you can use test_preds and test_targets for further analysis
# Convert predictions and targets to numpy arrays
def assess_model(val_preds, val_targets):
= val_preds.numpy().flatten()
val_preds = val_targets.numpy()
val_targets
# Create a DataFrame with the results
= pd.DataFrame({
results_df 'Predicted': val_preds,
'Actual': val_targets
})
# Display the first few rows of the results
print(results_df.head())
# Calculate and print the R2 score
from sklearn.metrics import r2_score
= r2_score(val_targets, val_preds)
r2 print(f"R2 Score on validation set: {r2:.4f}")
assess_model(val_preds, val_targets)
Predicted Actual
0 0.312483 0.000000
1 0.126990 0.184960
2 0.365726 0.194201
3 0.239089 0.262364
4 0.402980 0.355799
R2 Score on validation set: 0.8325
assess_model(val_preds_tta, val_targets_tta)
Predicted Actual
0 0.246857 0.000000
1 0.148590 0.184960
2 0.371643 0.194201
3 0.226535 0.262364
4 0.407333 0.355799
R2 Score on validation set: 0.8378
= val_preds
val_preds_np = val_targets
val_targets_np
# Apply the transformation: exp(y) - 1
= np.exp(val_preds_np) - 1
val_preds_transformed = np.exp(val_targets_np) - 1
val_targets_transformed
# Create a DataFrame with the results
= pd.DataFrame({
results_df 'Predicted': val_preds_transformed,
'Actual': val_targets_transformed
})
# Display the first few rows of the results
print(results_df.head())
# Calculate and print the R2 score
from sklearn.metrics import r2_score
= r2_score(val_targets_transformed, val_preds_transformed)
r2 print(f"R2 Score on validation set (after transformation): {r2:.4f}")
# Calculate and print the MAPE, handling zero values
def mean_absolute_percentage_error(y_true, y_pred):
= (y_true != 0)
non_zero return np.mean(np.abs((y_true[non_zero] - y_pred[non_zero]) / y_true[non_zero])) * 100
= mean_absolute_percentage_error(val_targets_transformed, val_preds_transformed)
mape print(f"Mean Absolute Percentage Error (MAPE) on validation set: {mape:.2f}%")
# Calculate and print the MAE as an alternative metric
from sklearn.metrics import mean_absolute_error
= mean_absolute_error(val_targets_transformed, val_preds_transformed)
mae print(f"Mean Absolute Error (MAE) on validation set: {mae:.4f}")
Predicted Actual
0 0.366814 0.00000
1 0.135405 0.20317
2 0.441560 0.21434
3 0.270092 0.30000
4 0.496277 0.42732
R2 Score on validation set (after transformation): 0.6936
Mean Absolute Percentage Error (MAPE) on validation set: 50.72%
Mean Absolute Error (MAE) on validation set: 0.1956
=(6, 6))
plt.figure(figsize
# Use logarithmic bins for the colormap
= plt.hexbin(val_targets, val_preds, gridsize=65,
h ='log', cmap='Spectral_r', mincnt=1,
bins=0.9)
alpha
# Get the actual min and max counts from the hexbin data
= h.get_array()
counts = counts[counts > 0].min() # Minimum non-zero count
min_count = counts.max()
max_count
# Create a logarithmic colorbar
= plt.colorbar(h, label='Count in bin', shrink=0.73)
cb = np.logspace(np.log10(min_count), np.log10(max_count), 5)
tick_locations
cb.set_ticks(tick_locations)f'{int(x)}' for x in tick_locations])
cb.set_ticklabels([
# Add the diagonal line
= min(val_targets.min(), val_preds.min())
min_val = max(val_targets.max(), val_preds.max())
max_val 'k--', lw=1)
plt.plot([min_val, max_val], [min_val, max_val],
# Set labels and title
'Actual Values')
plt.xlabel('Predicted Values')
plt.ylabel('Predicted vs Actual Values (Hexbin with Log Scale)')
plt.title(
# Add grid lines
True, linestyle='--', alpha=0.65)
plt.grid(
# Set the same limits for both axes
plt.xlim(min_val, max_val)
plt.ylim(min_val, max_val)
# Make the plot square
'equal', adjustable='box')
plt.gca().set_aspect(
plt.tight_layout()
plt.show()
# Print the range of counts in the hexbins
print(f"Min non-zero count in hexbins: {min_count}")
print(f"Max count in hexbins: {max_count}")
Min non-zero count in hexbins: 1.0
Max count in hexbins: 157.0
= Path('./models')
path_model / '0.pkl') learn.export(path_model
Inference
= Path('../../_data/ossl-tfm/img')
ossl_source / '0a0a0c647671fd3030cc13ba5432eb88.png') learn.predict(ossl_source
((0.5229991674423218,), tensor([0.5230]), tensor([0.5230]))
'fname'] == '0a0a0c647671fd3030cc13ba5432eb88.png'] df[df[
fname | kex | |
---|---|---|
28867 | 0a0a0c647671fd3030cc13ba5432eb88.png | 0.525379 |
3) - 1 np.exp(
19.085536923187668
Experiments:
Color scale: viridis
| Discretization: percentiles = [i for i in range(60, 100)]
Model | Image Size | Learning Rate | Epochs | R2 Score | Time per Epoch | Finetuning | with axis ticks |
---|---|---|---|---|---|---|---|
ResNet-18 | 100 | 1e-3 | 10 | 0.648 | 05:12 | No | Yes |
ResNet-18 | 224 | 2e-3 | 10 | 0.69 | 07:30 | No | Yes |
ResNet-18 | 750 (original size) | 1e-3 | 10 | 0.71 | 36:00 | No | Yes |
ResNet-18 | 224 | 2e-3 | 20 | 0.704 | 07:30 | No | Yes |
ResNet-18 | 224 | 2e-3 | 10 | 0.71 | 07:00 | No | No |
Discretization: percentiles = [i for i in range(20, 100)]
Model | Image Size | Learning Rate | Epochs | R2 Score | Time per Epoch | Finetuning | with axis ticks | colour scale |
---|---|---|---|---|---|---|---|---|
ResNet-18 | 224 | 2e-3 | 10 | 0.7 | 05:12 | No | No | viridis |
ResNet-18 | 224 | 3e-3 | 10 | 0.71 | 05:12 | No | No | jet |
From now on with axis ticks
is always No
.
Discretization: esimated on 10000
cwt power percentiles [20, 30, 40, 50, 60, 70, 80, 90, 95, 97, 99]
Model | Image Size | Learning Rate | Epochs | R2 Score | Time per Epoch | Finetuning | remark | colour scale |
---|---|---|---|---|---|---|---|---|
ResNet-18 | 224 | 2e-3 | 10 | 0.71 | 05:12 | No | None | jet |
ResNet-18 | 224 | 2e-3 | 10 | 0.685 | 05:12 | No | y range added | jet |
From now on random splitter with 10%
validation and random seed 41
.
Discretization: esimated on 10000
cwt power percentiles [20, 30, 40, 50, 60, 70, 80, 90, 95, 97, 99]
Model | Image Size | Learning Rate | Epochs | R2 Score | Time per Epoch | Finetuning | remark | colour scale |
---|---|---|---|---|---|---|---|---|
ResNet-18 | 224 | 2e-3 | 10 | 0.7 | 05:12 | No | Pre-train & normalize: True | jet |
ResNet-18 | 224 | 2e-3 | 10 | 0.796 | 08:12 | No | No Pre-train | jet |
ResNet-18 | 224 | 3e-3 | 10 | 0.7 | 05:12 | No | Pre-train & normalize: False | jet |
ResNet-18 (id=0) | 224 | 2e-3 | 20 | 0.829 | 08:12 | No | No Pre-train (try 18 epochs) | jet |