Regresja Grzbietowa i Lasso
Wykorzystamy pakiet glmnet w celu przeprowadzenia
regresji ridge i lasso. Główną funkcją w tym pakiecie jest
glmnet(), która może być użyta do dopasowania modeli
regresji grzbietowej, modeli lasso i innych.
Funkcja ta ma nieco inną składnię niż inne funkcje dopasowujące
modele, z którymi zetknęliśmy się do tej pory. W szczególności, musimy
przekazać macierz \(x\) jak również
wektor \(y\) i nie używamy składni
\(y \sim x\).
Zanim przejdziemy dalej, upewnijmy się najpierw, że brakujące
wartości zostały zostały usunięte z danych, jak opisano w poprzednim
laboratorium.
Hitters = na.omit(Hitters)
W raporcie tym przeprowadzimy regresję grzbietową i lasso, aby
przewidzieć Salary na danych Hitters.
Skonfigurujmy nasze dane:
x = model.matrix(Salary~., Hitters)[,-1] # przycinam pierwszą kolumnę
# zostawiam predyktory
y = Hitters %>%
select(Salary) %>%
unlist() %>%
as.numeric()
Funkcja model.matrix() jest szczególnie przydatna do
tworzenia \(x\); nie tylko nie tylko
tworzy macierz odpowiadającą 19 predyktorom, ale również automatycznie
przekształca wszelkie zmienne jakościowe w zmienne dummy.
Ta ostatnia właściwość jest ważna, ponieważ glmnet()
może przyjmować tylko numeryczne, ilościowe dane wejściowe.
Bias vs Variance
Wybór modelu w problemach uczenia nadzorowanego wiąże się z
realizacją dwóch sprzecznych celów:
1.) Model powienien być dobrze dopasowany do danych uczących, aby
uchwycić zależność pomiędzy danymi.
2.) Model powinien dobrze przybliżać nieznane dane (zapewniać mały
błąd generalizacji).
Modele złożone dobdrze dopasowują się do danych wyjściowych, ale
charakteryzują się dużą zmiennością wartości wyjściowych. Ryzykiem jest
nadmierne dopasowanie = overfitting!
Modele prostsze są obciążone dużym błędem systematyczny (bias) i ich
zastosowanie niesie ryzyko niewystarczającego dopasowania
(underfitting)!
Składnikiem błędów generalizacji jest nieredukowalny błąd związany ze
zmiennością danych.
Regularyzacja
Duża liczna zmiennych objaśniających (predyktorów): Metoda OLS nie
daje jednoznacznego rozwiązania, gdy macierz XTX nie jest odwracalna
(tzn. gdy zmienne objaśniające są liniowo zależne).
Taka sytuacja może mieć miejsce gdy zmiennych objaśniających jest
tyle samo lub więcej niż obserwacji.
Duża wartość θi oznacza dużą wrażliwość funkcji regresji na drobne
fluktuacje cechy!
Lepszym rozwiązaniem jest gorsze dopasowanie do danych uczących przy
równoczesnym ograniczeniu parametrów świadczących o potencjalnie dużym
błędzie generalizacji.
Regresja Grzbietowa
Wprowadzenie
Regresja grzbietowa (ang. Ridge regression) to technika
regresji liniowej, która wprowadza regularyzację \(L_2\) do estymacji współczynników modelu.
Regularyzacja \(L_2\) polega na dodaniu
do funkcji celu kary proporcjonalnej do kwadratu wartości współczynników
regresji.
Podstawową ideą regresji grzbietowej jest minimalizacja funkcji celu,
która składa się z dwóch składników: błędu dopasowania (sumy kwadratów
różnic pomiędzy rzeczywistymi wartościami odpowiedzi a przewidywanymi
wartościami modelu) i kary regularyzacyjnej \(L_2\).
Wzór funkcji celu dla regresji grzbietowej można przedstawić jako:
Minimize: RSS + \(\lambda
\|\beta\|_2^2\), gdzie:
RSS to suma kwadratów różnic pomiędzy rzeczywistymi wartościami
odpowiedzi a przewidywanymi wartościami modelu (błąd
dopasowania),
\(\lambda\) (lambda) to parametr
regularyzacji, który kontroluje siłę regularyzacji,
\(\|\beta\|_2^2\) to norma \(L_2\) współczynników regresji podniesiona
do kwadratu.
Dodanie kary regularyzacyjnej \(L_2\) powoduje, że współczynniki regresji
są skupione wokół zera, ale nie dokładnie równe zeru (chyba że \(\lambda\)=0).
Regresja grzbietowa zmniejsza wartości współczynników, ale nie
powoduje, że stają się one równe zero. Im większa wartość \(\lambda\), tym bardziej są “sciskane”
współczynniki regresji.
Regresja grzbietowa jest szczególnie przydatna, gdy mamy do czynienia
z modelem, w którym występuje nadmierna wielowymiarowość lub wysokie
korelacje między zmiennymi niezależnymi.
Poprzez zmniejszanie wartości współczynników, regresja grzbietowa
może pomóc w redukcji wpływu mało istotnych cech, poprawić stabilność
modelu i zmniejszyć ryzyko przeuczenia
(overfitting).
Jednym ze sposobów kontroli złożoności modelu jest penalizacja jego
wielkości. Na przykład, w problemie regresji liniowej:
\[
\min_{\beta \in \mathbb{R}^p} \sum_{i=1}^n (y_i - x_i^\top \beta)^2,
\]
możemy kontrolować wielkość współczynników \(\beta\). Oczywiście wielkość \(\beta\) można zdefiniować na różne sposoby,
np. norma-2: \(\|\beta\|_2\), norma-1:
\(\|\beta\|_1\) czy
norma-nieskończoność: \(\|\beta\|_{\infty}\). Regresja grzbietowa
wiąże się z karą dwóch norm:
\[
\min_{\beta \in \mathbb{R}^p} \sum_{i=1}^n (y_i - x_i^\top \beta)^2 +
\lambda \|\beta\|_2^2
\]
gdzie \(\lambda\) jest parametrem
kontrolującym poziom regularyzacji. Zauważ, że \(X\) to macierz \(n\) na \(p\) wymiarów z wierszami: \(x_i^\top\), oraz \(Y\) to \(n\) na 1 wektor \(y_i\). Załóżmy, że \(X^\top X + \lambda I\) jest odwracalna,
mamy dokładne rozwiązanie problemu regresji grzbietowej:
\[
\hat \beta_{`ridge`} = (X^\top X + \lambda I)^{-1}X^\top Y.
\]
Przypomnijmy, że rozwiązaniem zwykłej regresji najmniejszych
kwadratów jest (zakładając odwracalność macierzy \(X^\top X\)):
\[
\hat \beta_{ols} = (X^\top X)^{-1}X^\top Y.
\]
Dwa fakty: kiedy \(\lambda \to 0\),
\(\hat \beta_{`ridge`} \to \hat
\beta_{ols}\); kiedy \(\lambda \to
\infty\), \(\hat \beta_{`ridge`} \to
0\).
W szczególnych przypadkach \(X\)
jest ortogonalna (tzn. kolumny \(X\) są
ortogonalne), mamy:
\[
\hat \beta_{`ridge`} = \frac{\hat \beta_{ols}}{1 + \lambda}.
\]
Widzimy więc, że estymator grzbietowy ma dodatkowo \(1/(1 + \lambda)\) tzw. “shrinkage factor”.
W związku z tym na estymatorze grzbietowym występuje obciążliwość
(bias).
Przykład
Funkcja glmnet() posiada argument alfa, który określa,
jaki typ modelu jest dopasowywany.
Jeśli alfa = 0 to dopasowywany jest model regresji
grzbietowej, a jeśli alfa = 1 to dopasowywany jest model
lasso.
Najpierw dopasowujemy model regresji grzbietowej:
grid <- 10^seq(10, -2, length = 100) # Tworzenie siatki lambda
ridge_mod <- glmnet(x, y, alpha = 0, lambda = grid) # Dopasowanie modelu ridge regression
Domyślnie funkcja glmnet() wykonuje regresję grzbietową
dla automatycznie wybranego wybranego zakresu wartości \(\lambda\). Jednakże, tutaj wybraliśmy
implementację funkcję w zakresie wartości od \(\lambda = 10^{10}\) do \(\lambda = 10^{-2}\), zasadniczo pokrywając
pełen zakres scenariuszy od modelu zerowego zawierającego tylko
przechwyt, do dopasowania najmniejszego kwadratu.
Jak widać, możemy również obliczyć dopasowanie modelu dla konkretnej
wartości \(\lambda\), która nie jest
jedną z oryginalnych wartości siatki.
Zauważ, że domyślnie funkcja glmnet() standaryzuje
zmienne tak, by były w tej samej skali. Aby wyłączyć to domyślne
ustawienie, użyj argumentu standardize = FALSE.
Z każdą wartością \(\lambda\)
związany jest wektor współczynników regresji grzbietowej, przechowywany
w macierzy, do której można uzyskać dostęp przez coef(). W
tym przypadku jest to macierz \(20 \times
100\), z 20 wierszami (po jednym dla każdego predyktora, plus
intercept) i 100 kolumnami (po jednej dla każdej wartości \(\lambda\)).
library(glmnet)
dim(coef(ridge_mod))
## [1] 20 100
plot(ridge_mod) # wykres współczynników

Spodziewamy się, że oszacowania współczynników będą znacznie
mniejsze, w sensie normy \(l_2\), gdy
używana jest duża wartość \(\lambda\),
w porównaniu z małą wartością \(\lambda\).
Oto współczynniki, gdy \(\lambda =
11498\), wraz z ich normą \(l_2\):
ridge_mod$lambda[50] # Wyświetl 50-tą wartość lambdy
## [1] 11497.57
coef(ridge_mod)[,50] # Wyświetl współczynniki związane z 50-tą wartością lambdy
## (Intercept) AtBat Hits HmRun Runs
## 407.356050200 0.036957182 0.138180344 0.524629976 0.230701523
## RBI Walks Years CAtBat CHits
## 0.239841459 0.289618741 1.107702929 0.003131815 0.011653637
## CHmRun CRuns CRBI CWalks LeagueN
## 0.087545670 0.023379882 0.024138320 0.025015421 0.085028114
## DivisionW PutOuts Assists Errors NewLeagueN
## -6.215440973 0.016482577 0.002612988 -0.020502690 0.301433531
sqrt(sum(coef(ridge_mod)[-1,50]^2)) # Oblicz normę l2
## [1] 6.360612
Dla kontrastu, oto współczynniki, gdy \(\lambda = 705\), wraz z ich \(l_2\) normą. Zwróć uwagę na znacznie
większą normę \(l_2\) współczynników
związanych z tą mniejszą wartością \(\lambda\).
ridge_mod$lambda[60] # Wyświetl 60-tą wartość lambdy
## [1] 705.4802
coef(ridge_mod)[,60] # Wyświetl współczynniki powiązane z 60-tą wartość lambdy
## (Intercept) AtBat Hits HmRun Runs RBI
## 54.32519950 0.11211115 0.65622409 1.17980910 0.93769713 0.84718546
## Walks Years CAtBat CHits CHmRun CRuns
## 1.31987948 2.59640425 0.01083413 0.04674557 0.33777318 0.09355528
## CRBI CWalks LeagueN DivisionW PutOuts Assists
## 0.09780402 0.07189612 13.68370191 -54.65877750 0.11852289 0.01606037
## Errors NewLeagueN
## -0.70358655 8.61181213
sqrt(sum(coef(ridge_mod)[-1,60]^2)) # Oblicz normę l2
## [1] 57.11001
Funkcję predict() możemy wykorzystać do wielu celów. Na
przykład, możemy uzyskać współczynniki regresji grzbietowej dla nowej
wartości \(\lambda\), powiedzmy 50:
predict(ridge_mod, s = 50, type = "coefficients")[1:20,]
## (Intercept) AtBat Hits HmRun Runs
## 4.876610e+01 -3.580999e-01 1.969359e+00 -1.278248e+00 1.145892e+00
## RBI Walks Years CAtBat CHits
## 8.038292e-01 2.716186e+00 -6.218319e+00 5.447837e-03 1.064895e-01
## CHmRun CRuns CRBI CWalks LeagueN
## 6.244860e-01 2.214985e-01 2.186914e-01 -1.500245e-01 4.592589e+01
## DivisionW PutOuts Assists Errors NewLeagueN
## -1.182011e+02 2.502322e-01 1.215665e-01 -3.278600e+00 -9.496680e+00
Podzielimy teraz próbki na zbiór treningowy i testowy w celu
oszacować błąd testu regresji grzbietowej i lasso.
set.seed(1)
train = Hitters %>%
sample_frac(0.5)
test = Hitters %>%
setdiff(train)
x_train = model.matrix(Salary~., train)[,-1]
x_test = model.matrix(Salary~., test)[,-1]
y_train = train %>%
select(Salary) %>%
unlist() %>%
as.numeric()
y_test = test %>%
select(Salary) %>%
unlist() %>%
as.numeric()
Następnie dopasowujemy model regresji grzbietowej na zbiorze
treningowym i oceniamy jego MSE na zbiorze testowym, używając \(\lambda = 4\). Zwróć uwagę na użycie
funkcji predict(). Ponownie: tym razem otrzymujemy
przewidywania dla zbioru testowego, zastępując
type="coefficients" argumentem newx.
ridge_mod = glmnet(x_train, y_train, alpha=0, lambda = grid, thresh = 1e-12)
ridge_pred = predict(ridge_mod, s = 4, newx = x_test)
mean((ridge_pred - y_test)^2)
## [1] 139858.6
Testowe MSE wynosi 139858. Zauważ, że gdybyśmy zamiast tego
dopasowali po prostu model tylko z wyrazem wolnym, przewidywalibyśmy
każdą obserwację testową używając średniej z obserwacji zbioru
treningowego. W takim przypadku moglibyśmy obliczyć MSE zestawu
testowego w ten sposób:
mean((mean(y_train) - y_test)^2)
## [1] 224692.1
Moglibyśmy również uzyskać ten sam wynik, dopasowując model regresji
grzbietowej z bardzo dużą wartością \(\lambda\). Zauważ, że 1e10
oznacza \(10^{10}\).
ridge_pred = predict(ridge_mod, s = 1e10, newx = x_test)
mean((ridge_pred - y_test)^2)
## [1] 224692.1
Tak więc dopasowanie modelu regresji grzbietowej z \(\lambda = 4\) prowadzi do znacznie niższego
testu MSE niż dopasowanie modelu z samym przechwytem.
Sprawdzimy teraz, czy jest jakaś korzyść z wykonania regresji
grzbietowej z \(\lambda = 4\) zamiast
po prostu wykonać regresję najmniejszych kwadratów.
Przypomnijmy, że najmniejsza kwadratura to po prostu regresja
grzbietowa z \(\lambda = 0\).
* Uwaga: Aby glmnet() dawał dokładne
(exact) współczynniki najmniejszego kwadratu, gdy \(\lambda = 0\), używamy argumentu
exact=T przy wywołaniu funkcji predict(). W
przeciwnym razie, funkcja predict() będzie interpolować nad
siatką wartości \(\lambda\) użytą w
dopasowaniu modelu glmnet(), dając przybliżone wyniki.
Nawet gdy użyjemy exact = T, pozostaje niewielka
rozbieżność na trzecim miejscu po przecinku między wynikami
glmnet(), gdy \(\lambda =
0\) i wyjściem z lm(); jest to spowodowane
numerycznym przybliżeniem ze strony glmnet().
ridge_pred = predict(ridge_mod, s = 0, newx = x_test)
mean((ridge_pred - y_test)^2)
## [1] 174060
lm(Salary~., data = train)
##
## Call:
## lm(formula = Salary ~ ., data = train)
##
## Coefficients:
## (Intercept) AtBat Hits HmRun Runs RBI
## 2.398e+02 -1.639e-03 -2.179e+00 6.337e+00 7.139e-01 8.735e-01
## Walks Years CAtBat CHits CHmRun CRuns
## 3.594e+00 -1.309e+01 -7.136e-01 3.316e+00 3.407e+00 -5.671e-01
## CRBI CWalks LeagueN DivisionW PutOuts Assists
## -7.525e-01 2.347e-01 1.322e+02 -1.346e+02 2.099e-01 6.229e-01
## Errors NewLeagueN
## -4.616e+00 -8.330e+01
predict(ridge_mod, s = 0, type="coefficients")[1:20,]
## (Intercept) AtBat Hits HmRun Runs
## 239.89368111 -0.01946204 -2.07305757 6.44254692 0.64610179
## RBI Walks Years CAtBat CHits
## 0.82179888 3.62448842 -13.28142313 -0.70314292 3.26064805
## CHmRun CRuns CRBI CWalks LeagueN
## 3.33170237 -0.54000590 -0.72015101 0.22582579 131.41324242
## DivisionW PutOuts Assists Errors NewLeagueN
## -134.76073238 0.20949301 0.61942855 -4.58545824 -82.35090554
Wygląda na to, że rzeczywiście poprawiamy się w stosunku do zwykłego
najmniejszego kwadratu!
Uwaga: ogólnie, jeśli chcemy dopasować (niespenalizowany) model
najmniejszych kwadratów, to powinniśmy użyć funkcji lm(),
ponieważ ta funkcja dostarcza bardziej użytecznych wyjścia, takie jak
błędy standardowe i wartości \(p\) dla
współczynników.
Zamiast arbitralnie wybierać \(\lambda =
4\), lepiej byłoby użyć walidacji krzyżowej do wyboru parametru
dostrojenia \(\lambda\). Możemy to
zrobić używając wbudowanej funkcji walidacji krzyżowej,
cv.glmnet(). Domyślnie funkcja ta wykonuje 10-krotną
walidację krzyżową, choć można to zmienić używając argumentu argumentu
folds. Zauważ, że najpierw ustawiamy losowe ziarno, aby
nasze wyniki były powtarzalne, ponieważ wybór krotności walidacji
krzyżowej jest losowy.
set.seed(1)
cv.out = cv.glmnet(x_train, y_train, alpha = 0) # Dopasuj model regresji grzbietowej na danych treningowych
bestlam_ridge = cv.out$lambda.min # Wybierz lamdę, która minimalizuje treningowy MSE
bestlam_ridge
## [1] 326.1406
Widzimy zatem, że wartość \(\lambda\), która powoduje najmniejszy błąd
walidacji krzyżowej to 326. Możemy również wykreślić MSE jako funkcję
\(\lambda\):
plot(cv.out) # Narysuj wykres treningowego MSE jako funkcję lambda

Jaki jest testowy MSE związany z tą wartością \(\lambda\)?
ridge_pred = predict(ridge_mod, s = bestlam_ridge, newx = x_test) # Użyj najlepszej lambdy do przewidywania danych testowych
mean((ridge_pred - y_test)^2) # Oblicz testowe MSE
## [1] 140056.2
Stanowi to dalszą poprawę w stosunku do testowego MSE, które
uzyskaliśmy używając \(\lambda = 4\).
Ostatecznie, ponownie wyznaczamy nasz model regresji grzbietowej na
pełnym zestawie danych, używając wartości \(\lambda\) wybranej w walidacji krzyżowej, i
sprawdzamy oszacowania współczynników.
out = glmnet(x, y, alpha = 0) # Dopasuj model regresji grzbietowej do pełnego zbioru danych
predict(out, type = "coefficients", s = bestlam_ridge)[1:20,] # Wyświetlanie współczynników przy użyciu lambda wybranego przez CV
## (Intercept) AtBat Hits HmRun Runs RBI
## 15.44834992 0.07716945 0.85906253 0.60120338 1.06366687 0.87936073
## Walks Years CAtBat CHits CHmRun CRuns
## 1.62437580 1.35296285 0.01134998 0.05746377 0.40678422 0.11455696
## CRBI CWalks LeagueN DivisionW PutOuts Assists
## 0.12115916 0.05299953 22.08942756 -79.03490992 0.16618830 0.02941513
## Errors NewLeagueN
## -1.36075645 9.12528397
Zgodnie z oczekiwaniami, żaden ze współczynników nie jest dokładnie
zerowy - regresja grzbietowa nie dokonuje selekcji zmiennych!
Regresja Lasso
Wprowadzenie
Zamiast regularyzacji \(L_2\), LASSO
używa penalizacji \(L_1\), to
znaczy:
\[
\min_{\beta \in \mathbb{R}^p} \sum_{i=1}^n (y_i - x_i^\top \beta)^2 +
\lambda \|\beta\|_1.
\]
Ze względu na charakter normy \(L_1\), LASSO ma tendencję do dawania
bardziej rzadkich rozwiązań niż regresja grzbietowa. Jest to typowo
użyteczne w ustawieniach wielowymiarowych, gdy prawdziwy model jest w
rzeczywistości niskowymiarowym osadzeniem.
Model regresji lasso został pierwotnie opracowany w 1989 roku. Jest
to alternatywa dla klasycznego oszacowania metodą najmniejszych
kwadratów, która unika wielu problemów z nadmiernym dopasowaniem
(overfittingiem), gdy mamy dużą liczbę niezależnych
zmiennych.
Regresja Lasso (Least Absolute Shrinkage and Selection Operator) to
technika regresji liniowej stosowana do oszacowania współczynników
modelu, która wprowadza regularyzację \(L_1\). Regularyzacja L1 polega na dodaniu
do funkcji celu kary proporcjonalnej do wartości bezwzględnej
współczynników regresji.
Regresja Lasso ma zdolność do jednoczesnego wykonania selekcji cech i
regularyzacji, co oznacza, że może pomóc w identyfikacji najbardziej
istotnych cech modelu, a także zmniejszyć wpływ mniej istotnych
cech.
Podstawowym celem regresji Lasso jest minimalizacja funkcji celu,
która składa się z dwóch składników: błędu dopasowania (sumy kwadratów
różnic pomiędzy rzeczywistymi wartościami odpowiedzi a przewidywanymi
wartościami modelu) i kary regularyzacyjnej \(L_1\).
Wzór funkcji celu dla regresji Lasso może być przedstawiony jako:
Minimize: RSS + \(\lambda
\|\beta\|_1\), gdzie:
RSS to suma kwadratów różnic pomiędzy rzeczywistymi wartościami
odpowiedzi a przewidywanymi wartościami modelu (błąd
dopasowania),
\(\lambda\) (lambda) to parametr
regularyzacji, który kontroluje siłę regularyzacji, a \(\|\beta\|_1\) to norma \(L_1\) współczynników regresji.
Dodanie kary regularyzacyjnej \(L_1\) powoduje, że niektóre współczynniki
regresji stają się równe zero, co prowadzi do selekcji cech. Im większa
wartość \(\lambda\), tym większa jest
tendencja do redukcji współczynników do zera, prowadząc do bardziej
rzadkiego modelu z mniejszą liczbą cech.
Regresja Lasso jest przydatna w przypadkach, gdy mamy do czynienia z
wieloma cechami, z których niektóre mogą być nieistotne. Może pomóc w
identyfikacji istotnych cech, redukcji nadmiaru danych i zwiększeniu
interpretowalności modelu.
Przykład
Zobaczyliśmy, że regresja grzbietowa z mądrym wyborem \(\lambda\) może przewyższać metodę
najmniejszych kwadratów, jak również model zerowy na zbiorze danych
Hitters.
Teraz zobaczmy, czy lasso może dać albo dokładniejszy, albo bardziej
interpretowalny model niż regresja grzbietowa.
W celu dopasowania modelu lasso, po raz kolejny używamy funkcji
glmnet(), jednak tym razem używamy argumentu
alpha=1. Poza tą zmianą postępujemy tak samo jak w
przypadku dopasowywania modelu regresji grzbietowej:
lasso_mod = glmnet(x_train,
y_train,
alpha = 1,
lambda = grid) # Dopasuj model lasso do danych treningowych
plot(lasso_mod) # Wykreśl współczynniki

Zauważmy, że na wykresie współczynników, w zależności od wyboru
dostrojenia parametru, niektóre ze współczynników są dokładnie równe
zeru. Teraz przeprowadzimy walidację krzyżową i obliczymy związany z nią
błąd testu:
set.seed(1)
cv.out = cv.glmnet(x_train, y_train, alpha = 1) # Dopasuj model lasso do danych treningowych
plot(cv.out) # Narysuj wykres MSE dla próby uczącej jako funkcję lambda

bestlam_lasso = cv.out$lambda.min # Wybierz lamdę, która minimalizuje MSE w próbie uczącej
lasso_pred = predict(lasso_mod, s = bestlam_lasso, newx = x_test) # Użyj najlepszej lambdy do przewidywania danych testowych
mean((lasso_pred - y_test)^2) # Oblicz MSE w próbie testowej
## [1] 143273
Jest to znacznie niższe MSE zbioru testowego niż modelu zerowego i
modelu najmniejszych kwadratów, i bardzo podobny do MSE testu regresji
grzbietowej z \(\lambda\) wybranej
przez walidację krzyżową.
Jednakże lasso ma istotną przewagę nad regresją grzbietową w tym, że
wynikowe oszacowania współczynników są rzadkie. Tutaj widzimy, że 12 z
19 oszacowań współczynników jest dokładnie zerowych:
out = glmnet(x, y, alpha = 1, lambda = grid) # Dopasuj model lasso do pełnego zbioru danych
lasso_coef = predict(out, type = "coefficients", s = bestlam_lasso)[1:20,] # Wyświetlanie współczynników przy użyciu lambda wybranego przez CV
lasso_coef
## (Intercept) AtBat Hits HmRun Runs
## 1.27429897 -0.05490834 2.18012455 0.00000000 0.00000000
## RBI Walks Years CAtBat CHits
## 0.00000000 2.29189433 -0.33767315 0.00000000 0.00000000
## CHmRun CRuns CRBI CWalks LeagueN
## 0.02822467 0.21627609 0.41713051 0.00000000 20.28190194
## DivisionW PutOuts Assists Errors NewLeagueN
## -116.16524424 0.23751978 0.00000000 -0.85604181 0.00000000
Wybierając tylko predyktory o niezerowych współczynnikach widzimy, że
model lasso z \(\lambda\) wybranym
przez walidację krzyżową zawiera tylko siedem zmiennych:
lasso_coef[lasso_coef != 0] # Wyświetlanie tylko niezerowych współczynników
## (Intercept) AtBat Hits Walks Years
## 1.27429897 -0.05490834 2.18012455 2.29189433 -0.33767315
## CHmRun CRuns CRBI LeagueN DivisionW
## 0.02822467 0.21627609 0.41713051 20.28190194 -116.16524424
## PutOuts Errors
## 0.23751978 -0.85604181
Twoja kolej!
Teraz nadszedł czas na przetestowanie tych metod (regresja grzbietowa
i lasso) oraz metod oceny (zestaw walidacyjny, walidacja krzyżowa) na
innych zbiorach danych. Możesz pracować z zespołem nad tą częścią
laboratorium.
Możesz użyć dowolnego zbioru danych zawartego w ISLR
lub wybrać jeden z pakietów danych na Kaggle/Data World itp. (zmienna
zależna musi być ciągła).
Pobierz zbiór danych i spróbuj określić optymalny zestaw parametrów,
które należy użyć do jego modelowania!
#Przygotowanie zbioru danych
College = na.omit(College)
- Który zbiór danych wybrałeś?
Wybrałam zbiór danych -> College (ISLR).
- Jaka była Twoja zmienna zależna (tzn. co próbowałeś modelować)?
Zmienną zależną w analizie jest Grad.Rate, czyli odsetek studentów,
którzy ukończyli studia, będący wskaźnikiem sukcesu zarówno uczelni, jak
i studentów. Większość zmiennych niezależnych w zbiorze danych może być
uznana za istotne predyktory tej zmiennej. Na Grad.Rate mogą wpływać
takie czynniki, jak liczba aplikacji (Apps), liczba przyjętych studentów
(Accept), liczba studentów rozpoczynających naukę (Enroll), wysokość
czesnego (Outstate), poziom kwalifikacji kadry akademickiej (PhD,
Terminal) oraz dostępność zasobów uczelni, takich jak wydatki na
studenta (Expend) i koszty zakwaterowania oraz wyżywienia
(Room.Board).
#Tworzenie macierzy projektowej
x1 = model.matrix(Grad.Rate~., College)[,-1] # przycinam pierwszą kolumnę
# zostawiam predyktory
#Wybór zmiennej objaśnianej
y1 = College %>%
select(Grad.Rate) %>%
unlist() %>%
as.numeric()
Utworzenie macierzy projektowej x1, zawierającej wszystkie zmienne
predykcyjne ze zbioru danych College, z pominięciem pierwszej kolumny
(intercept). Następnie wyodrębniono kolumnę Grad.Rate jako zmienną
objaśnianą y1, przedstawioną w formie wektora liczbowego.
Regresja grzbietowa
#Ustawienie siatki parametrów lambda
grid = 10^seq(10, -2, length = 100)
#Dopasowanie modelu regresji grzbietowej
ridge_mod2 = glmnet(x1, y1, alpha = 0, lambda = grid)
Utworzenie siatki potencjalnych wartości parametru regularizacji
(lambda) oraz dopasowanie modelu ridge regression przy
użyciu funkcji glmnet, gdzie ustawienie parametru alpha = 0 oznacza
zastosowanie regresji grzbietowej.
#Wymiary współczynników
dim(coef(ridge_mod2))
## [1] 18 100
plot(ridge_mod2) # wykres współczynników funkcji normy L1

Wyświetlenie wymiarów macierzy współczynników, gdzie pierwsza wartość
(18) reprezentuje liczbę współczynników dla każdego poziomu parametru
lambda. Dodatkowo, wizualizacja zmian współczynników w funkcji normy 𝐿1
ilustruje wpływ regularizacji na model.
ridge_mod2$lambda[50] # Wyświetl 50-tą wartość lambdy
## [1] 11497.57
coef(ridge_mod2)[,50] # Wyświetl współczynniki związane z 50-tą wartością lambdy
## (Intercept) PrivateYes Apps Accept Enroll
## 6.524147e+01 1.924300e-02 9.686567e-07 7.024066e-07 -6.112685e-07
## Top10perc Top25perc F.Undergrad P.Undergrad Outstate
## 7.153334e-04 6.143038e-04 -4.144832e-07 -4.310351e-06 3.619870e-06
## Room.Board Books Personal PhD Terminal
## 9.873884e-06 1.194325e-07 -1.014868e-05 4.754458e-04 5.000512e-04
## S.F.Ratio perc.alumni Expend
## -1.970232e-03 1.009748e-03 1.899832e-06
sqrt(sum(coef(ridge_mod2)[-1,50]^2)) # Oblicz normę l2
## [1] 0.01940515
Wynik 11497.57 wskazuje na wysoki poziom regularizacji, który
znacząco ogranicza wartości współczynników. Większość współczynników
jest bliska zeru, co odzwierciedla silny wpływ regularizacji w
redukowaniu ich wielkości. Z kolei wynik 0.01949515 dla 50-tej wartości
lambda pokazuje bardzo niskie wartości współczynników, będące efektem
zastosowania intensywnej regularizacji.
ridge_mod2$lambda[60] # Wyświetl 60-tą wartość lambdy
## [1] 705.4802
coef(ridge_mod2)[,60] # Wyświetl współczynniki powiązane z 60-tą wartość lambdy
## (Intercept) PrivateYes Apps Accept Enroll
## 6.222586e+01 2.855277e-01 1.464045e-05 1.093961e-05 -8.384396e-06
## Top10perc Top25perc F.Undergrad P.Undergrad Outstate
## 1.046539e-02 9.031997e-03 -6.110823e-06 -6.531529e-05 5.334163e-05
## Room.Board Books Personal PhD Terminal
## 1.453208e-04 -8.045918e-06 -1.524621e-04 6.855198e-03 7.141166e-03
## S.F.Ratio perc.alumni Expend
## -2.809567e-02 1.502890e-02 2.710929e-05
sqrt(sum(coef(ridge_mod2)[-1,60]^2)) # Oblicz normę l2
## [1] 0.2878028
Wynik 705.482 wskazuje na niższy poziom regularizacji w porównaniu do
50-tej wartości lambda. Wartości współczynników są wyższe, co oznacza,
że słabsza regularizacja pozwala na większą swobodę w ich ustalaniu. Z
kolei wynik 0.2878028 jest wyższy niż dla 50-tej wartości lambda, co
odzwierciedla mniejsze ograniczenie wartości współczynników przez
regularizację.
#Predykcja współczynników dla zadanej wartości lambda
#Generuje współczynniki regresji `ridge` dla konkretnej wartości lambda (s = 50).
predict(ridge_mod2, s = 50, type = "coefficients")[1:18,]
## (Intercept) PrivateYes Apps Accept Enroll
## 4.671830e+01 1.903498e+00 1.234870e-04 1.146584e-04 3.405181e-06
## Top10perc Top25perc F.Undergrad P.Undergrad Outstate
## 6.176559e-02 5.629912e-02 -3.664026e-05 -5.169350e-04 3.385363e-04
## Room.Board Books Personal PhD Terminal
## 9.118980e-04 -5.822815e-04 -1.095459e-03 3.462916e-02 3.067362e-02
## S.F.Ratio perc.alumni Expend
## -1.141984e-01 1.039575e-01 1.091441e-04
#Podział danych na zbiór treningowy i testowy 50/50
set.seed(1)
train = College %>%
sample_frac(0.5)
test = College %>%
setdiff(train)
#Tworzenie macierzy projektowych dla zbiorów treningowego i testowego
x_train = model.matrix(Grad.Rate~., train)[,-1]
x_test = model.matrix(Grad.Rate~., test)[,-1]
#Przygotowanie zmiennej objaśnianej
y_train = train %>%
select(Grad.Rate) %>%
unlist() %>%
as.numeric()
y_test = test %>%
select(Grad.Rate) %>%
unlist() %>%
as.numeric()
#Dopasowanie modelu `ridge` regression
ridge_mod2 = glmnet(x_train, y_train, alpha=0, lambda = grid, thresh = 1e-12)
#Predykcja dla zbioru testowego
ridge_pred2 = predict(ridge_mod2, s = 4, newx = x_test)
mean((ridge_pred2 - y_test)^2)
## [1] 167.8003
Utworzono model regresji grzbietowej z różnymi poziomami
regularizacji. Dokonano predykcji na zbiorze testowym przy użyciu
wartości lambda = 4. Obliczony błąd średniokwadratowy (MSE) dla
predykcji wyniósł 167.8003. Niska wartość MSE wskazuje na dobrą jakość
modelu regresji grzbietowej.
mean((mean(y_train) - y_test)^2)
## [1] 288.1504
#Porównanie wyników z różnymi wartościami lambda
ridge_pred2 = predict(ridge_mod2, s = 1e10, newx = x_test)
mean((ridge_pred2 - y_test)^2)
## [1] 288.1504
Dla wartości lambda = 10^10 (bardzo wysoka regularizacja) wynik MSE
wynosi 288.1504. Tak wysoki poziom regularizacji powoduje znaczne
uproszczenie modelu, co skutkuje większym błędem predykcji.
ridge_pred2 = predict(ridge_mod2, s = 0, newx = x_test)
mean((ridge_pred2 - y_test)^2)
## [1] 168.3203
#Regresja liniowa jako model bazowy
lm(Grad.Rate~., data = train)
##
## Call:
## lm(formula = Grad.Rate ~ ., data = train)
##
## Coefficients:
## (Intercept) PrivateYes Apps Accept Enroll Top10perc
## 34.3360980 2.4279951 0.0010389 -0.0013828 0.0078608 0.0371502
## Top25perc F.Undergrad P.Undergrad Outstate Room.Board Books
## 0.1233484 -0.0010036 -0.0018086 0.0009734 0.0019312 -0.0026554
## Personal PhD Terminal S.F.Ratio perc.alumni Expend
## -0.0028311 0.1160148 -0.0463643 0.0201218 0.2333938 -0.0003622
#Predykcja współczynników dla wybranej lambda
predict(ridge_mod2, s = 0, type="coefficients")[1:18,]
## (Intercept) PrivateYes Apps Accept Enroll
## 34.3390554500 2.4433687719 0.0010142146 -0.0013205300 0.0076338298
## Top10perc Top25perc F.Undergrad P.Undergrad Outstate
## 0.0395564696 0.1223166262 -0.0009717872 -0.0018151895 0.0009692031
## Room.Board Books Personal PhD Terminal
## 0.0019333571 -0.0026600736 -0.0028335583 0.1153062666 -0.0457212412
## S.F.Ratio perc.alumni Expend
## 0.0207052181 0.2336039536 -0.0003604071
Dla wartości lambda = 0 (brak regularizacji) wynik MSE wynosi
168.3203. Brak regularizacji umożliwia modelowi pełne dopasowanie do
danych, co jednak nie zawsze prowadzi do lepszych wyników predykcji w
porównaniu z modelem z optymalną wartością lambda.
#Walidacja krzyżowa dla regresji `ridge`
set.seed(1)
cv.out = cv.glmnet(x_train, y_train, alpha = 0) # Dopasuj model regresji grzbietowej na danych treningowych
bestlam_ridge = cv.out$lambda.min # Wybierz lamdę, która minimalizuje treningowy MSE
bestlam_ridge
## [1] 4.093486
Zatem wartość \(\lambda = 4\), która
minimalizuje błąd walidacji krzyżowej, jest optymalna. Interpretacja
optymalnej wartości lambda:
Optymalna wartość lambda stanowi kompromis między prostotą modelu a
jego dokładnością predykcji.
Zbyt wysokie wartości lambda (nadmierna regularizacja) mogą prowadzić do zredukowania wpływu istotnych zmiennych, co skutkuje utratą ważnych informacji.
Z kolei zbyt niskie wartości lambda (lub brak regularizacji) mogą skutkować przeuczeniem modelu, gdzie dopasowuje się on zbyt dokładnie do danych uczących, tracąc zdolność do generalizacji na nowych danych.
plot(cv.out) # Narysuj wykres treningowego MSE jako funkcję lambda

Czerwona kropka wskazuje minimalny błąd MSE, odpowiadający optymalnej
wartości lambda.
Szare paski reprezentują przedziały błędu dla różnych wartości
lambda.
Obserwujemy, że:
Przy bardzo małych wartościach lambda, model charakteryzuje się wyższym błędem, co wynika z jego nadmiernej złożoności i ryzyka przeuczenia.
Przy bardzo dużych wartościach lambda, błąd również wzrasta, ponieważ model staje się zbyt uproszczony, tracąc zdolność do uchwycenia istotnych zależności w danych.
#Predykcja na zbiorze testowym przy użyciu optymalnej wartości lambda
ridge_pred2 = predict(ridge_mod2, s = bestlam_ridge, newx = x_test) # Użyj najlepszej lambdy do przewidywania danych testowych
mean((ridge_pred2 - y_test)^2) # Oblicz testowe MSE
## [1] 167.8427
Optymalna wartość lambda (bestlam uzyskana z walidacji krzyżowej)
została zastosowana do przewidywania na danych testowych.
Wynikowy błąd średniokwadratowy (MSE) wynosi 167.8427.
Model regresji grzbietowej z optymalną regularizacją zapewnia wysoką
jakość predykcji na danych testowych, skutecznie minimalizując błąd i
równoważąc złożoność modelu z jego zdolnością do generalizacji.
#Dopasowanie modelu `ridge` regression do pełnego zbioru danych
out = glmnet(x1, y1, alpha = 0) # Dopasuj model regresji grzbietowej do pełnego zbioru danych
predict(out, type = "coefficients", s = bestlam_ridge)[1:18,] # Wyświetlanie współczynników przy użyciu lambda wybranego przez CV
## (Intercept) PrivateYes Apps Accept Enroll
## 35.7809244560 3.5091501069 0.0004522487 0.0003691766 0.0001702477
## Top10perc Top25perc F.Undergrad P.Undergrad Outstate
## 0.0926780495 0.1037475794 -0.0001112942 -0.0012767754 0.0006979171
## Room.Board Books Personal PhD Terminal
## 0.0017355002 -0.0021783531 -0.0018556999 0.0470636613 -0.0114667987
## S.F.Ratio perc.alumni Expend
## 0.0319588603 0.2327598672 -0.0001406210
Współczynniki w modelu z optymalną wartością lambda są większe niż w
przypadku bardzo wysokiej regularizacji, ale mniejsze niż w modelu bez
regularizacji.
Taki wynik wskazuje na zrównoważony wpływ zmiennych na predykcję, co
pozwala modelowi uwzględniać istotne zależności bez nadmiernego
dopasowania lub uproszczenia.
Regresja Lasso
lasso_mod = glmnet(x_train,
y_train,
alpha = 1,
lambda = grid) # Dopasuj model lasso do danych treningowych
plot(lasso_mod) # Wykreśl współczynniki

Dopasowano model lasso regression (z alpha = 1) na zbiorze
treningowym, uwzględniając różne wartości lambda.
W miarę wzrostu wartości lambda, wiele współczynników stopniowo
zmniejsza się do zera, co świadczy o procesie selekcji zmiennych w
modelu lasso.
Model lasso regression nie tylko regularizuje, ale także eliminuje
nieistotne zmienne, co czyni go szczególnie użytecznym w analizach
obejmujących dużą liczbę predyktorów, pomagając w identyfikacji
najważniejszych czynników wpływających na wynik.
set.seed(1)
cv.out = cv.glmnet(x_train, y_train, alpha = 1) # Dopasuj model lasso do danych treningowych
plot(cv.out) # Narysuj wykres MSE dla próby uczącej jako funkcję lambda

bestlam_lasso = cv.out$lambda.min # Wybierz lamdę, która minimalizuje MSE w próbie uczącej
lasso_pred = predict(lasso_mod, s = bestlam_lasso, newx = x_test) # Użyj najlepszej lambdy do przewidywania danych testowych
mean((lasso_pred - y_test)^2) # Oblicz MSE w próbie testowej
## [1] 166.8336
Przeprowadzono walidację krzyżową dla modelu lasso regression (alpha
= 1).
Wykres ilustruje błąd średniokwadratowy (MSE) w funkcji log(𝜆).
Czerwona kropka wskazuje wartość 𝜆, dla której błąd walidacji krzyżowej jest najmniejszy, co oznacza optymalną wartość regularizacji.
Szare paski reprezentują przedziały błędu dla różnych wartości 𝜆, a najlepsza wartość 𝜆 minimalizuje MSE i znajduje się w punkcie o najniższym błędzie.
Optymalna wartość 𝜆 dla modelu lasso została automatycznie wyznaczona
na podstawie wyników walidacji krzyżowej. Model z tą wartością 𝜆 został
użyty do przewidywania na zbiorze testowym.
Obliczono MSE dla predykcji na danych testowych, który wyniósł 168.8336.
Wynik pokazuje, że model lasso regression osiąga porównywalny poziom błędu z modelem `ridge` regression (MSE = 167.8427), co sugeruje podobną skuteczność obu podejść w analizowanym przypadku.
#Wyświetlenie współczynników modelu lasso dla pełnego zbioru danych
out = glmnet(x1, y1, alpha = 1, lambda = grid) # Dopasuj model lasso do pełnego zbioru danych
lasso_coef = predict(out, type = "coefficients", s = bestlam_lasso)[1:18,] # Wyświetlanie współczynników przy użyciu lambda wybranego przez CV
lasso_coef
## (Intercept) PrivateYes Apps Accept Enroll
## 34.9830711329 2.8429940894 0.0007166096 0.0000000000 0.0000000000
## Top10perc Top25perc F.Undergrad P.Undergrad Outstate
## 0.0507860016 0.1303008938 0.0000000000 -0.0014704750 0.0009254499
## Room.Board Books Personal PhD Terminal
## 0.0016861054 -0.0013031184 -0.0016745514 0.0139488663 0.0000000000
## S.F.Ratio perc.alumni Expend
## 0.0000000000 0.2672573462 -0.0002371574
lasso_coef[lasso_coef != 0] # Wyświetlanie tylko niezerowych współczynników
## (Intercept) PrivateYes Apps Top10perc Top25perc
## 34.9830711329 2.8429940894 0.0007166096 0.0507860016 0.1303008938
## P.Undergrad Outstate Room.Board Books Personal
## -0.0014704750 0.0009254499 0.0016861054 -0.0013031184 -0.0016745514
## PhD perc.alumni Expend
## 0.0139488663 0.2672573462 -0.0002371574
Lasso wybrało podzbiór najważniejszych zmiennych, eliminując te,
które nie miały istotnego wpływu na zmienną objaśnianą.
Wnioski
- Czy oczekiwałeś, że regresja grzbietowa będzie lepsza od lasso, czy
odwrotnie? Jak wypada w stosunku do OLS? Pokaż odpowiednie raporty,
miary dopasowania i krótko je omów (porównaj).
W analizie danych zawierających wiele predyktorów, często wzajemnie
skorelowanych, modele regularizacyjne, takie jak regresja grzbietowa
(ridge regression) i lasso, zwykle przewyższają klasyczną
regresję liniową (OLS) pod względem zdolności do generalizacji na nowych
danych.
Regresja grzbietowa okazuje się szczególnie skuteczna w przypadku wielu skorelowanych predyktorów, ponieważ rozkłada wagi między nimi w sposób bardziej równomierny, ograniczając problem kolinearności.
Lasso nie tylko regularizuje, ale także przeprowadza selekcję zmiennych, redukując współczynniki mniej istotnych predyktorów do zera. Dzięki temu upraszcza model i jest szczególnie użyteczne, gdy tylko kilka predyktorów ma istotny wpływ na zmienną zależną.
Na podstawie tych właściwości można oczekiwać, że:
Lasso przewyższy regresję grzbietową w sytuacjach, gdy niewielka liczba predyktorów jest istotna.
Regresja grzbietowa będzie bardziej odpowiednia, gdy wszystkie predyktory wnoszą wartość do modelu.
Porównanie modeli:
Aby zweryfikować skuteczność obu modeli, obliczamy błąd
średniokwadratowy (MSE) na zbiorze testowym zarówno dla regresji
grzbietowej (ridge regression), jak i regresji lasso (lasso
regression). Obliczone wartości MSE pozwalają na ocenę zdolności każdego
z modeli do generalizacji, czyli przewidywania wyników na nowych danych,
poza zbiorem treningowym.
- Regresja grzbietowa (
ridge regression):
# Predykcja na zbiorze testowym z optymalną lambda
ridge_pred = predict(ridge_mod2, s = bestlam_ridge, newx = x_test)
# Obliczenie MSE
mse_ridge = mean((ridge_pred - y_test)^2)
MSE ridge: 167.8427
- Regresja lasso:
# Predykcja na zbiorze testowym z optymalną lambda
lasso_pred = predict(lasso_mod, s = bestlam_lasso, newx = x_test)
# Obliczenie MSE
mse_lasso = mean((lasso_pred - y_test)^2)
MSE lasso: 168.8336
- Regresja liniowa (OLS):
# Dopasowanie modelu OLS na zbiorze treningowym
ols_mod = lm(Grad.Rate ~ ., data = train)
# Predykcja na zbiorze testowym
ols_pred = predict(ols_mod, newdata = test)
# Obliczenie MSE
mse_ols = mean((ols_pred - y_test)^2)
MSE OLS: 168.3203
Regresja grzbietowa uzyskała najniższy MSE na zbiorze testowym, co
sugeruje, że spośród trzech porównywanych modeli najlepiej przewiduje
wartość Grad.Rate.
Regresja liniowa (OLS) osiągnęła MSE nieznacznie wyższe niż regresja grzbietowa, co wskazuje, że brak regularizacji nie spowodował znacznego pogorszenia wyników.
Regresja lasso miała nieco wyższe MSE niż pozostałe modele, jednak różnice były minimalne.
Wyniki wskazują, że regresja grzbietowa nieznacznie przewyższyła
lasso i OLS pod względem MSE na zbiorze testowym. Oczekiwania dotyczące
lepszej wydajności lasso nie potwierdziły się, co może być wynikiem
charakterystyki danych, gdzie wszystkie predyktory wnoszą istotną
informację do modelu.
Bliskie wyniki OLS sugerują również, że problem nadmiernego
dopasowania lub wielokolinearności w tych danych nie był dominujący.
Wnioski:
Wybór odpowiedniego modelu powinien zależeć nie tylko od miar
dopasowania, takich jak MSE, ale również od celów analizy oraz potrzeby
interpretowalności modelu. Regresja grzbietowa może być preferowana w
przypadku danych o wielu skorelowanych predyktorach, podczas gdy lasso
sprawdzi się lepiej w analizach wymagających selekcji zmiennych. OLS
pozostaje prostym i skutecznym podejściem w sytuacjach, gdzie
regularizacja nie jest konieczna.
- Które predyktory okazały się ważne w ostatecznym modelu
(modelach)?
- Regresja grzbietowa (
ridge regression) W regresji
grzbietowej wszystkie predyktory zachowują swoje współczynniki, ponieważ
model nie eliminuje zmiennych, a jedynie regularizuje ich wartości.
Ostateczne współczynniki dla optymalnej wartości𝜆 (wyznaczonej przez
walidację krzyżową) są następujące:
predict(ridge_mod2, type = "coefficients", s = bestlam_ridge)[1:18,]
## (Intercept) PrivateYes Apps Accept Enroll
## 3.590439e+01 3.079648e+00 3.056605e-04 2.900056e-04 1.056032e-03
## Top10perc Top25perc F.Undergrad P.Undergrad Outstate
## 9.179092e-02 9.254084e-02 -7.773098e-05 -1.602190e-03 6.641146e-04
## Room.Board Books Personal PhD Terminal
## 1.715050e-03 -3.032236e-03 -2.722368e-03 6.887792e-02 1.412520e-02
## S.F.Ratio perc.alumni Expend
## -7.720675e-03 2.133569e-01 -1.243617e-04
Regresja grzbietowa (ridge regression):
Wszystkie predyktory są uwzględnione w modelu, jednak ich wpływ jest
regularizowany w celu zmniejszenia ryzyka przeuczenia. Wyższe wartości
współczynników, takie jak PrivateYes czy Top25perc, wskazują na
silniejszy wpływ tych zmiennych na wartość Grad.Rate. Regresja lasso
(lasso regression):
- Regresja lasso eliminuje mniej istotne predyktory, zmniejszając ich
współczynniki do zera. Dzięki temu model automatycznie identyfikuje
zmienne o największym wpływie na zmienną objaśnianą. To podejście
upraszcza model i może być szczególnie przydatne w sytuacjach, gdy
liczba predyktorów jest duża, a tylko niektóre z nich mają istotny
wpływ.
Wybrane predyktory (współczynniki różne od zera):
predict(lasso_mod, type = "coefficients", s = bestlam_lasso)[lasso_coef != 0]
## [1] 34.9844432060 2.0645478257 0.0004612948 0.0536851583 0.1128949262
## [6] -0.0018452355 0.0008882663 0.0017012293 -0.0014600889 -0.0028088133
## [11] 0.0635913900 0.2347684266 -0.0001734067
Interpretacja: Lasso wyeliminowało mniej istotne predyktory, takie
jak Apps, Accept, Enroll, F.Undergrad, czy P.Undergrad. Istotne zmienne:
PrivateYes: Zmienna binarna, która wskazuje, czy
uczelnia jest prywatna. Top10perc i
Top25perc: Procent studentów z najlepszych wyników
akademickich. Room.Board i Terminal:
Koszty zakwaterowania oraz liczba nauczycieli z tytułem terminalnym (np.
doktorem).
- Regresja liniowa (OLS) OLS nie stosuje regularizacji ani selekcji
zmiennych. Wszystkie predyktory są uwzględniane w modelu, a ich
współczynniki są następujące:
Współczynniki OLS:
summary(ols_mod)$coefficients
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 34.3360979737 7.3160760874 4.69323960 3.792381e-06
## PrivateYes 2.4279951257 2.3662117311 1.02611068 3.055098e-01
## Apps 0.0010388731 0.0006098643 1.70344958 8.932366e-02
## Accept -0.0013827754 0.0012606191 -1.09690179 2.733977e-01
## Enroll 0.0078607547 0.0037550790 2.09336602 3.699686e-02
## Top10perc 0.0371501790 0.1060376862 0.35034883 7.262763e-01
## Top25perc 0.1233484128 0.0778745455 1.58393750 1.140623e-01
## F.Undergrad -0.0010035769 0.0006515845 -1.54021006 1.243639e-01
## P.Undergrad -0.0018085761 0.0006400996 -2.82546057 4.977280e-03
## Outstate 0.0009734411 0.0003208022 3.03439637 2.580524e-03
## Room.Board 0.0019311849 0.0008547150 2.25944891 2.443665e-02
## Books -0.0026554051 0.0046945010 -0.56564159 5.719803e-01
## Personal -0.0028310655 0.0010262313 -2.75870105 6.091398e-03
## PhD 0.1160148011 0.0790250027 1.46807715 1.429328e-01
## Terminal -0.0463643398 0.0859115839 -0.53967507 5.897458e-01
## S.F.Ratio 0.0201217619 0.2415342143 0.08330812 9.336516e-01
## perc.alumni 0.2333938111 0.0706091036 3.30543512 1.041000e-03
## Expend -0.0003622122 0.0001920999 -1.88554090 6.014007e-02
Interpretacja: Model OLS uwzględnia wszystkie zmienne, a predyktory o
dużych wartościach współczynników, takie jak PrivateYes, Top10perc, i
Top25perc, wydają się najbardziej istotne. Wnioski:
Lasso regression zidentyfikowało najbardziej istotne predyktory, eliminując mniej ważne zmienne, co jest szczególnie przydatne, gdy uproszczenie modelu ma kluczowe znaczenie.
`Ridge` regression uwzględniło wszystkie zmienne, regularizując ich współczynniki, co sprawdza się, gdy każda zmienna wnosi istotną informację do modelu.
Regresja liniowa (OLS) zawiera wszystkie predyktory, ale brak regularizacji może prowadzić do problemów z generalizacją w przypadku skorelowanych zmiennych.
LS0tDQp0aXRsZTogJ05pZWtsYXN5Y3puZSBtZXRvZHkgc3RhdHlzdHlraScNCnN1YnRpdGxlOiAnUmVndWxhcnl6YWNqYScNCmRhdGU6ICIxNS4wMS4yMDI1Ig0KYXV0aG9yOiAiS2Fyb2xpbmEgV2FqYyINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdGhlbWU6IGNlcnVsZWFuDQogICAgaGlnaGxpZ2h0OiB0ZXh0bWF0ZQ0KICAgIGZvbnRzaXplOiAxMHB0DQogICAgdG9jOiB5ZXMNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICB0b2NfZmxvYXQ6DQogICAgICBjb2xsYXBzZWQ6IG5vDQogICAgZGZfcHJpbnQ6IGRlZmF1bHQNCiAgICB0b2NfZGVwdGg6IDUNCmVkaXRvcl9vcHRpb25zOiANCiAgbWFya2Rvd246IA0KICAgIHdyYXA6IDcyDQotLS0NCg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZWNobz1GQUxTRX0NCmxpYnJhcnkoSVNMUikNCmxpYnJhcnkoZ2xtbmV0KQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkodGlkeXIpDQpgYGANCg0KVG8gbGFib3JhdG9yaXVtIG5hIHRlbWF0IFJlZ3Jlc2ppIGdyemJpZXRvd2VqIChgUmlkZ2VgIFJlZ3Jlc3Npb24gLSBSUikgaSBMYXNzbyB3IFIgcG9jaG9kemkgemUgc3Ryb24gMjUxLTI1NSBrc2nEhcW8a2kgIkludHJvZHVjdGlvbiB0byBTdGF0aXN0aWNhbCBMZWFybmluZyB3aXRoIEFwcGxpY2F0aW9ucyBpbiBSIiBhdXRvcnN0d2EgR2FyZXRoYSBKYW1lc2EsIERhbmllbGkgV2l0dGVuLCBUcmV2b3JhIEhhc3RpZSBpIFJvYmVydGEgVGlic2hpcmFuaS4gWm9zdGHFgm8gb25vIHBvbm93bmllIHphaW1wbGVtZW50b3dhbmUgamVzaWVuacSFIDIwMTYgcm9rdSB3IGZvcm1hY2llIGB0aWR5dmVyc2VgIHByemV6IEFtZWxpxJkgTWNOYW1hcsSZIGkgUi4gSm9yZGFuYSBDcm91c2VyYSB3IFNtaXRoIENvbGxlZ2UuDQoNClcgdHltIHR5Z29kbml1IG9tw7N3aW15IGR3aWUgYWx0ZXJuYXR5d25lIGZvcm15IHJlZ3Jlc2ppIGxpbmlvd2VqIHp3YW5lICoqcmVncmVzasSFIGdyemJpZXRvd8SFKiogaSAqKnJlZ3Jlc2rEhSBMQVNTTyoqLiBUZSBkd2llIG1ldG9keSBzxIUgcHJ6eWvFgmFkYW1pIG1ldG9kICoqcmVndWxhcnl6YWNqaSoqIGx1YiAqKnptbmllanN6YW5pYSoqLCB3IGt0w7NyeWNoIHphY2jEmWNhIHNpxJkgZG8gdGVnbywgYWJ5IHBhcmFtZXRyeSBtb2RlbHUgYnnFgnkgbWHFgmUuIA0KDQoNCiMgUmVncmVzamEgR3J6YmlldG93YSBpIExhc3NvDQoNCg0KV3lrb3J6eXN0YW15IHBha2lldCBgZ2xtbmV0YCB3IGNlbHUgcHJ6ZXByb3dhZHplbmlhIHJlZ3Jlc2ppIGByaWRnZWAgaSBsYXNzby4gR8WCw7N3bsSFIGZ1bmtjasSFIHcgdHltIHBha2llY2llIGplc3QgYGdsbW5ldCgpYCwga3TDs3JhIG1vxbxlIGJ5xIcgdcW8eXRhIGRvIGRvcGFzb3dhbmlhIG1vZGVsaSByZWdyZXNqaSBncnpiaWV0b3dlaiwgbW9kZWxpIGxhc3NvIGkgaW5ueWNoLg0KDQpGdW5rY2phIHRhIG1hIG5pZWNvIGlubsSFIHNrxYJhZG5pxJkgbmnFvCBpbm5lIGZ1bmtjamUgZG9wYXNvd3VqxIVjZSBtb2RlbGUsIHoga3TDs3J5bWkgemV0a27EmWxpxZtteSBzacSZIGRvIHRlaiBwb3J5LiBXIHN6Y3plZ8OzbG5vxZtjaSwgbXVzaW15IHByemVrYXphxIcgbWFjaWVyeiAkeCQgamFrIHLDs3duaWXFvCB3ZWt0b3IgJHkkIGkgbmllIHXFvHl3YW15IHNrxYJhZG5pICR5IFxzaW0geCQuDQoNClphbmltIHByemVqZHppZW15IGRhbGVqLCB1cGV3bmlqbXkgc2nEmSBuYWpwaWVydywgxbxlIGJyYWt1asSFY2Ugd2FydG/Fm2NpIHpvc3RhxYJ5IHpvc3RhxYJ5IHVzdW5pxJl0ZSB6IGRhbnljaCwgamFrIG9waXNhbm8gdyBwb3ByemVkbmltIGxhYm9yYXRvcml1bS4NCg0KDQpgYGB7cn0NCkhpdHRlcnMgPSBuYS5vbWl0KEhpdHRlcnMpDQpgYGANCg0KVyByYXBvcmNpZSB0eW0gcHJ6ZXByb3dhZHppbXkgcmVncmVzasSZIGdyemJpZXRvd8SFIGkgbGFzc28sIGFieSBwcnpld2lkemllxIcgYFNhbGFyeWAgbmEgZGFueWNoIGBIaXR0ZXJzYC4NCg0KU2tvbmZpZ3VydWpteSBuYXN6ZSBkYW5lOg0KDQoNCmBgYHtyfQ0KeCA9IG1vZGVsLm1hdHJpeChTYWxhcnl+LiwgSGl0dGVycylbLC0xXSAjIHByenljaW5hbSBwaWVyd3N6xIUga29sdW1uxJkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyB6b3N0YXdpYW0gcHJlZHlrdG9yeQ0KeSA9IEhpdHRlcnMgJT4lDQogIHNlbGVjdChTYWxhcnkpICU+JQ0KICB1bmxpc3QoKSAlPiUNCiAgYXMubnVtZXJpYygpDQpgYGANCg0KRnVua2NqYSBgbW9kZWwubWF0cml4KClgIGplc3Qgc3pjemVnw7NsbmllIHByenlkYXRuYSBkbyB0d29yemVuaWEgJHgkOyBuaWUgdHlsa28gbmllIHR5bGtvIHR3b3J6eSBtYWNpZXJ6IG9kcG93aWFkYWrEhWPEhSAxOSBwcmVkeWt0b3JvbSwgYWxlIHLDs3duaWXFvCBhdXRvbWF0eWN6bmllIHByemVrc3p0YcWCY2Egd3N6ZWxraWUgem1pZW5uZSBqYWtvxZtjaW93ZSB3IHptaWVubmUgZHVtbXkuDQoNClRhIG9zdGF0bmlhIHfFgmHFm2Npd2/Fm8SHIGplc3Qgd2HFvG5hLCBwb25pZXdhxbwgYGdsbW5ldCgpYCBtb8W8ZSBwcnp5am1vd2HEhyB0eWxrbyBudW1lcnljem5lLCBpbG/Fm2Npb3dlIGRhbmUgd2VqxZtjaW93ZS4NCg0KIyMgQmlhcyB2cyBWYXJpYW5jZQ0KDQpXeWLDs3IgbW9kZWx1IHcgcHJvYmxlbWFjaCB1Y3plbmlhIG5hZHpvcm93YW5lZ28gd2nEhcW8ZSBzacSZIHogcmVhbGl6YWNqxIUgZHfDs2NoIHNwcnplY3pueWNoIGNlbMOzdzoNCg0KMS4pIE1vZGVsIHBvd2llbmllbiBiecSHIGRvYnJ6ZSBkb3Bhc293YW55IGRvIGRhbnljaCB1Y3rEhWN5Y2gsIGFieSB1Y2h3eWNpxIcgemFsZcW8bm/Fm8SHIHBvbWnEmWR6eSBkYW55bWkuDQoNCjIuKSBNb2RlbCBwb3dpbmllbiBkb2JyemUgcHJ6eWJsacW8YcSHIG5pZXpuYW5lIGRhbmUgKHphcGV3bmlhxIcgbWHFgnkgYsWCxIVkIGdlbmVyYWxpemFjamkpLg0KDQpNb2RlbGUgesWCb8W8b25lIGRvYmRyemUgZG9wYXNvd3VqxIUgc2nEmSBkbyBkYW55Y2ggd3lqxZtjaW93eWNoLCBhbGUgY2hhcmFrdGVyeXp1asSFIHNpxJkgZHXFvMSFIHptaWVubm/Fm2NpxIUgd2FydG/Fm2NpIHd5asWbY2lvd3ljaC4gUnl6eWtpZW0gamVzdCBuYWRtaWVybmUgZG9wYXNvd2FuaWUgPSBvdmVyZml0dGluZyENCg0KTW9kZWxlIHByb3N0c3plIHPEhSBvYmNpxIXFvG9uZSBkdcW8eW0gYsWCxJlkZW0gc3lzdGVtYXR5Y3pueSAoYmlhcykgaSBpY2ggemFzdG9zb3dhbmllIG5pZXNpZSByeXp5a28gbmlld3lzdGFyY3phasSFY2VnbyBkb3Bhc293YW5pYSAodW5kZXJmaXR0aW5nKSENCg0KU2vFgmFkbmlraWVtIGLFgsSZZMOzdyBnZW5lcmFsaXphY2ppIGplc3QgbmllcmVkdWtvd2FsbnkgYsWCxIVkIHp3acSFemFueSB6ZSB6bWllbm5vxZtjacSFIGRhbnljaC4NCg0KIyMgUmVndWxhcnl6YWNqYQ0KDQpEdcW8YSBsaWN6bmEgem1pZW5ueWNoIG9iamHFm25pYWrEhWN5Y2ggKHByZWR5a3RvcsOzdyk6IE1ldG9kYSBPTFMgbmllIGRhamUgamVkbm96bmFjem5lZ28gcm96d2nEhXphbmlhLCBnZHkgbWFjaWVyeiBYVFggbmllIGplc3Qgb2R3cmFjYWxuYSAodHpuLiBnZHkgem1pZW5uZSBvYmphxZtuaWFqxIVjZSBzxIUgbGluaW93byB6YWxlxbxuZSkuIA0KDQpUYWthIHN5dHVhY2phIG1vxbxlIG1pZcSHIG1pZWpzY2UgZ2R5IHptaWVubnljaCBvYmphxZtuaWFqxIVjeWNoIGplc3QgdHlsZSBzYW1vIGx1YiB3acSZY2VqIG5pxbwgb2JzZXJ3YWNqaS4NCg0KRHXFvGEgd2FydG/Fm8SHIM64aSBvem5hY3phIGR1xbzEhSB3cmHFvGxpd2/Fm8SHIGZ1bmtjamkgcmVncmVzamkgbmEgZHJvYm5lIGZsdWt0dWFjamUgY2VjaHkhDQoNCkxlcHN6eW0gcm96d2nEhXphbmllbSBqZXN0IGdvcnN6ZSBkb3Bhc293YW5pZSBkbyBkYW55Y2ggdWN6xIVjeWNoIHByenkgcsOzd25vY3plc255bSBvZ3JhbmljemVuaXUgcGFyYW1ldHLDs3cgxZt3aWFkY3rEhWN5Y2ggbyBwb3RlbmNqYWxuaWUgZHXFvHltIGLFgsSZZHppZSBnZW5lcmFsaXphY2ppLg0KDQoNCiMjIFJlZ3Jlc2phIEdyemJpZXRvd2ENCg0KIyMjIFdwcm93YWR6ZW5pZSANCg0KUmVncmVzamEgZ3J6YmlldG93YSAoYW5nLiBgUmlkZ2VgIHJlZ3Jlc3Npb24pIHRvIHRlY2huaWthIHJlZ3Jlc2ppIGxpbmlvd2VqLCBrdMOzcmEgd3Byb3dhZHphIHJlZ3VsYXJ5emFjasSZICRMXzIkIGRvIGVzdHltYWNqaSB3c3DDs8WCY3p5bm5pa8OzdyBtb2RlbHUuIFJlZ3VsYXJ5emFjamEgJExfMiQgcG9sZWdhIG5hIGRvZGFuaXUgZG8gZnVua2NqaSBjZWx1IGthcnkgcHJvcG9yY2pvbmFsbmVqIGRvIGt3YWRyYXR1IHdhcnRvxZtjaSB3c3DDs8WCY3p5bm5pa8OzdyByZWdyZXNqaS4NCg0KUG9kc3Rhd293xIUgaWRlxIUgcmVncmVzamkgZ3J6YmlldG93ZWogamVzdCBtaW5pbWFsaXphY2phIGZ1bmtjamkgY2VsdSwga3TDs3JhIHNrxYJhZGEgc2nEmSB6IGR3w7NjaCBza8WCYWRuaWvDs3c6IGLFgsSZZHUgZG9wYXNvd2FuaWEgKHN1bXkga3dhZHJhdMOzdyByw7PFvG5pYyBwb21pxJlkenkgcnplY3p5d2lzdHltaSB3YXJ0b8WbY2lhbWkgb2Rwb3dpZWR6aSBhIHByemV3aWR5d2FueW1pIHdhcnRvxZtjaWFtaSBtb2RlbHUpIGkga2FyeSByZWd1bGFyeXphY3lqbmVqICRMXzIkLiANCg0KV3rDs3IgZnVua2NqaSBjZWx1IGRsYSByZWdyZXNqaSBncnpiaWV0b3dlaiBtb8W8bmEgcHJ6ZWRzdGF3acSHIGpha286IE1pbmltaXplOiBSU1MgKyAkXGxhbWJkYSBcfFxiZXRhXHxfMl4yJCwgZ2R6aWU6DQoNCi0gUlNTIHRvIHN1bWEga3dhZHJhdMOzdyByw7PFvG5pYyBwb21pxJlkenkgcnplY3p5d2lzdHltaSB3YXJ0b8WbY2lhbWkgb2Rwb3dpZWR6aSBhIHByemV3aWR5d2FueW1pIHdhcnRvxZtjaWFtaSBtb2RlbHUgKGLFgsSFZCBkb3Bhc293YW5pYSksDQoNCi0gJFxsYW1iZGEkIChsYW1iZGEpIHRvIHBhcmFtZXRyIHJlZ3VsYXJ5emFjamksIGt0w7NyeSBrb250cm9sdWplIHNpxYLEmSByZWd1bGFyeXphY2ppLA0KDQotICRcfFxiZXRhXHxfMl4yJCB0byBub3JtYSAkTF8yJCB3c3DDs8WCY3p5bm5pa8OzdyByZWdyZXNqaSBwb2RuaWVzaW9uYSBkbyBrd2FkcmF0dS4NCg0KRG9kYW5pZSBrYXJ5IHJlZ3VsYXJ5emFjeWpuZWogJExfMiQgcG93b2R1amUsIMW8ZSB3c3DDs8WCY3p5bm5pa2kgcmVncmVzamkgc8SFIHNrdXBpb25lIHdva8OzxYIgemVyYSwgYWxlIG5pZSBkb2vFgmFkbmllIHLDs3duZSB6ZXJ1IChjaHliYSDFvGUgJFxsYW1iZGEkPTApLiANCg0KUmVncmVzamEgZ3J6YmlldG93YSB6bW5pZWpzemEgd2FydG/Fm2NpIHdzcMOzxYJjenlubmlrw7N3LCBhbGUgbmllIHBvd29kdWplLCDFvGUgc3RhasSFIHNpxJkgb25lIHLDs3duZSB6ZXJvLiBJbSB3acSZa3N6YSB3YXJ0b8WbxIcgJFxsYW1iZGEkLCB0eW0gYmFyZHppZWogc8SFICJzY2lza2FuZSIgd3Nww7PFgmN6eW5uaWtpIHJlZ3Jlc2ppLg0KDQpSZWdyZXNqYSBncnpiaWV0b3dhIGplc3Qgc3pjemVnw7NsbmllIHByenlkYXRuYSwgZ2R5IG1hbXkgZG8gY3p5bmllbmlhIHogbW9kZWxlbSwgdyBrdMOzcnltIHd5c3TEmXB1amUgbmFkbWllcm5hIHdpZWxvd3ltaWFyb3dvxZvEhyBsdWIgd3lzb2tpZSBrb3JlbGFjamUgbWnEmWR6eSB6bWllbm55bWkgbmllemFsZcW8bnltaS4gDQoNClBvcHJ6ZXogem1uaWVqc3phbmllIHdhcnRvxZtjaSB3c3DDs8WCY3p5bm5pa8OzdywgcmVncmVzamEgZ3J6YmlldG93YSBtb8W8ZSBwb23Ds2MgdyByZWR1a2NqaSB3cMWCeXd1IG1hxYJvIGlzdG90bnljaCBjZWNoLCBwb3ByYXdpxIcgc3RhYmlsbm/Fm8SHIG1vZGVsdSBpIHptbmllanN6ecSHIHJ5enlrbyBwcnpldWN6ZW5pYSAoKipvdmVyZml0dGluZyoqKS4NCg0KSmVkbnltIHplIHNwb3NvYsOzdyBrb250cm9saSB6xYJvxbxvbm/Fm2NpIG1vZGVsdSBqZXN0IHBlbmFsaXphY2phIGplZ28gd2llbGtvxZtjaS4gTmEgcHJ6eWvFgmFkLCB3IHByb2JsZW1pZSByZWdyZXNqaSBsaW5pb3dlajoNCg0KJCQNClxtaW5fe1xiZXRhIFxpbiBcbWF0aGJie1J9XnB9IFxzdW1fe2k9MX1ebiAoeV9pIC0geF9pXlx0b3AgXGJldGEpXjIsDQokJA0KDQptb8W8ZW15IGtvbnRyb2xvd2HEhyB3aWVsa2/Fm8SHIHdzcMOzxYJjenlubmlrw7N3ICRcYmV0YSQuIE9jenl3acWbY2llIHdpZWxrb8WbxIcgJFxiZXRhJCBtb8W8bmEgemRlZmluaW93YcSHIG5hIHLDs8W8bmUgc3Bvc29ieSwgbnAuIG5vcm1hLTI6ICRcfFxiZXRhXHxfMiQsIG5vcm1hLTE6ICRcfFxiZXRhXHxfMSQgY3p5IG5vcm1hLW5pZXNrb8WEY3pvbm/Fm8SHOiAkXHxcYmV0YVx8X3tcaW5mdHl9JC4gDQpSZWdyZXNqYSBncnpiaWV0b3dhIHdpxIXFvGUgc2nEmSB6IGthcsSFIGR3w7NjaCBub3JtOg0KDQokJA0KXG1pbl97XGJldGEgXGluIFxtYXRoYmJ7Un1ecH0gXHN1bV97aT0xfV5uICh5X2kgLSB4X2leXHRvcCBcYmV0YSleMiArIFxsYW1iZGEgXHxcYmV0YVx8XzJeMg0KJCQNCg0KZ2R6aWUgJFxsYW1iZGEkIGplc3QgcGFyYW1ldHJlbSBrb250cm9sdWrEhWN5bSBwb3ppb20gcmVndWxhcnl6YWNqaS4gWmF1d2HFvCwgxbxlICRYJCB0byBtYWNpZXJ6ICRuJCBuYSAkcCQgd3ltaWFyw7N3IHogd2llcnN6YW1pOiAkeF9pXlx0b3AkLCBvcmF6ICRZJCB0byAkbiQgbmEgMSB3ZWt0b3IgJHlfaSQuIFphxYLDs8W8bXksIMW8ZSAkWF5cdG9wIFggKyBcbGFtYmRhIEkkIGplc3Qgb2R3cmFjYWxuYSwgbWFteSBkb2vFgmFkbmUgcm96d2nEhXphbmllIHByb2JsZW11IHJlZ3Jlc2ppIGdyemJpZXRvd2VqOg0KDQokJA0KXGhhdCBcYmV0YV97YHJpZGdlYH0gPSAoWF5cdG9wIFggKyBcbGFtYmRhIEkpXnstMX1YXlx0b3AgWS4NCiQkDQoNClByenlwb21uaWpteSwgxbxlIHJvendpxIV6YW5pZW0gend5a8WCZWogcmVncmVzamkgbmFqbW5pZWpzenljaCBrd2FkcmF0w7N3IGplc3QgKHpha8WCYWRhasSFYyBvZHdyYWNhbG5vxZvEhyBtYWNpZXJ6eSAkWF5cdG9wIFgkKToNCg0KJCQNClxoYXQgXGJldGFfe29sc30gPSAoWF5cdG9wIFgpXnstMX1YXlx0b3AgWS4NCiQkDQoNCkR3YSBmYWt0eToga2llZHkgJFxsYW1iZGEgXHRvIDAkLCAkXGhhdCBcYmV0YV97YHJpZGdlYH0gXHRvIFxoYXQgXGJldGFfe29sc30kOyBraWVkeSAkXGxhbWJkYSBcdG8gXGluZnR5JCwgJFxoYXQgXGJldGFfe2ByaWRnZWB9IFx0byAwJC4NCg0KVyBzemN6ZWfDs2xueWNoIHByenlwYWRrYWNoICRYJCBqZXN0IG9ydG9nb25hbG5hICh0em4uIGtvbHVtbnkgJFgkIHPEhSBvcnRvZ29uYWxuZSksIG1hbXk6DQoNCiQkDQpcaGF0IFxiZXRhX3tgcmlkZ2VgfSA9IFxmcmFje1xoYXQgXGJldGFfe29sc319ezEgKyBcbGFtYmRhfS4NCiQkDQoNCldpZHppbXkgd2nEmWMsIMW8ZSBlc3R5bWF0b3IgZ3J6YmlldG93eSBtYSBkb2RhdGtvd28gJDEvKDEgKyBcbGFtYmRhKSQgdHp3LiAic2hyaW5rYWdlIGZhY3RvciIuIFcgendpxIV6a3UgeiB0eW0gbmEgZXN0eW1hdG9yemUgZ3J6YmlldG93eW0gd3lzdMSZcHVqZSBvYmNpxIXFvGxpd2/Fm8SHIChiaWFzKS4NCg0KIyMjIFByenlrxYJhZA0KDQpGdW5rY2phIGBnbG1uZXQoKWAgcG9zaWFkYSBhcmd1bWVudCBhbGZhLCBrdMOzcnkgb2tyZcWbbGEsIGpha2kgdHlwIG1vZGVsdSBqZXN0IGRvcGFzb3d5d2FueS4NCg0KSmXFm2xpIGBhbGZhID0gMGAgdG8gZG9wYXNvd3l3YW55IGplc3QgbW9kZWwgcmVncmVzamkgZ3J6YmlldG93ZWosIGEgamXFm2xpIGBhbGZhID0gMWAgdG8gZG9wYXNvd3l3YW55IGplc3QgbW9kZWwgbGFzc28uIA0KDQpOYWpwaWVydyBkb3Bhc293dWplbXkgbW9kZWwgcmVncmVzamkgZ3J6YmlldG93ZWo6DQoNCg0KYGBge3J9DQpncmlkIDwtIDEwXnNlcSgxMCwgLTIsIGxlbmd0aCA9IDEwMCkgICMgVHdvcnplbmllIHNpYXRraSBsYW1iZGENCnJpZGdlX21vZCA8LSBnbG1uZXQoeCwgeSwgYWxwaGEgPSAwLCBsYW1iZGEgPSBncmlkKSAgIyBEb3Bhc293YW5pZSBtb2RlbHUgcmlkZ2UgcmVncmVzc2lvbg0KYGBgDQoNCkRvbXnFm2xuaWUgZnVua2NqYSBgZ2xtbmV0KClgIHd5a29udWplIHJlZ3Jlc2rEmSBncnpiaWV0b3fEhSBkbGEgYXV0b21hdHljem5pZSB3eWJyYW5lZ28gd3licmFuZWdvIHpha3Jlc3Ugd2FydG/Fm2NpICRcbGFtYmRhJC4gSmVkbmFrxbxlLCB0dXRhaiB3eWJyYWxpxZtteSBpbXBsZW1lbnRhY2rEmSBmdW5rY2rEmSB3IHpha3Jlc2llIHdhcnRvxZtjaSBvZCAkXGxhbWJkYSA9IDEwXnsxMH0kIGRvICRcbGFtYmRhID0gMTBeey0yfSQsIHphc2Fkbmljem8gcG9rcnl3YWrEhWMgcGXFgmVuIHpha3JlcyBzY2VuYXJpdXN6eSBvZCBtb2RlbHUgemVyb3dlZ28gemF3aWVyYWrEhWNlZ28gdHlsa28gcHJ6ZWNod3l0LCBkbyBkb3Bhc293YW5pYSBuYWptbmllanN6ZWdvIGt3YWRyYXR1LiANCg0KSmFrIHdpZGHEhywgbW/FvGVteSByw7N3bmllxbwgb2JsaWN6ecSHIGRvcGFzb3dhbmllIG1vZGVsdSBkbGEga29ua3JldG5laiB3YXJ0b8WbY2kgJFxsYW1iZGEkLCBrdMOzcmEgbmllIGplc3QgamVkbsSFIHogb3J5Z2luYWxueWNoIHdhcnRvxZtjaSBzaWF0a2kuIA0KDQpaYXV3YcW8LCDFvGUgZG9tecWbbG5pZSBmdW5rY2phIGBnbG1uZXQoKWAgc3RhbmRhcnl6dWplIHptaWVubmUgdGFrLCBieSBiecWCeSB3IHRlaiBzYW1laiBza2FsaS4gQWJ5IHd5xYLEhWN6ecSHIHRvIGRvbXnFm2xuZSB1c3Rhd2llbmllLCB1xbx5aiBhcmd1bWVudHUgYHN0YW5kYXJkaXplID0gRkFMU0VgLg0KDQpaIGthxbxkxIUgd2FydG/Fm2NpxIUgJFxsYW1iZGEkIHp3acSFemFueSBqZXN0IHdla3RvciB3c3DDs8WCY3p5bm5pa8OzdyByZWdyZXNqaSBncnpiaWV0b3dlaiwgcHJ6ZWNob3d5d2FueSB3IG1hY2llcnp5LCBkbyBrdMOzcmVqIG1vxbxuYSB1enlza2HEhyBkb3N0xJlwIHByemV6IGBjb2VmKClgLiBXIHR5bSBwcnp5cGFka3UgamVzdCB0byBtYWNpZXJ6ICQyMCBcdGltZXMgMTAwJCwgeiAyMCB3aWVyc3phbWkgKHBvIGplZG55bSBkbGEga2HFvGRlZ28gcHJlZHlrdG9yYSwgcGx1cyBpbnRlcmNlcHQpIGkgMTAwIGtvbHVtbmFtaSAocG8gamVkbmVqIGRsYSBrYcW8ZGVqIHdhcnRvxZtjaSAkXGxhbWJkYSQpLg0KDQoNCmBgYHtyfQ0KbGlicmFyeShnbG1uZXQpDQpkaW0oY29lZihyaWRnZV9tb2QpKQ0KcGxvdChyaWRnZV9tb2QpICAgICMgd3lrcmVzIHdzcMOzxYJjenlubmlrw7N3DQpgYGANCg0KU3BvZHppZXdhbXkgc2nEmSwgxbxlIG9zemFjb3dhbmlhIHdzcMOzxYJjenlubmlrw7N3IGLEmWTEhSB6bmFjem5pZSBtbmllanN6ZSwgdyBzZW5zaWUgbm9ybXkgJGxfMiQsIGdkeSB1xbx5d2FuYSBqZXN0IGR1xbxhIHdhcnRvxZvEhyAkXGxhbWJkYSQsIHcgcG9yw7N3bmFuaXUgeiBtYcWCxIUgd2FydG/Fm2NpxIUgJFxsYW1iZGEkLg0KDQpPdG8gd3Nww7PFgmN6eW5uaWtpLCBnZHkgJFxsYW1iZGEgPSAxMTQ5OCQsIHdyYXogeiBpY2ggbm9ybcSFICRsXzIkOg0KDQoNCmBgYHtyfQ0KcmlkZ2VfbW9kJGxhbWJkYVs1MF0gIyBXecWbd2lldGwgNTAtdMSFIHdhcnRvxZvEhyBsYW1iZHkNCmNvZWYocmlkZ2VfbW9kKVssNTBdICMgV3nFm3dpZXRsIHdzcMOzxYJjenlubmlraSB6d2nEhXphbmUgeiA1MC10xIUgd2FydG/Fm2NpxIUgbGFtYmR5DQpzcXJ0KHN1bShjb2VmKHJpZGdlX21vZClbLTEsNTBdXjIpKSAjIE9ibGljeiBub3JtxJkgbDINCmBgYA0KDQpEbGEga29udHJhc3R1LCBvdG8gd3Nww7PFgmN6eW5uaWtpLCBnZHkgJFxsYW1iZGEgPSA3MDUkLCB3cmF6IHogaWNoICRsXzIkIG5vcm3EhS4gWndyw7PEhyB1d2FnxJkgbmEgem5hY3puaWUgd2nEmWtzesSFIG5vcm3EmSAkbF8yJCB3c3DDs8WCY3p5bm5pa8OzdyB6d2nEhXphbnljaCB6IHTEhSBtbmllanN6xIUgd2FydG/Fm2NpxIUgJFxsYW1iZGEkLg0KDQoNCmBgYHtyfQ0KcmlkZ2VfbW9kJGxhbWJkYVs2MF0gIyBXecWbd2lldGwgNjAtdMSFIHdhcnRvxZvEhyBsYW1iZHkNCmNvZWYocmlkZ2VfbW9kKVssNjBdICMgV3nFm3dpZXRsIHdzcMOzxYJjenlubmlraSBwb3dpxIV6YW5lIHogNjAtdMSFIHdhcnRvxZvEhyBsYW1iZHkNCnNxcnQoc3VtKGNvZWYocmlkZ2VfbW9kKVstMSw2MF1eMikpICMgT2JsaWN6IG5vcm3EmSBsMg0KYGBgDQoNCkZ1bmtjasSZIGBwcmVkaWN0KClgIG1vxbxlbXkgd3lrb3J6eXN0YcSHIGRvIHdpZWx1IGNlbMOzdy4gTmEgcHJ6eWvFgmFkLCBtb8W8ZW15IHV6eXNrYcSHIHdzcMOzxYJjenlubmlraSByZWdyZXNqaSBncnpiaWV0b3dlaiBkbGEgbm93ZWogd2FydG/Fm2NpICRcbGFtYmRhJCwgcG93aWVkem15IDUwOg0KDQoNCmBgYHtyfQ0KcHJlZGljdChyaWRnZV9tb2QsIHMgPSA1MCwgdHlwZSA9ICJjb2VmZmljaWVudHMiKVsxOjIwLF0NCmBgYA0KDQpQb2R6aWVsaW15IHRlcmF6IHByw7Nia2kgbmEgemJpw7NyIHRyZW5pbmdvd3kgaSB0ZXN0b3d5IHcgY2VsdSBvc3phY293YcSHIGLFgsSFZCB0ZXN0dSByZWdyZXNqaSBncnpiaWV0b3dlaiBpIGxhc3NvLg0KDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCg0KdHJhaW4gPSBIaXR0ZXJzICU+JQ0KICBzYW1wbGVfZnJhYygwLjUpDQoNCnRlc3QgPSBIaXR0ZXJzICU+JQ0KICBzZXRkaWZmKHRyYWluKQ0KDQp4X3RyYWluID0gbW9kZWwubWF0cml4KFNhbGFyeX4uLCB0cmFpbilbLC0xXQ0KeF90ZXN0ID0gbW9kZWwubWF0cml4KFNhbGFyeX4uLCB0ZXN0KVssLTFdDQoNCnlfdHJhaW4gPSB0cmFpbiAlPiUNCiAgc2VsZWN0KFNhbGFyeSkgJT4lDQogIHVubGlzdCgpICU+JQ0KICBhcy5udW1lcmljKCkNCg0KeV90ZXN0ID0gdGVzdCAlPiUNCiAgc2VsZWN0KFNhbGFyeSkgJT4lDQogIHVubGlzdCgpICU+JQ0KICBhcy5udW1lcmljKCkNCmBgYA0KDQpOYXN0xJlwbmllIGRvcGFzb3d1amVteSBtb2RlbCByZWdyZXNqaSBncnpiaWV0b3dlaiBuYSB6YmlvcnplIHRyZW5pbmdvd3ltIGkgb2NlbmlhbXkgamVnbyBNU0UgbmEgemJpb3J6ZSB0ZXN0b3d5bSwgdcW8eXdhasSFYyAkXGxhbWJkYSA9IDQkLiBad3LDs8SHIHV3YWfEmSBuYSB1xbx5Y2llIGZ1bmtjamkgYHByZWRpY3QoKWAuIFBvbm93bmllOiB0eW0gcmF6ZW0gb3RyenltdWplbXkgcHJ6ZXdpZHl3YW5pYSBkbGEgemJpb3J1IHRlc3Rvd2VnbywgemFzdMSZcHVqxIVjIGB0eXBlPSJjb2VmZmljaWVudHMiYCBhcmd1bWVudGVtIGBuZXd4YC4NCg0KDQpgYGB7cn0NCnJpZGdlX21vZCA9IGdsbW5ldCh4X3RyYWluLCB5X3RyYWluLCBhbHBoYT0wLCBsYW1iZGEgPSBncmlkLCB0aHJlc2ggPSAxZS0xMikNCnJpZGdlX3ByZWQgPSBwcmVkaWN0KHJpZGdlX21vZCwgcyA9IDQsIG5ld3ggPSB4X3Rlc3QpDQptZWFuKChyaWRnZV9wcmVkIC0geV90ZXN0KV4yKQ0KYGBgDQoNClRlc3Rvd2UgTVNFIHd5bm9zaSAxMzk4NTguIFphdXdhxbwsIMW8ZSBnZHliecWbbXkgemFtaWFzdCB0ZWdvIGRvcGFzb3dhbGkgcG8gcHJvc3R1IG1vZGVsIHR5bGtvIHogd3lyYXplbSB3b2xueW0sIHByemV3aWR5d2FsaWJ5xZtteSBrYcW8ZMSFIG9ic2Vyd2FjasSZIHRlc3Rvd8SFIHXFvHl3YWrEhWMgxZtyZWRuaWVqIHogb2JzZXJ3YWNqaSB6YmlvcnUgdHJlbmluZ293ZWdvLiBXIHRha2ltIHByenlwYWRrdSBtb2dsaWJ5xZtteSBvYmxpY3p5xIcgTVNFIHplc3Rhd3UgdGVzdG93ZWdvIHcgdGVuIHNwb3PDs2I6DQoNCg0KYGBge3J9DQptZWFuKChtZWFuKHlfdHJhaW4pIC0geV90ZXN0KV4yKQ0KYGBgDQoNCk1vZ2xpYnnFm215IHLDs3duaWXFvCB1enlza2HEhyB0ZW4gc2FtIHd5bmlrLCBkb3Bhc293dWrEhWMgbW9kZWwgcmVncmVzamkgZ3J6YmlldG93ZWogeiBiYXJkem8gZHXFvMSFIHdhcnRvxZtjacSFICRcbGFtYmRhJC4gWmF1d2HFvCwgxbxlIGAxZTEwYCBvem5hY3phICQxMF57MTB9JC4NCg0KDQpgYGB7cn0NCnJpZGdlX3ByZWQgPSBwcmVkaWN0KHJpZGdlX21vZCwgcyA9IDFlMTAsIG5ld3ggPSB4X3Rlc3QpDQptZWFuKChyaWRnZV9wcmVkIC0geV90ZXN0KV4yKQ0KYGBgDQoNClRhayB3acSZYyBkb3Bhc293YW5pZSBtb2RlbHUgcmVncmVzamkgZ3J6YmlldG93ZWogeiAkXGxhbWJkYSA9IDQkIHByb3dhZHppIGRvIHpuYWN6bmllIG5pxbxzemVnbyB0ZXN0dSBNU0UgbmnFvCBkb3Bhc293YW5pZSBtb2RlbHUgeiBzYW15bSBwcnplY2h3eXRlbS4gDQoNClNwcmF3ZHppbXkgdGVyYXosIGN6eSBqZXN0IGpha2HFmyBrb3J6ecWbxIcgeiB3eWtvbmFuaWEgcmVncmVzamkgZ3J6YmlldG93ZWogeiAkXGxhbWJkYSA9IDQkIHphbWlhc3QgcG8gcHJvc3R1IHd5a29uYcSHIHJlZ3Jlc2rEmSBuYWptbmllanN6eWNoIGt3YWRyYXTDs3cuDQoNClByenlwb21uaWpteSwgxbxlIG5ham1uaWVqc3phIGt3YWRyYXR1cmEgdG8gcG8gcHJvc3R1IHJlZ3Jlc2phIGdyemJpZXRvd2EgeiAkXGxhbWJkYSA9IDAkLg0KDQoNClwqIFV3YWdhOiBBYnkgYGdsbW5ldCgpYCBkYXdhxYIgKipkb2vFgmFkbmUgKGV4YWN0KSoqIHdzcMOzxYJjenlubmlraSBuYWptbmllanN6ZWdvIGt3YWRyYXR1LCBnZHkgJFxsYW1iZGEgPSAwJCwgdcW8eXdhbXkgYXJndW1lbnR1IGBleGFjdD1UYCBwcnp5IHd5d2/FgmFuaXUgZnVua2NqaSBgcHJlZGljdCgpYC4gVyBwcnplY2l3bnltIHJhemllLCBmdW5rY2phIGBwcmVkaWN0KClgIGLEmWR6aWUgaW50ZXJwb2xvd2HEhyBuYWQgc2lhdGvEhSB3YXJ0b8WbY2kgJFxsYW1iZGEkIHXFvHl0xIUgdyBkb3Bhc293YW5pdSBtb2RlbHUgYGdsbW5ldCgpYCwgZGFqxIVjIHByenlibGnFvG9uZSB3eW5pa2kuIE5hd2V0IGdkeSB1xbx5amVteSBgZXhhY3QgPSBUYCwgcG96b3N0YWplIG5pZXdpZWxrYSByb3piaWXFvG5vxZvEhyBuYSB0cnplY2ltIG1pZWpzY3UgcG8gcHJ6ZWNpbmt1IG1pxJlkenkgd3luaWthbWkgYGdsbW5ldCgpYCwgZ2R5ICRcbGFtYmRhID0gMCQgaSB3eWrFm2NpZW0geiBgbG0oKWA7IGplc3QgdG8gc3Bvd29kb3dhbmUgbnVtZXJ5Y3pueW0gcHJ6eWJsacW8ZW5pZW0gemUgc3Ryb255IGBnbG1uZXQoKWAuDQoNCg0KYGBge3J9DQpyaWRnZV9wcmVkID0gcHJlZGljdChyaWRnZV9tb2QsIHMgPSAwLCBuZXd4ID0geF90ZXN0KQ0KbWVhbigocmlkZ2VfcHJlZCAtIHlfdGVzdCleMikNCg0KbG0oU2FsYXJ5fi4sIGRhdGEgPSB0cmFpbikNCnByZWRpY3QocmlkZ2VfbW9kLCBzID0gMCwgdHlwZT0iY29lZmZpY2llbnRzIilbMToyMCxdDQpgYGANCg0KV3lnbMSFZGEgbmEgdG8sIMW8ZSByemVjenl3acWbY2llIHBvcHJhd2lhbXkgc2nEmSB3IHN0b3N1bmt1IGRvIHp3eWvFgmVnbyBuYWptbmllanN6ZWdvIGt3YWRyYXR1ISANCg0KVXdhZ2E6IG9nw7NsbmllLCBqZcWbbGkgY2hjZW15IGRvcGFzb3dhxIcgKG5pZXNwZW5hbGl6b3dhbnkpIG1vZGVsIG5ham1uaWVqc3p5Y2gga3dhZHJhdMOzdywgdG8gcG93aW5uacWbbXkgdcW8ecSHIGZ1bmtjamkgYGxtKClgLCBwb25pZXdhxbwgdGEgZnVua2NqYSBkb3N0YXJjemEgYmFyZHppZWogdcW8eXRlY3pueWNoIHd5asWbY2lhLCB0YWtpZSBqYWsgYsWCxJlkeSBzdGFuZGFyZG93ZSBpIHdhcnRvxZtjaSAkcCQgZGxhIHdzcMOzxYJjenlubmlrw7N3Lg0KDQpaYW1pYXN0IGFyYml0cmFsbmllIHd5YmllcmHEhyAkXGxhbWJkYSA9IDQkLCBsZXBpZWogYnnFgm9ieSB1xbx5xIcgd2FsaWRhY2ppIGtyennFvG93ZWogZG8gd3lib3J1IHBhcmFtZXRydSBkb3N0cm9qZW5pYSAkXGxhbWJkYSQuIE1vxbxlbXkgdG8genJvYmnEhyB1xbx5d2FqxIVjIHdidWRvd2FuZWogZnVua2NqaSB3YWxpZGFjamkga3J6ecW8b3dlaiwgYGN2LmdsbW5ldCgpYC4gRG9tecWbbG5pZSBmdW5rY2phIHRhIHd5a29udWplIDEwLWtyb3RuxIUgd2FsaWRhY2rEmSBrcnp5xbxvd8SFLCBjaG/EhyBtb8W8bmEgdG8gem1pZW5pxIcgdcW8eXdhasSFYyBhcmd1bWVudHUgYXJndW1lbnR1IGBmb2xkc2AuIFphdXdhxbwsIMW8ZSBuYWpwaWVydyB1c3Rhd2lhbXkgbG9zb3dlIHppYXJubywgYWJ5IG5hc3plIHd5bmlraSBiecWCeSBwb3d0YXJ6YWxuZSwgcG9uaWV3YcW8IHd5YsOzciBrcm90bm/Fm2NpIHdhbGlkYWNqaSBrcnp5xbxvd2VqIGplc3QgbG9zb3d5Lg0KDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCmN2Lm91dCA9IGN2LmdsbW5ldCh4X3RyYWluLCB5X3RyYWluLCBhbHBoYSA9IDApICMgRG9wYXN1aiBtb2RlbCByZWdyZXNqaSBncnpiaWV0b3dlaiBuYSBkYW55Y2ggdHJlbmluZ293eWNoDQpiZXN0bGFtX3JpZGdlID0gY3Yub3V0JGxhbWJkYS5taW4gICMgV3liaWVyeiBsYW1kxJksIGt0w7NyYSBtaW5pbWFsaXp1amUgdHJlbmluZ293eSBNU0UgDQpiZXN0bGFtX3JpZGdlDQpgYGANCg0KV2lkemlteSB6YXRlbSwgxbxlIHdhcnRvxZvEhyAkXGxhbWJkYSQsIGt0w7NyYSBwb3dvZHVqZSBuYWptbmllanN6eSBixYLEhWQgd2FsaWRhY2ppIGtyennFvG93ZWogdG8gMzI2LiBNb8W8ZW15IHLDs3duaWXFvCB3eWtyZcWbbGnEhyBNU0UgamFrbyBmdW5rY2rEmSAkXGxhbWJkYSQ6DQoNCg0KYGBge3J9DQpwbG90KGN2Lm91dCkgIyBOYXJ5c3VqIHd5a3JlcyB0cmVuaW5nb3dlZ28gTVNFIGpha28gZnVua2NqxJkgbGFtYmRhDQpgYGANCg0KSmFraSBqZXN0IHRlc3Rvd3kgTVNFIHp3acSFemFueSB6IHTEhSB3YXJ0b8WbY2nEhSAkXGxhbWJkYSQ/DQoNCg0KYGBge3J9DQpyaWRnZV9wcmVkID0gcHJlZGljdChyaWRnZV9tb2QsIHMgPSBiZXN0bGFtX3JpZGdlLCBuZXd4ID0geF90ZXN0KSAjIFXFvHlqIG5hamxlcHN6ZWogbGFtYmR5IGRvIHByemV3aWR5d2FuaWEgZGFueWNoIHRlc3Rvd3ljaA0KbWVhbigocmlkZ2VfcHJlZCAtIHlfdGVzdCleMikgIyBPYmxpY3ogdGVzdG93ZSBNU0UNCmBgYA0KDQpTdGFub3dpIHRvIGRhbHN6xIUgcG9wcmF3xJkgdyBzdG9zdW5rdSBkbyB0ZXN0b3dlZ28gTVNFLCBrdMOzcmUgdXp5c2thbGnFm215IHXFvHl3YWrEhWMgJFxsYW1iZGEgPSA0JC4gT3N0YXRlY3puaWUsIHBvbm93bmllIHd5em5hY3phbXkgbmFzeiBtb2RlbCByZWdyZXNqaSBncnpiaWV0b3dlaiBuYSBwZcWCbnltIHplc3Rhd2llIGRhbnljaCwgdcW8eXdhasSFYyB3YXJ0b8WbY2kgJFxsYW1iZGEkIHd5YnJhbmVqIHcgd2FsaWRhY2ppIGtyennFvG93ZWosIGkgc3ByYXdkemFteSBvc3phY293YW5pYSB3c3DDs8WCY3p5bm5pa8Ozdy4NCg0KDQpgYGB7cn0NCm91dCA9IGdsbW5ldCh4LCB5LCBhbHBoYSA9IDApICMgRG9wYXN1aiBtb2RlbCByZWdyZXNqaSBncnpiaWV0b3dlaiBkbyBwZcWCbmVnbyB6YmlvcnUgZGFueWNoDQpwcmVkaWN0KG91dCwgdHlwZSA9ICJjb2VmZmljaWVudHMiLCBzID0gYmVzdGxhbV9yaWRnZSlbMToyMCxdICMgV3nFm3dpZXRsYW5pZSB3c3DDs8WCY3p5bm5pa8OzdyBwcnp5IHXFvHljaXUgbGFtYmRhIHd5YnJhbmVnbyBwcnpleiBDVg0KYGBgDQoNClpnb2RuaWUgeiBvY3pla2l3YW5pYW1pLCDFvGFkZW4gemUgd3Nww7PFgmN6eW5uaWvDs3cgbmllIGplc3QgZG9rxYJhZG5pZSB6ZXJvd3kgLSByZWdyZXNqYSBncnpiaWV0b3dhIG5pZSBkb2tvbnVqZSBzZWxla2NqaSB6bWllbm55Y2ghDQoNCiMjIFJlZ3Jlc2phIExhc3NvDQoNCiMjIyBXcHJvd2FkemVuaWUNCg0KWmFtaWFzdCByZWd1bGFyeXphY2ppICRMXzIkLCBMQVNTTyB1xbx5d2EgcGVuYWxpemFjamkgJExfMSQsIHRvIHpuYWN6eToNCg0KJCQNClxtaW5fe1xiZXRhIFxpbiBcbWF0aGJie1J9XnB9IFxzdW1fe2k9MX1ebiAoeV9pIC0geF9pXlx0b3AgXGJldGEpXjIgKyBcbGFtYmRhIFx8XGJldGFcfF8xLiANCiQkDQoNClplIHd6Z2zEmWR1IG5hIGNoYXJha3RlciBub3JteSAkTF8xJCwgTEFTU08gbWEgdGVuZGVuY2rEmSBkbyBkYXdhbmlhIGJhcmR6aWVqIHJ6YWRraWNoIHJvendpxIV6YcWEIG5pxbwgcmVncmVzamEgZ3J6YmlldG93YS4gSmVzdCB0byB0eXBvd28gdcW8eXRlY3puZSB3IHVzdGF3aWVuaWFjaCB3aWVsb3d5bWlhcm93eWNoLCBnZHkgcHJhd2R6aXd5IG1vZGVsIGplc3QgdyByemVjenl3aXN0b8WbY2kgbmlza293eW1pYXJvd3ltIG9zYWR6ZW5pZW0uDQoNCk1vZGVsIHJlZ3Jlc2ppIGxhc3NvIHpvc3RhxYIgcGllcndvdG5pZSBvcHJhY293YW55IHcgMTk4OSByb2t1LiBKZXN0IHRvIGFsdGVybmF0eXdhIGRsYSBrbGFzeWN6bmVnbyBvc3phY293YW5pYSBtZXRvZMSFIG5ham1uaWVqc3p5Y2gga3dhZHJhdMOzdywga3TDs3JhIHVuaWthIHdpZWx1IHByb2JsZW3Ds3cgeiBuYWRtaWVybnltIGRvcGFzb3dhbmllbSAoKipvdmVyZml0dGluZ2llbSoqKSwgZ2R5IG1hbXkgZHXFvMSFIGxpY3pixJkgbmllemFsZcW8bnljaCB6bWllbm55Y2guIA0KDQpSZWdyZXNqYSBMYXNzbyAoTGVhc3QgQWJzb2x1dGUgU2hyaW5rYWdlIGFuZCBTZWxlY3Rpb24gT3BlcmF0b3IpIHRvIHRlY2huaWthIHJlZ3Jlc2ppIGxpbmlvd2VqIHN0b3Nvd2FuYSBkbyBvc3phY293YW5pYSB3c3DDs8WCY3p5bm5pa8OzdyBtb2RlbHUsIGt0w7NyYSB3cHJvd2FkemEgcmVndWxhcnl6YWNqxJkgJExfMSQuIFJlZ3VsYXJ5emFjamEgTDEgcG9sZWdhIG5hIGRvZGFuaXUgZG8gZnVua2NqaSBjZWx1IGthcnkgcHJvcG9yY2pvbmFsbmVqIGRvIHdhcnRvxZtjaSBiZXp3emdsxJlkbmVqIHdzcMOzxYJjenlubmlrw7N3IHJlZ3Jlc2ppLg0KDQpSZWdyZXNqYSBMYXNzbyBtYSB6ZG9sbm/Fm8SHIGRvIGplZG5vY3plc25lZ28gd3lrb25hbmlhIHNlbGVrY2ppIGNlY2ggaSByZWd1bGFyeXphY2ppLCBjbyBvem5hY3phLCDFvGUgbW/FvGUgcG9tw7NjIHcgaWRlbnR5ZmlrYWNqaSBuYWpiYXJkemllaiBpc3RvdG55Y2ggY2VjaCBtb2RlbHUsIGEgdGFrxbxlIHptbmllanN6ecSHIHdwxYJ5dyBtbmllaiBpc3RvdG55Y2ggY2VjaC4NCg0KUG9kc3Rhd293eW0gY2VsZW0gcmVncmVzamkgTGFzc28gamVzdCBtaW5pbWFsaXphY2phIGZ1bmtjamkgY2VsdSwga3TDs3JhIHNrxYJhZGEgc2nEmSB6IGR3w7NjaCBza8WCYWRuaWvDs3c6IGLFgsSZZHUgZG9wYXNvd2FuaWEgKHN1bXkga3dhZHJhdMOzdyByw7PFvG5pYyBwb21pxJlkenkgcnplY3p5d2lzdHltaSB3YXJ0b8WbY2lhbWkgb2Rwb3dpZWR6aSBhIHByemV3aWR5d2FueW1pIHdhcnRvxZtjaWFtaSBtb2RlbHUpIGkga2FyeSByZWd1bGFyeXphY3lqbmVqICRMXzEkLiANCg0KV3rDs3IgZnVua2NqaSBjZWx1IGRsYSByZWdyZXNqaSBMYXNzbyBtb8W8ZSBiecSHIHByemVkc3Rhd2lvbnkgamFrbzogTWluaW1pemU6IFJTUyArICRcbGFtYmRhIFx8XGJldGFcfF8xJCwgZ2R6aWU6DQoNCi0gUlNTIHRvIHN1bWEga3dhZHJhdMOzdyByw7PFvG5pYyBwb21pxJlkenkgcnplY3p5d2lzdHltaSB3YXJ0b8WbY2lhbWkgb2Rwb3dpZWR6aSBhIHByemV3aWR5d2FueW1pIHdhcnRvxZtjaWFtaSBtb2RlbHUgKGLFgsSFZCBkb3Bhc293YW5pYSksDQoNCi0gJFxsYW1iZGEkIChsYW1iZGEpIHRvIHBhcmFtZXRyIHJlZ3VsYXJ5emFjamksIGt0w7NyeSBrb250cm9sdWplIHNpxYLEmSByZWd1bGFyeXphY2ppLCBhICRcfFxiZXRhXHxfMSQgdG8gbm9ybWEgJExfMSQgd3Nww7PFgmN6eW5uaWvDs3cgcmVncmVzamkuDQoNCkRvZGFuaWUga2FyeSByZWd1bGFyeXphY3lqbmVqICRMXzEkIHBvd29kdWplLCDFvGUgbmlla3TDs3JlIHdzcMOzxYJjenlubmlraSByZWdyZXNqaSBzdGFqxIUgc2nEmSByw7N3bmUgemVybywgY28gcHJvd2FkemkgZG8gc2VsZWtjamkgY2VjaC4gSW0gd2nEmWtzemEgd2FydG/Fm8SHICRcbGFtYmRhJCwgdHltIHdpxJlrc3phIGplc3QgdGVuZGVuY2phIGRvIHJlZHVrY2ppIHdzcMOzxYJjenlubmlrw7N3IGRvIHplcmEsIHByb3dhZHrEhWMgZG8gYmFyZHppZWogcnphZGtpZWdvIG1vZGVsdSB6IG1uaWVqc3rEhSBsaWN6YsSFIGNlY2guDQoNClJlZ3Jlc2phIExhc3NvIGplc3QgcHJ6eWRhdG5hIHcgcHJ6eXBhZGthY2gsIGdkeSBtYW15IGRvIGN6eW5pZW5pYSB6IHdpZWxvbWEgY2VjaGFtaSwgeiBrdMOzcnljaCBuaWVrdMOzcmUgbW9nxIUgYnnEhyBuaWVpc3RvdG5lLiBNb8W8ZSBwb23Ds2MgdyBpZGVudHlmaWthY2ppIGlzdG90bnljaCBjZWNoLCByZWR1a2NqaSBuYWRtaWFydSBkYW55Y2ggaSB6d2nEmWtzemVuaXUgaW50ZXJwcmV0b3dhbG5vxZtjaSBtb2RlbHUuDQoNCg0KIyMjIFByenlrxYJhZA0KDQpab2JhY3p5bGnFm215LCDFvGUgcmVncmVzamEgZ3J6YmlldG93YSB6IG3EhWRyeW0gd3lib3JlbSAkXGxhbWJkYSQgbW/FvGUgcHJ6ZXd5xbxzemHEhyBtZXRvZMSZIG5ham1uaWVqc3p5Y2gga3dhZHJhdMOzdywgamFrIHLDs3duaWXFvCBtb2RlbCB6ZXJvd3kgbmEgemJpb3J6ZSBkYW55Y2ggSGl0dGVycy4gDQoNClRlcmF6IHpvYmFjem15LCBjenkgbGFzc28gbW/FvGUgZGHEhyBhbGJvIGRva8WCYWRuaWVqc3p5LCBhbGJvIGJhcmR6aWVqIGludGVycHJldG93YWxueSBtb2RlbCBuacW8IHJlZ3Jlc2phIGdyemJpZXRvd2EuIA0KDQpXIGNlbHUgZG9wYXNvd2FuaWEgbW9kZWx1IGxhc3NvLCBwbyByYXoga29sZWpueSB1xbx5d2FteSBmdW5rY2ppIGBnbG1uZXQoKWAsIGplZG5hayB0eW0gcmF6ZW0gdcW8eXdhbXkgYXJndW1lbnR1IGBhbHBoYT0xYC4gUG96YSB0xIUgem1pYW7EhSBwb3N0xJlwdWplbXkgdGFrIHNhbW8gamFrIHcgcHJ6eXBhZGt1IGRvcGFzb3d5d2FuaWEgbW9kZWx1IHJlZ3Jlc2ppIGdyemJpZXRvd2VqOg0KDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsYXNzb19tb2QgPSBnbG1uZXQoeF90cmFpbiwgDQogICAgICAgICAgICAgICAgICAgeV90cmFpbiwgDQogICAgICAgICAgICAgICAgICAgYWxwaGEgPSAxLCANCiAgICAgICAgICAgICAgICAgICBsYW1iZGEgPSBncmlkKSAjIERvcGFzdWogbW9kZWwgbGFzc28gZG8gZGFueWNoIHRyZW5pbmdvd3ljaA0KDQpwbG90KGxhc3NvX21vZCkgICAgIyBXeWtyZcWbbCB3c3DDs8WCY3p5bm5pa2kNCmBgYA0KDQpaYXV3YcW8bXksIMW8ZSBuYSB3eWtyZXNpZSB3c3DDs8WCY3p5bm5pa8OzdywgdyB6YWxlxbxub8WbY2kgb2Qgd3lib3J1IGRvc3Ryb2plbmlhIHBhcmFtZXRydSwgbmlla3TDs3JlIHplIHdzcMOzxYJjenlubmlrw7N3IHPEhSBkb2vFgmFkbmllIHLDs3duZSB6ZXJ1LiBUZXJheiBwcnplcHJvd2FkemlteSB3YWxpZGFjasSZIGtyennFvG93xIUgaSBvYmxpY3p5bXkgendpxIV6YW55IHogbmnEhSBixYLEhWQgdGVzdHU6DQoNCg0KYGBge3J9DQpzZXQuc2VlZCgxKQ0KY3Yub3V0ID0gY3YuZ2xtbmV0KHhfdHJhaW4sIHlfdHJhaW4sIGFscGhhID0gMSkgIyBEb3Bhc3VqIG1vZGVsIGxhc3NvIGRvIGRhbnljaCB0cmVuaW5nb3d5Y2gNCnBsb3QoY3Yub3V0KSAjIE5hcnlzdWogd3lrcmVzIE1TRSBkbGEgcHLDs2J5IHVjesSFY2VqIGpha28gZnVua2NqxJkgbGFtYmRhDQpiZXN0bGFtX2xhc3NvID0gY3Yub3V0JGxhbWJkYS5taW4gIyBXeWJpZXJ6IGxhbWTEmSwga3TDs3JhIG1pbmltYWxpenVqZSBNU0UgdyBwcsOzYmllIHVjesSFY2VqDQpsYXNzb19wcmVkID0gcHJlZGljdChsYXNzb19tb2QsIHMgPSBiZXN0bGFtX2xhc3NvLCBuZXd4ID0geF90ZXN0KSAjIFXFvHlqIG5hamxlcHN6ZWogbGFtYmR5IGRvIHByemV3aWR5d2FuaWEgZGFueWNoIHRlc3Rvd3ljaA0KbWVhbigobGFzc29fcHJlZCAtIHlfdGVzdCleMikgIyBPYmxpY3ogTVNFIHcgcHLDs2JpZSB0ZXN0b3dlag0KYGBgDQoNCkplc3QgdG8gem5hY3puaWUgbmnFvHN6ZSBNU0UgemJpb3J1IHRlc3Rvd2VnbyBuacW8IG1vZGVsdSB6ZXJvd2VnbyBpIG1vZGVsdSBuYWptbmllanN6eWNoIGt3YWRyYXTDs3csIGkgYmFyZHpvIHBvZG9ibnkgZG8gTVNFIHRlc3R1IHJlZ3Jlc2ppIGdyemJpZXRvd2VqIHogJFxsYW1iZGEkIHd5YnJhbmVqIHByemV6IHdhbGlkYWNqxJkga3J6ecW8b3fEhS4NCg0KSmVkbmFrxbxlIGxhc3NvIG1hIGlzdG90bsSFIHByemV3YWfEmSBuYWQgcmVncmVzasSFIGdyemJpZXRvd8SFIHcgdHltLCDFvGUgd3luaWtvd2Ugb3N6YWNvd2FuaWEgd3Nww7PFgmN6eW5uaWvDs3cgc8SFIHJ6YWRraWUuIFR1dGFqIHdpZHppbXksIMW8ZSAxMiB6IDE5IG9zemFjb3dhxYQgd3Nww7PFgmN6eW5uaWvDs3cgamVzdCBkb2vFgmFkbmllIHplcm93eWNoOg0KDQoNCmBgYHtyfQ0Kb3V0ID0gZ2xtbmV0KHgsIHksIGFscGhhID0gMSwgbGFtYmRhID0gZ3JpZCkgIyBEb3Bhc3VqIG1vZGVsIGxhc3NvIGRvIHBlxYJuZWdvIHpiaW9ydSBkYW55Y2gNCmxhc3NvX2NvZWYgPSBwcmVkaWN0KG91dCwgdHlwZSA9ICJjb2VmZmljaWVudHMiLCBzID0gYmVzdGxhbV9sYXNzbylbMToyMCxdICMgV3nFm3dpZXRsYW5pZSB3c3DDs8WCY3p5bm5pa8OzdyBwcnp5IHXFvHljaXUgbGFtYmRhIHd5YnJhbmVnbyBwcnpleiBDVg0KbGFzc29fY29lZg0KYGBgDQoNCld5YmllcmFqxIVjIHR5bGtvIHByZWR5a3RvcnkgbyBuaWV6ZXJvd3ljaCB3c3DDs8WCY3p5bm5pa2FjaCB3aWR6aW15LCDFvGUgbW9kZWwgbGFzc28geiAkXGxhbWJkYSQgd3licmFueW0gcHJ6ZXogd2FsaWRhY2rEmSBrcnp5xbxvd8SFIHphd2llcmEgdHlsa28gc2llZGVtIHptaWVubnljaDoNCg0KDQpgYGB7cn0NCmxhc3NvX2NvZWZbbGFzc29fY29lZiAhPSAwXSAjIFd5xZt3aWV0bGFuaWUgdHlsa28gbmllemVyb3d5Y2ggd3Nww7PFgmN6eW5uaWvDs3cNCmBgYA0KDQoNCiMgVHdvamEga29sZWohDQoNClRlcmF6IG5hZHN6ZWTFgiBjemFzIG5hIHByemV0ZXN0b3dhbmllIHR5Y2ggbWV0b2QgKHJlZ3Jlc2phIGdyemJpZXRvd2EgaSBsYXNzbykgb3JheiBtZXRvZCBvY2VueSAoemVzdGF3IHdhbGlkYWN5am55LCB3YWxpZGFjamEga3J6ecW8b3dhKSBuYSBpbm55Y2ggemJpb3JhY2ggZGFueWNoLiBNb8W8ZXN6IHByYWNvd2HEhyB6IHplc3BvxYJlbSBuYWQgdMSFIGN6xJnFm2NpxIUgbGFib3JhdG9yaXVtLg0KDQpNb8W8ZXN6IHXFvHnEhyBkb3dvbG5lZ28gemJpb3J1IGRhbnljaCB6YXdhcnRlZ28gdyAqKklTTFIqKiBsdWIgd3licmHEhyBqZWRlbiB6IHBha2lldMOzdyBkYW55Y2ggbmEgS2FnZ2xlL0RhdGEgV29ybGQgaXRwLiAoem1pZW5uYSB6YWxlxbxuYSBtdXNpIGJ5xIcgY2nEhWfFgmEpLiANCg0KUG9iaWVyeiB6YmnDs3IgZGFueWNoIGkgc3Byw7NidWogb2tyZcWbbGnEhyBvcHR5bWFsbnkgemVzdGF3IHBhcmFtZXRyw7N3LCBrdMOzcmUgbmFsZcW8eSB1xbx5xIcgZG8gamVnbyBtb2RlbG93YW5pYSENCg0KYGBge3J9DQojUHJ6eWdvdG93YW5pZSB6YmlvcnUgZGFueWNoDQpDb2xsZWdlID0gbmEub21pdChDb2xsZWdlKQ0KYGBgDQoNCiAtIEt0w7NyeSB6YmnDs3IgZGFueWNoIHd5YnJhxYJlxZs/DQogDQogV3licmHFgmFtIHpiacOzciBkYW55Y2ggLT4gQ29sbGVnZSAoSVNMUikuDQogDQogLSBKYWthIGJ5xYJhIFR3b2phIHptaWVubmEgemFsZcW8bmEgKHR6bi4gY28gcHLDs2Jvd2HFgmXFmyBtb2RlbG93YcSHKT8NCiANClptaWVubsSFIHphbGXFvG7EhSB3IGFuYWxpemllIGplc3QgR3JhZC5SYXRlLCBjenlsaSBvZHNldGVrIHN0dWRlbnTDs3csIGt0w7NyenkgdWtvxYRjenlsaSBzdHVkaWEsIGLEmWTEhWN5IHdza2HFum5pa2llbSBzdWtjZXN1IHphcsOzd25vIHVjemVsbmksIGphayBpIHN0dWRlbnTDs3cuIFdpxJlrc3pvxZvEhyB6bWllbm55Y2ggbmllemFsZcW8bnljaCB3IHpiaW9yemUgZGFueWNoIG1vxbxlIGJ5xIcgdXpuYW5hIHphIGlzdG90bmUgcHJlZHlrdG9yeSB0ZWogem1pZW5uZWouIE5hIEdyYWQuUmF0ZSBtb2fEhSB3cMWCeXdhxIcgdGFraWUgY3p5bm5pa2ksIGphayBsaWN6YmEgYXBsaWthY2ppIChBcHBzKSwgbGljemJhIHByenlqxJl0eWNoIHN0dWRlbnTDs3cgKEFjY2VwdCksIGxpY3piYSBzdHVkZW50w7N3IHJvenBvY3p5bmFqxIVjeWNoIG5hdWvEmSAoRW5yb2xsKSwgd3lzb2tvxZvEhyBjemVzbmVnbyAoT3V0c3RhdGUpLCBwb3ppb20ga3dhbGlmaWthY2ppIGthZHJ5IGFrYWRlbWlja2llaiAoUGhELCBUZXJtaW5hbCkgb3JheiBkb3N0xJlwbm/Fm8SHIHphc29iw7N3IHVjemVsbmksIHRha2ljaCBqYWsgd3lkYXRraSBuYSBzdHVkZW50YSAoRXhwZW5kKSBpIGtvc3p0eSB6YWt3YXRlcm93YW5pYSBvcmF6IHd5xbx5d2llbmlhIChSb29tLkJvYXJkKS4NCg0KYGBge3J9DQojVHdvcnplbmllIG1hY2llcnp5IHByb2pla3Rvd2VqDQp4MSA9IG1vZGVsLm1hdHJpeChHcmFkLlJhdGV+LiwgQ29sbGVnZSlbLC0xXSAjIHByenljaW5hbSBwaWVyd3N6xIUga29sdW1uxJkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHpvc3Rhd2lhbSBwcmVkeWt0b3J5DQoNCiNXeWLDs3Igem1pZW5uZWogb2JqYcWbbmlhbmVqDQp5MSA9IENvbGxlZ2UgJT4lDQogIHNlbGVjdChHcmFkLlJhdGUpICU+JQ0KICB1bmxpc3QoKSAlPiUNCiAgYXMubnVtZXJpYygpDQpgYGANCg0KVXR3b3J6ZW5pZSBtYWNpZXJ6eSBwcm9qZWt0b3dlaiB4MSwgemF3aWVyYWrEhWNlaiB3c3p5c3RraWUgem1pZW5uZSBwcmVkeWtjeWpuZSB6ZSB6YmlvcnUgZGFueWNoIENvbGxlZ2UsIHogcG9taW5pxJljaWVtIHBpZXJ3c3plaiBrb2x1bW55IChpbnRlcmNlcHQpLiBOYXN0xJlwbmllIHd5b2RyxJlibmlvbm8ga29sdW1uxJkgR3JhZC5SYXRlIGpha28gem1pZW5uxIUgb2JqYcWbbmlhbsSFIHkxLCBwcnplZHN0YXdpb27EhSB3IGZvcm1pZSB3ZWt0b3JhIGxpY3pib3dlZ28uDQoNCiMjIFJlZ3Jlc2phIGdyemJpZXRvd2ENCg0KYGBge3J9DQojVXN0YXdpZW5pZSBzaWF0a2kgcGFyYW1ldHLDs3cgbGFtYmRhDQpncmlkID0gMTBec2VxKDEwLCAtMiwgbGVuZ3RoID0gMTAwKQ0KI0RvcGFzb3dhbmllIG1vZGVsdSByZWdyZXNqaSBncnpiaWV0b3dlag0KcmlkZ2VfbW9kMiA9IGdsbW5ldCh4MSwgeTEsIGFscGhhID0gMCwgbGFtYmRhID0gZ3JpZCkNCmBgYA0KDQpVdHdvcnplbmllIHNpYXRraSBwb3RlbmNqYWxueWNoIHdhcnRvxZtjaSBwYXJhbWV0cnUgcmVndWxhcml6YWNqaSAobGFtYmRhKSBvcmF6IGRvcGFzb3dhbmllIG1vZGVsdSBgcmlkZ2VgIHJlZ3Jlc3Npb24gcHJ6eSB1xbx5Y2l1IGZ1bmtjamkgZ2xtbmV0LCBnZHppZSB1c3Rhd2llbmllIHBhcmFtZXRydSBhbHBoYSA9IDAgb3puYWN6YSB6YXN0b3Nvd2FuaWUgcmVncmVzamkgZ3J6YmlldG93ZWouDQoNCmBgYHtyfQ0KI1d5bWlhcnkgd3Nww7PFgmN6eW5uaWvDs3cNCmRpbShjb2VmKHJpZGdlX21vZDIpKQ0KcGxvdChyaWRnZV9tb2QyKSAgICAjIHd5a3JlcyB3c3DDs8WCY3p5bm5pa8OzdyBmdW5rY2ppIG5vcm15IEwxDQpgYGANCg0KV3nFm3dpZXRsZW5pZSB3eW1pYXLDs3cgbWFjaWVyenkgd3Nww7PFgmN6eW5uaWvDs3csIGdkemllIHBpZXJ3c3phIHdhcnRvxZvEhyAoMTgpIHJlcHJlemVudHVqZSBsaWN6YsSZIHdzcMOzxYJjenlubmlrw7N3IGRsYSBrYcW8ZGVnbyBwb3ppb211IHBhcmFtZXRydSBsYW1iZGEuIERvZGF0a293bywgd2l6dWFsaXphY2phIHptaWFuIHdzcMOzxYJjenlubmlrw7N3IHcgZnVua2NqaSBub3JteSDwnZC/MSBpbHVzdHJ1amUgd3DFgnl3IHJlZ3VsYXJpemFjamkgbmEgbW9kZWwuDQoNCmBgYHtyfQ0KcmlkZ2VfbW9kMiRsYW1iZGFbNTBdICMgV3nFm3dpZXRsIDUwLXTEhSB3YXJ0b8WbxIcgbGFtYmR5DQpjb2VmKHJpZGdlX21vZDIpWyw1MF0gIyBXecWbd2lldGwgd3Nww7PFgmN6eW5uaWtpIHp3acSFemFuZSB6IDUwLXTEhSB3YXJ0b8WbY2nEhSBsYW1iZHkNCnNxcnQoc3VtKGNvZWYocmlkZ2VfbW9kMilbLTEsNTBdXjIpKSAjIE9ibGljeiBub3JtxJkgbDINCmBgYA0KDQpXeW5payAxMTQ5Ny41NyB3c2thenVqZSBuYSB3eXNva2kgcG96aW9tIHJlZ3VsYXJpemFjamksIGt0w7NyeSB6bmFjesSFY28gb2dyYW5pY3phIHdhcnRvxZtjaSB3c3DDs8WCY3p5bm5pa8Ozdy4gV2nEmWtzem/Fm8SHIHdzcMOzxYJjenlubmlrw7N3IGplc3QgYmxpc2thIHplcnUsIGNvIG9kendpZXJjaWVkbGEgc2lsbnkgd3DFgnl3IHJlZ3VsYXJpemFjamkgdyByZWR1a293YW5pdSBpY2ggd2llbGtvxZtjaS4gWiBrb2xlaSB3eW5payAwLjAxOTQ5NTE1IGRsYSA1MC10ZWogd2FydG/Fm2NpIGxhbWJkYSBwb2thenVqZSBiYXJkem8gbmlza2llIHdhcnRvxZtjaSB3c3DDs8WCY3p5bm5pa8OzdywgYsSZZMSFY2UgZWZla3RlbSB6YXN0b3Nvd2FuaWEgaW50ZW5zeXduZWogcmVndWxhcml6YWNqaS4NCg0KYGBge3J9DQpyaWRnZV9tb2QyJGxhbWJkYVs2MF0gIyBXecWbd2lldGwgNjAtdMSFIHdhcnRvxZvEhyBsYW1iZHkNCmNvZWYocmlkZ2VfbW9kMilbLDYwXSAjIFd5xZt3aWV0bCB3c3DDs8WCY3p5bm5pa2kgcG93acSFemFuZSB6IDYwLXTEhSB3YXJ0b8WbxIcgbGFtYmR5DQpzcXJ0KHN1bShjb2VmKHJpZGdlX21vZDIpWy0xLDYwXV4yKSkgIyBPYmxpY3ogbm9ybcSZIGwyDQpgYGANCg0KV3luaWsgNzA1LjQ4MiB3c2thenVqZSBuYSBuacW8c3p5IHBvemlvbSByZWd1bGFyaXphY2ppIHcgcG9yw7N3bmFuaXUgZG8gNTAtdGVqIHdhcnRvxZtjaSBsYW1iZGEuIFdhcnRvxZtjaSB3c3DDs8WCY3p5bm5pa8OzdyBzxIUgd3nFvHN6ZSwgY28gb3puYWN6YSwgxbxlIHPFgmFic3phIHJlZ3VsYXJpemFjamEgcG96d2FsYSBuYSB3acSZa3N6xIUgc3dvYm9kxJkgdyBpY2ggdXN0YWxhbml1LiBaIGtvbGVpIHd5bmlrIDAuMjg3ODAyOCBqZXN0IHd5xbxzenkgbmnFvCBkbGEgNTAtdGVqIHdhcnRvxZtjaSBsYW1iZGEsIGNvIG9kendpZXJjaWVkbGEgbW5pZWpzemUgb2dyYW5pY3plbmllIHdhcnRvxZtjaSB3c3DDs8WCY3p5bm5pa8OzdyBwcnpleiByZWd1bGFyaXphY2rEmS4NCg0KYGBge3J9DQojUHJlZHlrY2phIHdzcMOzxYJjenlubmlrw7N3IGRsYSB6YWRhbmVqIHdhcnRvxZtjaSBsYW1iZGENCiNHZW5lcnVqZSB3c3DDs8WCY3p5bm5pa2kgcmVncmVzamkgYHJpZGdlYCBkbGEga29ua3JldG5laiB3YXJ0b8WbY2kgbGFtYmRhIChzID0gNTApLg0KcHJlZGljdChyaWRnZV9tb2QyLCBzID0gNTAsIHR5cGUgPSAiY29lZmZpY2llbnRzIilbMToxOCxdDQpgYGANCg0KDQpgYGB7cn0NCiNQb2R6aWHFgiBkYW55Y2ggbmEgemJpw7NyIHRyZW5pbmdvd3kgaSB0ZXN0b3d5IDUwLzUwDQpzZXQuc2VlZCgxKQ0KDQp0cmFpbiA9IENvbGxlZ2UgJT4lDQogIHNhbXBsZV9mcmFjKDAuNSkNCg0KdGVzdCA9IENvbGxlZ2UgJT4lDQogIHNldGRpZmYodHJhaW4pDQoNCiNUd29yemVuaWUgbWFjaWVyenkgcHJvamVrdG93eWNoIGRsYSB6YmlvcsOzdyB0cmVuaW5nb3dlZ28gaSB0ZXN0b3dlZ28NCnhfdHJhaW4gPSBtb2RlbC5tYXRyaXgoR3JhZC5SYXRlfi4sIHRyYWluKVssLTFdDQp4X3Rlc3QgPSBtb2RlbC5tYXRyaXgoR3JhZC5SYXRlfi4sIHRlc3QpWywtMV0NCg0KI1Byenlnb3Rvd2FuaWUgem1pZW5uZWogb2JqYcWbbmlhbmVqDQp5X3RyYWluID0gdHJhaW4gJT4lDQogIHNlbGVjdChHcmFkLlJhdGUpICU+JQ0KICB1bmxpc3QoKSAlPiUNCiAgYXMubnVtZXJpYygpDQoNCnlfdGVzdCA9IHRlc3QgJT4lDQogIHNlbGVjdChHcmFkLlJhdGUpICU+JQ0KICB1bmxpc3QoKSAlPiUNCiAgYXMubnVtZXJpYygpDQpgYGANCg0KYGBge3J9DQojRG9wYXNvd2FuaWUgbW9kZWx1IGByaWRnZWAgcmVncmVzc2lvbg0KcmlkZ2VfbW9kMiA9IGdsbW5ldCh4X3RyYWluLCB5X3RyYWluLCBhbHBoYT0wLCBsYW1iZGEgPSBncmlkLCB0aHJlc2ggPSAxZS0xMikNCiNQcmVkeWtjamEgZGxhIHpiaW9ydSB0ZXN0b3dlZ28NCnJpZGdlX3ByZWQyID0gcHJlZGljdChyaWRnZV9tb2QyLCBzID0gNCwgbmV3eCA9IHhfdGVzdCkNCm1lYW4oKHJpZGdlX3ByZWQyIC0geV90ZXN0KV4yKQ0KYGBgDQoNClV0d29yem9ubyBtb2RlbCByZWdyZXNqaSBncnpiaWV0b3dlaiB6IHLDs8W8bnltaSBwb3ppb21hbWkgcmVndWxhcml6YWNqaS4gRG9rb25hbm8gcHJlZHlrY2ppIG5hIHpiaW9yemUgdGVzdG93eW0gcHJ6eSB1xbx5Y2l1IHdhcnRvxZtjaSBsYW1iZGEgPSA0LiBPYmxpY3pvbnkgYsWCxIVkIMWbcmVkbmlva3dhZHJhdG93eSAoTVNFKSBkbGEgcHJlZHlrY2ppIHd5bmnDs3PFgiAxNjcuODAwMy4gTmlza2Egd2FydG/Fm8SHIE1TRSB3c2thenVqZSBuYSBkb2JyxIUgamFrb8WbxIcgbW9kZWx1IHJlZ3Jlc2ppIGdyemJpZXRvd2VqLg0KDQpgYGB7cn0NCm1lYW4oKG1lYW4oeV90cmFpbikgLSB5X3Rlc3QpXjIpDQpgYGANCg0KYGBge3J9DQojUG9yw7N3bmFuaWUgd3luaWvDs3cgeiByw7PFvG55bWkgd2FydG/Fm2NpYW1pIGxhbWJkYQ0KcmlkZ2VfcHJlZDIgPSBwcmVkaWN0KHJpZGdlX21vZDIsIHMgPSAxZTEwLCBuZXd4ID0geF90ZXN0KQ0KbWVhbigocmlkZ2VfcHJlZDIgLSB5X3Rlc3QpXjIpDQpgYGANCg0KRGxhIHdhcnRvxZtjaSBsYW1iZGEgPSAxMF4xMCAoYmFyZHpvIHd5c29rYSByZWd1bGFyaXphY2phKSB3eW5payBNU0Ugd3lub3NpIDI4OC4xNTA0LiBUYWsgd3lzb2tpIHBvemlvbSByZWd1bGFyaXphY2ppIHBvd29kdWplIHpuYWN6bmUgdXByb3N6Y3plbmllIG1vZGVsdSwgY28gc2t1dGt1amUgd2nEmWtzenltIGLFgsSZZGVtIHByZWR5a2NqaS4NCg0KYGBge3J9DQpyaWRnZV9wcmVkMiA9IHByZWRpY3QocmlkZ2VfbW9kMiwgcyA9IDAsIG5ld3ggPSB4X3Rlc3QpDQptZWFuKChyaWRnZV9wcmVkMiAtIHlfdGVzdCleMikNCg0KI1JlZ3Jlc2phIGxpbmlvd2EgamFrbyBtb2RlbCBiYXpvd3kNCmxtKEdyYWQuUmF0ZX4uLCBkYXRhID0gdHJhaW4pDQojUHJlZHlrY2phIHdzcMOzxYJjenlubmlrw7N3IGRsYSB3eWJyYW5laiBsYW1iZGENCnByZWRpY3QocmlkZ2VfbW9kMiwgcyA9IDAsIHR5cGU9ImNvZWZmaWNpZW50cyIpWzE6MTgsXQ0KYGBgDQpEbGEgd2FydG/Fm2NpIGxhbWJkYSA9IDAgKGJyYWsgcmVndWxhcml6YWNqaSkgd3luaWsgTVNFIHd5bm9zaSAxNjguMzIwMy4gQnJhayByZWd1bGFyaXphY2ppIHVtb8W8bGl3aWEgbW9kZWxvd2kgcGXFgm5lIGRvcGFzb3dhbmllIGRvIGRhbnljaCwgY28gamVkbmFrIG5pZSB6YXdzemUgcHJvd2FkemkgZG8gbGVwc3p5Y2ggd3luaWvDs3cgcHJlZHlrY2ppIHcgcG9yw7N3bmFuaXUgeiBtb2RlbGVtIHogb3B0eW1hbG7EhSB3YXJ0b8WbY2nEhSBsYW1iZGEuDQoNCmBgYHtyfQ0KI1dhbGlkYWNqYSBrcnp5xbxvd2EgZGxhIHJlZ3Jlc2ppIGByaWRnZWANCnNldC5zZWVkKDEpDQpjdi5vdXQgPSBjdi5nbG1uZXQoeF90cmFpbiwgeV90cmFpbiwgYWxwaGEgPSAwKSAjIERvcGFzdWogbW9kZWwgcmVncmVzamkgZ3J6YmlldG93ZWogbmEgZGFueWNoIHRyZW5pbmdvd3ljaA0KYmVzdGxhbV9yaWRnZSA9IGN2Lm91dCRsYW1iZGEubWluICAjIFd5YmllcnogbGFtZMSZLCBrdMOzcmEgbWluaW1hbGl6dWplIHRyZW5pbmdvd3kgTVNFIA0KYmVzdGxhbV9yaWRnZQ0KYGBgDQoNCg0KWmF0ZW0gd2FydG/Fm8SHICRcbGFtYmRhID0gNCQsIGt0w7NyYSBtaW5pbWFsaXp1amUgYsWCxIVkIHdhbGlkYWNqaSBrcnp5xbxvd2VqLCBqZXN0IG9wdHltYWxuYS4NCkludGVycHJldGFjamEgb3B0eW1hbG5laiB3YXJ0b8WbY2kgbGFtYmRhOg0KDQpPcHR5bWFsbmEgd2FydG/Fm8SHIGxhbWJkYSBzdGFub3dpIGtvbXByb21pcyBtacSZZHp5IHByb3N0b3TEhSBtb2RlbHUgYSBqZWdvIGRva8WCYWRub8WbY2nEhSBwcmVkeWtjamkuDQoNCiAgICBaYnl0IHd5c29raWUgd2FydG/Fm2NpIGxhbWJkYSAobmFkbWllcm5hIHJlZ3VsYXJpemFjamEpIG1vZ8SFIHByb3dhZHppxIcgZG8genJlZHVrb3dhbmlhIHdwxYJ5d3UgaXN0b3RueWNoIHptaWVubnljaCwgY28gc2t1dGt1amUgdXRyYXTEhSB3YcW8bnljaCBpbmZvcm1hY2ppLg0KICAgIFoga29sZWkgemJ5dCBuaXNraWUgd2FydG/Fm2NpIGxhbWJkYSAobHViIGJyYWsgcmVndWxhcml6YWNqaSkgbW9nxIUgc2t1dGtvd2HEhyBwcnpldWN6ZW5pZW0gbW9kZWx1LCBnZHppZSBkb3Bhc293dWplIHNpxJkgb24gemJ5dCBkb2vFgmFkbmllIGRvIGRhbnljaCB1Y3rEhWN5Y2gsIHRyYWPEhWMgemRvbG5vxZvEhyBkbyBnZW5lcmFsaXphY2ppIG5hIG5vd3ljaCBkYW55Y2guDQoNCmBgYHtyfQ0KcGxvdChjdi5vdXQpICMgTmFyeXN1aiB3eWtyZXMgdHJlbmluZ293ZWdvIE1TRSBqYWtvIGZ1bmtjasSZIGxhbWJkYQ0KYGBgDQoNCkN6ZXJ3b25hIGtyb3BrYSB3c2thenVqZSBtaW5pbWFsbnkgYsWCxIVkIE1TRSwgb2Rwb3dpYWRhasSFY3kgb3B0eW1hbG5laiB3YXJ0b8WbY2kgbGFtYmRhLg0KDQpTemFyZSBwYXNraSByZXByZXplbnR1asSFIHByemVkemlhxYJ5IGLFgsSZZHUgZGxhIHLDs8W8bnljaCB3YXJ0b8WbY2kgbGFtYmRhLg0KDQpPYnNlcnd1amVteSwgxbxlOg0KDQogICAgUHJ6eSBiYXJkem8gbWHFgnljaCB3YXJ0b8WbY2lhY2ggbGFtYmRhLCBtb2RlbCBjaGFyYWt0ZXJ5enVqZSBzacSZIHd5xbxzenltIGLFgsSZZGVtLCBjbyB3eW5pa2EgeiBqZWdvIG5hZG1pZXJuZWogesWCb8W8b25vxZtjaSBpIHJ5enlrYSBwcnpldWN6ZW5pYS4NCiAgICBQcnp5IGJhcmR6byBkdcW8eWNoIHdhcnRvxZtjaWFjaCBsYW1iZGEsIGLFgsSFZCByw7N3bmllxbwgd3pyYXN0YSwgcG9uaWV3YcW8IG1vZGVsIHN0YWplIHNpxJkgemJ5dCB1cHJvc3pjem9ueSwgdHJhY8SFYyB6ZG9sbm/Fm8SHIGRvIHVjaHd5Y2VuaWEgaXN0b3RueWNoIHphbGXFvG5vxZtjaSB3IGRhbnljaC4NCg0KYGBge3J9DQojUHJlZHlrY2phIG5hIHpiaW9yemUgdGVzdG93eW0gcHJ6eSB1xbx5Y2l1IG9wdHltYWxuZWogd2FydG/Fm2NpIGxhbWJkYQ0KcmlkZ2VfcHJlZDIgPSBwcmVkaWN0KHJpZGdlX21vZDIsIHMgPSBiZXN0bGFtX3JpZGdlLCBuZXd4ID0geF90ZXN0KSAjIFXFvHlqIG5hamxlcHN6ZWogbGFtYmR5IGRvIHByemV3aWR5d2FuaWEgZGFueWNoIHRlc3Rvd3ljaA0KbWVhbigocmlkZ2VfcHJlZDIgLSB5X3Rlc3QpXjIpICMgT2JsaWN6IHRlc3Rvd2UgTVNFDQpgYGANCg0KT3B0eW1hbG5hIHdhcnRvxZvEhyBsYW1iZGEgKGJlc3RsYW0gdXp5c2thbmEgeiB3YWxpZGFjamkga3J6ecW8b3dlaikgem9zdGHFgmEgemFzdG9zb3dhbmEgZG8gcHJ6ZXdpZHl3YW5pYSBuYSBkYW55Y2ggdGVzdG93eWNoLg0KDQpXeW5pa293eSBixYLEhWQgxZtyZWRuaW9rd2FkcmF0b3d5IChNU0UpIHd5bm9zaSAxNjcuODQyNy4NCg0KTW9kZWwgcmVncmVzamkgZ3J6YmlldG93ZWogeiBvcHR5bWFsbsSFIHJlZ3VsYXJpemFjasSFIHphcGV3bmlhIHd5c29rxIUgamFrb8WbxIcgcHJlZHlrY2ppIG5hIGRhbnljaCB0ZXN0b3d5Y2gsIHNrdXRlY3puaWUgbWluaW1hbGl6dWrEhWMgYsWCxIVkIGkgcsOzd25vd2HFvMSFYyB6xYJvxbxvbm/Fm8SHIG1vZGVsdSB6IGplZ28gemRvbG5vxZtjacSFIGRvIGdlbmVyYWxpemFjamkuDQoNCmBgYHtyfQ0KI0RvcGFzb3dhbmllIG1vZGVsdSBgcmlkZ2VgIHJlZ3Jlc3Npb24gZG8gcGXFgm5lZ28gemJpb3J1IGRhbnljaA0Kb3V0ID0gZ2xtbmV0KHgxLCB5MSwgYWxwaGEgPSAwKSAjIERvcGFzdWogbW9kZWwgcmVncmVzamkgZ3J6YmlldG93ZWogZG8gcGXFgm5lZ28gemJpb3J1IGRhbnljaA0KcHJlZGljdChvdXQsIHR5cGUgPSAiY29lZmZpY2llbnRzIiwgcyA9IGJlc3RsYW1fcmlkZ2UpWzE6MTgsXSAjIFd5xZt3aWV0bGFuaWUgd3Nww7PFgmN6eW5uaWvDs3cgcHJ6eSB1xbx5Y2l1IGxhbWJkYSB3eWJyYW5lZ28gcHJ6ZXogQ1YNCmBgYA0KDQpXc3DDs8WCY3p5bm5pa2kgdyBtb2RlbHUgeiBvcHR5bWFsbsSFIHdhcnRvxZtjacSFIGxhbWJkYSBzxIUgd2nEmWtzemUgbmnFvCB3IHByenlwYWRrdSBiYXJkem8gd3lzb2tpZWogcmVndWxhcml6YWNqaSwgYWxlIG1uaWVqc3plIG5pxbwgdyBtb2RlbHUgYmV6IHJlZ3VsYXJpemFjamkuDQoNClRha2kgd3luaWsgd3NrYXp1amUgbmEgenLDs3dub3dhxbxvbnkgd3DFgnl3IHptaWVubnljaCBuYSBwcmVkeWtjasSZLCBjbyBwb3p3YWxhIG1vZGVsb3dpIHV3emdsxJlkbmlhxIcgaXN0b3RuZSB6YWxlxbxub8WbY2kgYmV6IG5hZG1pZXJuZWdvIGRvcGFzb3dhbmlhIGx1YiB1cHJvc3pjemVuaWEuDQoNCiMjIFJlZ3Jlc2phIExhc3NvDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0V9DQpsYXNzb19tb2QgPSBnbG1uZXQoeF90cmFpbiwgDQogICAgICAgICAgICAgICAgICAgeV90cmFpbiwgDQogICAgICAgICAgICAgICAgICAgYWxwaGEgPSAxLCANCiAgICAgICAgICAgICAgICAgICBsYW1iZGEgPSBncmlkKSAjIERvcGFzdWogbW9kZWwgbGFzc28gZG8gZGFueWNoIHRyZW5pbmdvd3ljaA0KDQpwbG90KGxhc3NvX21vZCkgICAgIyBXeWtyZcWbbCB3c3DDs8WCY3p5bm5pa2kNCmBgYA0KDQpEb3Bhc293YW5vIG1vZGVsIGxhc3NvIHJlZ3Jlc3Npb24gKHogYWxwaGEgPSAxKSBuYSB6YmlvcnplIHRyZW5pbmdvd3ltLCB1d3pnbMSZZG5pYWrEhWMgcsOzxbxuZSB3YXJ0b8WbY2kgbGFtYmRhLg0KDQpXIG1pYXLEmSB3enJvc3R1IHdhcnRvxZtjaSBsYW1iZGEsIHdpZWxlIHdzcMOzxYJjenlubmlrw7N3IHN0b3BuaW93byB6bW5pZWpzemEgc2nEmSBkbyB6ZXJhLCBjbyDFm3dpYWRjenkgbyBwcm9jZXNpZSBzZWxla2NqaSB6bWllbm55Y2ggdyBtb2RlbHUgbGFzc28uDQoNCk1vZGVsIGxhc3NvIHJlZ3Jlc3Npb24gbmllIHR5bGtvIHJlZ3VsYXJpenVqZSwgYWxlIHRha8W8ZSBlbGltaW51amUgbmllaXN0b3RuZSB6bWllbm5lLCBjbyBjenluaSBnbyBzemN6ZWfDs2xuaWUgdcW8eXRlY3pueW0gdyBhbmFsaXphY2ggb2Jlam11asSFY3ljaCBkdcW8xIUgbGljemLEmSBwcmVkeWt0b3LDs3csIHBvbWFnYWrEhWMgdyBpZGVudHlmaWthY2ppIG5handhxbxuaWVqc3p5Y2ggY3p5bm5pa8OzdyB3cMWCeXdhasSFY3ljaCBuYSB3eW5pay4NCg0KYGBge3J9DQpzZXQuc2VlZCgxKQ0KY3Yub3V0ID0gY3YuZ2xtbmV0KHhfdHJhaW4sIHlfdHJhaW4sIGFscGhhID0gMSkgIyBEb3Bhc3VqIG1vZGVsIGxhc3NvIGRvIGRhbnljaCB0cmVuaW5nb3d5Y2gNCnBsb3QoY3Yub3V0KSAjIE5hcnlzdWogd3lrcmVzIE1TRSBkbGEgcHLDs2J5IHVjesSFY2VqIGpha28gZnVua2NqxJkgbGFtYmRhDQpiZXN0bGFtX2xhc3NvID0gY3Yub3V0JGxhbWJkYS5taW4gIyBXeWJpZXJ6IGxhbWTEmSwga3TDs3JhIG1pbmltYWxpenVqZSBNU0UgdyBwcsOzYmllIHVjesSFY2VqDQpsYXNzb19wcmVkID0gcHJlZGljdChsYXNzb19tb2QsIHMgPSBiZXN0bGFtX2xhc3NvLCBuZXd4ID0geF90ZXN0KSAjIFXFvHlqIG5hamxlcHN6ZWogbGFtYmR5IGRvIHByemV3aWR5d2FuaWEgZGFueWNoIHRlc3Rvd3ljaA0KbWVhbigobGFzc29fcHJlZCAtIHlfdGVzdCleMikgIyBPYmxpY3ogTVNFIHcgcHLDs2JpZSB0ZXN0b3dlag0KYGBgDQoNClByemVwcm93YWR6b25vIHdhbGlkYWNqxJkga3J6ecW8b3fEhSBkbGEgbW9kZWx1IGxhc3NvIHJlZ3Jlc3Npb24gKGFscGhhID0gMSkuDQoNCiAgICBXeWtyZXMgaWx1c3RydWplIGLFgsSFZCDFm3JlZG5pb2t3YWRyYXRvd3kgKE1TRSkgdyBmdW5rY2ppIGxvZyjwnZyGKS4NCiAgICBDemVyd29uYSBrcm9wa2Egd3NrYXp1amUgd2FydG/Fm8SHIPCdnIYsIGRsYSBrdMOzcmVqIGLFgsSFZCB3YWxpZGFjamkga3J6ecW8b3dlaiBqZXN0IG5ham1uaWVqc3p5LCBjbyBvem5hY3phIG9wdHltYWxuxIUgd2FydG/Fm8SHIHJlZ3VsYXJpemFjamkuDQogICAgU3phcmUgcGFza2kgcmVwcmV6ZW50dWrEhSBwcnplZHppYcWCeSBixYLEmWR1IGRsYSByw7PFvG55Y2ggd2FydG/Fm2NpIPCdnIYsIGEgbmFqbGVwc3phIHdhcnRvxZvEhyDwnZyGIG1pbmltYWxpenVqZSBNU0UgaSB6bmFqZHVqZSBzacSZIHcgcHVua2NpZSBvIG5ham5pxbxzenltIGLFgsSZZHppZS4NCg0KT3B0eW1hbG5hIHdhcnRvxZvEhyDwnZyGIGRsYSBtb2RlbHUgbGFzc28gem9zdGHFgmEgYXV0b21hdHljem5pZSB3eXpuYWN6b25hIG5hIHBvZHN0YXdpZSB3eW5pa8OzdyB3YWxpZGFjamkga3J6ecW8b3dlai4gTW9kZWwgeiB0xIUgd2FydG/Fm2NpxIUg8J2chiB6b3N0YcWCIHXFvHl0eSBkbyBwcnpld2lkeXdhbmlhIG5hIHpiaW9yemUgdGVzdG93eW0uDQoNCiAgICBPYmxpY3pvbm8gTVNFIGRsYSBwcmVkeWtjamkgbmEgZGFueWNoIHRlc3Rvd3ljaCwga3TDs3J5IHd5bmnDs3PFgiAxNjguODMzNi4NCiAgICBXeW5payBwb2thenVqZSwgxbxlIG1vZGVsIGxhc3NvIHJlZ3Jlc3Npb24gb3NpxIVnYSBwb3LDs3dueXdhbG55IHBvemlvbSBixYLEmWR1IHogbW9kZWxlbSBgcmlkZ2VgIHJlZ3Jlc3Npb24gKE1TRSA9IDE2Ny44NDI3KSwgY28gc3VnZXJ1amUgcG9kb2JuxIUgc2t1dGVjem5vxZvEhyBvYnUgcG9kZWrFm8SHIHcgYW5hbGl6b3dhbnltIHByenlwYWRrdS4NCg0KYGBge3J9DQojV3nFm3dpZXRsZW5pZSB3c3DDs8WCY3p5bm5pa8OzdyBtb2RlbHUgbGFzc28gZGxhIHBlxYJuZWdvIHpiaW9ydSBkYW55Y2gNCg0Kb3V0ID0gZ2xtbmV0KHgxLCB5MSwgYWxwaGEgPSAxLCBsYW1iZGEgPSBncmlkKSAjIERvcGFzdWogbW9kZWwgbGFzc28gZG8gcGXFgm5lZ28gemJpb3J1IGRhbnljaA0KbGFzc29fY29lZiA9IHByZWRpY3Qob3V0LCB0eXBlID0gImNvZWZmaWNpZW50cyIsIHMgPSBiZXN0bGFtX2xhc3NvKVsxOjE4LF0gIyBXecWbd2lldGxhbmllIHdzcMOzxYJjenlubmlrw7N3IHByenkgdcW8eWNpdSBsYW1iZGEgd3licmFuZWdvIHByemV6IENWDQpsYXNzb19jb2VmDQpgYGANCmBgYHtyfQ0KbGFzc29fY29lZltsYXNzb19jb2VmICE9IDBdICMgV3nFm3dpZXRsYW5pZSB0eWxrbyBuaWV6ZXJvd3ljaCB3c3DDs8WCY3p5bm5pa8Ozdw0KYGBgDQoNCkxhc3NvIHd5YnJhxYJvIHBvZHpiacOzciBuYWp3YcW8bmllanN6eWNoIHptaWVubnljaCwgZWxpbWludWrEhWMgdGUsIGt0w7NyZSBuaWUgbWlhxYJ5IGlzdG90bmVnbyB3cMWCeXd1IG5hIHptaWVubsSFIG9iamHFm25pYW7EhS4NCg0KIyMgV25pb3NraQ0KDQogLSBDenkgb2N6ZWtpd2HFgmXFmywgxbxlIHJlZ3Jlc2phIGdyemJpZXRvd2EgYsSZZHppZSBsZXBzemEgb2QgbGFzc28sIGN6eSBvZHdyb3RuaWU/IEphayB3eXBhZGEgdyBzdG9zdW5rdSBkbyBPTFM/IFBva2HFvCBvZHBvd2llZG5pZSByYXBvcnR5LCBtaWFyeSBkb3Bhc293YW5pYSBpIGtyw7N0a28gamUgb23Ds3cgKHBvcsOzd25haikuDQogDQogVyBhbmFsaXppZSBkYW55Y2ggemF3aWVyYWrEhWN5Y2ggd2llbGUgcHJlZHlrdG9yw7N3LCBjesSZc3RvIHd6YWplbW5pZSBza29yZWxvd2FueWNoLCBtb2RlbGUgcmVndWxhcml6YWN5am5lLCB0YWtpZSBqYWsgcmVncmVzamEgZ3J6YmlldG93YSAoYHJpZGdlYCByZWdyZXNzaW9uKSBpIGxhc3NvLCB6d3lrbGUgcHJ6ZXd5xbxzemFqxIUga2xhc3ljem7EhSByZWdyZXNqxJkgbGluaW93xIUgKE9MUykgcG9kIHd6Z2zEmWRlbSB6ZG9sbm/Fm2NpIGRvIGdlbmVyYWxpemFjamkgbmEgbm93eWNoIGRhbnljaC4NCg0KICAgIFJlZ3Jlc2phIGdyemJpZXRvd2Egb2thenVqZSBzacSZIHN6Y3plZ8OzbG5pZSBza3V0ZWN6bmEgdyBwcnp5cGFka3Ugd2llbHUgc2tvcmVsb3dhbnljaCBwcmVkeWt0b3LDs3csIHBvbmlld2HFvCByb3prxYJhZGEgd2FnaSBtacSZZHp5IG5pbWkgdyBzcG9zw7NiIGJhcmR6aWVqIHLDs3dub21pZXJueSwgb2dyYW5pY3phasSFYyBwcm9ibGVtIGtvbGluZWFybm/Fm2NpLg0KDQogICAgTGFzc28gbmllIHR5bGtvIHJlZ3VsYXJpenVqZSwgYWxlIHRha8W8ZSBwcnplcHJvd2FkemEgc2VsZWtjasSZIHptaWVubnljaCwgcmVkdWt1asSFYyB3c3DDs8WCY3p5bm5pa2kgbW5pZWogaXN0b3RueWNoIHByZWR5a3RvcsOzdyBkbyB6ZXJhLiBEemnEmWtpIHRlbXUgdXByYXN6Y3phIG1vZGVsIGkgamVzdCBzemN6ZWfDs2xuaWUgdcW8eXRlY3puZSwgZ2R5IHR5bGtvIGtpbGthIHByZWR5a3RvcsOzdyBtYSBpc3RvdG55IHdwxYJ5dyBuYSB6bWllbm7EhSB6YWxlxbxuxIUuDQoNCk5hIHBvZHN0YXdpZSB0eWNoIHfFgmHFm2Npd2/Fm2NpIG1vxbxuYSBvY3pla2l3YcSHLCDFvGU6DQoNCiAgICBMYXNzbyBwcnpld3nFvHN6eSByZWdyZXNqxJkgZ3J6YmlldG93xIUgdyBzeXR1YWNqYWNoLCBnZHkgbmlld2llbGthIGxpY3piYSBwcmVkeWt0b3LDs3cgamVzdCBpc3RvdG5hLg0KICAgIFJlZ3Jlc2phIGdyemJpZXRvd2EgYsSZZHppZSBiYXJkemllaiBvZHBvd2llZG5pYSwgZ2R5IHdzenlzdGtpZSBwcmVkeWt0b3J5IHdub3N6xIUgd2FydG/Fm8SHIGRvIG1vZGVsdS4NCg0KUG9yw7N3bmFuaWUgbW9kZWxpOg0KDQpBYnkgendlcnlmaWtvd2HEhyBza3V0ZWN6bm/Fm8SHIG9idSBtb2RlbGksIG9ibGljemFteSBixYLEhWQgxZtyZWRuaW9rd2FkcmF0b3d5IChNU0UpIG5hIHpiaW9yemUgdGVzdG93eW0gemFyw7N3bm8gZGxhIHJlZ3Jlc2ppIGdyemJpZXRvd2VqIChgcmlkZ2VgIHJlZ3Jlc3Npb24pLCBqYWsgaSByZWdyZXNqaSBsYXNzbyAobGFzc28gcmVncmVzc2lvbikuIE9ibGljem9uZSB3YXJ0b8WbY2kgTVNFIHBvendhbGFqxIUgbmEgb2NlbsSZIHpkb2xub8WbY2kga2HFvGRlZ28geiBtb2RlbGkgZG8gZ2VuZXJhbGl6YWNqaSwgY3p5bGkgcHJ6ZXdpZHl3YW5pYSB3eW5pa8OzdyBuYSBub3d5Y2ggZGFueWNoLCBwb3phIHpiaW9yZW0gdHJlbmluZ293eW0uDQogDQogYSkgUmVncmVzamEgZ3J6YmlldG93YSAoYHJpZGdlYCByZWdyZXNzaW9uKToNCiANCmBgYHtyfQ0KIyBQcmVkeWtjamEgbmEgemJpb3J6ZSB0ZXN0b3d5bSB6IG9wdHltYWxuxIUgbGFtYmRhDQpyaWRnZV9wcmVkID0gcHJlZGljdChyaWRnZV9tb2QyLCBzID0gYmVzdGxhbV9yaWRnZSwgbmV3eCA9IHhfdGVzdCkNCiMgT2JsaWN6ZW5pZSBNU0UNCm1zZV9yaWRnZSA9IG1lYW4oKHJpZGdlX3ByZWQgLSB5X3Rlc3QpXjIpDQpgYGANCiANCiBNU0UgYHJpZGdlYDogMTY3Ljg0MjcNCiANCiBiKSBSZWdyZXNqYSBsYXNzbzoNCiANCmBgYHtyfQ0KIyBQcmVkeWtjamEgbmEgemJpb3J6ZSB0ZXN0b3d5bSB6IG9wdHltYWxuxIUgbGFtYmRhDQpsYXNzb19wcmVkID0gcHJlZGljdChsYXNzb19tb2QsIHMgPSBiZXN0bGFtX2xhc3NvLCBuZXd4ID0geF90ZXN0KQ0KIyBPYmxpY3plbmllIE1TRQ0KbXNlX2xhc3NvID0gbWVhbigobGFzc29fcHJlZCAtIHlfdGVzdCleMikNCmBgYA0KIA0KIE1TRSBsYXNzbzogMTY4LjgzMzYNCiANCmMpIFJlZ3Jlc2phIGxpbmlvd2EgKE9MUyk6DQoNCmBgYHtyfQ0KIyBEb3Bhc293YW5pZSBtb2RlbHUgT0xTIG5hIHpiaW9yemUgdHJlbmluZ293eW0NCm9sc19tb2QgPSBsbShHcmFkLlJhdGUgfiAuLCBkYXRhID0gdHJhaW4pDQojIFByZWR5a2NqYSBuYSB6YmlvcnplIHRlc3Rvd3ltDQpvbHNfcHJlZCA9IHByZWRpY3Qob2xzX21vZCwgbmV3ZGF0YSA9IHRlc3QpDQojIE9ibGljemVuaWUgTVNFDQptc2Vfb2xzID0gbWVhbigob2xzX3ByZWQgLSB5X3Rlc3QpXjIpDQoNCmBgYA0KDQpNU0UgT0xTOiAxNjguMzIwMw0KDQpSZWdyZXNqYSBncnpiaWV0b3dhIHV6eXNrYcWCYSBuYWpuacW8c3p5IE1TRSBuYSB6YmlvcnplIHRlc3Rvd3ltLCBjbyBzdWdlcnVqZSwgxbxlIHNwb8WbcsOzZCB0cnplY2ggcG9yw7N3bnl3YW55Y2ggbW9kZWxpIG5hamxlcGllaiBwcnpld2lkdWplIHdhcnRvxZvEhyBHcmFkLlJhdGUuDQoNCiAgICBSZWdyZXNqYSBsaW5pb3dhIChPTFMpIG9zacSFZ27EmcWCYSBNU0Ugbmllem5hY3puaWUgd3nFvHN6ZSBuacW8IHJlZ3Jlc2phIGdyemJpZXRvd2EsIGNvIHdza2F6dWplLCDFvGUgYnJhayByZWd1bGFyaXphY2ppIG5pZSBzcG93b2Rvd2HFgiB6bmFjem5lZ28gcG9nb3JzemVuaWEgd3luaWvDs3cuDQogICAgUmVncmVzamEgbGFzc28gbWlhxYJhIG5pZWNvIHd5xbxzemUgTVNFIG5pxbwgcG96b3N0YcWCZSBtb2RlbGUsIGplZG5hayByw7PFvG5pY2UgYnnFgnkgbWluaW1hbG5lLg0KDQpXeW5pa2kgd3NrYXp1asSFLCDFvGUgcmVncmVzamEgZ3J6YmlldG93YSBuaWV6bmFjem5pZSBwcnpld3nFvHN6ecWCYSBsYXNzbyBpIE9MUyBwb2Qgd3pnbMSZZGVtIE1TRSBuYSB6YmlvcnplIHRlc3Rvd3ltLiBPY3pla2l3YW5pYSBkb3R5Y3rEhWNlIGxlcHN6ZWogd3lkYWpub8WbY2kgbGFzc28gbmllIHBvdHdpZXJkemnFgnkgc2nEmSwgY28gbW/FvGUgYnnEhyB3eW5pa2llbSBjaGFyYWt0ZXJ5c3R5a2kgZGFueWNoLCBnZHppZSB3c3p5c3RraWUgcHJlZHlrdG9yeSB3bm9zesSFIGlzdG90bsSFIGluZm9ybWFjasSZIGRvIG1vZGVsdS4NCg0KQmxpc2tpZSB3eW5pa2kgT0xTIHN1Z2VydWrEhSByw7N3bmllxbwsIMW8ZSBwcm9ibGVtIG5hZG1pZXJuZWdvIGRvcGFzb3dhbmlhIGx1YiB3aWVsb2tvbGluZWFybm/Fm2NpIHcgdHljaCBkYW55Y2ggbmllIGJ5xYIgZG9taW51asSFY3kuDQpXbmlvc2tpOg0KDQpXeWLDs3Igb2Rwb3dpZWRuaWVnbyBtb2RlbHUgcG93aW5pZW4gemFsZcW8ZcSHIG5pZSB0eWxrbyBvZCBtaWFyIGRvcGFzb3dhbmlhLCB0YWtpY2ggamFrIE1TRSwgYWxlIHLDs3duaWXFvCBvZCBjZWzDs3cgYW5hbGl6eSBvcmF6IHBvdHJ6ZWJ5IGludGVycHJldG93YWxub8WbY2kgbW9kZWx1LiBSZWdyZXNqYSBncnpiaWV0b3dhIG1vxbxlIGJ5xIcgcHJlZmVyb3dhbmEgdyBwcnp5cGFka3UgZGFueWNoIG8gd2llbHUgc2tvcmVsb3dhbnljaCBwcmVkeWt0b3JhY2gsIHBvZGN6YXMgZ2R5IGxhc3NvIHNwcmF3ZHppIHNpxJkgbGVwaWVqIHcgYW5hbGl6YWNoIHd5bWFnYWrEhWN5Y2ggc2VsZWtjamkgem1pZW5ueWNoLiBPTFMgcG96b3N0YWplIHByb3N0eW0gaSBza3V0ZWN6bnltIHBvZGVqxZtjaWVtIHcgc3l0dWFjamFjaCwgZ2R6aWUgcmVndWxhcml6YWNqYSBuaWUgamVzdCBrb25pZWN6bmEuDQoNCiAtIEt0w7NyZSBwcmVkeWt0b3J5IG9rYXphxYJ5IHNpxJkgd2HFvG5lIHcgb3N0YXRlY3pueW0gbW9kZWx1IChtb2RlbGFjaCk/DQogDQogMS4gUmVncmVzamEgZ3J6YmlldG93YSAoYHJpZGdlYCByZWdyZXNzaW9uKQ0KVyByZWdyZXNqaSBncnpiaWV0b3dlaiB3c3p5c3RraWUgcHJlZHlrdG9yeSB6YWNob3d1asSFIHN3b2plIHdzcMOzxYJjenlubmlraSwgcG9uaWV3YcW8IG1vZGVsIG5pZSBlbGltaW51amUgem1pZW5ueWNoLCBhIGplZHluaWUgcmVndWxhcml6dWplIGljaCB3YXJ0b8WbY2kuIE9zdGF0ZWN6bmUgd3Nww7PFgmN6eW5uaWtpIGRsYSBvcHR5bWFsbmVqIHdhcnRvxZtjafCdnIYgKHd5em5hY3pvbmVqIHByemV6IHdhbGlkYWNqxJkga3J6ecW8b3fEhSkgc8SFIG5hc3TEmXB1asSFY2U6DQogDQpgYGB7cn0NCnByZWRpY3QocmlkZ2VfbW9kMiwgdHlwZSA9ICJjb2VmZmljaWVudHMiLCBzID0gYmVzdGxhbV9yaWRnZSlbMToxOCxdDQpgYGANCiANClJlZ3Jlc2phIGdyemJpZXRvd2EgKGByaWRnZWAgcmVncmVzc2lvbik6DQoNCldzenlzdGtpZSBwcmVkeWt0b3J5IHPEhSB1d3pnbMSZZG5pb25lIHcgbW9kZWx1LCBqZWRuYWsgaWNoIHdwxYJ5dyBqZXN0IHJlZ3VsYXJpem93YW55IHcgY2VsdSB6bW5pZWpzemVuaWEgcnl6eWthIHByemV1Y3plbmlhLiBXecW8c3plIHdhcnRvxZtjaSB3c3DDs8WCY3p5bm5pa8OzdywgdGFraWUgamFrIFByaXZhdGVZZXMgY3p5IFRvcDI1cGVyYywgd3NrYXp1asSFIG5hIHNpbG5pZWpzenkgd3DFgnl3IHR5Y2ggem1pZW5ueWNoIG5hIHdhcnRvxZvEhyBHcmFkLlJhdGUuDQpSZWdyZXNqYSBsYXNzbyAobGFzc28gcmVncmVzc2lvbik6DQoNCjIuIFJlZ3Jlc2phIGxhc3NvIGVsaW1pbnVqZSBtbmllaiBpc3RvdG5lIHByZWR5a3RvcnksIHptbmllanN6YWrEhWMgaWNoIHdzcMOzxYJjenlubmlraSBkbyB6ZXJhLiBEemnEmWtpIHRlbXUgbW9kZWwgYXV0b21hdHljem5pZSBpZGVudHlmaWt1amUgem1pZW5uZSBvIG5handpxJlrc3p5bSB3cMWCeXdpZSBuYSB6bWllbm7EhSBvYmphxZtuaWFuxIUuIFRvIHBvZGVqxZtjaWUgdXByYXN6Y3phIG1vZGVsIGkgbW/FvGUgYnnEhyBzemN6ZWfDs2xuaWUgcHJ6eWRhdG5lIHcgc3l0dWFjamFjaCwgZ2R5IGxpY3piYSBwcmVkeWt0b3LDs3cgamVzdCBkdcW8YSwgYSB0eWxrbyBuaWVrdMOzcmUgeiBuaWNoIG1hasSFIGlzdG90bnkgd3DFgnl3Lg0KDQpXeWJyYW5lIHByZWR5a3RvcnkgKHdzcMOzxYJjenlubmlraSByw7PFvG5lIG9kIHplcmEpOg0KDQpgYGB7cn0NCnByZWRpY3QobGFzc29fbW9kLCB0eXBlID0gImNvZWZmaWNpZW50cyIsIHMgPSBiZXN0bGFtX2xhc3NvKVtsYXNzb19jb2VmICE9IDBdDQpgYGANCg0KIEludGVycHJldGFjamE6DQpMYXNzbyB3eWVsaW1pbm93YcWCbyBtbmllaiBpc3RvdG5lIHByZWR5a3RvcnksIHRha2llIGphayBBcHBzLCBBY2NlcHQsIEVucm9sbCwgRi5VbmRlcmdyYWQsIGN6eSBQLlVuZGVyZ3JhZC4NCklzdG90bmUgem1pZW5uZToNCioqUHJpdmF0ZVllcyoqOiBabWllbm5hIGJpbmFybmEsIGt0w7NyYSB3c2thenVqZSwgY3p5IHVjemVsbmlhIGplc3QgcHJ5d2F0bmEuDQoqKlRvcDEwcGVyYyoqIGkgKipUb3AyNXBlcmMqKjogUHJvY2VudCBzdHVkZW50w7N3IHogbmFqbGVwc3p5Y2ggd3luaWvDs3cgYWthZGVtaWNraWNoLg0KKipSb29tLkJvYXJkKiogaSAqKlRlcm1pbmFsKio6IEtvc3p0eSB6YWt3YXRlcm93YW5pYSBvcmF6IGxpY3piYSBuYXVjenljaWVsaSB6IHR5dHXFgmVtIHRlcm1pbmFsbnltIChucC4gZG9rdG9yZW0pLg0KDQozLiBSZWdyZXNqYSBsaW5pb3dhIChPTFMpDQpPTFMgbmllIHN0b3N1amUgcmVndWxhcml6YWNqaSBhbmkgc2VsZWtjamkgem1pZW5ueWNoLiBXc3p5c3RraWUgcHJlZHlrdG9yeSBzxIUgdXd6Z2zEmWRuaWFuZSB3IG1vZGVsdSwgYSBpY2ggd3Nww7PFgmN6eW5uaWtpIHPEhSBuYXN0xJlwdWrEhWNlOg0KDQpXc3DDs8WCY3p5bm5pa2kgT0xTOg0KDQpgYGB7cn0NCnN1bW1hcnkob2xzX21vZCkkY29lZmZpY2llbnRzDQpgYGANCg0KSW50ZXJwcmV0YWNqYToNCk1vZGVsIE9MUyB1d3pnbMSZZG5pYSB3c3p5c3RraWUgem1pZW5uZSwgYSBwcmVkeWt0b3J5IG8gZHXFvHljaCB3YXJ0b8WbY2lhY2ggd3Nww7PFgmN6eW5uaWvDs3csIHRha2llIGphayBQcml2YXRlWWVzLCBUb3AxMHBlcmMsIGkgVG9wMjVwZXJjLCB3eWRhasSFIHNpxJkgbmFqYmFyZHppZWogaXN0b3RuZS4NClduaW9za2k6DQoNCiAgICBMYXNzbyByZWdyZXNzaW9uIHppZGVudHlmaWtvd2HFgm8gbmFqYmFyZHppZWogaXN0b3RuZSBwcmVkeWt0b3J5LCBlbGltaW51asSFYyBtbmllaiB3YcW8bmUgem1pZW5uZSwgY28gamVzdCBzemN6ZWfDs2xuaWUgcHJ6eWRhdG5lLCBnZHkgdXByb3N6Y3plbmllIG1vZGVsdSBtYSBrbHVjem93ZSB6bmFjemVuaWUuDQogICAgYFJpZGdlYCByZWdyZXNzaW9uIHV3emdsxJlkbmnFgm8gd3N6eXN0a2llIHptaWVubmUsIHJlZ3VsYXJpenVqxIVjIGljaCB3c3DDs8WCY3p5bm5pa2ksIGNvIHNwcmF3ZHphIHNpxJksIGdkeSBrYcW8ZGEgem1pZW5uYSB3bm9zaSBpc3RvdG7EhSBpbmZvcm1hY2rEmSBkbyBtb2RlbHUuDQogICAgUmVncmVzamEgbGluaW93YSAoT0xTKSB6YXdpZXJhIHdzenlzdGtpZSBwcmVkeWt0b3J5LCBhbGUgYnJhayByZWd1bGFyaXphY2ppIG1vxbxlIHByb3dhZHppxIcgZG8gcHJvYmxlbcOzdyB6IGdlbmVyYWxpemFjasSFIHcgcHJ6eXBhZGt1IHNrb3JlbG93YW55Y2ggem1pZW5ueWNoLg==