Skip to content

Commit

Permalink
- Add the model architecture in the console header #32
Browse files Browse the repository at this point in the history
- Increase F1-Score precision to four digit floating point #25
- Change CPU / GPU device identifier for training and inference #28
- Input the file log to the Trainer for the train and dev accuracy / loss #29
- Print at the end the best model path and full f1-score #30
- Print the best model score and path for every epochs #31
- Add the support for Tensorboard #23
  • Loading branch information
qanastek committed Dec 11, 2021
1 parent d93cdd1 commit 8529361
Show file tree
Hide file tree
Showing 15 changed files with 157 additions and 28 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ Build: `python setup.py sdist bdist_wheel`
Upload: `twine upload dist/*`

# Citation

If you want to cite the tool you can use this:

```bibtex
Expand Down
11 changes: 7 additions & 4 deletions build/lib/hugsvision/inference/TorchVisionClassifierInference.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ class TorchVisionClassifierInference:
"""
def __init__(self, model_path: str, transform=transformTorchVision, device=None):

self.transform = transform

if device == None:
self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
else:
self.device = device

self.model_path = model_path if model_path.endswith("/") else model_path + "/"
self.transform = transform

self.model = torch.load(self.model_path + "best_model.pth", map_location=self.device)

Expand All @@ -42,8 +43,10 @@ def predict_image(self, img, save_preview=False):
pil_img = transforms.ToPILImage()(img)
pil_img.save("preview.jpg")

img = torch.unsqueeze(img, 0).to(self.device)

# Predict and get the corresponding label identifier
pred = self.model(torch.unsqueeze(img, 0))
pred = self.model(img)

# Get string label from index
label = self.config["id2label"][str(torch.max(pred, 1)[1].item())]
Expand All @@ -53,5 +56,5 @@ def predict_image(self, img, save_preview=False):
"""
🤔 Predict from one image path
"""
def predict(self, img_path: str):
return self.predict_image(img=Image.open(img_path))
def predict(self, img_path: str, save_preview=False):
return self.predict_image(img=Image.open(img_path), save_preview=save_preview)
56 changes: 49 additions & 7 deletions build/lib/hugsvision/nnet/TorchVisionClassifierTrainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ def __init__(
force_cpu = False,
requires_grad = False, # True: Only last layer of the classifier are updated
load_best_model_at_end = True,
classification_report_digits = 4,
parallelized = False,
):

self.train = train
Expand All @@ -69,12 +71,15 @@ def __init__(
self.ids2labels = id2label
self.labels2ids = label2id
self.best_acc = 0
self.best_path = ""
self.logs_path = self.output_dir + "logs/"
self.config_path = self.output_dir + "config.json"
self.current_date = datetime.today().strftime("%Y-%m-%d-%H-%M-%S")
self.config = {}
self.requires_grad = requires_grad
self.load_best_model_at_end = load_best_model_at_end
self.classification_report_digits = classification_report_digits
self.parallelized = parallelized

self.tensor_board = SummaryWriter()

Expand All @@ -96,8 +101,9 @@ def __init__(
self.num_classes = len(self.ids2labels.keys())
self.config["num_classes"] = self.num_classes

# Open the logs file
# Open the logs files
self.__openLogs()
self.__openLogsLossAcc()

# Load the model from the TorchVision Models Zoo
if pretrained:
Expand All @@ -114,7 +120,10 @@ def __init__(
# """
if self.model_name.startswith('alexnet') or self.model_name.startswith('vgg'):
self.model.classifier[6] = nn.Linear(self.model.classifier[6].in_features, self.num_classes)
self.model.features = torch.nn.DataParallel(self.model.features)

if self.parallelized == True:
self.model.features = torch.nn.DataParallel(self.model.features)

self.model.to(self.device)
self.config["hidden_size"] = self.model.classifier[6].in_features
else:
Expand Down Expand Up @@ -143,7 +152,10 @@ def __init__(
self.model.classifier[3] = nn.Linear(self.model.classifier[3].in_features, self.num_classes)
self.config["hidden_size"] = self.model.classifier[3].in_features

self.model = torch.nn.DataParallel(self.model).to(self.device)
if self.parallelized == True:
self.model = torch.nn.DataParallel(self.model)

self.model = self.model.to(self.device)

if self.requires_grad:
for param in self.model.parameters():
Expand Down Expand Up @@ -181,13 +193,15 @@ def __init__(
self.training()

if self.load_best_model_at_end:
print("Best model loaded!")
print("\033[95mBest model loaded!\033[0m")
self.model = torch.load(self.output_dir + 'best_model.pth', map_location=self.device)

self.evaluate_f1_score()

# Close the logs file
self.logs_file.close()
self.logs_loss.close()
self.logs_acc.close()

"""
📜 Open the logs file
Expand All @@ -200,6 +214,21 @@ def __openLogs(self):
# Open the logs file
self.logs_file = open(self.logs_path + "logs_" + self.current_date + ".txt", "a")

"""
📜 Open the logs file for loss and accuracy
"""
def __openLogsLossAcc(self):

if not self.logs_path.endswith("/"):
self.logs_path += "/"

# Check if the directory already exist
os.makedirs(self.logs_path, exist_ok=True)

# Open the logs file
self.logs_loss = open(self.logs_path + "logs_loss_" + self.current_date + ".txt", "a")
self.logs_acc = open(self.logs_path + "logs_acc_" + self.current_date + ".txt", "a")

"""
🧪 Evaluate the performances of the system of the test sub-dataset given a f1-score
"""
Expand All @@ -214,6 +243,7 @@ def evaluate_f1_score(self):
labels = [int(a) for a in list(self.ids2labels.keys())],
target_names = list(self.labels2ids.keys()),
zero_division = 0,
digits=self.classification_report_digits,
)
print(table)

Expand All @@ -223,6 +253,7 @@ def evaluate_f1_score(self):
self.logs_file.close()

print("Logs saved at: \033[93m" + self.logs_path + "\033[0m")
print("\033[93m[" + self.model_name + "]\033[0m Best model saved at: \033[93m" + self.best_path + "\033[0m" + " - Accuracy " + "{:.2f}".format(self.best_acc*100))

return all_target, all_preds

Expand All @@ -242,13 +273,19 @@ def training(self):
all_targets, all_predictions = self.evaluate()
total_acc = accuracy_score(all_targets, all_predictions)

f1_score = classification_report(all_targets, all_predictions, target_names=list(self.ids2labels.values()))
f1_score = classification_report(
all_targets,
all_predictions,
target_names=list(self.ids2labels.values()),
digits=self.classification_report_digits
)
print(f1_score)

os.makedirs(self.output_dir, exist_ok=True)

if total_acc > self.best_acc:
filename = self.output_dir + 'best_model.pth'
self.best_path = filename
else:
filename = self.output_dir + 'last_model.pth'

Expand All @@ -257,9 +294,12 @@ def training(self):
torch.save(self.model, filename)
saved_at = "Model saved at: \033[93m" + filename + "\033[0m"
print(saved_at)
best_model_path = "\033[93m[" + self.model_name + "]\033[0m Best model saved at: \033[93m" + self.best_path + "\033[0m" + " - Accuracy " + "{:.2f}".format(self.best_acc*100) + "%"
print(best_model_path)

self.logs_file.write(f1_score + "\n")
self.logs_file.write(saved_at + "\n")
self.logs_file.write(best_model_path + "\n")
self.logs_file.close()

self.tensor_board.add_scalar('Loss/train', batches_loss, epoch)
Expand All @@ -280,7 +320,7 @@ def compute_batches(self, epoch):
all_targets = []

# For each batch
for i, (input, target) in enumerate(self.data_loader_train):
for i, (input, target) in tqdm(enumerate(self.data_loader_train)):

input = input.to(self.device)
target = target.to(self.device)
Expand All @@ -303,7 +343,6 @@ def compute_batches(self, epoch):

if i % (len(self.data_loader_train) / 10) == 0:
log_line = "[Epoch " + str(epoch) + "], [Batch " + str(i) + " / " + str(self.max_epochs) + "], [Loss " + str(loss.item()) + "]"
print(log_line)
self.logs_file.write(log_line + "\n")

self.scheduler.step()
Expand All @@ -312,6 +351,9 @@ def compute_batches(self, epoch):
total_acc = accuracy_score(all_targets, all_preds)
avg_loss = sum_loss / len(self.data_loader_train)

self.logs_loss.write(str(epoch) + "," + str(avg_loss.item()) + "\n")
self.logs_acc.write(str(epoch) + "," + str(total_acc.item()) + "\n")

return total_acc, avg_loss

"""
Expand Down
5 changes: 4 additions & 1 deletion build/lib/hugsvision/nnet/VisionClassifierTrainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ def __init__(
batch_size = 8,
lr = 2e-5,
eval_metric = "accuracy",
fp16 = True,
fp16 = False,
classification_report_digits = 4,
):

self.model_name = model_name
Expand All @@ -61,6 +62,7 @@ def __init__(
self.eval_metric = eval_metric
self.ids2labels = self.model.config.id2label
self.labels2ids = self.model.config.label2id
self.classification_report_digits = classification_report_digits

print(self.ids2labels)
print(self.labels2ids)
Expand Down Expand Up @@ -161,6 +163,7 @@ def evaluate_f1_score(self):
labels = [int(a) for a in list(self.ids2labels.keys())],
target_names = list(self.labels2ids.keys()),
zero_division = 0,
digits=self.classification_report_digits,
)
print(table)

Expand Down
2 changes: 1 addition & 1 deletion build/lib/hugsvision/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.70
0.72
Binary file removed dist/hugsvision-0.70-py3-none-any.whl
Binary file not shown.
Binary file removed dist/hugsvision-0.70.tar.gz
Binary file not shown.
Binary file added dist/hugsvision-0.72-py3-none-any.whl
Binary file not shown.
Binary file added dist/hugsvision-0.72.tar.gz
Binary file not shown.
35 changes: 33 additions & 2 deletions hugsvision.egg-info/PKG-INFO
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: hugsvision
Version: 0.70
Version: 0.72
Summary: A easy to use huggingface wrapper for computer vision.
Home-page: https://HugsVision.github.io/
Author: Yanis Labrak & Others
Expand All @@ -14,7 +14,7 @@ Description: <p align="center">
[![GitHub Issues](https://img.shields.io/github/issues/qanastek/HugsVision.svg)](https://github.com/qanastek/HugsVision/issues)
[![Contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](CONTRIBUTING.md)
[![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT)

[![Downloads](https://static.pepy.tech/personalized-badge/hugsvision?period=total&units=international_system&left_color=grey&right_color=orange&left_text=Downloads)](https://pepy.tech/project/hugsvision)

HugsVision is an open-source and easy to use all-in-one huggingface wrapper for computer vision.

Expand Down Expand Up @@ -161,6 +161,36 @@ Description: <p align="center">
</a>
</td>
</tr>

<!-- ------------------------------------------------------------------- -->

<tr>
<td rowspan="3" width="160">
<img src="https://raw.githubusercontent.com/qanastek/HugsVision/main/ressources/images/receipes/HAM10000.png" width="256">
</td>
<td rowspan="3">
<b>Training and using a TorchVision Image Classifier in 5 min to identify skin cancer:</b> A fast and easy tutorial to train a TorchVision Image Classifier that can help dermatologist in their identification procedures Melanoma cases with HugsVision and HAM10000 dataset.
</td>
<td align="center" width="80">
<a href="https://nbviewer.jupyter.org/github/qanastek/HugsVision/blob/main/recipes/HAM10000/binary_classification/HAM10000_Image_Classifier.ipynb">
<img src="https://raw.githubusercontent.com/qanastek/HugsVision/main/ressources/images/receipes/nbviewer_logo.svg" height="34">
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/qanastek/HugsVision/blob/main/recipes/HAM10000/binary_classification/HAM10000_Image_Classifier.ipynb">
<img src="https://raw.githubusercontent.com/qanastek/HugsVision/main/ressources/images/receipes/github_logo.png" height="32">
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://colab.research.google.com/drive/1tfRpFTT1GJUgrcwHI0pYdAZ5_z0VSevJ?usp=sharing">
<img src="https://raw.githubusercontent.com/qanastek/HugsVision/main/ressources/images/receipes/colab_logo.png" height="28">
</a>
</td>
</tr>
</table>

# Model architectures
Expand All @@ -183,6 +213,7 @@ Description: <p align="center">
Upload: `twine upload dist/*`

# Citation

If you want to cite the tool you can use this:

```bibtex
Expand Down
11 changes: 7 additions & 4 deletions hugsvision/inference/TorchVisionClassifierInference.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ class TorchVisionClassifierInference:
"""
def __init__(self, model_path: str, transform=transformTorchVision, device=None):

self.transform = transform

if device == None:
self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
else:
self.device = device

self.model_path = model_path if model_path.endswith("/") else model_path + "/"
self.transform = transform

self.model = torch.load(self.model_path + "best_model.pth", map_location=self.device)

Expand All @@ -42,8 +43,10 @@ def predict_image(self, img, save_preview=False):
pil_img = transforms.ToPILImage()(img)
pil_img.save("preview.jpg")

img = torch.unsqueeze(img, 0).to(self.device)

# Predict and get the corresponding label identifier
pred = self.model(torch.unsqueeze(img, 0))
pred = self.model(img)

# Get string label from index
label = self.config["id2label"][str(torch.max(pred, 1)[1].item())]
Expand All @@ -53,5 +56,5 @@ def predict_image(self, img, save_preview=False):
"""
🤔 Predict from one image path
"""
def predict(self, img_path: str):
return self.predict_image(img=Image.open(img_path))
def predict(self, img_path: str, save_preview=False):
return self.predict_image(img=Image.open(img_path), save_preview=save_preview)
Loading

0 comments on commit 8529361

Please sign in to comment.