In [1]:
import time
start_time = time.time() ## pomiar czasu: start pomiaru czasu
print(time.ctime())
import torch
import torch.nn as nn
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
Importuję dane¶
import pandas as pd
df = pd.read_csv('/home/wojciech/Pulpit/1/Stroke_Prediction_CLEAR.csv')
df.head(3)
df.shape
df.Stroke.value_counts().plot(kind='pie', autopct='%1.0f%%', colors=['#45818e', '#f1c232'], explode=(0.05, 0.05))
Uporządkowanie kolumn z danymi kategorycznymi i danymi ciągłymi¶
df.columns
df.Type_Of_Work
categorical_columns = ['Gender','Hypertension', 'Heart_Disease','Ever_Married','Type_Of_Work','Residence','Smoking_Status','Age_years_10']
numerical_columns = ['Avg_Glucose', 'BMI', 'Age_years']
Ustalamy, że zmienną wynikową jest kolumna ‘Stroke’¶
outputs = ['Stroke']
Cyfryzacja zmiennych tekstowych¶
df.dtypes
Musimy przekonwertować typy dla kolumn jakościowych na category. Możemy to zrobić za pomocą astype() funkcji, jak pokazano poniżej:
Wprowadzam nowy typ danych: ‘category’¶
for category in categorical_columns:
df[category] = df[category].astype('category')
df.dtypes
df['Residence'].cat.categories
df['Ever_Married'].cat.categories
df['Age_years_10'].cat.categories
Cyfryzacja danych¶
df.dtypes
Dlaczego zcyfrowaliśmy dane w formacie?¶
Podstawowym celem oddzielenia kolumn kategorycznych od kolumn numerycznych jest to, że wartości w kolumnie numerycznej mogą być bezpośrednio wprowadzane do sieci neuronowych. Jednak wartości kolumn kategorialnych należy najpierw przekonwertować na typy liczbowe.
categorical_columns
Konwersja zmiennych kategorycznych na macierz Numpy¶
p1 = df['Gender'].cat.codes.values
p2 = df['Hypertension'].cat.codes.values
p3 = df['Heart_Disease'].cat.codes.values
p4 = df['Ever_Married'].cat.codes.values
p5 = df['Type_Of_Work'].cat.codes.values
p6 = df['Residence'].cat.codes.values
p7 = df['Smoking_Status'].cat.codes.values
p8 = df['Age_years_10'].cat.codes.values
NumP_matrix = np.stack([p1, p2, p3, p4, p5, p6, p7, p8], 1)
NumP_matrix[:10]
Tworzenie tensora Pytorch z macierzy Numpy¶
categorical_data = torch.tensor(NumP_matrix, dtype=torch.int64)
categorical_data[:10]
Konwersja kolumn numerycznych DataFrame na tensor Pytorch¶
numerical_data = np.stack([df[col].values for col in numerical_columns], 1)
numerical_data = torch.tensor(numerical_data, dtype=torch.float)
numerical_data[:5]
Konwersja zmiennych wynikowych na tensor Pytorch¶
outputs = torch.tensor(df[outputs].values).flatten()
outputs[:5]
Podsumujmy tensory¶
print('categorical_data: ',categorical_data.shape)
print('numerical_data: ',numerical_data.shape)
print('outputs: ',outputs.shape)
OSADZANIE¶
Przekształciliśmy nasze kolumny kategorialne na numeryczne, w których unikatowa wartość jest reprezentowana przez jedną liczbę całkowitą (cyfryzacja – np. palący to 1). Na podstawie takiej kolumny (zmiennej) możemy wyszkolić model, jednak jest lepszy sposób…
Lepszym sposobem jest reprezentowanie wartości w kolumnie kategorialnej w postaci wektora N-wymiarowego zamiast pojedynczej liczby całkowitej. Ten proces nazywa się osadzaniem. Wektor jest w stanie przechwycić więcej informacji i może znaleźć związki między różnymi wartościami kategorycznymi w bardziej odpowiedni sposób. Dlatego będziemy reprezentować wartości w kolumnach kategorialnych w postaci wektorów N-wymiarowych.
Musimy zdefiniować rozmiar osadzania (wymiary wektorowe) dla wszystkich kolumn jakościowych. Nie ma twardej i szybkiej reguły dotyczącej liczby wymiarów. Dobrą zasadą przy definiowaniu rozmiaru osadzania dla kolumny jest podzielenie liczby unikalnych wartości w kolumnie przez 2 (ale nie więcej niż 50). Na przykład dla ‘Smoking_Status’ kolumny liczba unikalnych wartości wynosi 3. Odpowiedni rozmiar osadzenia dla kolumny ‘Smoking_Status’będzie wynosił 3/2 = 1,5 = 2 (zaokrąglenie).
Poniższy skrypt tworzy krotkę zawierającą liczbę unikalnych wartości i rozmiarów wymiarów dla wszystkich kolumn jakościowych.
Zasada jest prosta: macierz embadding musi być zawsze w ilości wierszy większa niż zakres zmiennych w ilości wierszy: dlatego dodałem col_size+2, to duży zapas..
categorical_column_sizes = [len(df[column].cat.categories) for column in categorical_columns]
categorical_embedding_sizes = [(col_size+2, min(50, (col_size+5)//2)) for col_size in categorical_column_sizes]
print(categorical_embedding_sizes)
Dzielenie zestawu na szkoleniowy i testowy¶
total_records = df['ID'].count()
test_records = int(total_records * .2)
categorical_train_data = categorical_data[:total_records-test_records]
categorical_test_data = categorical_data[total_records-test_records:total_records]
numerical_train_data = numerical_data[:total_records-test_records]
numerical_test_data = numerical_data[total_records-test_records:total_records]
train_outputs = outputs[:total_records-test_records]
test_outputs = outputs[total_records-test_records:total_records]
Aby sprawdzić, czy poprawnie podzieliliśmy dane na zestawy treningów i testów, wydrukujmy długości rekordów szkolenia i testów:
print('categorical_train_data: ',categorical_train_data.shape)
print('numerical_train_data: ',numerical_train_data.shape)
print('train_outputs: ', train_outputs.shape)
print('----------------------------------------------------')
print('categorical_test_data: ',categorical_test_data.shape)
print('numerical_test_data: ',numerical_test_data.shape)
print('test_outputs: ',test_outputs.shape)
Tworzenie modelu klasyfikacji Pytorch¶
class Model(nn.Module):
def __init__(self, embedding_size, num_numerical_cols, output_size, layers, p=0.4):
super().__init__()
self.all_embeddings = nn.ModuleList([nn.Embedding(ni, nf) for ni, nf in embedding_size])
self.embedding_dropout = nn.Dropout(p)
self.batch_norm_num = nn.BatchNorm1d(num_numerical_cols)
all_layers = []
num_categorical_cols = sum((nf for ni, nf in embedding_size))
input_size = num_categorical_cols + num_numerical_cols
for i in layers:
all_layers.append(nn.Linear(input_size, i))
all_layers.append(nn.ReLU(inplace=True))
all_layers.append(nn.BatchNorm1d(i))
all_layers.append(nn.Dropout(p))
input_size = i
all_layers.append(nn.Linear(layers[-1], output_size))
self.layers = nn.Sequential(*all_layers)
def forward(self, x_categorical, x_numerical):
embeddings = []
for i,e in enumerate(self.all_embeddings):
embeddings.append(e(x_categorical[:,i]))
x = torch.cat(embeddings, 1)
x = self.embedding_dropout(x)
x_numerical = self.batch_norm_num(x_numerical)
x = torch.cat([x, x_numerical], 1)
x = self.layers(x)
return x
print('categorical_embedding_sizes: ',categorical_embedding_sizes)
print(numerical_data.shape[1])
model = Model(categorical_embedding_sizes, numerical_data.shape[1], 2, [200,100,50], p=0.4)
print(model)
Tworzenie dunkcji straty¶
#loss_function = torch.nn.MSELoss(reduction='sum')
loss_function = nn.CrossEntropyLoss()
#loss_function = nn.BCEWithLogitsLoss()
Definiowanie optymalizatora¶
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
#optimizer = torch.optim.SGD(model.parameters(), lr=0.001)
#optimizer = torch.optim.Rprop(model.parameters(), lr=0.001, etas=(0.5, 1.2), step_sizes=(1e-06, 50))
print('categorical_embedding_sizes: ',categorical_embedding_sizes)
print(numerical_data.shape[1])
print('categorical_train_data: ',categorical_train_data.shape)
print('numerical_train_data: ',numerical_train_data.shape)
print('outputs: ',train_outputs.shape)
y_pred = model(categorical_train_data, numerical_train_data)
epochs = 300
aggregated_losses = []
for i in range(epochs):
i += 1
y_pred = model(categorical_train_data, numerical_train_data)
single_loss = loss_function(y_pred, train_outputs)
aggregated_losses.append(single_loss)
if i%30 == 1:
print(f'epoch: {i:3} loss: {single_loss.item():10.8f}')
optimizer.zero_grad()
single_loss.backward()
optimizer.step()
print(f'epoch: {i:3} loss: {single_loss.item():10.10f}')
plt.plot(range(epochs), aggregated_losses)
plt.ylabel('Loss')
plt.xlabel('epoch');
Prognoza na podstawie modelu¶
with torch.no_grad():
y_val_train = model(categorical_train_data, numerical_train_data)
loss = loss_function( y_val_train, train_outputs)
print(f'Loss train_set: {loss:.8f}')
with torch.no_grad():
y_val = model(categorical_test_data, numerical_test_data)
loss = loss_function(y_val, test_outputs)
print(f'Loss: {loss:.8f}')
Ponieważ ustaliliśmy, że nasza warstwa wyjściowa będzie zawierać 2 neurony, każda prognoza będzie zawierać 2 wartości. Przykładowo pierwsze 5 przewidywanych wartości wygląda następująco:
print(y_val[:5])
Celem takich prognoz jest to, że jeśli rzeczywisty wynik wynosi 0, wartość przy indeksie 0 powinna być wyższa niż wartość przy indeksie 1 i odwrotnie. Możemy pobrać indeks największej wartości z listy za pomocą następującego skryptu:
y_val = np.argmax(y_val, axis=1)
Powyższe równanie zwraca wskaźniki wartości maksymalnych wzdłuż osi.
print(y_val[:195])
Ponieważ na liście pierwotnie przewidywanych wyników dla pierwszych pięciu rekordów wartości przy zerowych indeksach są większe niż wartości przy pierwszych indeksach, możemy zobaczyć 0 w pierwszych pięciu wierszach przetworzonych danych wyjściowych.
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
print(confusion_matrix(test_outputs,y_val))
print(classification_report(test_outputs,y_val))
print(accuracy_score(test_outputs, y_val))
Model słabo wykrywa udar.
Zapisujemy cały model¶
torch.save(model,'/home/wojciech/Pulpit/3/byk.pb')
Odtwarzamy cały model¶
KOT = torch.load('/home/wojciech/Pulpit/3/byk.pb')
KOT.eval()
Podstawiając inne zmienne niezależne można uzyskać wektor zmiennych wyjściowych¶
A = categorical_train_data[::50]
A
B = numerical_train_data[::50]
B
y =train_outputs[::50]
y_pred_AB = KOT(A, B)
y_pred_AB[:10]
with torch.no_grad():
y_val_AB = KOT(A,B)
loss = loss_function( y_val_AB, y)
print(f'Loss train_set: {loss:.8f}')
y_val = np.argmax(y_val_AB, axis=1)
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
print(confusion_matrix(y,y_val))
print(classification_report(y,y_val))
print(accuracy_score(y, y_val))
print('Pomiar czasu wykonania tego zadania:')
print(time.time() - start_time) ## koniec pomiaru czasu
