You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

184 lines
7.1 KiB

import random
import numpy as np
import pandas as pd
from keras import Input
from keras.src.losses import SparseCategoricalCrossentropy
from keras.src.metrics import SparseCategoricalAccuracy
from pandas import DataFrame
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Bidirectional,GRU
from tensorflow.keras.optimizers import Adam
from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score, confusion_matrix
epochs = 5#50
model_type_gru = 'GRU'
model_type_lstm = 'LSTM'
model_type_bilstm = 'BiLSTM'
def make_sequences(data, sequence_length):
"""
Converts the data into sequences of the given length
:param data: Original data
:param sequence_length: length of intended sequences
:return: x,y for the sequences
"""
x, y = [], []
features = data.drop('user', axis=1).values
labels = data['user'].values
for i in range(len(features) - sequence_length+1): # with overlap on days
# for i in range(0, len(features) - sequence_length + 1, sequence_length): # without overlap on days
x.append(features[i:i + sequence_length])
y.append(labels[i + sequence_length-1])
return x, y
def prepare_data_for_basic_algorithm(user_data, sequence_length):
"""
Converts the data into a format the sklearn algorithms can work with. Does not change the data, only the structure
:param user_data: the dict of dataframe with the data
:param sequence_length: intended sequence length
:return: the formatted data
"""
combined = pd.DataFrame()
for user, data in user_data.items():
x_new, y_new = make_sequences(data, sequence_length)
if len(x_new)>0:
var = [[pd.DataFrame(a[s]) for s in range(sequence_length)] for a in x_new]
df_var = pd.concat([pd.concat(seq_list).T for seq_list in var])
df_var['user'] = user
combined = pd.concat([combined, df_var], ignore_index=True)
return combined.drop(columns=['user']), combined['user']
def prepare_data_for_neural_model(user_data, sequence_length, print_counts=False):
"""
Converts the data into a format the neural model can work with. Does not change the data, only the structure
:param print_counts: Whether to print some additional debug data
:param user_data: the dict of dataframe with the data
:param sequence_length: intended sequence length
:return: the formatted data
"""
x, y = [], []
combined = pd.DataFrame()
for user, data in user_data.items():
x_new, y_new = make_sequences(data, sequence_length)
x = x + x_new
y = y + y_new
if print_counts and len(x_new)>0:
var = [[pd.DataFrame(a[s])for s in range(sequence_length)] for a in x_new ]
df_var = pd.concat([pd.concat(seq_list).T for seq_list in var])
df_var['user'] = user
combined = pd.concat([combined, df_var], ignore_index=True)
if print_counts:
combined_ohne = combined.drop('user', axis=1)
print('Alle', len(combined))
print('Unique mit user', len(combined.drop_duplicates()))
print('Unique ohne user', len(combined_ohne.drop_duplicates()))
print('Unique')
print(combined.drop_duplicates()['user'].value_counts())
print('Alle')
print(combined['user'].value_counts())
random.Random(17).shuffle(x)
random.Random(17).shuffle(y)
x = np.array(x)
y = np.array(y)
return x,y
def train_one_model(train_data, val_data, n_batch, n_epochs, n_neurons, sequence_length, model_type):
x, y = train_data
x_v, y_v = val_data
users = list(set(y))
# renumber users
user_map = {users[i]:i for i in range(len(users))}
y = np.array([user_map[x] for x in y])
y_v = np.array([user_map[x] for x in y_v])
n_features = x.shape[2]
user_num = len(users)
# prepare model
def build_model():
model = Sequential()
model.add(Input(shape=(sequence_length, n_features), batch_size=n_batch))
if model_type == model_type_bilstm:
model.add(Bidirectional(LSTM(n_neurons)))
if model_type == model_type_lstm:
model.add(LSTM(n_neurons))
if model_type == model_type_gru:
model.add(GRU(n_neurons))
model.add(Dense(user_num, activation='softmax'))
model.compile(
optimizer=Adam(),
loss=SparseCategoricalCrossentropy(),
metrics=[SparseCategoricalAccuracy()],
)
return model
model = build_model()
# fit model
train_acc, test_acc, train_p, test_p, train_r, test_r, train_f1, test_f1 = list(), list(),list(), list(),list(), list(),list(), list()
for i in range(n_epochs):
model.fit(x, y, batch_size=n_batch, epochs=1, verbose=0, shuffle=False)
# evaluate model on train data
acc, p, r, f1 = evaluate(model, (x, y), sequence_length, n_batch)
train_acc.append(acc)
train_p.append(p)
train_r.append(r)
train_f1.append(f1)
# evaluate model on test data
savename = 'cf_matrix_'+get_save_id(n_epochs, n_neurons, n_batch)+'.json'
acc, p, r, f1 = evaluate(model, (x_v, y_v), n_batch, save_name=savename)
test_acc.append(acc)
test_p.append(p)
test_r.append(r)
test_f1.append(f1)
history = DataFrame()
history['train_acc'], history['test_acc'] = train_acc, test_acc
history['train_p'], history['test_p'] = train_p, test_p
history['train_r'], history['test_r'] = train_r, test_r
history['train_f1'], history['test_f1'] = train_f1, test_f1
return history
def get_save_id(n_epochs, n_neurons, n_batch):
return '_e'+str(n_epochs)+'_n'+str(n_neurons)+'_b'+ str(n_batch)
def evaluate(model, data, batch_size, save_name=None):
"""
GIven a model, the data is used for prediction and then evaluated.
:param model: Model to use with a .predict() call
:param data: x, y_true of the data, already prepared for the model
:param batch_size: batch size for prediction
:param save_name: if provided, results will be saved to a json file of that name
:return: the evaluation results
"""
x, y_true = data
y_pred = model.predict(x, verbose=0, batch_size=batch_size)
y_pred_classes = np.argmax(y_pred, axis=1)
cf_matrix = pd.DataFrame(confusion_matrix(y_true, y_pred_classes))
if save_name is not None:
cf_matrix.to_json('results/'+save_name)
true_counts = pd.DataFrame(y_true).value_counts()
print('Top true occurrences', true_counts[:6])
predicted_counts = pd.DataFrame(y_pred_classes).value_counts()
print('Top predicted occurrences', predicted_counts[:6])
return eval_metrics(y_true=y_true, y_pred=y_pred_classes)
def eval_metrics(y_true, y_pred):
"""
Calculate the evaluation metrics
:param y_true:
:param y_pred:
:return: acc, p, r, f1
"""
f1 = f1_score(y_true=y_true, y_pred=y_pred, average='weighted')
p = precision_score(y_true=y_true, y_pred=y_pred, average='weighted')
r = recall_score(y_true=y_true, y_pred=y_pred, average='weighted')
acc = accuracy_score(y_true=y_true, y_pred=y_pred)
return acc, p, r, f1