Adding files
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# Mixed Precision Quantization
|
||||
|
||||
This directory provides details and examples on building and evaluating the performance (in terms of accuracy) of Quantized Neural Networks with Mixed-Precision variables. After completing the Design Space Exploration (DSE) and selecting the optimal solution, the necessary files for inference and simulation of each model on the modified Ibex core will be stored in the [inference_codes](https://github.com/alexmr09/Mixed-precision-Neural-Networks-on-RISC-V-Cores/tree/main/inference_codes) directory. The generated folder, named after the executed .py file, will contain the codes and parameters for both the optimized (with the new instructions) and the unoptimized (original RV32IMC) algorithms.
|
||||
|
||||
To start with, we will need to install all the necessary dependencies:
|
||||
|
||||
```
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
## File Description
|
||||
|
||||
- **setup.sh**: Sets up the paths and environmental variables for smoother execution of the programs.
|
||||
- **init_utils.py**: Reads the arguments passed.
|
||||
- **mpq_quantize.py**: Performs the training and evaluation of both full-precision and quantized models, as well as executes a DSE to find optimal solutions.
|
||||
- **pareto_sols.py**: For *Exhaustive* search, it identifies the Pareto optimal solutions and exports a PNG image of the Pareto space.
|
||||
- **configure_ibex.py**: Generates the header files that contain the network's parameters (weights, inputs and scale parameters) and the C program that defines its architecture.
|
||||
- **simulate_ibex.py**: For the examples included on the subfolders, we also provide scripts that simulate the behavior of the modified core, ensuring that the results from Python match those produced by Ibex.
|
||||
- **common.py**: Contains the main function, which is called after defining the model and dataset, and initiates the procedures described in the previous files.
|
||||
|
||||
|
||||
## Use Cases
|
||||
|
||||
On the subfolders of this repository, you can check some simple examples that demonstrate how to execute the steps for the DSE. To run them:
|
||||
|
||||
```
|
||||
source .venv/bin/activate
|
||||
source setup_env.sh
|
||||
python3 <folder_name>/<python_script_name>.py --max_acc_drop <max_acc_drop> --device <device>
|
||||
```
|
||||
|
||||
#### Parameters Explained
|
||||
|
||||
- **max_acc_drop**: This parameter allows the user to define the maximum acceptable accuracy degradation for the Quantized Model compared to the full-precision model. When specified, a Binary search algorithm is used to explore the design space. If this parameter is not defined, an Exhaustive search approach is employed (default).
|
||||
- **device**: Specifies the target device for running the model. The available options are CPU (default) and CUDA.
|
||||
|
||||
#### Exhaustive DSE example
|
||||
|
||||
```
|
||||
python3 elderly_fall/elderly_fall.py --device 'cuda'
|
||||
```
|
||||
|
||||
#### Binary DSE example
|
||||
|
||||
```
|
||||
python3 elderly_fall/elderly_fall.py --max_acc_drop 2 --device 'cpu'
|
||||
```
|
||||
|
||||
## How to test a new model
|
||||
|
||||
To use the provided scripts on a new model, follow these four simple steps:
|
||||
|
||||
1. **Initialization and setup**: At the beginning of your Python script, include the following lines to initialize the environment and retrieve arguments:
|
||||
|
||||
```python
|
||||
import init_utils
|
||||
import common
|
||||
|
||||
# Initialize the environment and get the name
|
||||
name = init_utils.initialize_environment(__file__)
|
||||
args = init_utils.get_args()
|
||||
```
|
||||
|
||||
2. **Import and Preprocess Dataset**: Import your dataset and preprocess it. Ensure to split it into training, testing, and (optionally) validation subsets. Save these subsets as numpy arrays.
|
||||
|
||||
3. **Model Architecture**: Define your neural network architecture. Ensure that activation functions for hidden layers are defined as separate layers (objects of the torch.nn library).
|
||||
|
||||
4. **Create and Run the Quantized Neural Network**: Create an instance of your neural network and call the *create_ibex_qnn* function from common.py.
|
||||
|
||||
```python
|
||||
common.create_ibex_qnn(net, name, device, X_train, y_train, X_test, y_test,
|
||||
X_val = X_val, y_val = y_val, BATCH_SIZE = BATCH_SIZE,
|
||||
epochs = epochs, lr = lr, max_acc_drop = max_acc_drop)
|
||||
```
|
||||
|
||||
#### Notes
|
||||
- The parameters for epochs and learning rate can be either single values or lists of numbers, allowing for fine-tuning of the model.
|
||||
- Currently, the provided scripts do not support models with Residual connections or Depthwise Convolutions.
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
import mpq_quantize
|
||||
import pareto_sols
|
||||
import configure_ibex
|
||||
import simulate_ibex
|
||||
import os
|
||||
import sys
|
||||
import numpy as np
|
||||
|
||||
def create_ibex_qnn(net, name, device, X_train, y_train, X_test, y_test, X_val = None, y_val = None,
|
||||
BATCH_SIZE = 32, epochs = 20, lr = 0.0001, max_acc_drop = None):
|
||||
|
||||
net = net.to(device)
|
||||
|
||||
in_shape = mpq_quantize.net_input_size(X_train)
|
||||
|
||||
macc_per_layer, _ = mpq_quantize.display_model_info(net, in_shape)
|
||||
|
||||
train_loader, val_loader, test_loader = mpq_quantize.create_dataloaders(BATCH_SIZE, X_train, y_train, X_test, y_test,
|
||||
X_val, y_val)
|
||||
|
||||
if isinstance(epochs, (int, float)):
|
||||
tr_epochs = [epochs]
|
||||
else:
|
||||
tr_epochs = epochs
|
||||
|
||||
if isinstance(lr, (int, float)):
|
||||
tr_lr = [lr]
|
||||
else:
|
||||
tr_lr = lr
|
||||
|
||||
if len(tr_epochs) != len(tr_lr):
|
||||
print("Error: The lengths of 'epochs' and 'lr' are not equal.")
|
||||
sys.exit(1)
|
||||
|
||||
print('\nFULL PRECISION MODEL TRAINING ...')
|
||||
|
||||
for i in range(len(tr_epochs)):
|
||||
print(f'Round No{i+1}')
|
||||
fp_net = mpq_quantize.fp_train(net, train_loader, val_loader, device,
|
||||
epochs = tr_epochs[i], lr = tr_lr[i])
|
||||
|
||||
fp_accuracy = mpq_quantize.fp_evaluate(net, test_loader, device)
|
||||
|
||||
weights_per_layer, total_macc_opt_sorted = mpq_quantize.create_weight_confs(macc_per_layer)
|
||||
|
||||
if(max_acc_drop is None):
|
||||
|
||||
quant_net, accuracy = mpq_quantize.dse(fp_net, max_acc_drop, weights_per_layer, fp_accuracy, train_loader,
|
||||
test_loader, val_loader, device, tr_epochs, tr_lr)
|
||||
|
||||
optimal_config = pareto_sols.pareto_space(fp_accuracy, accuracy, weights_per_layer, macc_per_layer,
|
||||
total_macc_opt_sorted, name)
|
||||
|
||||
layer_mapping = mpq_quantize.create_layer_mapping(optimal_config)
|
||||
|
||||
quant_net = mpq_quantize.Quant_Model(fp_net, optimal_config, layer_mapping,
|
||||
mpq_quantize.calculate_minimum(train_loader) >= 0)
|
||||
|
||||
quant_net = mpq_quantize.train_quant_model(quant_net, train_loader, val_loader, device, epochs, lr)
|
||||
|
||||
mpq_quantize.quant_net_evaluation(quant_net, test_loader, device)
|
||||
|
||||
else:
|
||||
quant_net, optimal_config = mpq_quantize.dse(fp_net, max_acc_drop, weights_per_layer, fp_accuracy, train_loader,
|
||||
test_loader, val_loader, device, tr_epochs, tr_lr)
|
||||
|
||||
print('\nCREATING FILES FOR SIMULATION ON IBEX CORE ...')
|
||||
mode_per_layer, layer_type = configure_ibex.decide_mode(fp_net,
|
||||
optimal_config, mpq_quantize.calculate_minimum(train_loader) >= 0)
|
||||
|
||||
int_weights, int_og_bias, int_biases, shift_biases, mul_vals, shift_vals = configure_ibex.get_int_params(quant_net)
|
||||
|
||||
input, padded_input, padded_w, padded_b, padded_sb_v, padded_mv, padded_sv = configure_ibex.pad_inputs_weights(quant_net,
|
||||
test_loader, mode_per_layer, int_weights,
|
||||
int_biases, shift_biases, mul_vals, shift_vals)
|
||||
|
||||
combined_input, new_int_w, new_int_b, shift_b, mul_v, shift_v = configure_ibex.concat_inputs_weights(mode_per_layer,
|
||||
padded_input, padded_w, padded_b,
|
||||
padded_sb_v, padded_mv, padded_sv)
|
||||
|
||||
optimized_path = '../inference_codes/' + name + '/optimized'
|
||||
original_path = '../inference_codes/' + name + '/original'
|
||||
|
||||
os.makedirs(optimized_path, exist_ok = True)
|
||||
os.makedirs(original_path, exist_ok = True)
|
||||
|
||||
configure_ibex.generate_Makefile(original_path, name)
|
||||
|
||||
if np.unique(layer_type)[0] =='Linear':
|
||||
configure_ibex.save_1d_inputs(original_path, input)
|
||||
configure_ibex.save_1d_inputs(optimized_path, combined_input)
|
||||
|
||||
configure_ibex.save_mlp_net_params(original_path, int_weights, int_og_bias, mul_vals, shift_vals)
|
||||
configure_ibex.save_mlp_net_params(optimized_path, new_int_w, new_int_b, mul_v, shift_v, shift_b)
|
||||
|
||||
configure_ibex.generate_og_c_code_mlp(original_path, name, int_weights, optimal_config, layer_type)
|
||||
configure_ibex.generate_opt_c_code_mlp(optimized_path, name, new_int_w, optimal_config, layer_type)
|
||||
|
||||
else:
|
||||
configure_ibex.save_2d_inputs(original_path, input)
|
||||
configure_ibex.save_2d_inputs(optimized_path, combined_input)
|
||||
|
||||
configure_ibex.save_cnn_net_params(original_path, int_weights, int_og_bias, mul_vals, shift_vals)
|
||||
configure_ibex.save_cnn_net_params(optimized_path, new_int_w, new_int_b, mul_v, shift_v, shift_b)
|
||||
|
||||
cnn_details = configure_ibex.get_cnn_details(fp_net)
|
||||
|
||||
configure_ibex.generate_og_c_code_cnn(original_path, name, input, cnn_details, int_weights)
|
||||
configure_ibex.generate_opt_c_code_cnn(optimized_path, name, combined_input, cnn_details,
|
||||
new_int_w, optimal_config)
|
||||
|
||||
print('FINISHED ...')
|
||||
|
||||
if(name == 'elderly_fall'):
|
||||
print('\nSIMULATING MODEL ON IBEX CORE\nUSE THE OUTPUTS TO VERIFY THAT THE RESULTS ARE CORRECT !!')
|
||||
ibex_model = simulate_ibex.create_fann_model(int_weights, int_og_bias, mul_vals, shift_vals)
|
||||
simulate_ibex.eval_sim_model(quant_net, ibex_model, test_loader)
|
||||
|
||||
elif(name == 'uci_fann_net'):
|
||||
print('\nSIMULATING MODEL ON IBEX CORE\nUSE THE OUTPUTS TO VERIFY THAT THE RESULTS ARE CORRECT !!')
|
||||
ibex_model = simulate_ibex.create_uci_model(int_weights, int_og_bias, mul_vals, shift_vals)
|
||||
simulate_ibex.eval_sim_model(quant_net, ibex_model, test_loader)
|
||||
|
||||
elif(name == 'lenet5_mnist'):
|
||||
print('\nSIMULATING MODEL ON IBEX CORE\nUSE THE OUTPUTS TO VERIFY THAT THE RESULTS ARE CORRECT !!')
|
||||
ibex_model = simulate_ibex.create_lenet_model(int_weights, int_og_bias, mul_vals, shift_vals)
|
||||
simulate_ibex.eval_sim_model(quant_net, ibex_model, test_loader)
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,84 @@
|
||||
import init_utils
|
||||
import common
|
||||
|
||||
# Initialize the environment and get the name
|
||||
name = init_utils.initialize_environment(__file__)
|
||||
args = init_utils.get_args()
|
||||
|
||||
# Set arguments from command line
|
||||
max_acc_drop = args.max_acc_drop
|
||||
device = args.device
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from sklearn.model_selection import train_test_split
|
||||
|
||||
# Load our Dataset
|
||||
|
||||
pelvis_acc = pd.read_excel('elderly_fall/Posterior Pelvis Accelerometer Measures.xlsx',
|
||||
sheet_name = 'ST')
|
||||
|
||||
y_data = pelvis_acc['Faller (1), Non-Faller (0)'].to_numpy()
|
||||
|
||||
pelvis_acc = pelvis_acc.drop(columns = [pelvis_acc.columns[0], 'Faller (1), Non-Faller (0)'])
|
||||
|
||||
head_acc = pd.read_excel('elderly_fall/Head Accelerometer Measures.xlsx',
|
||||
sheet_name = 'ST')
|
||||
|
||||
head_acc = head_acc.drop(columns = [head_acc.columns[0], 'Faller (1), Non-Faller (0)'])
|
||||
|
||||
|
||||
pressure_sen = pd.read_excel('elderly_fall/Pressure-Sensing Insole Measures.xlsx',
|
||||
sheet_name = 'ST')
|
||||
|
||||
pressure_sen = pressure_sen.drop(columns = [pressure_sen.columns[0], 'Faller (1), Non-Faller (0)'])
|
||||
|
||||
|
||||
left_shank = pd.read_excel('elderly_fall/Left Shank Accelerometer Measures.xlsx',
|
||||
sheet_name = 'ST')
|
||||
|
||||
left_shank = left_shank.drop(columns = [left_shank.columns[0], 'Faller (1), Non-Faller (0)'])
|
||||
|
||||
|
||||
X_data = pd.concat([head_acc, pressure_sen, pelvis_acc, left_shank], axis = 1)
|
||||
|
||||
# Preprocess the Data
|
||||
|
||||
def normalize_column(column):
|
||||
old_min = column.min()
|
||||
old_max = column.max()
|
||||
new_min = 0
|
||||
new_max = 255
|
||||
normalized_column = ((column - old_min) / (old_max - old_min)) * (new_max - new_min) + new_min
|
||||
return normalized_column.astype(int)
|
||||
|
||||
X_data = X_data.apply(normalize_column)
|
||||
X_d = X_data/255.0
|
||||
X_train, X_test, y_train, y_test = train_test_split(X_d, y_data,
|
||||
test_size = 0.15, random_state = 42)
|
||||
|
||||
X_train = X_train.to_numpy()
|
||||
X_test = X_test.to_numpy()
|
||||
|
||||
BATCH_SIZE = 1
|
||||
epochs = 5
|
||||
lr = 0.0001
|
||||
|
||||
class EF_MLP(nn.Module):
|
||||
def __init__(self):
|
||||
super(EF_MLP, self).__init__()
|
||||
self.linear1 = nn.Linear(117, 20)
|
||||
self.relu1 = nn.ReLU()
|
||||
self.linear2 = nn.Linear(20, 2)
|
||||
|
||||
def forward(self,X):
|
||||
X = self.relu1(self.linear1(X))
|
||||
X = self.linear2(X)
|
||||
return F.log_softmax(X, dim = 1)
|
||||
|
||||
net = EF_MLP()
|
||||
|
||||
common.create_ibex_qnn(net, name, device, X_train, y_train, X_test, y_test, BATCH_SIZE = BATCH_SIZE,
|
||||
epochs = epochs, lr = lr, max_acc_drop = max_acc_drop)
|
||||
@@ -0,0 +1,28 @@
|
||||
import os
|
||||
import warnings
|
||||
import argparse
|
||||
|
||||
def positive_float(value):
|
||||
f_value = float(value)
|
||||
if f_value < 0:
|
||||
raise argparse.ArgumentTypeError(f"{value} is not a positive float value")
|
||||
return f_value
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser(description = 'Run elderly fall detection script.')
|
||||
parser.add_argument('--max_acc_drop', type = positive_float, default = None,
|
||||
help = 'Maximum accuracy drop (default: None)')
|
||||
parser.add_argument('--device', type = str, choices = ['cpu', 'cuda'], default = 'cpu',
|
||||
help = 'Device to run the model on (default: cpu)')
|
||||
return parser.parse_args()
|
||||
|
||||
def initialize_environment(file_name):
|
||||
# Suppress specific warnings
|
||||
warnings.filterwarnings("ignore",
|
||||
message="Named tensors and all their associated APIs are an experimental feature and subject to change.")
|
||||
|
||||
# Get the file name without extension
|
||||
file_name = os.path.basename(file_name)
|
||||
name = file_name.split(".")[0]
|
||||
|
||||
return name
|
||||
@@ -0,0 +1,70 @@
|
||||
import init_utils
|
||||
import common
|
||||
|
||||
# Initialize the environment and get the name
|
||||
name = init_utils.initialize_environment(__file__)
|
||||
args = init_utils.get_args()
|
||||
|
||||
# Set arguments from command line
|
||||
max_acc_drop = args.max_acc_drop
|
||||
device = args.device
|
||||
|
||||
from sklearn.model_selection import train_test_split
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import tensorflow as tf
|
||||
import numpy as np
|
||||
|
||||
# Load our Dataset
|
||||
|
||||
(X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data()
|
||||
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size = 0.15)
|
||||
|
||||
X_train = np.expand_dims(X_train, axis = 1)
|
||||
X_test = np.expand_dims(X_test, axis = 1)
|
||||
X_val = np.expand_dims(X_val, axis = 1)
|
||||
|
||||
BATCH_SIZE = 32
|
||||
epochs = 1
|
||||
lr = 0.0001
|
||||
|
||||
class LeNet5(nn.Module):
|
||||
def __init__(self):
|
||||
super(LeNet5, self).__init__()
|
||||
self.conv1 = nn.Conv2d(in_channels = 1, out_channels = 6, kernel_size = 5, padding = 'same')
|
||||
self.relu1 = nn.ReLU()
|
||||
self.avg1 = nn.AvgPool2d(2,2)
|
||||
|
||||
self.conv2 = nn.Conv2d(in_channels = 6, out_channels = 16, kernel_size = 5)
|
||||
self.relu2 = nn.ReLU()
|
||||
self.avg2 = nn.AvgPool2d(2,2)
|
||||
|
||||
self.flatten = nn.Flatten()
|
||||
|
||||
self.linear1 = nn.Linear(400, 120)
|
||||
self.relu3 = nn.ReLU()
|
||||
|
||||
self.linear2 = nn.Linear(120, 84)
|
||||
self.relu4 = nn.ReLU()
|
||||
|
||||
self.linear3 = nn.Linear(84, 10)
|
||||
|
||||
def forward(self,X):
|
||||
X = self.relu1(self.conv1(X))
|
||||
X = self.avg1(X)
|
||||
|
||||
X = self.relu2(self.conv2(X))
|
||||
X = self.avg2(X)
|
||||
|
||||
X = self.flatten(X)
|
||||
|
||||
X = self.relu3(self.linear1(X))
|
||||
X = self.relu4(self.linear2(X))
|
||||
X = self.linear3(X)
|
||||
return F.log_softmax(X, dim = 1)
|
||||
|
||||
net = LeNet5()
|
||||
|
||||
common.create_ibex_qnn(net, name, device, X_train, y_train, X_test, y_test,
|
||||
X_val = X_val, y_val = y_val, BATCH_SIZE = BATCH_SIZE,
|
||||
epochs = epochs, lr = lr, max_acc_drop = max_acc_drop)
|
||||
@@ -0,0 +1,449 @@
|
||||
import numpy as np
|
||||
import sys
|
||||
|
||||
# Import Torch
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from torch.autograd import Variable
|
||||
import torch.nn.functional as F
|
||||
from torch import nn, optim
|
||||
|
||||
import brevitas.nn as qnn
|
||||
from brevitas.quant import *
|
||||
from torchinfo import summary
|
||||
|
||||
def net_input_size(X_train):
|
||||
example = X_train[0]
|
||||
if(len(np.shape(example)) == 1 or (len(np.shape(example)) == 3)):
|
||||
example = np.expand_dims(example, axis = 0)
|
||||
else:
|
||||
example = np.expand_dims(example, axis = (0,1))
|
||||
return np.shape(example)
|
||||
|
||||
def display_model_info(model, input_size):
|
||||
a = summary(model, input_size, col_names = ("output_size", "num_params", "mult_adds"))
|
||||
model_params_str = str(a)
|
||||
|
||||
lines = model_params_str.split('\n')
|
||||
lines_with_macc = []
|
||||
for line in lines:
|
||||
if(line.startswith('├─') or line.startswith('│')):
|
||||
lines_with_macc.append(line)
|
||||
|
||||
type_of_layer = []
|
||||
macc_per_layer = []
|
||||
|
||||
for l in lines_with_macc:
|
||||
k = l.split()[-1]
|
||||
if(k != '--'):
|
||||
type_of_layer.append(l.split()[0].replace(':','').replace('├─',''))
|
||||
macc_per_layer.append(int(l.split()[-1].replace(',','')))
|
||||
else:
|
||||
type_of_layer.append(l.split()[0].replace(':','').replace('├─',''))
|
||||
|
||||
return macc_per_layer, type_of_layer
|
||||
|
||||
def create_dataloaders(BATCH_SIZE, X_train, y_train, X_test, y_test,
|
||||
X_val = None, y_val = None):
|
||||
|
||||
if(X_val is None):
|
||||
torch_X_train = torch.from_numpy(X_train).type(torch.FloatTensor)
|
||||
torch_y_train = torch.from_numpy(y_train).type(torch.LongTensor)
|
||||
|
||||
# Create feature and targets tensor for test set.
|
||||
|
||||
torch_X_test = torch.from_numpy(X_test).type(torch.FloatTensor)
|
||||
torch_y_test = torch.from_numpy(y_test).type(torch.LongTensor)
|
||||
|
||||
train = torch.utils.data.TensorDataset(torch_X_train, torch_y_train)
|
||||
test = torch.utils.data.TensorDataset(torch_X_test, torch_y_test)
|
||||
|
||||
# Data Loaders
|
||||
train_loader = torch.utils.data.DataLoader(train, batch_size = BATCH_SIZE, shuffle = True)
|
||||
test_loader = torch.utils.data.DataLoader(test, batch_size = BATCH_SIZE, shuffle = False)
|
||||
val_loader = None
|
||||
|
||||
else:
|
||||
torch_X_train = torch.from_numpy(X_train).type(torch.FloatTensor)
|
||||
torch_y_train = torch.from_numpy(y_train).type(torch.LongTensor)
|
||||
|
||||
torch_X_test = torch.from_numpy(X_test).type(torch.FloatTensor)
|
||||
torch_y_test = torch.from_numpy(y_test).type(torch.LongTensor)
|
||||
|
||||
torch_X_val = torch.from_numpy(X_val).type(torch.FloatTensor)
|
||||
torch_y_val = torch.from_numpy(y_val).type(torch.LongTensor)
|
||||
|
||||
train = torch.utils.data.TensorDataset(torch_X_train, torch_y_train)
|
||||
test = torch.utils.data.TensorDataset(torch_X_test, torch_y_test)
|
||||
val = torch.utils.data.TensorDataset(torch_X_val, torch_y_val)
|
||||
|
||||
# Data Loaders
|
||||
train_loader = torch.utils.data.DataLoader(train, batch_size = BATCH_SIZE, shuffle = True)
|
||||
test_loader = torch.utils.data.DataLoader(test, batch_size = BATCH_SIZE, shuffle = False)
|
||||
val_loader = torch.utils.data.DataLoader(val, batch_size = BATCH_SIZE, shuffle = True)
|
||||
|
||||
|
||||
return train_loader, val_loader, test_loader
|
||||
|
||||
# Function to calculate the minimum value of a DataLoader
|
||||
def calculate_minimum(dataloader):
|
||||
global_min = float('inf')
|
||||
for batch in dataloader:
|
||||
inputs, _ = batch
|
||||
batch_min = inputs.min().item()
|
||||
if batch_min < global_min:
|
||||
global_min = batch_min
|
||||
return global_min
|
||||
|
||||
def fp_train(net, train_loader, val_loader = None, device = 'cpu', epochs = 20, lr = 0.0001):
|
||||
|
||||
criterion = nn.CrossEntropyLoss()
|
||||
optimizer = optim.Adam(net.parameters(), lr = lr)
|
||||
|
||||
patience = 10
|
||||
best_val_loss = float('inf')
|
||||
train_losses, val_losses = [], []
|
||||
|
||||
net = net.to(device)
|
||||
for e in range(epochs):
|
||||
running_loss = 0
|
||||
for images, labels in train_loader:
|
||||
images, labels = images.to(device), labels.to(device)
|
||||
# Prevent accumulation of gradients
|
||||
optimizer.zero_grad()
|
||||
# Make predictions
|
||||
log_ps = net(images.float())
|
||||
loss = criterion(log_ps, labels)
|
||||
# Backpropagation
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
running_loss += loss.item()
|
||||
|
||||
val_loss = 0
|
||||
accuracy = 0
|
||||
|
||||
# Turn off gradients for validation, to save memory and computations
|
||||
with torch.no_grad():
|
||||
net.eval()
|
||||
if(val_loader != None):
|
||||
for images, labels in val_loader:
|
||||
images, labels = images.to(device), labels.to(device)
|
||||
log_ps = net(images.float())
|
||||
val_loss += criterion(log_ps, labels)
|
||||
|
||||
ps = torch.exp(log_ps)
|
||||
# Get top predictions
|
||||
_, top_class = ps.topk(1, dim=1)
|
||||
equals = top_class == labels.view(*top_class.shape)
|
||||
accuracy += torch.mean(equals.type(torch.FloatTensor))
|
||||
|
||||
net.train()
|
||||
|
||||
train_losses.append(running_loss/len(train_loader))
|
||||
|
||||
if(val_loader != None):
|
||||
val_losses.append(val_loss/len(val_loader))
|
||||
print(f"Epoch {e+1}/{epochs}.. "
|
||||
f"Train loss: {train_losses[-1]:.3f}.. "
|
||||
f"Validation loss: {val_losses[-1]:.3f}.. "
|
||||
f"Validation accuracy: {accuracy/len(val_loader):.3f}")
|
||||
|
||||
# Check for early stopping
|
||||
avg_val_loss = val_loss/len(val_loader)
|
||||
if avg_val_loss < best_val_loss:
|
||||
best_val_loss = avg_val_loss
|
||||
counter = 0
|
||||
else:
|
||||
counter += 1
|
||||
|
||||
if counter >= patience:
|
||||
break
|
||||
else:
|
||||
print(f"Epoch {e+1}/{epochs}.. "
|
||||
f"Train loss: {train_losses[-1]:.3f}.. ")
|
||||
|
||||
return net
|
||||
|
||||
def fp_evaluate(net, test_loader, device):
|
||||
print('\nFULL PRECISION MODEL EVALUATION ...')
|
||||
# Turn off gradients for validation
|
||||
with torch.no_grad():
|
||||
net.eval()
|
||||
correct = 0
|
||||
y_size = 0
|
||||
for test_imgs, test_labels in test_loader:
|
||||
test_imgs, test_labels = test_imgs.to(device), test_labels.to(device)
|
||||
test_imgs = Variable(test_imgs).float()
|
||||
output = net(test_imgs)
|
||||
predicted = torch.max(output,1)[1]
|
||||
correct += (predicted == test_labels).sum()
|
||||
y_size += len(test_labels)
|
||||
print("Test accuracy: {:.3f}% ".format(100*float(correct)/(y_size)))
|
||||
floating_acc = 100*float(correct)/y_size
|
||||
return floating_acc
|
||||
|
||||
|
||||
def generate_sequences(length, values = [2, 4, 8]):
|
||||
sequences = []
|
||||
|
||||
def generate_sequence_helper(seq):
|
||||
if len(seq) == length:
|
||||
sequences.append(seq)
|
||||
return
|
||||
|
||||
for value in values:
|
||||
generate_sequence_helper(seq + [value])
|
||||
|
||||
generate_sequence_helper([])
|
||||
|
||||
return sequences
|
||||
|
||||
def create_weight_confs(macc_per_layer):
|
||||
total_macc_opt = []
|
||||
weights_per_layer = generate_sequences(len(macc_per_layer))
|
||||
|
||||
for w_conf in weights_per_layer:
|
||||
macc = 0
|
||||
for i, w in enumerate(w_conf):
|
||||
if(w == 2):
|
||||
macc += macc_per_layer[i]/16
|
||||
elif(w == 4):
|
||||
macc += macc_per_layer[i]/8
|
||||
else:
|
||||
macc += macc_per_layer[i]/4
|
||||
|
||||
total_macc_opt.append(np.round(macc))
|
||||
|
||||
# Get the indexes in descending order based on the values
|
||||
|
||||
sorted_indexes = sorted(enumerate(total_macc_opt), key=lambda x: x[1])
|
||||
|
||||
# Extract the sorted indexes
|
||||
|
||||
ascending_indexes = [index for index, _ in sorted_indexes]
|
||||
|
||||
weights_per_layer = [weights_per_layer[i] for i in ascending_indexes]
|
||||
|
||||
total_macc_opt_sorted = [total_macc_opt[i] for i in ascending_indexes]
|
||||
|
||||
return weights_per_layer, total_macc_opt_sorted
|
||||
|
||||
|
||||
# Define a mapping from PyTorch layers to Brevitas layers
|
||||
def create_layer_mapping(bit_width):
|
||||
mapping = {
|
||||
nn.Conv2d: lambda layer, bw: qnn.QuantConv2d(in_channels = layer.in_channels,
|
||||
out_channels = layer.out_channels,
|
||||
kernel_size = layer.kernel_size,
|
||||
stride = layer.stride[0],
|
||||
padding = layer.padding,
|
||||
bias = True,
|
||||
cache_inference_bias = True,
|
||||
bias_quant = Int32Bias,
|
||||
weight_bit_width = bw,
|
||||
weight_quant = Int8WeightPerTensorFloat),
|
||||
|
||||
nn.Linear: lambda layer, bw: qnn.QuantLinear(in_features = layer.in_features,
|
||||
out_features = layer.out_features,
|
||||
cache_inference_bias = True,
|
||||
weight_quant = Int8WeightPerTensorFloat,
|
||||
bias_quant = Int32Bias,
|
||||
bias = True,
|
||||
weight_bit_width = bw),
|
||||
|
||||
nn.ReLU: lambda _, bw: qnn.QuantReLU(bit_width = bw,
|
||||
return_quant_tensor = True),
|
||||
|
||||
nn.MaxPool2d: lambda layer, _: qnn.QuantMaxPool2d(kernel_size = layer.kernel_size,
|
||||
stride = layer.stride,
|
||||
padding = layer.padding,
|
||||
return_quant_tensor = True),
|
||||
|
||||
nn.AvgPool2d: lambda layer, _: qnn.TruncAvgPool2d(kernel_size = layer.kernel_size,
|
||||
stride = layer.stride,
|
||||
padding = layer.padding,
|
||||
return_quant_tensor = True),
|
||||
}
|
||||
|
||||
return mapping
|
||||
|
||||
# Function to convert a PyTorch layer to a Brevitas layer with a specified bit width
|
||||
def convert_layer(layer, bit_width, layer_mapping):
|
||||
layer_type = type(layer)
|
||||
if layer_type in layer_mapping:
|
||||
return layer_mapping[layer_type](layer, bit_width)
|
||||
else:
|
||||
return layer
|
||||
|
||||
# Function to convert a PyTorch model to a Brevitas model
|
||||
def convert_model(module, bit_widths, layer_mapping):
|
||||
layer_idx = [0]
|
||||
brevitas_module = nn.Sequential()
|
||||
|
||||
for name, layer in module.named_children():
|
||||
if list(layer.children()): # If the layer has children, recurse
|
||||
brevitas_module.add_module(name, convert_model(layer, bit_widths, layer_mapping))
|
||||
else:
|
||||
layer_type = type(layer)
|
||||
if layer_type in [nn.Conv2d, nn.Linear]:
|
||||
bit_width = bit_widths[layer_idx[0]]
|
||||
layer_idx[0] += 1
|
||||
else:
|
||||
bit_width = 8
|
||||
brevitas_module.add_module(name, convert_layer(layer, bit_width, layer_mapping))
|
||||
return brevitas_module
|
||||
|
||||
class Quant_Model(nn.Module):
|
||||
def __init__(self, og_model, w, layer_mapping, input_sign = True):
|
||||
super(Quant_Model, self).__init__()
|
||||
if(input_sign):
|
||||
self.quant_inp = qnn.QuantIdentity(bit_width = 8, return_quant_tensor = True,
|
||||
act_quant = Uint8ActPerTensorFloat)
|
||||
|
||||
else:
|
||||
self.quant_inp = qnn.QuantIdentity(bit_width = 8, return_quant_tensor = True,
|
||||
act_quant = Int8ActPerTensorFloat)
|
||||
|
||||
self.sequential = convert_model(og_model, w, layer_mapping)
|
||||
self.o_quant = qnn.QuantIdentity(bit_width = 8, return_quant_tensor = True)
|
||||
|
||||
def forward(self, X):
|
||||
X = self.quant_inp(X)
|
||||
X = self.sequential(X)
|
||||
X = self.o_quant(X)
|
||||
return F.log_softmax(X, dim = 1)
|
||||
|
||||
def train_quant_model(quant_net, train_loader, val_loader = None, device = 'cpu',
|
||||
epochs = 20, lr = 0.0001):
|
||||
|
||||
criterion = nn.CrossEntropyLoss()
|
||||
optimizer = optim.Adam(quant_net.parameters(), lr = lr)
|
||||
|
||||
patience = 10
|
||||
best_val_loss = float('inf')
|
||||
|
||||
for e in range(epochs):
|
||||
running_loss = 0
|
||||
for images, labels in train_loader:
|
||||
images, labels = images.to(device), labels.to(device)
|
||||
# Prevent accumulation of gradients
|
||||
optimizer.zero_grad()
|
||||
# Make predictions
|
||||
log_ps = quant_net(images.float())
|
||||
loss = criterion(log_ps, labels)
|
||||
#backprop
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
running_loss += loss.item()
|
||||
|
||||
val_loss = 0
|
||||
accuracy = 0
|
||||
|
||||
# Turn off gradients for validation
|
||||
with torch.no_grad():
|
||||
quant_net.eval()
|
||||
if(val_loader != None):
|
||||
for images, labels in val_loader:
|
||||
images, labels = images.to(device), labels.to(device)
|
||||
log_ps = quant_net(images.float())
|
||||
val_loss += criterion(log_ps, labels)
|
||||
|
||||
ps = torch.exp(log_ps)
|
||||
# Get our top predictions
|
||||
top_p, top_class = ps.topk(1, dim=1)
|
||||
equals = top_class == labels.view(*top_class.shape)
|
||||
accuracy += torch.mean(equals.type(torch.FloatTensor))
|
||||
|
||||
if(val_loader != None):
|
||||
# Check for early stopping
|
||||
avg_val_loss = val_loss/len(val_loader)
|
||||
if avg_val_loss < best_val_loss:
|
||||
best_val_loss = avg_val_loss
|
||||
counter = 0
|
||||
else:
|
||||
counter += 1
|
||||
|
||||
if counter >= patience:
|
||||
break
|
||||
|
||||
quant_net.train()
|
||||
|
||||
return quant_net
|
||||
|
||||
def quant_net_evaluation(quant_net, test_loader, device = 'cpu'):
|
||||
with torch.no_grad():
|
||||
quant_net.eval()
|
||||
correct = 0
|
||||
y_size = 0
|
||||
for test_imgs, test_labels in test_loader:
|
||||
test_imgs, test_labels = test_imgs.to(device), test_labels.to(device)
|
||||
test_imgs = Variable(test_imgs).float()
|
||||
output = quant_net(test_imgs)
|
||||
predicted = torch.max(output, 1)[1]
|
||||
correct += (predicted == test_labels).sum()
|
||||
y_size += len(test_labels)
|
||||
print("Test accuracy: {:.3f}% ".format(100*float(correct)/y_size))
|
||||
return 100 * float(correct)/y_size
|
||||
|
||||
def dse(og_model, max_acc_drop, weights_per_layer, fp_accuracy, train_loader, test_loader, val_loader = None,
|
||||
device = 'cpu', epochs = 5, lr = 0.0001):
|
||||
|
||||
sign = calculate_minimum(train_loader) >= 0
|
||||
|
||||
if max_acc_drop is not None:
|
||||
print('\nDSE STARTING ... BINARY SEARCH')
|
||||
opt_found = 0
|
||||
low = 0
|
||||
high = len(weights_per_layer) - 1
|
||||
while low <= high:
|
||||
mid = (low + high) // 2
|
||||
w = weights_per_layer[mid]
|
||||
|
||||
# Create and train the quantized network
|
||||
layer_mapping = create_layer_mapping(w)
|
||||
quant_net = Quant_Model(og_model, w, layer_mapping, sign)
|
||||
quant_net = quant_net.to(device)
|
||||
print(f'==========================\nEvaluating Configuration: {mid} --> Weights: {w}')
|
||||
|
||||
for i in range(len(epochs)):
|
||||
quant_net = train_quant_model(quant_net, train_loader, val_loader, device,
|
||||
epochs = epochs[i], lr = lr[i])
|
||||
|
||||
# Evaluate the trained quantized network
|
||||
accuracy = quant_net_evaluation(quant_net, test_loader, device)
|
||||
|
||||
# Check if the accuracy drop is within the acceptable range
|
||||
if fp_accuracy - accuracy <= max_acc_drop:
|
||||
opt_found = 1
|
||||
optimal_quant_net = quant_net
|
||||
optimal_config = w
|
||||
high = mid - 1 # Try to find a less complex configuration that meets the criteria
|
||||
else:
|
||||
low = mid + 1 # Too much accuracy loss, look for a more complex configuration
|
||||
|
||||
quant_net = optimal_quant_net
|
||||
|
||||
if(opt_found == 0):
|
||||
print("No solution that meets user's criteria was found !!")
|
||||
optimal_config = w
|
||||
|
||||
return quant_net, optimal_config
|
||||
|
||||
else: # Exhaustive Search for optimal solutions & to create Pareto Space for the specific Model
|
||||
print('\nDSE STARTING ... EXHAUSTIVE SEARCH')
|
||||
test_accuracy = []
|
||||
for i, w in enumerate(weights_per_layer):
|
||||
layer_mapping = create_layer_mapping(w)
|
||||
quant_net = Quant_Model(og_model, w, layer_mapping, sign)
|
||||
quant_net = quant_net.to(device)
|
||||
print(f'===================================\nModel No {i} --> {w}')
|
||||
for i in range(len(epochs)):
|
||||
quant_net = train_quant_model(quant_net, train_loader, val_loader, device,
|
||||
epochs = epochs[i], lr = lr[i])
|
||||
accuracy = quant_net_evaluation(quant_net, test_loader, device)
|
||||
test_accuracy.append(accuracy)
|
||||
|
||||
return quant_net, test_accuracy
|
||||
@@ -0,0 +1,158 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.ticker as ticker
|
||||
from brokenaxes import brokenaxes
|
||||
|
||||
def find_pareto_optimal_points(latencies, mpq_accuracies, mpq_confs):
|
||||
"""
|
||||
Finds the Pareto optimal points given latencies and accuracies.
|
||||
|
||||
Args:
|
||||
latencies (numpy.ndarray): An array of latency values.
|
||||
accuracies (numpy.ndarray): An array of accuracy values.
|
||||
mpq_confs (numpy.ndarray): An array with the sorted weight_bit_width per layer list based on
|
||||
estimated latency
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing the following:
|
||||
- numpy.ndarray: Array of Pareto optimal points (Latency, Accuracy).
|
||||
- numpy.ndarray: Indices of Pareto optimal accuracies in the original array.
|
||||
"""
|
||||
data = {
|
||||
'Configuration': [mpq_confs[idx].astype(int) for idx in range(len(mpq_confs))],
|
||||
'Accuracy': mpq_accuracies,
|
||||
'MACC Instrs': latencies
|
||||
}
|
||||
|
||||
# Combine latencies, accuracies, and indices
|
||||
combined = np.column_stack((latencies, mpq_accuracies, np.arange(len(mpq_accuracies))))
|
||||
|
||||
# Initialize the Pareto front with the first point
|
||||
pareto_front = [combined[0]]
|
||||
for pair in combined[1:]:
|
||||
# Check if the accuracy of the current pair is better than the last point in the Pareto front
|
||||
if pair[1] > pareto_front[-1][1]:
|
||||
pareto_front.append(pair)
|
||||
|
||||
# Extract Pareto points and indices
|
||||
pareto_points_with_indices = np.array(pareto_front)
|
||||
pareto_points = pareto_points_with_indices[:, :2]
|
||||
pareto_indices = pareto_points_with_indices[:, 2].astype(int)
|
||||
|
||||
# Set print options for clarity
|
||||
np.set_printoptions(precision = 2, suppress = True)
|
||||
|
||||
# Create DataFrame
|
||||
data = {
|
||||
'Index': pareto_indices,
|
||||
'Weights Resolutions': [mpq_confs[idx].astype(int) for idx in pareto_indices],
|
||||
'Accuracy': [point[1] for point in pareto_points],
|
||||
'MACC Instrs': [point[0] for point in pareto_points]
|
||||
}
|
||||
|
||||
df = pd.DataFrame(data)
|
||||
|
||||
return df, pareto_points, pareto_indices
|
||||
|
||||
def identify_best_solution(pareto_solutions_df, fp_accuracy, maximum_acc_loss = 1):
|
||||
threshold = fp_accuracy - maximum_acc_loss
|
||||
filtered_df = pareto_solutions_df[pareto_solutions_df['Accuracy'] >= threshold]
|
||||
filtered_df = filtered_df.sort_values(by = 'MACC Instrs', ascending=True)
|
||||
|
||||
if not filtered_df.empty:
|
||||
best_config = filtered_df.iloc[0] # Get the first row
|
||||
print(f"\nBest Configuration Details with Maximum Accuracy loss < {maximum_acc_loss}%:")
|
||||
print(f"Index: {best_config['Index']}")
|
||||
print(f"Weights Resolutions: {best_config['Weights Resolutions']}")
|
||||
print(f"Accuracy: {best_config['Accuracy']:.2f}%")
|
||||
print(f"MACC Instrs: {best_config['MACC Instrs']}")
|
||||
else:
|
||||
print("No configurations meet the specified accuracy threshold.")
|
||||
|
||||
return best_config
|
||||
|
||||
def plot_pareto_solutions(mpq_mac, fp_mac_instr, accuracy,
|
||||
fp_accuracy, pareto_points, name):
|
||||
"""
|
||||
Plots Pareto solutions based on MAC instructions, accuracy, and other parameters.
|
||||
|
||||
Args:
|
||||
mpq_mac (numpy.ndarray): Array of MAC instructions for mixed precision layer configurations.
|
||||
fp_mac_instr (float): Original (non-optimized) model's MAC instructions.
|
||||
accuracy (numpy.ndarray): Array of accuracy values for mixed precision layer configurations.
|
||||
fp_accuracy (float): Original (non-optimized) model's accuracy.
|
||||
pareto_points (numpy.ndarray): Array of Pareto optimal points (Latency, Accuracy).
|
||||
|
||||
Returns:
|
||||
None: Displays the plot and saves it as a PNG file.
|
||||
"""
|
||||
# Define the x-axis and y-axis ranges
|
||||
|
||||
ranges = [(0, max(mpq_mac) + 0.5 * max(mpq_mac)),
|
||||
(fp_mac_instr - 0.15 * fp_mac_instr, fp_mac_instr + 0.15 * fp_mac_instr)]
|
||||
|
||||
# Calculate the exponent for notation
|
||||
exponent = int(np.log10(fp_mac_instr))
|
||||
notation = 10 ** exponent
|
||||
|
||||
# Create the figure and broken axes
|
||||
fig = plt.figure(figsize = (6, 4))
|
||||
fig.subplots_adjust(left = 0.148, right = 0.88, top = 0.95, bottom = 0.2)
|
||||
bax = brokenaxes(xlims = ranges, hspace = 0.05)
|
||||
|
||||
# Scatter plot for original model, mixed precision, and Pareto points
|
||||
bax.scatter(fp_mac_instr, fp_accuracy, color = 'black', s = 9, marker = '*',
|
||||
label = 'original model')
|
||||
|
||||
bax.scatter(mpq_mac, accuracy, color = 'gray', marker = 'o', s = 9,
|
||||
label = 'mixed configs')
|
||||
|
||||
bax.scatter(pareto_points[:, 0], pareto_points[:, 1], color = 'green', marker = 's', s = 9,
|
||||
label = 'pareto points')
|
||||
|
||||
# Customize the plot
|
||||
bax.set_ylabel('Accuracy (%)', fontsize = 10, labelpad = 35)
|
||||
bax.grid(True)
|
||||
|
||||
# Set the y-axis limit if provided
|
||||
|
||||
y_lims = [max(50, 0.8 * np.min(accuracy)), min(1.1 * fp_accuracy, 100)]
|
||||
bax.set_ylim(y_lims[0], y_lims[1])
|
||||
|
||||
# Place the legend
|
||||
bax.legend(loc = 'upper center', bbox_to_anchor = (0.5, 1.2), ncol = 3, fontsize = 10)
|
||||
|
||||
# Format x-axis labels
|
||||
sFormatter1 = ticker.FuncFormatter(lambda x, _: '{:0.2f}'.format(x / notation))
|
||||
sFormatter2 = ticker.ScalarFormatter(useOffset = False, useMathText = True)
|
||||
sFormatter2.set_powerlimits((0, 2))
|
||||
|
||||
bax.axs[0].xaxis.set_major_formatter(sFormatter1)
|
||||
bax.axs[1].xaxis.set_major_formatter(sFormatter2)
|
||||
|
||||
bax.axs[0].tick_params(axis = 'y', labelsize = 10)
|
||||
bax.axs[0].tick_params(axis = 'x', labelsize = 10)
|
||||
bax.axs[1].tick_params(axis = 'x', labelsize = 10)
|
||||
|
||||
bax.axs[1].xaxis.get_offset_text().set_position((1, 0))
|
||||
bax.axs[1].xaxis.get_offset_text().set_fontsize(10)
|
||||
|
||||
# Reposition the x-axis label
|
||||
bax.set_xlabel('MAC Instructions', ha = 'center', fontsize = 10, labelpad = 25)
|
||||
|
||||
plt.savefig(name + ".png")
|
||||
|
||||
def pareto_space(fp_accuracy, test_accuracy, weights_per_layer, macc_per_layer, total_macc_opt_sorted, name):
|
||||
df, pareto_points, _ = find_pareto_optimal_points(np.array(total_macc_opt_sorted),
|
||||
np.array(test_accuracy), np.array(weights_per_layer))
|
||||
|
||||
optimal_config = identify_best_solution(df, np.array(fp_accuracy))
|
||||
|
||||
plot_pareto_solutions(np.array(total_macc_opt_sorted),
|
||||
sum(macc_per_layer), np.array(test_accuracy),
|
||||
float(fp_accuracy), pareto_points, name)
|
||||
|
||||
optimal_config = optimal_config.iloc[1]
|
||||
|
||||
return optimal_config.tolist()
|
||||
@@ -0,0 +1,12 @@
|
||||
# Install the latest version of Brevitas for the QAT of the networks
|
||||
git+https://github.com/Xilinx/brevitas
|
||||
|
||||
numpy
|
||||
pandas
|
||||
torch
|
||||
matplotlib
|
||||
brokenaxes
|
||||
torchinfo
|
||||
tensorflow
|
||||
scikit-learn
|
||||
openpyxl
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Set environment variables
|
||||
export PYTHONPATH="$PWD:$PYTHONPATH"
|
||||
export TF_ENABLE_ONEDNN_OPTS="0"
|
||||
@@ -0,0 +1,220 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.autograd import Variable
|
||||
|
||||
class Ibex_FANN(nn.Module):
|
||||
def __init__(self, mul_vals, shift_vals):
|
||||
super(Ibex_FANN, self).__init__()
|
||||
self.m0 = mul_vals[0]
|
||||
self.m1 = mul_vals[1]
|
||||
|
||||
self.s0 = shift_vals[0] + 7
|
||||
self.s1 = shift_vals[1] + 7
|
||||
|
||||
self.linear1 = nn.Linear(117, 20, bias = True)
|
||||
self.linear2 = nn.Linear(20, 2, bias = True)
|
||||
|
||||
def forward(self, X, print_out = False):
|
||||
X = self.linear1(X)
|
||||
X = torch.mul(X, self.m0)
|
||||
X = torch.add(X, torch.bitwise_left_shift(torch.tensor(1), self.s0 - 1)).type(torch.LongTensor)
|
||||
X = torch.bitwise_right_shift(X, self.s0).type(torch.FloatTensor)
|
||||
X = torch.clamp(X, min = 0, max = 255).type(torch.FloatTensor)
|
||||
|
||||
X = self.linear2(X)
|
||||
X = torch.mul(X, self.m1)
|
||||
X = torch.add(X, torch.bitwise_left_shift(torch.tensor(1), self.s1 - 1)).type(torch.LongTensor)
|
||||
X = torch.bitwise_right_shift(X, self.s1)
|
||||
X = torch.clamp(X, min = 0, max = 255).type(torch.FloatTensor)
|
||||
|
||||
if(print_out):
|
||||
print(X)
|
||||
|
||||
return X
|
||||
|
||||
class Ibex_UCI_MLP(nn.Module):
|
||||
def __init__(self, mul_vals, shift_vals):
|
||||
super(Ibex_UCI_MLP, self).__init__()
|
||||
self.m0 = mul_vals[0]
|
||||
self.m1 = mul_vals[1]
|
||||
self.m2 = mul_vals[2]
|
||||
self.m3 = mul_vals[3]
|
||||
|
||||
self.s0 = shift_vals[0] + 7
|
||||
self.s1 = shift_vals[1] + 7
|
||||
self.s2 = shift_vals[2] + 7
|
||||
self.s3 = shift_vals[3] + 7
|
||||
|
||||
self.fc0 = nn.Linear(76, 300, bias = True)
|
||||
self.fc1 = nn.Linear(300, 200, bias = True)
|
||||
self.fc2 = nn.Linear(200, 100, bias = True)
|
||||
self.fc3 = nn.Linear(100, 10, bias = True)
|
||||
|
||||
def forward(self, X, print_out = False):
|
||||
X = self.fc0(X)
|
||||
X = torch.mul(X, self.m0)
|
||||
X = torch.add(X, torch.bitwise_left_shift(torch.tensor(1), self.s0 -1)).type(torch.LongTensor)
|
||||
X = torch.bitwise_right_shift(X, self.s0).type(torch.FloatTensor)
|
||||
X = torch.clamp(X, min = 0, max = 255).type(torch.FloatTensor)
|
||||
|
||||
X = self.fc1(X)
|
||||
X = torch.mul(X, self.m1)
|
||||
X = torch.add(X, torch.bitwise_left_shift(torch.tensor(1),self.s1 -1)).type(torch.LongTensor)
|
||||
X = torch.bitwise_right_shift(X, self.s1)
|
||||
X = torch.clamp(X, min = 0, max = 255).type(torch.FloatTensor)
|
||||
|
||||
X = self.fc2(X)
|
||||
X = torch.mul(X, self.m2)
|
||||
X = torch.add(X, torch.bitwise_left_shift(torch.tensor(1),self.s2 -1)).type(torch.LongTensor)
|
||||
X = torch.bitwise_right_shift(X, self.s2)
|
||||
X = torch.clamp(X, min = 0, max = 255).type(torch.FloatTensor)
|
||||
|
||||
X = self.fc3(X)
|
||||
X = torch.mul(X, self.m3)
|
||||
X = torch.add(X, torch.bitwise_left_shift(torch.tensor(1),self.s3 -1)).type(torch.LongTensor)
|
||||
X = torch.bitwise_right_shift(X, self.s3)
|
||||
X = torch.clamp(X, min = 0, max = 255).type(torch.FloatTensor)
|
||||
|
||||
if(print_out):
|
||||
print(X[0])
|
||||
|
||||
return X
|
||||
|
||||
class Ibex_Lenet5(nn.Module):
|
||||
def __init__(self, mul_vals, shift_vals):
|
||||
super(Ibex_Lenet5, self).__init__()
|
||||
|
||||
self.m0 = mul_vals[0]
|
||||
self.m1 = mul_vals[1]
|
||||
self.m2 = mul_vals[2]
|
||||
self.m3 = mul_vals[3]
|
||||
self.m4 = mul_vals[4]
|
||||
|
||||
self.s0 = shift_vals[0] + 7
|
||||
self.s1 = shift_vals[1] + 7
|
||||
self.s2 = shift_vals[2] + 7
|
||||
self.s3 = shift_vals[3] + 7
|
||||
self.s4 = shift_vals[4] + 7
|
||||
|
||||
self.conv1 = nn.Conv2d(in_channels = 1, out_channels = 6, kernel_size = 5, padding= 'same')
|
||||
|
||||
self.avg1 = nn.AvgPool2d(2,2)
|
||||
|
||||
self.conv2 = nn.Conv2d(in_channels = 6, out_channels = 16, kernel_size = 5)
|
||||
self.avg2 = nn.AvgPool2d(2,2)
|
||||
|
||||
self.fc1 = nn.Linear(400, 120)
|
||||
self.fc2 = nn.Linear(120, 84)
|
||||
self.fc3 = nn.Linear(84, 10)
|
||||
|
||||
def forward(self, X, print_out = False):
|
||||
X = self.conv1(X)
|
||||
|
||||
X = torch.mul(X, self.m0)
|
||||
X = torch.add(X, torch.bitwise_left_shift(torch.tensor(1), self.s0 -1)).type(torch.LongTensor)
|
||||
X = torch.bitwise_right_shift(X, self.s0).type(torch.FloatTensor)
|
||||
X = torch.clamp(X, min = 0, max = 255).type(torch.FloatTensor)
|
||||
|
||||
X = self.avg1(X).type(torch.LongTensor)
|
||||
X = X.type(torch.FloatTensor)
|
||||
|
||||
X = self.conv2(X)
|
||||
X = torch.mul(X, self.m1)
|
||||
X = torch.add(X, torch.bitwise_left_shift(torch.tensor(1), self.s1 -1)).type(torch.LongTensor)
|
||||
X = torch.bitwise_right_shift(X, self.s1).type(torch.FloatTensor)
|
||||
X = torch.clamp(X, min = 0, max = 255).type(torch.FloatTensor)
|
||||
|
||||
X = self.avg2(X).type(torch.LongTensor)
|
||||
X = X.type(torch.FloatTensor)
|
||||
X = X.reshape(X.shape[0], -1)
|
||||
|
||||
X = self.fc1(X)
|
||||
X = torch.mul(X, self.m2)
|
||||
X = torch.add(X, torch.bitwise_left_shift(torch.tensor(1), self.s2 -1)).type(torch.LongTensor)
|
||||
X = torch.bitwise_right_shift(X, self.s2).type(torch.FloatTensor)
|
||||
X = torch.clamp(X, min = 0, max = 255).type(torch.FloatTensor)
|
||||
|
||||
X = self.fc2(X)
|
||||
X = torch.mul(X, self.m3)
|
||||
X = torch.add(X, torch.bitwise_left_shift(torch.tensor(1), self.s3 -1)).type(torch.LongTensor)
|
||||
X = torch.bitwise_right_shift(X, self.s3).type(torch.FloatTensor)
|
||||
X = torch.clamp(X, min = 0, max = 255).type(torch.FloatTensor)
|
||||
|
||||
X = self.fc3(X)
|
||||
X = torch.mul(X, self.m4)
|
||||
X = torch.add(X, torch.bitwise_left_shift(torch.tensor(1), self.s4 -1)).type(torch.LongTensor)
|
||||
X = torch.bitwise_right_shift(X, self.s4).type(torch.FloatTensor)
|
||||
X = torch.clamp(X, min = 0, max = 255).type(torch.FloatTensor)
|
||||
|
||||
if(print_out):
|
||||
print(X)
|
||||
|
||||
return X
|
||||
|
||||
def create_fann_model(int_weights, int_biases, mul_vals, shift_vals):
|
||||
ibex_model = Ibex_FANN(mul_vals, shift_vals)
|
||||
ibex_model_dict = ibex_model.state_dict()
|
||||
|
||||
ibex_model_dict['linear1.weight'] = torch.tensor(int_weights[0])
|
||||
ibex_model_dict['linear2.weight'] = torch.tensor(int_weights[1])
|
||||
|
||||
ibex_model_dict['linear1.bias'] = torch.tensor(int_biases[0])
|
||||
ibex_model_dict['linear2.bias'] = torch.tensor(int_biases[1])
|
||||
|
||||
ibex_model.load_state_dict(ibex_model_dict)
|
||||
return ibex_model
|
||||
|
||||
def create_uci_model(int_weights, int_biases, mul_vals, shift_vals):
|
||||
ibex_model = Ibex_UCI_MLP(mul_vals, shift_vals)
|
||||
ibex_model_dict = ibex_model.state_dict()
|
||||
|
||||
ibex_model_dict['fc0.weight'] = torch.tensor(int_weights[0])
|
||||
ibex_model_dict['fc1.weight'] = torch.tensor(int_weights[1])
|
||||
ibex_model_dict['fc2.weight'] = torch.tensor(int_weights[2])
|
||||
ibex_model_dict['fc3.weight'] = torch.tensor(int_weights[3])
|
||||
|
||||
ibex_model_dict['fc0.bias'] = torch.tensor(int_biases[0])
|
||||
ibex_model_dict['fc1.bias'] = torch.tensor(int_biases[1])
|
||||
ibex_model_dict['fc2.bias'] = torch.tensor(int_biases[2])
|
||||
ibex_model_dict['fc3.bias'] = torch.tensor(int_biases[3])
|
||||
|
||||
ibex_model.load_state_dict(ibex_model_dict)
|
||||
|
||||
return ibex_model
|
||||
|
||||
def create_lenet_model(int_weights, int_biases, mul_vals, shift_vals):
|
||||
ibex_model = Ibex_Lenet5(mul_vals, shift_vals)
|
||||
ibex_model_dict = ibex_model.state_dict()
|
||||
|
||||
ibex_model_dict['conv1.weight'] = torch.tensor(int_weights[0])
|
||||
ibex_model_dict['conv2.weight'] = torch.tensor(int_weights[1])
|
||||
ibex_model_dict['fc1.weight'] = torch.tensor(int_weights[2])
|
||||
ibex_model_dict['fc2.weight'] = torch.tensor(int_weights[3])
|
||||
ibex_model_dict['fc3.weight'] = torch.tensor(int_weights[4])
|
||||
|
||||
ibex_model_dict['conv1.bias'] = torch.tensor(int_biases[0])
|
||||
ibex_model_dict['conv2.bias'] = torch.tensor(int_biases[1])
|
||||
ibex_model_dict['fc1.bias'] = torch.tensor(int_biases[2])
|
||||
ibex_model_dict['fc2.bias'] = torch.tensor(int_biases[3])
|
||||
ibex_model_dict['fc3.bias'] = torch.tensor(int_biases[4])
|
||||
|
||||
ibex_model.load_state_dict(ibex_model_dict)
|
||||
|
||||
return ibex_model
|
||||
|
||||
def eval_sim_model(quant_model, ibex_model, test_loader):
|
||||
# Turn off gradients for validation
|
||||
with torch.no_grad():
|
||||
ibex_model.eval()
|
||||
correct = 0
|
||||
y_size = 0
|
||||
for test_imgs, test_labels in test_loader:
|
||||
test_imgs = torch.round(Variable(test_imgs).float()/quant_model.quant_inp.quant_act_scale().cpu())
|
||||
output = ibex_model(test_imgs)
|
||||
predicted = torch.max(output, 1)[1]
|
||||
correct += (predicted == test_labels).sum()
|
||||
y_size += len(test_labels)
|
||||
print("Test accuracy: {:.3f}% ".format(100*float(correct)/y_size))
|
||||
|
||||
print(ibex_model(torch.unsqueeze(test_imgs[0], dim = 0)))
|
||||
return
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,88 @@
|
||||
import init_utils
|
||||
import common
|
||||
|
||||
# Initialize the environment and get the name
|
||||
name = init_utils.initialize_environment(__file__)
|
||||
args = init_utils.get_args()
|
||||
|
||||
# Set arguments from command line
|
||||
max_acc_drop = args.max_acc_drop
|
||||
device = args.device
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from sklearn.model_selection import train_test_split
|
||||
from sklearn import preprocessing
|
||||
from sklearn.preprocessing import MinMaxScaler
|
||||
|
||||
# Load our Dataset
|
||||
df = pd.read_csv("uci_fann_net/Data_Cortex_Nuclear.csv")
|
||||
# Fix the Missing values
|
||||
|
||||
missing = df.isnull().sum().sum()
|
||||
print("There are total {} null values".format(missing) )
|
||||
|
||||
missval_col = df.columns[df.isna().any()].tolist()
|
||||
|
||||
# Imputing Missing values with mean values
|
||||
for i in range(len(missval_col)):
|
||||
mean_value = np.mean(df[missval_col[i]])
|
||||
#df[missval_col[i]].fillna(value = mean_value, inplace = True)
|
||||
df.fillna({missval_col[i] : mean_value}, inplace = True)
|
||||
|
||||
print("Total number of Null Value in the updated dataset is {}".format(df.isnull().sum().sum()))
|
||||
|
||||
numcol = df.describe().columns.tolist()
|
||||
X = df[numcol] # X dataset should include cols with numerical datasets, the protein expression level
|
||||
|
||||
r, c = df.shape
|
||||
r2, c2 = X.shape
|
||||
|
||||
# Preprocess the Data with the MinMaxScaler
|
||||
|
||||
scaler = MinMaxScaler(feature_range = (0, 255))
|
||||
df_normalized = pd.DataFrame(scaler.fit_transform(X), columns = X.columns)
|
||||
|
||||
# Convert the normalized values to integers
|
||||
df_normalized = df_normalized.round()/255.0
|
||||
df_normalized = df_normalized.drop(columns = 'pAKT_N')
|
||||
|
||||
label_encoder = preprocessing.LabelEncoder()
|
||||
|
||||
X_data = df_normalized.to_numpy()
|
||||
y_data = label_encoder.fit_transform(df['class'])
|
||||
|
||||
X_train, X_val, y_train, y_val = train_test_split(X_data, y_data, test_size = 0.3)
|
||||
X_test, X_val, y_test, y_val = train_test_split(X_val, y_val, test_size = 0.5)
|
||||
|
||||
### The following variables could be set as inputs
|
||||
|
||||
BATCH_SIZE = 16
|
||||
epochs = [80, 20]
|
||||
lr = [0.0001, 0.00005]
|
||||
|
||||
class FANN_NET(nn.Module):
|
||||
def __init__(self):
|
||||
super(FANN_NET, self).__init__()
|
||||
self.fc1 = nn.Linear(76, 300)
|
||||
self.relu1 = nn.ReLU()
|
||||
self.fc2 = nn.Linear(300, 200)
|
||||
self.relu2 = nn.ReLU()
|
||||
self.fc3 = nn.Linear(200, 100)
|
||||
self.relu3 = nn.ReLU()
|
||||
self.fc4 = nn.Linear(100, 10)
|
||||
|
||||
def forward(self,X):
|
||||
X = self.relu1(self.fc1(X))
|
||||
X = self.relu2(self.fc2(X))
|
||||
X = self.relu3(self.fc3(X))
|
||||
X = self.fc4(X)
|
||||
return F.log_softmax(X, dim = 1)
|
||||
|
||||
net = FANN_NET()
|
||||
|
||||
common.create_ibex_qnn(net, name, device, X_train, y_train, X_test, y_test,
|
||||
X_val = X_val, y_val = y_val, BATCH_SIZE = BATCH_SIZE,
|
||||
epochs = epochs, lr = lr, max_acc_drop = max_acc_drop)
|
||||
Reference in New Issue
Block a user