Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import random | |
| from PIL import Image, ImageEnhance | |
| import torch | |
| from glob import glob | |
| from torch import nn | |
| from torchvision import transforms | |
| def predict_number(custom_image: Image.Image): | |
| custom_image = ImageEnhance.Contrast(custom_image.convert("L")).enhance(5.0).point(lambda p: 255 if p > 128 else 0).resize((28, 28)) | |
| transform = transforms.Compose([ | |
| transforms.ToTensor(), | |
| ]) | |
| model_0.eval() | |
| with torch.inference_mode(): | |
| pred = torch.softmax(model_0(transform(custom_image).unsqueeze(0).to(device)), dim=1) | |
| class_names = list("0123456789") | |
| return {class_names[i]: float(pred[0][i]) for i in range(len(class_names))} | |
| class NumberClassifier(nn.Module): | |
| def __init__(self, input_shape: int, hidden_units: int, output_shape: int) -> None: | |
| super().__init__() | |
| self.conv_block_1 = nn.Sequential( | |
| nn.Conv2d(in_channels=input_shape, | |
| out_channels=hidden_units, | |
| kernel_size=2, # how big is the square that's going over the image? | |
| stride=1, # default | |
| ), # options = "valid" (no padding) or "same" (output has same shape as input) or int for specific number | |
| nn.ReLU(), | |
| nn.Conv2d(in_channels=hidden_units, | |
| out_channels=hidden_units, | |
| kernel_size=2, | |
| stride=1, | |
| ), | |
| nn.ReLU(), | |
| nn.MaxPool2d(kernel_size=2, | |
| stride=2) # default stride value is same as kernel_size | |
| ) | |
| self.conv_block_2 = nn.Sequential( | |
| nn.Conv2d(hidden_units, hidden_units, kernel_size=2), | |
| nn.ReLU(), | |
| nn.Conv2d(hidden_units, hidden_units, kernel_size=2), | |
| nn.ReLU(), | |
| nn.MaxPool2d(2) | |
| ) | |
| self.classifier = nn.Sequential( | |
| nn.Flatten(), | |
| # Where did this in_features shape come from? | |
| # It's because each layer of our network compresses and changes the shape of our input data. | |
| nn.Linear(in_features=hidden_units*5*5, | |
| out_features=output_shape) | |
| ) | |
| def forward(self, x: torch.Tensor): | |
| x = self.conv_block_1(x) | |
| # print(x.shape) | |
| x = self.conv_block_2(x) | |
| # print(x.shape) | |
| x = self.classifier(x) | |
| # print(x.shape) | |
| return x | |
| # return self.classifier(self.conv_block_2(self.conv_block_1(x))) # <- leverage the benefits of operator fusion | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| torch.manual_seed(42) | |
| model_0 = NumberClassifier(input_shape=1, # number of color channels (3 for RGB) | |
| hidden_units=10, | |
| output_shape=10).to(device) | |
| model_0.load_state_dict(torch.load("models/pytorch_num_classifier_final_model_with_EMNIST.pth", map_location=torch.device('cpu'))) | |
| title = "Number Classifier Minimal" | |
| description = "An Image feature extractor computer vision model to classify images of handwritten digits." | |
| article = "Created at [09. PyTorch Model Deployment](https://www.learnpytorch.io/09_pytorch_model_deployment/)." | |
| example_list = [[str(filepath)] for filepath in random.sample(glob("examples/*"), k=25)] | |
| example_list | |
| demo = gr.Interface(fn=predict_number, | |
| inputs=gr.Image(type="pil"), | |
| outputs=[gr.Label(num_top_classes=10, label="Predictions")], | |
| examples=example_list, | |
| title=title, | |
| description=description, | |
| article=article) | |
| demo.launch() |