
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
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='
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 iprint(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