Commit 924819e6 authored by Martin Řepa's avatar Martin Řepa

clean the code + add training more NNs to find BR

parent 256aeed9
......@@ -8,14 +8,16 @@ from utility import rate_limit_utility
@attr.s
class NeuralNetworkConfig:
# Number of epochs in a neural network training phase
epochs: int = attr.ib(default=1000)
epochs: int = attr.ib(default=400)
# String with loss_function definition.
# List of available functions: https://keras.io/losses/
# !!! DEPRECATED !!! for now
loss_function: str = attr.ib(default='binary_crossentropy')
# String with optimizer definition used to compile neural network model
# List of available optimizers: https://keras.io/optimizers/
# !!! DEPRECATED !!! for now
optimizer: str = attr.ib(default='adam')
# Value used for weighting the loss function (during training only) for
......@@ -23,6 +25,7 @@ class NeuralNetworkConfig:
# attention" to malicious samples.
# Setting it to 1 makes loss function behave equally for both predictions
# during training
# !!! DEPRECATED !!!
fp_weight: int = attr.ib(default=1)
......@@ -56,16 +59,14 @@ class BaseConfig:
# Number of features
features_count: int = attr.ib(default=2)
# This number of neural networks will be trained in each double oracle
# iteration and the best one will be considered as a best response
number_of_nn_to_train: int = attr.ib(default=7)
# 2 neural networks are considered the same if difference of game value for
# them and each attacker's action is less than epsion
epsilon: float = attr.ib(default=0.05)
# Number of false positives allowed in defender's mixture strategy.
# Sum(probability of each action times its fp_rate) must be less than this
# number. Fp_rate of the action is total number of malicious prediction for
# given benign data set
false_positives_allowed: int = attr.ib(default=0.5)
# Function to calculate utility given the actions
# f: List[float], NeuralNetwork -> float
utility_function: Callable = attr.ib(default=rate_limit_utility)
......
......@@ -6,11 +6,11 @@ from typing import List
import attr
import numpy as np
import pulp
import torch
from config import RootConfig
from src.data.loader import np_arrays_from_scored_csv
from src.neural_networks.network import NeuralNetwork, FormattedBenignData, \
FormattedMaliciousData
from src.neural_networks.network import NeuralNetwork, FormattedData
logger = logging.getLogger(__name__)
......@@ -44,7 +44,6 @@ class GameSolver:
self.utility = conf.base_conf.utility_function
train = conf.nn_train_conf
raw_benign_x, _ = np_arrays_from_scored_csv(train.benign_data_file_name,
0, train.benign_data_count)
self.benign_data = self.prepare_benign_data(raw_benign_x)
......@@ -53,38 +52,30 @@ class GameSolver:
unique, counts = np.unique(raw_x_data, axis=0, return_counts=True)
probs = np.array([count/len(raw_x_data) for count in counts])
benign_y = np.zeros(len(unique))
return FormattedBenignData(unique, probs, benign_y)
# def calculate_benign_data_with_probs(self):
# # TODO maybe this rounding is not really good for real results
# benign_data = list(map(lambda x: tuple(map(lambda y: round(y, 2), x)),
# self.benign_data[0]))
# benign_data_counter = Counter(benign_data)
# benign_data_points = []
# benign_data_probs = []
# for key, val in benign_data_counter.items():
# benign_data_points.append(key)
# benign_data_probs.append(val / len(benign_data))
# return np.array(benign_data_points), np.array(benign_data_probs)
def _get_trained_nn(self, attack: FormattedMaliciousData) -> NeuralNetwork:
return FormattedData(unique, probs, benign_y)
def _get_trained_nn(self, attack: FormattedData) -> NeuralNetwork:
# Initialize the model
network = NeuralNetwork(self.conf.base_conf.features_count,
self.conf.nn_conf,
self.conf.nn_train_conf)
network.set_data(self.benign_data, attack)
network.train()
# TODO use different dataset to calc false_positives
# network.calc_n0_false_positives(self.benign_data[0])
return network
def get_empty_data(self) -> FormattedData:
non_features = np.ndarray((0, self.conf.base_conf.features_count))
non_1d = np.array([])
return FormattedData(non_features, non_1d, non_1d)
def double_oracle(self, actions_p1: List) -> Result:
non_attack = FormattedMaliciousData(np.empty(0), np.empty(0), np.empty(0))
non_attack = self.get_empty_data()
# Get initial actions as the first ones
played_actions_p1 = set(actions_p1[:1])
played_actions_p2 = {self._get_trained_nn(non_attack)}
# TODO refactor all python lists to numpy arrays
for i in count():
logger.debug(f'Iteration: {i}\n')
......@@ -165,11 +156,21 @@ class GameSolver:
unique_attack_x = np.asarray(used_actions_p1)[non_zero_p]
attack_probs = np.asarray(probs_p1)[non_zero_p]
attack_y = np.ones(len(unique_attack_x))
attack = FormattedMaliciousData(unique_attack_x, attack_probs, attack_y)
attack = FormattedData(unique_attack_x, attack_probs, attack_y)
logger.debug('Let\'s train new NN with this malicious data:')
logger.debug('Let\'s train new best NN with this malicious data:')
logger.debug(f'{unique_attack_x}\n')
return self._get_trained_nn(attack)
best_nn = self._get_trained_nn(attack)
for _ in range(1, self.conf.base_conf.number_of_nn_to_train):
new_nn = self._get_trained_nn(attack)
if new_nn.final_loss < best_nn.final_loss:
logger.debug(f'Found better nn. Old|New value: '
f'{best_nn.final_loss} | {new_nn.final_loss}')
best_nn = new_nn
else:
logger.debug(f'The previous nn was better')
return best_nn
def solve_zero_sum_game_pulp(self, actions_p1: List[List[float]],
actions_p2: List[NeuralNetwork]):
......@@ -190,22 +191,14 @@ class GameSolver:
range(len(actions_p2))]
m += pulp.lpSum(probs_p_two) == 1 # Probabilities sum to 1
# Set false positive constraint
# suma = []
# i = 0
# for a2 in actions_p2:
# suma.append(probs_p_two[i]*a2.get_false_positive_rate())
# i += 1
# fp_constraint = pulp.lpSum(suma) <= self.conf.base_conf.false_positives_allowed
# m += fp_constraint
# Calc false positive cost with benign data probability distribution
fp_cost = 0
benign_points, benign_probs = self.benign_data_with_probs
benign_points = self.benign_data.unique_x
benign_probs = self.benign_data.probs_x
for features, features_prob in zip(benign_points, benign_probs):
for nn, nn_prob in zip(actions_p2, probs_p_two):
l = nn.limit_predict(features)[0]
fp_cost += (l**4) * features_prob * nn_prob
l = nn.predict_single_limit(features)
fp_cost += (l**2) * features_prob * nn_prob
# Define main constraints
constraints = []
......
......@@ -27,7 +27,7 @@ class KerasNeuralNetwork:
)
# nn_conf.loss_function
self.model.compile(loss=tmp_loss_function,
self.model.compile(loss=tmp_loss_function, # Or 'binary_crossentropy'
optimizer=nn_conf.optimizer,
metrics=['accuracy'])
self.false_positives = None
......
......@@ -14,21 +14,13 @@ from src.data.loader import np_arrays_from_scored_csv
logger = logging.getLogger(__name__)
# TODO one class is enough
@attr.s
class FormattedBenignData:
class FormattedData:
unique_x: np.array = attr.ib()
probs_x: np.array = attr.ib()
y: np.array = attr.ib()
@attr.s
class FormattedMaliciousData:
features: np.array = attr.ib()
probs_features: np.array = attr.ib()
y: np.array = attr.ib()
class OrderCounter:
order = 0
......@@ -55,14 +47,17 @@ class NeuralNetwork:
self.id = OrderCounter.next()
# Variables used for loss function
self.attacker_actions: FormattedMaliciousData = None
self.benign_data: FormattedBenignData = None
self.attacker_actions: FormattedData = None
self.benign_data: FormattedData = None
# Variable for value of loss function in last epoch measuring quality
self.final_loss = None
# TODO Just tmp
self.loss_fn = nn.BCELoss()
# PyTorch built in Binary Cross-entropy loss function
# self.loss_fn = nn.BCELoss()
def __str__(self):
return f'Neural network with id: {self.id}'
return f'Neural network id:{self.id}, final loss: {self.final_loss}'
def set_data(self, benign_data, attack):
self.attacker_actions = attack
......@@ -70,66 +65,37 @@ class NeuralNetwork:
def loss_function(self, x, limits, real_y, probs):
zero_sum_part = real_y*(1-limits)*torch.prod(x, dim=1)*probs
fp_cost = (1-real_y)*probs*torch.pow(limits, 4)
fp_cost = (1-real_y)*probs*torch.pow(limits, 2)
sum_loss = torch.add(torch.sum(zero_sum_part), torch.sum(fp_cost))
return torch.div(sum_loss, len(x))
# Calc false positive cost
# def_indexes = (real_y == 0)
# def_limits = limits[def_indexes]
# def_probs = real_y[def_indexes]
# fp_cost = torch.pow(torch.pow(def_limits, 4), def_probs)
#
# # Calc zero sum part
# attacker_indexes = (real_y == 1)
# att_limits = limits[attacker_indexes]
# att_x = x[attacker_indexes]
# att_probs = probs[attacker_indexes]
# att_rewards = torch.prod(att_x, dim=1)
# att_rewards = torch.pow(att_rewards, att_probs)
# zero_sum = torch.pow(att_rewards, torch.sub(1, att_limits))
#
# final_fp_cost = torch.sum(fp_cost)
# final_zero_sum_part = torch.sum(zero_sum)
# loss = torch.add(final_fp_cost, final_zero_sum_part)
# return loss
def _prepare_data(self):
defender = self.benign_data
attacker = self.attacker_actions
x = np.concatenate((defender.unique_x, attacker.features), axis=0)
x = np.concatenate((defender.unique_x, attacker.unique_x), axis=0)
y = np.concatenate((defender.y, attacker.y), axis=0)
probs = np.concatenate((defender.probs_x, attacker.probs_features), axis=0)
probs = np.concatenate((defender.probs_x, attacker.probs_x), axis=0)
# Shuffle before splitting
x, y, probs = shuffle(x, y, probs, random_state=1)
# Split to train and train data given the ratio in config
data = train_test_split(x, y, probs, test_size=self.validation_split)
x_train, x_test, y_train, y_test, probs_train, probs_test = data
self.x_train = torch.from_numpy(x_train).float()
self.x_test = torch.from_numpy(x_test).float()
self.y_train = torch.from_numpy(y_train).float()
self.y_test = torch.from_numpy(y_test).float()
self.probs_train = torch.from_numpy(probs_train).float()
self.probs_test = torch.from_numpy(probs_test).float()
# TODO use validation data aswell
self.x_train = torch.from_numpy(x).float()
self.y_train = torch.from_numpy(y).float()
self.probs_train = torch.from_numpy(probs).float()
def train(self):
self._prepare_data()
self._train()
def _train(self):
learning_rate = 1e-4
learning_rate = 0.5e-2
optimizer = torch.optim.Adam(self.model.parameters(), lr=learning_rate)
for e in range(self.epochs):
# Forward pass: compute predicted y by passing x to the model.
train_limits = self.limit_predict(self.x_train, with_grad=True)
# for l in train_limits:
# print(l.dtype, end=' ')
# print()
train_limits = self._limit_predict(self.x_train, with_grad=True)
# Compute loss.
loss = self.loss_function(self.x_train, train_limits, self.y_train,
......@@ -138,12 +104,8 @@ class NeuralNetwork:
# Compute validation loss and report some info
if e % 5 == 0:
test_limits = self.limit_predict(self.x_test)
validate_loss = self.loss_function(self.x_test, test_limits,
self.y_test, self.probs_test)
logging.debug(f'Epoch: {e}/{self.epochs},\t'
f'TrainLoss: {loss},\t'
f'ValidateLoss: {validate_loss.item()},\t')
f'TrainLoss: {loss},\t')
# Before the backward pass, use the optimizer object to zero all of
# the gradients for the variables it will update
......@@ -156,12 +118,13 @@ class NeuralNetwork:
# Calling the step function on an Optimizer makes an update to its
# parameters
optimizer.step()
self.final_loss = loss
def _raw_predict(self, tensor: torch.Tensor):
# TODO maybe this can help
return self.model(tensor)
pred = self.model(tensor)
return pred.flatten().float()
def limit_predict(self, x: torch.Tensor, with_grad=False):
def _limit_predict(self, x: torch.Tensor, with_grad=False):
if with_grad:
raw_prediction = self._raw_predict(x)
else:
......@@ -173,6 +136,14 @@ class NeuralNetwork:
limit = torch.mul(torch.add(clamped, -0.5), 2)
return limit
def predict_single_limit(self, input):
in_type = type(input)
if in_type == list or in_type == tuple or \
in_type == np.array or in_type == np.ndarray:
input = torch.tensor(input).float()
return self._limit_predict(input)[0].item()
def setup_loger(conf):
log_format = ('%(asctime)-15s\t%(name)s:%(levelname)s\t'
......@@ -184,27 +155,21 @@ def setup_loger(conf):
if __name__ == '__main__':
setup_loger(RootConfig())
benign_x, _ = np_arrays_from_scored_csv(
Path('all_benign_scored.csv'), 0, 1000)
Path('all_benign_scored.csv'), 0, 500)
malicious_x, _ = np_arrays_from_scored_csv(
Path('scored_malicious.csv'), 1, 500)
Path('scored_malicious.csv'), 1, 1)
benign_unique_x, counts = np.unique(benign_x, axis=0, return_counts=True)
probs_benign = np.array([count / len(benign_x) for count in counts])
benign_y = np.zeros(len(benign_unique_x))
benign_data = FormattedBenignData(benign_unique_x, probs_benign, benign_y)
benign_data = FormattedData(benign_unique_x, probs_benign, benign_y)
malicious_unique_x, counts = np.unique(malicious_x, axis=0, return_counts=True)
probs_malicious = np.array([count / len(malicious_unique_x) for count in counts])
malicious_y = np.ones(len(malicious_unique_x))
malicious_data = FormattedMaliciousData(malicious_unique_x, probs_malicious, malicious_y)
malicious_data = FormattedData(malicious_unique_x, probs_malicious, malicious_y)
nn = NeuralNetwork()
nn.set_data(benign_data, malicious_data)
nn.train()
# test_loss, test_acc = network.model.evaluate(x_test, y_test)
# print('Test loss:', test_loss)
# print('Test accuracy:', test_acc)
#
# network.calc_n0_false_positives(benign_x)
# print(network.get_false_positive_rate())
......@@ -7,6 +7,8 @@ from typing import TYPE_CHECKING
# Hack to avoid cycle imports while using type checking
# The TYPE_CHECKING constant is always False at runtime
import torch
if TYPE_CHECKING:
from src.neural_networks.network import NeuralNetwork
......@@ -17,17 +19,18 @@ def base_utility(attacker_features: List[float], defender_network: 'NeuralNetwor
[ Prediction=1 => malicious
Prediction=0 => benign ]
"""
pred = defender_network.raw_predict(attacker_features)[0]
pred = defender_network._raw_predict(torch.tensor(attacker_features))[0]
return functools.reduce(operator.mul, attacker_features, 1) * (1 - pred)
def rate_limit_utility(attacker_features: List[float], defender_network: 'NeuralNetwork'):
pred = defender_network.limit_predict(attacker_features)[0]
pred = defender_network.predict_single_limit(attacker_features)
return functools.reduce(operator.mul, attacker_features, 1) * (1 - pred)
def middle_utility(attacker_features: List[float], defender_network: 'NeuralNetwork'):
pred = defender_network.limit_predict(attacker_features)[0]
middle = np.array([0.5 for i in attacker_features])
pred = defender_network.predict_single_limit(attacker_features)
middle = np.array([0.5 for _ in attacker_features])
attack = np.array(attacker_features)
return max(1-np.linalg.norm(middle-attack)*2, 0) * (1 - pred)
from pathlib import Path
import itertools
from typing import List
import matplotlib.pyplot as plt
import numpy as np
import torch
from src.data.loader import np_arrays_from_scored_csv
from src.neural_networks.network import NeuralNetwork
import itertools
class Plotter:
......@@ -27,7 +27,7 @@ class Plotter:
continue
self.plot_neural_network(nn, prob)
self.plot_heat_map()
# self.plot_heat_map()
self.plot_attacker_features()
def plot_heat_map(self):
......@@ -43,7 +43,7 @@ class Plotter:
for nn, prob in self.defenders:
if prob == 0:
continue
pred = nn.limit_predict(point)[0]
pred = nn.predict_single_limit(point)
if pred:
sum_prob += prob
......@@ -63,12 +63,9 @@ class Plotter:
plt.xlabel('entropy')
plt.ylabel('length')
for point in points:
pred = neural_network.raw_predict(point)[0]
# red = 1 if pred > 0.5 else 2*pred
# green = 1 if pred < 0.5 else 1-(pred-0.5)*2
red = 2*(pred-0.5) if pred > 0.5 else 0
green = 2-2*pred if pred > 0.5 else 1
pred = neural_network.predict_single_limit(point)
red = pred
green = 1-pred
plt.scatter(point[0], point[1], c=[(red, green, 0)])
plt.show()
......@@ -89,8 +86,8 @@ class Plotter:
plt.show()
def plot_csv_file_2d(file_name: str):
data, _ = np_arrays_from_scored_csv(file_name, 1)
def plot_csv_file_2d(file_name: str, count=None):
data, _ = np_arrays_from_scored_csv(file_name, 1, count)
plt.xlabel('x')
plt.ylabel('y')
plt.xlim(0, 1)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment