Open In Colab

In [84]:
 

Conformal prediction - wstęp i przypomnienie

Conformal prediction to metoda, która umożliwia konstruowanie prognoz z predefinowaną gwarancją dokładności. Metoda może być stosowana jako uzupełnienie dowolnego tradycyjnego algorytmu predykcyjnego zarówno do klasyfikacji, jak i regresji.

  1. Rozszerzymy prosty model SVM, który będzie zwracał zbiór (przedział).
  2. Zbudujemy klasyfikator (conformal predictor) krok po kroku.
  3. Potwierdzimy, że skonstruowany model daje takie same wyniki jak funkcje z biblioteki nonconformist.

Idea regresji (conformal regression) jest taka sama jak w przypadku klasyfikacji (konformal classification).

Konfig

Biblioteki

In [85]:
import pandas as pd
import numpy as np
from sklearn.svm import SVC

import matplotlib.pyplot as plt
In [86]:
#plt.style.use('dark_background')

Dataset

Jako danych, na których będziemy operować użyjemy syntetycznego zbioru danych z 4 klasami i 2000 punktami w każdej klasie. Punkty klasy będą rozmieszczone wokół odpowiedniego środka klasy na płaszczyźnie 2D.

In [87]:
np.random.seed(20)
# centers of 4 classes
centers = [
    [ 1,  0],
    [ 0,  1],
    [-1,  0],
    [ 0, -1],
]

# colors to plot members of different classes
color_arr = ['r', 'b', 'g', 'y']

# number of instances per class
n_points = 2000
# these arrays will store information about datapoints
data_x = []
data_y = []
data_class = []
data_color = []

# standard deviation to generate the class instances
sigma = 0.5
# data generation
for class_val in range(0, len(centers)):
    x, y = centers[class_val]
    data_class.extend([class_val for j in range(0, n_points)])
    data_color.extend([color_arr[class_val] for j in range(0, n_points)])
    data_x.extend(np.random.normal(x, sigma, size=n_points))
    data_y.extend(np.random.normal(y, sigma, size=n_points))
# putting everything into a dataframe
data_df = pd.DataFrame({
    'x': data_x,
    'y': data_y,
    'class': data_class,
    'color': data_color,
})
# plotting the dataset
data_df.plot(
    kind='scatter',
    x='x',
    y='y',
    c=data_df['color'],
    s=1,
    grid=True,
    figsize=(10,10),
)

# showing the centers in orange

plt.scatter(np.array(centers).T[0], np.array(centers).T[1], s=100, c='red', label='Centers')
plt.legend()
plt.show()

Prosty klasyfikator

Zbudujemy tradycyjny (punktowy) klasyfikator przy użyciu algorytmu SVM (Support Vector Machine).

Najpierw dzielimy dane na zbiór treningowy (90\%) i testowy (10\%).

In [88]:
# fraction of the dataset to be used for testing
test_frac = 0.1

np.random.seed(2)
# performing random permutation of the dataset
idx = np.random.permutation(len(data_df))
# constucting training and test datasets
test_size = int(len(data_df) * 0.1)
train_size = len(data_df) - test_size
idx_train = idx[:train_size]
idx_test = idx[train_size:]

print('Size of training set: {}'.format(len(idx_train)))
print('Size of test set: {}'.format(len(idx_test)))
Size of training set: 7200
Size of test set: 800

Teraz jesteśmy gotowi do zbudowania modelu SVM i przetestowania go.

In [89]:
model = SVC(probability=True)
model.fit(data_df[['x', 'y']].values[idx_train, :], data_df['class'].values[idx_train])
prediction = model.predict(data_df[['x', 'y']].values[idx_test, :])
acc = sum(prediction[:] == data_df['class'].values[idx_test][:]) / len(idx_test)
print('Accuracy: {}%'.format(acc * 100))
Accuracy: 84.5%

Osiągnęliśmy dokładność około $65\%$.

Analizując rozkład punktów danych (pokazany poniżej dla odniesienia), możemy stwierdzić, że prawdopodobnie żaden inny algorytm klasyfikacji nie poprawi znacząco tego wyniku. Wiele punktów danych nakłada się w naszej przestrzeni 2D i nie ma wystarczających informacji, aby je rozróżnić.

In [90]:
x_grid = np.linspace(-4,4,200)
y_grid = np.linspace(-4,4,200)

xx, yy = np.meshgrid(x_grid, y_grid)

r1, r2 = xx.flatten(), yy.flatten()
r1, r2 = r1.reshape((len(r1), 1)), r2.reshape((len(r2), 1))

grid = np.hstack((r1,r2))
yhat = model.predict(grid)
zz = yhat.reshape(xx.shape)
In [91]:
data_df.plot(kind='scatter', x='x', y='y', c=data_df['color'], s=1, grid=True, figsize=(10,10),)
# showing the centers in orange
plt.scatter(np.array(centers).T[0], np.array(centers).T[1], s=70, c='red', label='Centers')
plt.contourf(xx, yy, zz, levels = [-1,0,1,2,3], colors=color_arr, alpha=.2)
plt.legend()
plt.show()

Powiedzmy, że potrzebujemy większej dokładności w naszej aplikacji. Jesteśmy gotowi poświęcić precyzję naszych prognoz, aby osiągnąć wymaganą dokładność. Na przykład jesteśmy gotowi zaakceptować zdanie "$a$ należy do klasy 1 lub 3 z prawdopodobieństwem $0,95$" zamiast "$a$ należy do klasy 3 z prawdopodobieństwem $0,65$".

Przez precyzję rozumiemy tutaj przewidywaną liczbę etykiet klas przypadających na obserwację. Tradycyjny klasyfikator (punktowy) przewiduje tylko jedną etykietę klasy, jest ona możliwie najdokładniejsza. Tutaj jesteśmy gotowi poświęcić tę precyzję. Jesteśmy gotowi dopuścić 0, 1 lub więcej etykiet klas na punkt danych, aby zagwarantować wymaganą dokładność. W rezultacie będziemy tworzyć prognozy zbioru (przedziału).

Poniżej kilka przykładów możliwych zastosowań:

  1. medycyna
  2. ubezpieczenie
  3. bankowość
  4. ...

Czy jest więc sposób na osiągnięcie, np dokładności $95\%$ przy dopuszczeniu większej ilości klas?

Jak uzyskać dokładność $95\%$ używając prostego modelu?

Jeden z możliwych sposobów osiągnięcia wymaganej dokładności może opierać się na analizie prawdopodobieństw wygenerowanych przez podstawowy model klasyfikacji. Użyjemy do tego stworzonego już modelu SVM.

In [92]:
prediction_proba = model.predict_proba(data_df[['x', 'y']].values[idx_test, :])
print('Probabilities of 4 possible class labels generated by our SVM classifier \n\
for the first 5 datapoints in the test set:\n')
print('   class_1     class_2    class_3    class_4')
for i in range(0, 5):
    print('{}: {}'.format(i + 1, prediction_proba[i]))
Probabilities of 4 possible class labels generated by our SVM classifier 
for the first 5 datapoints in the test set:

   class_1     class_2    class_3    class_4
1: [0.92873933 0.01310488 0.00229287 0.05586292]
2: [0.0037713  0.09558676 0.88208088 0.01856106]
3: [0.00324774 0.02167155 0.96254488 0.01253583]
4: [9.33235525e-03 9.64049860e-01 2.57279493e-02 8.89835821e-04]
5: [0.00464072 0.01086412 0.72077146 0.2637237 ]

Predyktory punktu standardowego wyprowadzają etykietę klasy skojarzoną z najwyższą wartością prawdopodobieństwa. Wyniki będą wyglądać tak (True wskazuje przewidywaną etykietę klasy):

In [93]:
prediction_point = np.zeros_like(prediction_proba)
prediction_point[np.arange(len(prediction_proba)), prediction_proba.argmax(1)] = 1
np.array(prediction_point, dtype=bool)
for i in range(0, 5):
    print('{}: {}'.format(i + 1, np.array(prediction_point[i], dtype=bool)))
1: [ True False False False]
2: [False False  True False]
3: [False False  True False]
4: [False  True False False]
5: [False False  True False]

Jeśli musimy osiągnąć dokładność $95\%$, możemy na wyjściu wypluć wszystkie etykiety klas z powiązanym prawdopodobieństwem większym niż $\geq 1 - 0,95 = 0,05$. W ten sposób wszystkie odrzucone etykiety klas nie powinny powodować więcej niż $5\%$ błędów. Będziemy odnosić się do tej wartości jako dozwolony poziom błędów, istotność lub $\epsilon$.

In [94]:
significance = 0.05
In [95]:
prediction_region_naive = prediction_proba > significance
for i in range(0, 5):
    print('{}: {}'.format(i + 1, prediction_region_naive[i]))
1: [ True False False  True]
2: [False  True  True False]
3: [False False  True False]
4: [False  True False False]
5: [False False  True  True]

Zgodnie z oczekiwaniami, teraz przewidujemy się więcej niż 1 etykietę klasy dla danej obserwacji, otrzymaliśmy prognozę w postaci zbioru.

Oszacujmy teraz, jak dobre jest przewidywanie zbudowanego zbioru.

  1. Najpierw chcemy potwierdzić, że osiągnęliśmy pożądaną dokładność 95\%$.
  2. Po drugie, byłoby interesujące ocenić, jak daleko jesteśmy teraz od precyzyjnego predyktora punktowego. W szczególności możemy obliczyć wartości następujących metryk wydajności:
    1. $oneC$ - ułamek przewidywań zbioru jednoelementowego. W przypadku predyktora punktowego wszystkie predykcje są singletonami. Oczywiście chcielibyśmy, aby ta wartość była bliska 1; czyli preferujemy większe wartości.
    2. $avgC$ - średni rozmiar zbioru predykcyjnego. Dla predyktora punktowego mamy $avgC=1$. W przypadku predyktora regionu skonstruowanego powyżej wiele punktów danych ma więcej niż 1 przewidywaną etykietę. Dlatego preferowane są mniejsze wartości $avgC$.

Metryki do oceny klasyfikatorów:

In [96]:
# Accuracy for a region prediction:
# A region prediction produces an error if the resulting prediction set does not
# contain the true value 
def get_accuracy(prediction, real_class):
    correct = 0
    N = len(prediction)
    for i in range(0, N):
        if prediction[i][real_class[i]]:
            correct += 1
    return correct / N

# calculating metrics: oneC & avgC
def get_oneC_avgC(prediction):
    arr = np.array(prediction)
    oneC = 0
    avgC = 0
    for i in range(0, len(arr)):
        # number of predicted lables
        num_predicted = arr[i].sum()
        avgC += num_predicted
        # is it a singleton?
        if num_predicted == 1:
            oneC += 1
        pass
    oneC /= len(arr)
    avgC /= len(arr)
    return oneC, avgC

Oszacujmy teraz skuteczność zbioru predykcyjnego skonstruowanego powyżej:

In [97]:
acc = get_accuracy(prediction_region_naive, data_df['class'].values[idx_test])
oneC, avgC = get_oneC_avgC(prediction_region_naive)
print('Accuracy of the region predictor: {}%'.format(acc * 100))
print('Efficiency:\n\t oneC = {}\n\t avgC = {}'.format(oneC, avgC))
Accuracy of the region predictor: 96.5%
Efficiency:
	 oneC = 0.4825
	 avgC = 1.6

Osiągnęliśmy jeszcze lepszą dokładność niż wymagana: $96,6 \% $.

Być może, wykluczając niektóre z przewidywanych etykiet, moglibyśmy poprawić skuteczność przewidywania naszego regionu. Oznacza to, że możemy osiągnąć lepsze wartości $oneC$ i $avgC$. Właśnie w tym mogą nam pomóc conformal prediction.

Conformal classifier krok po kroku

Podzielmy zbiór danych na 3 części: treningowy, testowy i kalibracyjny.

Zbiory uczące i testowe będą wykorzystywane w taki sam sposób jak dotychczas: do trenowania modelu predykcyjnego i oceny jego efektywności. Użyjemy części dotyczącej kalibracji, aby oszacować, kiedy konkretna etykieta powinna zostać uwzględniona w ostatecznym przewidywaniu regionu.

Tak jak poprzednio, zbiór testowy będzie zawierał $10\%$ dostępnych danych. Reszta zostanie podzielona na treningowy, $80\%$ i kalibracyjny, $20\%$.

In [98]:
# fraction of the dataset to be used for testing
test_frac = 0.1
# fraction of the remaining dataset to be used for calibration
calib_frac = 0.2

np.random.seed(2)
# random permulation
idx = np.random.permutation(len(data_df))
# constucting test, training and calibration datasets
test_size = int(len(data_df) * test_frac) 
calib_size = int(len(data_df) * (1 - test_frac) * calib_frac)
train_size = len(data_df) - test_size - calib_size
idx_train, idx_cal, idx_test = idx[:train_size], idx[train_size:train_size + calib_size], idx[train_size + calib_size:]

print('Test size: {}'.format(test_size))
print('Calibration size: {}'.format(calib_size))
print('Train size: {}'.format(train_size))
Test size: 800
Calibration size: 1440
Train size: 5760

Trenujemy model SVM na zbiorze treningowym:

In [99]:
model.fit(data_df[['x', 'y']].values[idx_train, :], data_df['class'].values[idx_train])
Out[99]:
SVC(probability=True)

Intuition: measuring conformity of predictions on the calibration dataset

Przeanalizujmy przewidywania dotyczące zestawu kalibracyjnego. Zobaczmy, które wartości prawdopodobieństwaskojarzone z prawdziwymi etykietami klas i jak są dystrybuowane. Oznacza to, że przeanalizujemy, jak konformalny jest każdy punkt danych z zestawu kalibracyjnego (funkcja zgodności).

In [100]:
# generating predictions for the calibration dataset
predictions_cal = model.predict_proba(X=data_df[['x', 'y']].values[idx_cal, :])
# extracting the predicted probability for the correct class labesl
calib_conformal_vals = []
for i in range(0, len(idx_cal)):
    # correct class label
    cl = data_df['class'].values[idx_cal][i]
    # associated probability
    calib_conformal_vals.append(predictions_cal[i][cl])
In [101]:
calib_conformal_vals[:5]
Out[101]:
[0.902919799117938,
 0.8569255408969476,
 0.32872975950690086,
 0.6717743084280753,
 0.9629241939013803]
In [102]:
plt.hist(calib_conformal_vals)
plt.show()
In [103]:
np.quantile(calib_conformal_vals, 0.05)
Out[103]:
0.08268325623169455

Posortujmy teraz otrzymane wartości i narysujmy "dystrybuantę empiryczną"

In [104]:
calib_conformal_vals = np.sort(calib_conformal_vals)
plt.plot(calib_conformal_vals)
plt.grid(True)
plt.ylabel('Conformity value')
plt.title('Distribution of conformity values')
Out[104]:
Text(0.5, 1.0, 'Distribution of conformity values')

Przeanalizujmy teraz, gdzie w tym rozkładzie znajdują się przewidywane wartości prawdopodobieństwa dla testowego zbioru danych i jaki jest związany z nim empiryczny poziom błędu nieuwzględnienia odpowiedniej etykiety klasy zbiorze predykcyjnym.

In [105]:
predictions_test = model.predict_proba(X=data_df[['x', 'y']].values[idx_test, :])

Wykonamy analizę dla pierwszej obserwacje testowej. Oto wygenerowane prawdopodobieństwa związane z 4 możliwymi etykietami klas:

In [106]:
predictions_test[0]
Out[106]:
array([0.93743936, 0.01172259, 0.00248643, 0.04835161])

Czy 1 klasa powinna być w zbiorze predykcyjnym?

In [107]:
print('Distribution of probabilities for the first datapoint in the test set:')
predictions_test[0]
Distribution of probabilities for the first datapoint in the test set:
Out[107]:
array([0.93743936, 0.01172259, 0.00248643, 0.04835161])

Przeanalizujmy prawdopodobieństwo związane z klasą 1 w naszym rozkładzie empiryczny,:

In [108]:
plt.plot(calib_conformal_vals)
plt.axhline(y=predictions_test[0][0], color='r', linestyle='-')
plt.legend(['calib dist', 'class_1'])
plt.grid(True)
plt.ylabel('Conformity value')
plt.title('Distribution of conformity values')
Out[108]:
Text(0.5, 1.0, 'Distribution of conformity values')

Jeżeli do rozkładu empirycznego uwzględnimy nowo obliczoną wartość, to po lewej stronie znajduje się następujący ułamek punktów (w tym nowo dodany):

In [109]:
val = sum(calib_conformal_vals <= predictions_test[0][0]) + 1
print('Number of points on the left hand side (<=): {}'.format(val))
print('Fraction of points on the left hand side:    {}%'.format(np.round(val / (len(calib_conformal_vals) + 1) * 100)))
Number of points on the left hand side (<=): 972
Fraction of points on the left hand side:    67.0%

And the following fraction of the points are situated on the right-hand side:

In [110]:
val = (sum(calib_conformal_vals > predictions_test[0][0]) + 1) 
print('Number of points on the right hand side (>): {}'.format(val))
print('Fraction of points on the right hand side:   {}%'.format(np.round(val / (len(calib_conformal_vals) + 1) * 100)))
Number of points on the right hand side (>): 470
Fraction of points on the right hand side:   33.0%

Przypomnijmy, że ten rozkład został skonstruowany dla wartości prawdopodobieństwa prawdziwych etykiet klas. Oznacza to, że ponad $43\%$ zaobserwowanych prawdopodobieństw właściwych klas miało niższe wartości niż analizowane. Stąd możemy wywnioskować, co następuje. Jeśli nie uwzględnimy powiązanej etykiety klasy (w tym przypadku 1), to z obserwowanego rozkładu prawdopodobieństwo popełnienia błędu przy tej akcji wynosi $43\%$.

Jest wyższa niż dopuszczalna stopa błędów $\epsilon = 5\%$. Oznacza to, że należy uwzględnić tę etykietę klasy.

Czy 2 klasa powinna być w zbiorze predykcyjnym?

Zróbyme tę samą analizę dla kklasy 2

In [111]:
print('Distribution of probabilities for the first datapoint in the test set:')
predictions_test[0]
Distribution of probabilities for the first datapoint in the test set:
Out[111]:
array([0.93743936, 0.01172259, 0.00248643, 0.04835161])
In [112]:
plt.plot(calib_conformal_vals)
plt.axhline(y=predictions_test[0][1], color='r', linestyle='-')
plt.grid(True)
plt.legend(['calib dist', 'class_2'])
plt.ylabel('Conformity value')
plt.title('Distribution of conformity values')
Out[112]:
Text(0.5, 1.0, 'Distribution of conformity values')

Jeżeli do rozkładu empirycznego uwzględnimy nowo obliczoną wartość, to po lewej stronie znajduje się następujący odsetek obserwacji (wraz z nowo dodanym):

In [113]:
val = sum(calib_conformal_vals <= predictions_test[0][1]) + 1
print('Number of points on the left hand side (<=): {}'.format(val))
print('Fraction of points on the left hand side:    {}%'.format(np.round(val / (len(calib_conformal_vals) + 1) * 100, 2)))
Number of points on the left hand side (<=): 7
Fraction of points on the left hand side:    0.49%

A następująca część obserwacji znajduje się po prawej stronie:

In [114]:
val = (sum(calib_conformal_vals > predictions_test[0][1]) + 1) 
print('Number of points on the right hand side (>): {}'.format(val))
print('Fraction of points on the right hand side:   {}%'.format(np.round(val / (len(calib_conformal_vals) + 1) * 100, 2)))
Number of points on the right hand side (>): 1435
Fraction of points on the right hand side:   99.58%

Using the same reasoning, we can conclude that if this class label is not included in the region prediction, the probability of making a mistake is $3.33\%$. It is less than our allowed error rate $\epsilon = 5\%$. Thereby, this class label should not be included.

Generalizacja na cały zbiór testowy

Użyjemy tego samego rozumowania, aby zdecydować, czy każda etykieta klasy powinna zostać uwzględniona w końcowej prognozie regionu dla każdego punktu danych z naszego testowego zbioru danych. Korzystając z empirycznego rozkładu wartości zgodności obliczonych na zbiorze danych kalibracyjnych, obliczymy prawdopodobieństwo popełnienia błędu przez nieuwzględnienie określonej etykiety klasy. Ta wartość jest określana jako p-wartość. Jeśli $p\_value < \epsilon$, odpowiednia etykieta klasy jest odrzucana. W przeciwnym razie jest uwzględniany w prognozie regionu.

In [115]:
p_vals_inverse = np.zeros((len(idx_test), len(centers)))
for i in range(0, len(idx_test)):
    for j in range(0, len(centers)):
        p_vals_inverse[i,j] = (sum(calib_conformal_vals <= predictions_test[i,j]) + 1) / (len(calib_conformal_vals) + 1)

p_wartości dla pierwszej obserwacji:

In [116]:
p_vals_inverse[0]
Out[116]:
array([0.67453158, 0.00485774, 0.00069396, 0.03192228])

Zbiór predykcyjny:

In [117]:
p_vals_inverse[0] > significance
Out[117]:
array([ True, False, False, False])

Wyniki dla całego zbioru testowego:

In [118]:
prediction = p_vals_inverse > significance
prediction
Out[118]:
array([[ True, False, False, False],
       [False,  True,  True, False],
       [False, False,  True, False],
       ...,
       [False, False, False,  True],
       [ True,  True, False, False],
       [False, False, False,  True]])

Oszacujmy teraz dokładność wynikowego zbioru predykcyjnego:

In [119]:
print('Conformal classification based on conformity values:')
acc = get_accuracy(prediction, data_df['class'].values[idx_test])
oneC, avgC = get_oneC_avgC(prediction)
print('Accuracy of region predictor: {}%'.format(acc * 100))
print('Efficience:\n\t oneC = {}\n\t avgC = {}'.format(oneC, avgC))
Conformal classification based on conformity values:
Accuracy of region predictor: 94.75%
Efficience:
	 oneC = 0.63875
	 avgC = 1.4

Poprzednie podejście (nie korzystamy z technik conformal):

In [120]:
acc = get_accuracy(prediction_region_naive, data_df['class'].values[idx_test])
oneC, avgC = get_oneC_avgC(prediction_region_naive)
print('Accuracy of the region predictor: {}%'.format(acc * 100))
print('Efficiency:\n\t oneC = {}\n\t avgC = {}'.format(oneC, avgC))
Accuracy of the region predictor: 96.5%
Efficiency:
	 oneC = 0.4825
	 avgC = 1.6
In [121]:
plt.hist(np.sum(prediction, axis=1))
plt.show()

Adaptacyjność

Własność conformal prediction (marginal coverage):

$$ P [Y_{test} \in 𝓣 (X_{test})] \ge 1 - \alpha $$

Własność pokrycia warunkowego (conditional coverage):

$$ P [Y_{test} \in 𝓣 (X_{test}) | X_{test}] \ge 1 - \alpha $$
Poprzednia metoda Nowa metoda
najmniejsza możliwa średnia moc zbiorów C zwykle większy średna moc zbiorów C
nieadaptacyjna adaptacyjna
używa tylko wyjścia dla poprawnej klasy używa informacji ze wszystkich wyjść klas
In [122]:
# generating predictions for the calibration dataset
predictions_cal = model.predict_proba(X=data_df[['x', 'y']].values[idx_cal, :])
# extracting the predicted probability for the correct class labesl
calib_conformal_vals = []
for i in range(0, len(idx_cal)):
#for i in range(0, 10):
    # correct class label
    cl = data_df['class'].values[idx_cal][i]
    # associated probability
    calib_conformal_vals.append(predictions_cal[i][cl])
    threshold = predictions_cal[i][cl]
    calib_conformal_vals.append(sum(predictions_cal[i][predictions_cal[i] >= threshold]))
In [123]:
plt.hist(calib_conformal_vals)
plt.show()
In [124]:
calib_conformal_vals = np.sort(calib_conformal_vals)
plt.plot(calib_conformal_vals)
plt.grid(True)
plt.ylabel('Conformity value')
plt.title('Distribution of conformity values')
Out[124]:
Text(0.5, 1.0, 'Distribution of conformity values')
In [125]:
q_hat = np.quantile(calib_conformal_vals, 1-significance)
q_hat
Out[125]:
0.9715927521578981
In [126]:
idx_sort = np.argsort(-predictions_test, axis=1)
predictions = np.zeros(prediction.shape, dtype = bool)


i=0
for row in idx_sort:
  cumsum = 0
  true_idx = []
  for idx in row:
    true_idx.append(idx)
    cumsum += predictions_test[i][idx]
    if(cumsum >= q_hat):
      for t_idx in true_idx:
        predictions[i][t_idx] = True
      break

  i+=1


predictions
Out[126]:
array([[ True, False, False,  True],
       [False,  True,  True, False],
       [False,  True,  True, False],
       ...,
       [ True, False,  True,  True],
       [ True,  True, False, False],
       [ True, False,  True,  True]])
In [127]:
acc = get_accuracy(predictions, data_df['class'].values[idx_test])
oneC, avgC = get_oneC_avgC(predictions)
print('Accuracy of region predictor: {}%'.format(acc * 100))
print('Efficience:\n\t oneC = {}\n\t avgC = {}'.format(oneC, avgC))
Accuracy of region predictor: 99.5%
Efficience:
	 oneC = 0.00125
	 avgC = 2.44125
In [128]:
plt.hist(np.sum(predictions, axis=1))
plt.show()

Jak mierzyć zdolności adaptacyjne metody?

Metryka SSC (size-stratified coverage):

$$ SSC = \min_{g \in \{1, \dots, G \}} \sum_{i \in I_g } \mathbb{1} \{ Y_{i}^{(val)} \in 𝓣 (X_{i}^{(val)}) \} $$

Metody adaptacyjne zapewniają, że SSC $> 1-\alpha$

In [129]:
def get_ssc(prediction, real_class):
  ssc = 1
  for size in [1,2,3,4]:
    idx_size = np.argwhere(np.sum(prediction, axis=1) == size).reshape(-1)
    if(idx_size.shape[0] != 0):
      acc = get_accuracy(prediction[idx_size], real_class[idx_size])
    else:
      acc=1
    if (acc < ssc):
      ssc = acc
    print(f"Dla mocy zbioru = {size} mamy dokładność {acc}")
  return(ssc)

print("SSC dla podstawowej metody conformal prediction:")
round(get_ssc(prediction, data_df['class'].values[idx_test]),4)
SSC dla podstawowej metody conformal prediction:
Dla mocy zbioru = 1 mamy dokładność 0.9393346379647749
Dla mocy zbioru = 2 mamy dokładność 0.9662921348314607
Dla mocy zbioru = 3 mamy dokładność 0.8461538461538461
Dla mocy zbioru = 4 mamy dokładność 1.0
Out[129]:
0.8462
In [130]:
print("SSC dla podstawowej metody adaptacyjnej:")
round(get_ssc(predictions, data_df['class'].values[idx_test]),4)
SSC dla podstawowej metody adaptacyjnej:
Dla mocy zbioru = 1 mamy dokładność 1.0
Dla mocy zbioru = 2 mamy dokładność 0.9918367346938776
Dla mocy zbioru = 3 mamy dokładność 1.0
Dla mocy zbioru = 4 mamy dokładność 1.0
Out[130]:
0.9918
In [130]: