<jupyter_start><jupyter_text>BehaviorNet in PyTorchUsing a simple LSTM network to learn activities representations (activity embedding analog to a word embedding in a language model).<jupyter_code>device = t.device('cpu')<jupyter_output><empty_output><jupyter_text>Load and prepare the artifical logfiles<jupyter_code>%%bash
ls -lisa ./Data
logfile = pd.read_pickle('./Data/logfile.pkl')
id2action = np.load('./Data/id2action.npy')
action2id = {a : i for i,a in enumerate(id2action)}
logfile['SessionActivityInt'] = logfile.SessionActivity.map(lambda ls: np.array([action2id[a] for a in ls]+[action2id['start']]))
logfile.head()<jupyter_output><empty_output><jupyter_text>Network Design<jupyter_code>class BehaviourNet(nn.Module):
    '''
    Very simple network consisting of an embedding layer, LSTM layers and a decoder with dropouts
    '''
    def __init__(self, n_actions=6, embedding_size=3, n_nodes=6, n_layers=2, dropout=0.2, 
                 padding_idx=0, initrange=0.5):
        super(VerySimpleBehaviorNet, self).__init__()
        self.dropout = nn.Dropout(dropout)
        self.embedding = nn.Embedding(n_actions, embedding_size, padding_idx)
        self.rnn = nn.LSTM(embedding_size, n_nodes, n_layers, dropout=dropout)
        self.decoder = nn.Linear(n_nodes, n_actions)
        self.init_weights(initrange)
        self.n_nodes = n_nodes
        self.n_layers = n_layers
        
    def init_weights(self, initrange=0.1):
        self.embedding.weight.data.uniform_(-initrange, initrange)
        # Set the first row to zero (padding idx)
        self.embedding.weight.data[0,:] = 0
        print(self.embedding.weight)
        self.decoder.bias.data.zero_()
        self.decoder.weight.data.uniform_(-initrange, initrange)
        
    def init_hidden(self, batch_size):
        weight = next(self.parameters())
        return (weight.new_zeros(self.n_layers, batch_size, self.n_nodes),
                weight.new_zeros(self.n_layers, batch_size, self.n_nodes))
    
    def forward(self, input, hidden):
        emb = self.dropout(self.embedding(input))
        output, hidden = self.rnn(emb, hidden)
        output = self.dropout(output)
        decoded = self.decoder(output.view(output.size(0)*output.size(1), output.size(2)))
        return decoded.view(output.size(0), output.size(1), decoded.size(1)), hidden
def get_batch(i, batch_size, input):
    '''
    Takes a column/list of activity tensors of variable lenght
    and returns the padded i-th minibatch of batch_size activities 
    '''
    data = input[i*batch_size : (i+1) * batch_size]
    data = sorted(data, key=len, reverse=True)
    x = nn.utils.rnn.pad_sequence([x[:-1] for x in data])
    y = nn.utils.rnn.pad_sequence([y[1:] for y in data])
    return x, y
def split_train_test(input, device, prop=0.8, seed=42):
    np.random.seed(42)
    mask = np.random.uniform(size=input.shape[0])<=prop
    train = input[mask]
    test = input[~mask]
    train = [t.LongTensor(a).to(device) for a in train]
    test = [t.LongTensor(a).to(device) for a in test]
    return train, test, input[mask].index, input[~mask].index
train, test, train_idx, test_idx = split_train_test(logfile.SessionActivityInt, device)
get_batch(0, 2, train)
def training(model, optimizer, scheduler, loss_function, data, batch_size, n_actions, clipping=0.5):
    model.train()
    n_batch = int(np.ceil(len(data) // batch_size))
    hidden = model.init_hidden(batch_size)
    scheduler.step()
    total_loss = 0.0
    for batch in range(n_batch):
        hidden = tuple(h.detach() for h in hidden)
        x,y = get_batch(batch, batch_size, data)
        optimizer.zero_grad()
        output, hidden = model(x, hidden)
        output_flatten = output.view(-1, n_actions)
        y_flatten = y.view(-1)
        loss = loss_function(output_flatten, y_flatten)
        loss.backward()
        nn.utils.clip_grad_norm_(model.parameters(), clipping)
        optimizer.step()
        total_loss += loss
    return total_loss / n_batch

def evaluate(model, loss_function, data, n_actions):
    model.eval()
    batch_size = len(data)
    hidden = model.init_hidden(batch_size)
    x,y = get_batch(0, batch_size, data)
    output, hidden = model(x, hidden)
    output_flatten = output.view(-1, n_actions)
    y_flatten = y.view(-1)
    loss = loss_function(output_flatten, y_flatten)
    y_probs = nn.Softmax()(output)
    y_predict = t.argmax(output, 2)
    y_predict[y==0]=0
    acc = (y_predict==y).double()[y>0].sum() / y[y>0].size(0)
    return y_probs, y_predict, y, loss, acc<jupyter_output><empty_output><jupyter_text>Training<jupyter_code>modelname = 'model_1'
model = BehaviourNet(initrange=10, n_layers=2, n_nodes=20, n_actions=len(id2action)).to(device)
loss_func = nn.CrossEntropyLoss(ignore_index=0)
optimizer = t.optim.RMSprop(model.parameters(), lr=0.05)
scheduler = t.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5)
for epoch in range(20):
    training_loss = training(model, optimizer, scheduler, loss_func, train, 100, n_actions=len(id2action))
    y_prob, y_pred, y_true, test_loss, test_acc = evaluate(model, loss_func, test, n_actions=len(id2action))
    print(f'Epoch {epoch}\nTrain Loss : {training_loss} \t Val loss: {test_loss} \t Val Acc {test_acc}')<jupyter_output>/anaconda/lib/python3.6/site-packages/ipykernel/__main__.py:30: UserWarning: Implicit dimension choice for softmax has been deprecated. Change the call to include dim=X as an argument.<jupyter_text>Save the model<jupyter_code>try:
    os.mkdir('./models')
except Exception as e:
    print('Model dir already exists')
model_state_dict = model.state_dict()
optimizer_state_dict = optimizer.state_dict()

t.save({
     'epoch': epoch,
     'model_state_dict': model.state_dict(),
     'optimizer_state_dict': optimizer.state_dict(),
     'loss': training_loss,
}, f'./models/{modelname}')

model_state_dict<jupyter_output><empty_output><jupyter_text>Use Embedding to detect anomalies<jupyter_code>import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline
embeddings = model.embedding.weight.data.cpu().numpy()
fig = plt.figure(figsize=(12,8))
ax = fig.add_subplot(111, projection='3d')
for i in range(1, len(action2id)):
    ax.scatter(embeddings[i,0], embeddings[i,1], embeddings[i,2], s=25)
    ax.text(embeddings[i,0]+0.2, embeddings[i,1]+0.2, embeddings[i,2], s=id2action[i], fontsize=14)
    ax.grid(True)
ax.set_title('Action Embedding (latent space)')
plt.savefig('action_embeddings.png', dpi=500)
logfile['Embedded_Activities'] = logfile.SessionActivityInt.map(lambda x: embeddings[x].mean(axis=0))
logfile['Embedded_Activities_x'] = logfile.Embedded_Activities.map(lambda x: x[0])
logfile['Embedded_Activities_y'] = logfile.Embedded_Activities.map(lambda x: x[1])
logfile['Embedded_Activities_z'] = logfile.Embedded_Activities.map(lambda x: x[2])
fig = plt.figure(figsize=(16,8))
ax = fig.add_subplot(111, projection='3d')
plt.title('Activity profiles')
for i in range(1, len(action2id)):
    ax.scatter(logfile.loc[(logfile.FraudulentActivity==0) & (logfile.UserRole==0), 'Embedded_Activities_x'], 
               logfile.loc[(logfile.FraudulentActivity==0) & (logfile.UserRole==0), 'Embedded_Activities_y'], 
               logfile.loc[(logfile.FraudulentActivity==0) & (logfile.UserRole==0), 'Embedded_Activities_z'], color='green')
    ax.scatter(logfile.loc[(logfile.FraudulentActivity==0) & (logfile.UserRole==1), 'Embedded_Activities_x'], 
               logfile.loc[(logfile.FraudulentActivity==0) & (logfile.UserRole==1), 'Embedded_Activities_y'], 
               logfile.loc[(logfile.FraudulentActivity==0) & (logfile.UserRole==1), 'Embedded_Activities_z'], color='blue')
    ax.scatter(logfile.loc[logfile.FraudulentActivity==1, 'Embedded_Activities_x'], 
               logfile.loc[logfile.FraudulentActivity==1, 'Embedded_Activities_y'], 
               logfile.loc[logfile.FraudulentActivity==1, 'Embedded_Activities_z'], color='red')
    
    ax.grid(True)
plt.savefig('activityprofiles.png', dpi=500)
user_profiles = logfile.groupby(['UserID', 'UserRole', 'PotentialFraudster'], as_index=False).agg({
    'Embedded_Activities_x':np.mean,
    'Embedded_Activities_y':np.mean,
    'Embedded_Activities_z':np.mean,
})

fig = plt.figure(figsize=(16,8))
ax = fig.add_subplot(111, projection='3d')
plt.title('User profiles')
for i in range(1, len(action2id)):
    ax.scatter(user_profiles.loc[(user_profiles.UserRole==0) & (user_profiles.PotentialFraudster==0), 'Embedded_Activities_x'], 
               user_profiles.loc[(user_profiles.UserRole==0) & (user_profiles.PotentialFraudster==0), 'Embedded_Activities_y'], 
               user_profiles.loc[(user_profiles.UserRole==0) & (user_profiles.PotentialFraudster==0), 'Embedded_Activities_z'], color='green')
    ax.scatter(user_profiles.loc[(user_profiles.UserRole==1) & (user_profiles.PotentialFraudster==0), 'Embedded_Activities_x'], 
               user_profiles.loc[(user_profiles.UserRole==1) & (user_profiles.PotentialFraudster==0), 'Embedded_Activities_y'], 
               user_profiles.loc[(user_profiles.UserRole==1) & (user_profiles.PotentialFraudster==0), 'Embedded_Activities_z'], color='blue')
    ax.scatter(user_profiles.loc[user_profiles.PotentialFraudster==1, 'Embedded_Activities_x'], 
               user_profiles.loc[user_profiles.PotentialFraudster==1, 'Embedded_Activities_y'], 
               user_profiles.loc[user_profiles.PotentialFraudster==1, 'Embedded_Activities_z'], color='red')
    ax.grid(True)
plt.savefig('userprofiles.png', dpi=500)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13985355648535566, 'fraction_numerical': 0.01715481171548117, 'mean_word_length': 4.607624633431085, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 26, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '144320', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 3486, 'n_tokens_neox': 3350, 'n_words': 568, 'path': 'Anomaly_Detection_with_RNN/BehaviorNet.ipynb', 'repo_name': 'mgroncki/FraudDetection', 'size': '9560'}