<jupyter_start><jupyter_text>Smiles Image: Pytorch Binary ClassificationThis workbook illustrates binary classification of `Smiles Dataset` using a Pytorch based binary classifier. The dataset is available at [this GitHub location](https://github.com/hromi/SMILEsmileD)<jupyter_code># Connect to Google drive, so I can import modules from it into my workspace
# set this flag to True when you run this Notebook on Google Collaboratory
USE_COLAB = False

import os

#@see: https://colab.research.google.com/drive/1srw_HFWQ2SMgmWIawucXfusGzrj1_U0q
if USE_COLAB:
    # Load the Drive helper and mount
    from google.colab import drive

    # This will prompt for authorization.
    gdrive_mount_point = '/content/drive/'
    drive.mount(gdrive_mount_point)
    grive_path = os.path.join(gdrive_mount_point, "My Drive/")
    print('Your Google Drive is mounted at ', grive_path)
    COLAB_NOTEBOOKS_PATH = os.path.join(grive_path, "Colab Notebooks/")
    
    if os.path.exists(COLAB_NOTEBOOKS_PATH):    
        # display list of files
        #!ls '/content/drive/My Drive/Colab Notebooks/'

        # append our Google Drive folder to module search 
        # **NOTE:** comment this line out if you are NOT running this notebook on Google Colab
        import sys
        #sys.path.append("/content/drive/My Drive/Colab Notebooks/")
        pyt_path = os.path.join(COLAB_NOTEBOOKS_PATH, 'pytorch')
        sys.path.append(pyt_path)
        print('You Pytorch Colab notebooks are available at ', pyt_path)
        print('NOTE: {} is appended to sys.path!'.format(pyt_path))
        # and test it
        import pytorch_toolkit as pyt
        MODEL_SAVE_DIR = os.path.join(pyt_path, 'model_states')
    else:
        raise IOError("Unable to mount Google Drive!")
else:
    MODEL_SAVE_DIR = os.path.join('.','model_states')
    
if not os.path.exists(MODEL_SAVE_DIR):
    os.mkdir(MODEL_SAVE_DIR)

assert os.path.exists(MODEL_SAVE_DIR)
print('MODEL_SAVE_DIR = %s' % MODEL_SAVE_DIR)
import warnings
warnings.filterwarnings('ignore')

import os, sys, random
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import pathlib
import glob

# tweaks for libraries
np.set_printoptions(precision=6, linewidth=1024, suppress=True)
plt.style.use('seaborn')
sns.set_style('darkgrid')
sns.set_context('notebook',font_scale=1.10)

# Pytorch imports
import torch
gpu_available = torch.cuda.is_available()
print('Using Pytorch version: %s. GPU %s available' % (torch.__version__, "IS" if gpu_available else "is NOT"))
import torch.nn as nn
import torch.nn.functional as F
from torch import optim
from torchsummary import summary
from tensorflow.keras.preprocessing.image import *
# My helper functions for training/evaluating etc.
import pytorch_toolkit as pytk

SEED = 42
random.seed(SEED)
np.random.seed(SEED)
torch.manual_seed(SEED);

if torch.cuda.is_available():
    torch.cuda.manual_seed(SEED)
    torch.cuda.manual_seed_all(SEED)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False
    #torch.backends.cudnn.enabled = False
# download the data from GitHub - will download to /../data/SMILEsmileD folder
!git clone https://github.com/hromi/SMILEsmileD.git ../data/SMILEsmileD
def get_data(image_paths):

    images, labels = [], []

    for image_path in image_paths:
        image = load_img(image_path, target_size=(32,32), 
                         color_mode='grayscale')
        image = img_to_array(image)
        label = image_path.split(os.path.sep) [-2]
        label = 'positive' in label
        label = int(label)
        images.append(image)
        labels.append(label)

        #labels = np.expand_dims(labels, axis=0)

    return np.array(images), np.array(labels)
file_pattern = pathlib.Path.cwd() / 'data' / 'SMILEsmileD' / 'SMILEs' / '*' / '*' / '*.jpg'
dataset_paths = [*glob.glob(str(file_pattern))]
images, labels = get_data(dataset_paths)
print(f"images.shape: {images.shape} - labels.shape: {labels.shape}")
#images /= 255.
total = len(labels)
class_counts = [len(labels[labels == i]) for i in np.unique(labels)]
print(f"class_counts: {class_counts}")
# total_positives = len(labels[labels == 1])
# total_negatives = total - total_positives
# #weight_positives, weight_negatives = total / total_positives, total / total_negatives
# print(f"Total images: {total} - total_positive: {total_positives} - total_negative: {total_negatives}")
def build_model():
    net = nn.Sequential(
      nn.Conv2d(1, 32, kernel_size=5, padding=1), # 20
      nn.ReLU(),
      nn.BatchNorm2d(32),
      nn.MaxPool2d(2,2),
      nn.Dropout(0.4),

      nn.Conv2d(32, 64, kernel_size=5, padding=1), # 32
      nn.ReLU(),
      nn.BatchNorm2d(64),
      nn.MaxPool2d(2,2),
      nn.Dropout(0.4),       

      nn.Conv2d(64, 64, kernel_size=5, padding=1), # 32
      nn.ReLU(),
      nn.BatchNorm2d(64),
      nn.MaxPool2d(2,2),
      nn.Dropout(0.4),     

      nn.Flatten(),

      nn.Linear(64*2*2, 500),
      nn.ReLU(),
      nn.Dropout(0.4),

      nn.Linear(500, 2)
    )

    model = pytk.PytkModuleWrapper(net)
    # add weights to optimizer due to class imbalance
    weights = torch.FloatTensor(class_counts) / total
    weights = weights.cuda() if torch.cuda.is_available() else weights.cpu()
    criterion = nn.CrossEntropyLoss(weights)
    optimizer = optim.Adam(params=model.parameters(), lr=0.001)
    model.compile(loss=criterion, optimizer=optimizer, metrics=['acc'])
    return model
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(images, labels, test_size=0.2, 
                                                    random_state=SEED, stratify=labels)
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.3, 
                                                  random_state=SEED, stratify=y_train)

print(f"X_train.shape: {X_train.shape} - y_train.shape: {y_train.shape} - class dist: {np.bincount(y_train)}\n" +
      f"X_val.shape: {X_val.shape} - y_val.shape: {y_val.shape} - class dist: {np.bincount(y_val)}\n" +
      f"X_test.shape: {X_test.shape} - y_test.shape: {y_test.shape} - class dist: {np.bincount(y_test)}") 

# y_train = np.expand_dims(y_train, axis=1)
# y_val = np.expand_dims(y_val, axis=1)
# y_test = np.expand_dims(y_test, axis=1)
# define a dataset for Pytorch
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms

class SmilesDataset(Dataset):
    def __init__(self, x, y, transforms=None):
        self.x = x
        self.y = torch.LongTensor(y)
        self.transforms = transforms

    def __len__(self):
        return len(self.x)

    def __getitem__(self, ix):
        x, y = self.x[ix], self.y[ix]
        if self.transforms is not None:
            x = self.transforms(x)
        return x, y

image_transforms = transforms.Compose([
    transforms.ToTensor()   
])
train_dataset = SmilesDataset(X_train, y_train, image_transforms)
val_dataset = SmilesDataset(X_val, y_val, image_transforms)
test_dataset = SmilesDataset(X_test, y_test, image_transforms)
trainloader = DataLoader(test_dataset, batch_size=32, shuffle=True)
data_iter = iter(trainloader)
sample_images, sample_labels = data_iter.next()
sample_images, sample_labels = sample_images.cpu().numpy(), sample_labels.cpu().numpy()
print(f"images.shape: {sample_images.shape} - labels.shape: {sample_labels.shape}")

#plt.figure(figsize=(5,5))
for i in range(32):
    plt.subplot(4, 8, i + 1)
    image = sample_images[i]
    #print(f"images[{i}].shape: {image.shape} ")
    image = image.transpose((1,2,0))
    #print(f" - AP: images[{i}].shape: {image.shape}")
    plt.imshow(image.squeeze(), cmap='gray')
    plt.axis('off')
plt.show()
plt.close()
model = build_model()
print(model.summary((1, 32, 32)))
hist = model.fit_dataset(train_dataset, epochs=20, batch_size=32, 
                         validation_dataset=val_dataset)
pytk.show_plots(hist, metric='acc', plot_title='Model Performance')
loss, acc = model.evaluate_dataset(train_dataset)
print(f"Training  -> loss: {loss:.3f} - acc: {acc:.3f}")
loss, acc = model.evaluate_dataset(val_dataset)
print(f"Cross-val -> loss: {loss:.3f} - acc: {acc:.3f}")
loss, acc = model.evaluate_dataset(test_dataset)
print(f"Testing   -> loss: {loss:.3f} - acc: {acc:.3f}")
model.save('./model_states/pytk_smiles.pt')
del model
model = build_model()
model.load('./model_states/pytk_smiles.pt')
print(model.summary((1, 32, 32)))
probs, y_test = model.predict_dataset(test_dataset)
y_pred = np.argmax(probs, axis=1)
y_test[:20], y_pred[:20]
print(f"We got {(y_test == y_pred).sum()} of {len(y_test)} correct!")
del model<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13005414122796913, 'fraction_numerical': 0.015551203778366548, 'mean_word_length': 3.757260273972603, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 8, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '129987', 'license': 'MIT', 'n_tokens_mistral': 3177, 'n_tokens_neox': 3056, 'n_words': 657, 'path': 'Pytorch - Smiles - Binary Classification.ipynb', 'repo_name': 'mjbhobe/dl-pytorch', 'size': '8681'}