Adding files

This commit is contained in:
alexmr09
2024-07-19 13:30:31 +03:00
commit 08fb8ef728
7245 changed files with 3055662 additions and 0 deletions
+80
View File
@@ -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
View File
@@ -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.
+84
View File
@@ -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)
+28
View File
@@ -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
+70
View File
@@ -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)
+449
View File
@@ -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
+158
View File
@@ -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()
+12
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
# Set environment variables
export PYTHONPATH="$PWD:$PYTHONPATH"
export TF_ENABLE_ONEDNN_OPTS="0"
+220
View File
@@ -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
+88
View File
@@ -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)