Introduction
議題:用申請假釋者的屬性,預測他會不會違反假釋規定
學習重點:
- 設定隨機種子set.seed(),依比例分割資料
- 從邏輯式回歸的係數推算自變數的邊際效果
- 勝率和機率、勝率比和機率差 之間的換算
- 臨界機率對混淆矩陣(期望報酬)的影響payoff = matrix(c(0,-100,-10,-50),2,2); payoff
- AUC的實質意義payoff = matrix(c(100, -80, -20, 100),2,2); payoff
- 如何(從報酬矩陣)決定臨界機率
- 什麼是抽樣偏差,如何避免、如何修正
1 資料處理 Loading the Dataset
【1.1 讀進資料】How many parolees are contained in the dataset?
parole <- read.csv("parole.csv")
str(parole)
'data.frame': 675 obs. of 9 variables:
$ male : int 1 0 1 1 1 1 1 0 0 1 ...
$ race : int 1 1 2 1 2 2 1 1 1 2 ...
$ age : num 33.2 39.7 29.5 22.4 21.6 46.7 31 24.6 32.6 29.1 ...
$ state : int 1 1 1 1 1 1 1 1 1 1 ...
$ time.served : num 5.5 5.4 5.6 5.7 5.4 6 6 4.8 4.5 4.7 ...
$ max.sentence : int 18 12 12 18 12 18 18 12 13 12 ...
$ multiple.offenses: int 0 0 0 0 0 0 0 0 0 0 ...
$ crime : int 4 3 3 1 1 4 3 1 3 2 ...
$ violator : int 0 0 0 0 0 0 0 0 0 0 ...
summary(parole)
male race age state time.served
Min. :0.000 Min. :1.00 Min. :18.4 Min. :1.00 Min. :0.00
1st Qu.:1.000 1st Qu.:1.00 1st Qu.:25.4 1st Qu.:2.00 1st Qu.:3.25
Median :1.000 Median :1.00 Median :33.7 Median :3.00 Median :4.40
Mean :0.807 Mean :1.42 Mean :34.5 Mean :2.89 Mean :4.20
3rd Qu.:1.000 3rd Qu.:2.00 3rd Qu.:42.5 3rd Qu.:4.00 3rd Qu.:5.20
Max. :1.000 Max. :2.00 Max. :67.0 Max. :4.00 Max. :6.00
max.sentence multiple.offenses crime violator
Min. : 1.0 Min. :0.000 Min. :1.00 Min. :0.000
1st Qu.:12.0 1st Qu.:0.000 1st Qu.:1.00 1st Qu.:0.000
Median :12.0 Median :1.000 Median :2.00 Median :0.000
Mean :13.1 Mean :0.536 Mean :2.06 Mean :0.116
3rd Qu.:15.0 3rd Qu.:1.000 3rd Qu.:3.00 3rd Qu.:0.000
Max. :18.0 Max. :1.000 Max. :4.00 Max. :1.000
【1.2 底線機率】How many of the parolees in the dataset violated the terms of their parole?
table(parole$violator)
0 1
597 78
2 整理資料框 Creating Our Prediction Model
【2.1 類別變數】Which variables in this dataset are unordered factors with at least three levels?
### (4) state
### (8) crime
【2.2 資料框摘要】How does the output of summary() change for a factor variable as compared to a numerical variable?
### (1) The output becomes similar to that of the table() function applied to that variable
parole$state = as.factor(parole$state)
parole$crime = as.factor(parole$crime)
class(parole$state)
[1] "factor"
class(parole$crime)
[1] "factor"
3 資料分割 Splitting into a Training and Testing Set
【3.1 指定隨機種子、依比例分割資料】Roughly what proportion of parolees have been allocated to the training and testing sets?
### (1) 70% to the training set, 30% to the testing set
set.seed(144)
library(caTools)
split = sample.split(parole$violator, SplitRatio = 0.7)
train = subset(parole, split == TRUE)
test = subset(parole, split == FALSE)
【3.2 隨機種子的功用】Now, suppose you re-ran lines [1]-[5] of Problem 3.1. What would you expect? If you instead ONLY re-ran lines [3]-[5], what would you expect? If you instead called set.seed() with a different number and then re-ran lines [3]-[5] of Problem 3.1, what would you expect?
### (2) A different training/testing set split from the first execution of [1]-[5]
### (1) The exact same training/testing set split as the first execution of [1]-[5]
### (2) A different training/testing set split from the first execution of [1]-[5]
split = sample.split(parole$violator, SplitRatio = 0.7)
train = subset(parole, split == TRUE)
test = subset(parole, split == FALSE)
4 建立模型 Building a Logistic Regression Model
【4.1 顯著性】What variables are significant in this model?
### race/ state4/ multiple.offenses
set.seed(144)
library(caTools)
split = sample.split(parole$violator, SplitRatio = 0.7)
train = subset(parole, split == TRUE)
test = subset(parole, split == FALSE)
paroleLog <- glm(violator ~., data = parole, family = "binomial")
【4.2 從回歸係數估計邊際效用】What can we say based on the coefficient of the multiple.offenses variable?
# (4) Our model predicts that a parolee who committed multiple offenses has 5.01 times higher odds of being a violator than a parolee who did not commit multiple offenses but is otherwise identical.
### exp(ln(oddsA)) = exp(ln(oddsB) + 1.61)
### exp(ln(oddsA)) = exp(ln(oddsB)) * exp(1.61)
### oddsA = exp(1.61) * oddsB
### oddsA= 5.01 * oddsB
【4.3 從預測值估計勝率和機率】According to the model, what are the odds this individual is a violator? What is the probability this individual is a violator?
odds <- glm(violator ~ male + race + age + state + time.served + max.sentence + multiple.offenses + crime, data = parole, family = binomial)
odds
Call: glm(formula = violator ~ male + race + age + state + time.served +
max.sentence + multiple.offenses + crime, family = binomial,
data = parole)
Coefficients:
(Intercept) male race age
-4.05238 0.27062 0.75725 0.00655
state2 state3 state4 time.served
0.20840 0.89381 -3.28084 -0.07655
max.sentence multiple.offenses crime2 crime3
0.05329 1.53155 0.33689 -0.28099
crime4
-0.15781
Degrees of Freedom: 674 Total (i.e. Null); 662 Residual
Null Deviance: 483
Residual Deviance: 349 AIC: 375
logodds <- -4.05238 + 0.27062*male + 0.75725*race - 0.00655*age + 0.20840*state2 + 0.89381*state3 - 3.28084*state4 - 0.07655*time.served + 0.05329*max.sentence + 1.53155*multiple.offenses + 0.33689*crime2 - 0.28099*crime3 - 0.15781*crime4
male <- 1
race <- 1
age <- 50
state <- 1
time.served <- 3
max.sentence <- 12
multiple.offenses <- 0
crime <- 2
logodds
[1] -2.605
odds <- exp(logodds)
odds
[1] 0.07388
probViolation <- 1 / (1 + odds)
probViolation
[1] 0.9312
5 驗證模型 Evaluating the Model on the Testing Set
【5.1 從測試資料預測機率】What is the maximum predicted probability of a violation?
prediction <- predict(paroleLog, type = "response", newdata = test)
summary(prediction)
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.0024 0.0212 0.0529 0.1360 0.1254 0.8125
【5.2 從混淆矩陣計算敏感性、明確性、正確率】What is the model’s sensitivity, specificity, accuracy?
table(test$violator, prediction > 0.5)
FALSE TRUE
0 169 10
1 11 12
sensitivity <- 12 / (11 + 12)
specificity <- 169 / (169 +10)
accuracy <- (169 + 12) / (169 + 11 + 10 +12)
sensitivity
[1] 0.5217
specificity
[1] 0.9441
accuracy
[1] 0.896
【5.3 底線機率】What is the accuracy of a simple model that predicts that every parolee is a non-violator?
table(test$violator)
0 1
179 23
accuracy <- 179 / (179 + 23)
accuracy
[1] 0.8861
【5.4 根據報償矩陣調整臨界機率】Which of the following most likely describes their preferences and best course of action?
pred <- predict(paroleLog, type = "response", newdata = test)
summary(pred)
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.0024 0.0212 0.0529 0.1360 0.1254 0.8125
【5.5 正確率 vs 辨識率】Which of the following is the most accurate assessment of the value of the logistic regression model with a cutoff 0.5 to a parole board, based on the model’s accuracy as compared to the simple baseline model?
table(pred$violator, pred > 0.5)
Error in pred$violator : $ operator is invalid for atomic vectors
【5.6 計算辨識率】Using the ROCR package, what is the AUC value for the model?
library(ROCR)
ROCRpred <- prediction(prediction, test$violator)
as.numeric(performance(ROCRpred, "auc")@y.values)
[1] 0.9031
【5.7 辨識率的定義】Describe the meaning of AUC in this context.
# (1) The probability the model can correctly differentiate between a randomly selected parole violator and a randomly selected parole non-violator.
# AUC(Area Under Curve, 辨識力)
6 抽樣偏差 Identifying Bias in Observational Data
【6.1 如何避免、診斷、修正抽樣偏差】How could we improve our dataset to best address selection bias?
# (4) We should use a dataset tracking a group of parolees from the start of their parole until either they violated parole or they completed their term.
LS0tCnRpdGxlOiAiQVMzLTIgUHJlZGljdGluZyBQYXJvbGUgVmlvbGF0b3JzIgphdXRob3I6ICLmpYrlh7HlgKsgTTA2NDYxMDAyMSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3IgZWNobz1ULCBtZXNzYWdlPUYsIGNhY2hlPUYsIHdhcm5pbmc9Rn0Kcm0obGlzdD1scyhhbGw9VCkpCm9wdGlvbnMoZGlnaXRzPTQsIHNjaXBlbj0xMikKbGlicmFyeShtYWdyaXR0cikKYGBgCgotIC0gLQoKIyMjIEludHJvZHVjdGlvbgoKKirorbDpoYzvvJrnlKjnlLPoq4vlgYfph4vogIXnmoTlsazmgKfvvIzpoJDmuKzku5bmnIPkuI3mnIPpgZXlj43lgYfph4vopo/lrpoqKgoKKirlrbjnv5Lph43pu57vvJoqKgoKKyDoqK3lrprpmqjmqZ/nqK7lrZBzZXQuc2VlZCgp77yM5L6d5q+U5L6L5YiG5Ymy6LOH5paZCisg5b6e6YKP6Lyv5byP5Zue5q2455qE5L+C5pW45o6o566X6Ieq6K6K5pW455qE6YKK6Zqb5pWI5p6cCisg5Yud546H5ZKM5qmf546H44CB5Yud546H5q+U5ZKM5qmf546H5beuIOS5i+mWk+eahOaPm+eulyAKKyDoh6jnlYzmqZ/njoflsI3mt7fmt4bnn6npmaMo5pyf5pyb5aCx6YWsKeeahOW9semfv3BheW9mZiA9IG1hdHJpeChjKDAsLTEwMCwtMTAsLTUwKSwyLDIpOyBwYXlvZmYKKyBBVUPnmoTlr6bos6rmhI/nvqlwYXlvZmYgPSBtYXRyaXgoYygxMDAsIC04MCwgLTIwLCAxMDApLDIsMik7IHBheW9mZgorIOWmguS9lSjlvp7loLHphaznn6npmaMp5rG65a6a6Ieo55WM5qmf546HIAorIOS7gOm6vOaYr+aKveaoo+WBj+W3rizlpoLkvZXpgb/lhY3jgIHlpoLkvZXkv67mraMKCjxicj4KCi0gLSAtCgojIyMjIDEg6LOH5paZ6JmV55CGIExvYWRpbmcgdGhlIERhdGFzZXQKCuOAkCoqMS4xIOiugOmAsuizh+aWmSoq44CRSG93IG1hbnkgcGFyb2xlZXMgYXJlIGNvbnRhaW5lZCBpbiB0aGUgZGF0YXNldD8KYGBge3J9CnBhcm9sZSA8LSByZWFkLmNzdigicGFyb2xlLmNzdiIpCnN0cihwYXJvbGUpCnN1bW1hcnkocGFyb2xlKQpgYGAKCuOAkCoqMS4yIOW6lee3muapn+eOhyoq44CRSG93IG1hbnkgb2YgdGhlIHBhcm9sZWVzIGluIHRoZSBkYXRhc2V0IHZpb2xhdGVkIHRoZSB0ZXJtcyBvZiB0aGVpciBwYXJvbGU/CmBgYHtyfQp0YWJsZShwYXJvbGUkdmlvbGF0b3IpCmBgYAo8YnI+CgotIC0gLQoKIyMjIyAyIOaVtOeQhuizh+aWmeahhiBDcmVhdGluZyBPdXIgUHJlZGljdGlvbiBNb2RlbAoK44CQKioyLjEg6aGe5Yil6K6K5pW4KirjgJFXaGljaCB2YXJpYWJsZXMgaW4gdGhpcyBkYXRhc2V0IGFyZSB1bm9yZGVyZWQgZmFjdG9ycyB3aXRoIGF0IGxlYXN0IHRocmVlIGxldmVscz8gCmBgYHtyfQojIyMgKDQpIHN0YXRlCiMjIyAoOCkgY3JpbWUKYGBgCgrjgJAqKjIuMiDos4fmlpnmoYbmkZjopoEqKuOAkUhvdyBkb2VzIHRoZSBvdXRwdXQgb2YgYHN1bW1hcnkoKWAgY2hhbmdlIGZvciBhIGZhY3RvciB2YXJpYWJsZSBhcyBjb21wYXJlZCB0byBhIG51bWVyaWNhbCB2YXJpYWJsZT8gCmBgYHtyfQojIyMgKDEpIFRoZSBvdXRwdXQgYmVjb21lcyBzaW1pbGFyIHRvIHRoYXQgb2YgdGhlIHRhYmxlKCkgZnVuY3Rpb24gYXBwbGllZCB0byB0aGF0IHZhcmlhYmxlCnBhcm9sZSRzdGF0ZSA9IGFzLmZhY3RvcihwYXJvbGUkc3RhdGUpCnBhcm9sZSRjcmltZSA9IGFzLmZhY3RvcihwYXJvbGUkY3JpbWUpCmNsYXNzKHBhcm9sZSRzdGF0ZSkKY2xhc3MocGFyb2xlJGNyaW1lKQpgYGAKPGJyPgoKLSAtIC0KCiMjIyMgMyDos4fmlpnliIblibIgU3BsaXR0aW5nIGludG8gYSBUcmFpbmluZyBhbmQgVGVzdGluZyBTZXQKCuOAkCoqMy4xIOaMh+WumumaqOapn+eoruWtkOOAgeS+neavlOS+i+WIhuWJsuizh+aWmSoq44CRUm91Z2hseSB3aGF0IHByb3BvcnRpb24gb2YgcGFyb2xlZXMgaGF2ZSBiZWVuIGFsbG9jYXRlZCB0byB0aGUgdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0cz8KYGBge3J9CiMjIyAoMSkgNzAlIHRvIHRoZSB0cmFpbmluZyBzZXQsIDMwJSB0byB0aGUgdGVzdGluZyBzZXQKc2V0LnNlZWQoMTQ0KQpsaWJyYXJ5KGNhVG9vbHMpCnNwbGl0ID0gc2FtcGxlLnNwbGl0KHBhcm9sZSR2aW9sYXRvciwgU3BsaXRSYXRpbyA9IDAuNykKdHJhaW4gPSBzdWJzZXQocGFyb2xlLCBzcGxpdCA9PSBUUlVFKQp0ZXN0ID0gc3Vic2V0KHBhcm9sZSwgc3BsaXQgPT0gRkFMU0UpCmBgYAoK44CQKiozLjIg6Zqo5qmf56iu5a2Q55qE5Yqf55SoKirjgJFOb3csIHN1cHBvc2UgeW91IHJlLXJhbiBsaW5lcyBbMV0tWzVdIG9mIFByb2JsZW0gMy4xLiBXaGF0IHdvdWxkIHlvdSBleHBlY3Q/IElmIHlvdSBpbnN0ZWFkIE9OTFkgcmUtcmFuIGxpbmVzIFszXS1bNV0sIHdoYXQgd291bGQgeW91IGV4cGVjdD8gSWYgeW91IGluc3RlYWQgY2FsbGVkIHNldC5zZWVkKCkgd2l0aCBhIGRpZmZlcmVudCBudW1iZXIgYW5kIHRoZW4gcmUtcmFuIGxpbmVzIFszXS1bNV0gb2YgUHJvYmxlbSAzLjEsIHdoYXQgd291bGQgeW91IGV4cGVjdD8KYGBge3J9CiMjIyAoMikgQSBkaWZmZXJlbnQgdHJhaW5pbmcvdGVzdGluZyBzZXQgc3BsaXQgZnJvbSB0aGUgZmlyc3QgZXhlY3V0aW9uIG9mIFsxXS1bNV0KIyMjICgxKSBUaGUgZXhhY3Qgc2FtZSB0cmFpbmluZy90ZXN0aW5nIHNldCBzcGxpdCBhcyB0aGUgZmlyc3QgZXhlY3V0aW9uIG9mIFsxXS1bNV0KIyMjICgyKSBBIGRpZmZlcmVudCB0cmFpbmluZy90ZXN0aW5nIHNldCBzcGxpdCBmcm9tIHRoZSBmaXJzdCBleGVjdXRpb24gb2YgWzFdLVs1XQoKc3BsaXQgPSBzYW1wbGUuc3BsaXQocGFyb2xlJHZpb2xhdG9yLCBTcGxpdFJhdGlvID0gMC43KQp0cmFpbiA9IHN1YnNldChwYXJvbGUsIHNwbGl0ID09IFRSVUUpCnRlc3QgPSBzdWJzZXQocGFyb2xlLCBzcGxpdCA9PSBGQUxTRSkKCmBgYAo8YnI+CgotIC0gLQoKIyMjIyA0IOW7uueri+aooeWeiyBCdWlsZGluZyBhIExvZ2lzdGljIFJlZ3Jlc3Npb24gTW9kZWwKCuOAkCoqNC4xIOmhr+iRl+aApyoq44CRV2hhdCB2YXJpYWJsZXMgYXJlIHNpZ25pZmljYW50IGluIHRoaXMgbW9kZWw/CmBgYHtyfQojIyMgcmFjZS8gc3RhdGU0LyBtdWx0aXBsZS5vZmZlbnNlcwpzZXQuc2VlZCgxNDQpCmxpYnJhcnkoY2FUb29scykKc3BsaXQgPSBzYW1wbGUuc3BsaXQocGFyb2xlJHZpb2xhdG9yLCBTcGxpdFJhdGlvID0gMC43KQp0cmFpbiA9IHN1YnNldChwYXJvbGUsIHNwbGl0ID09IFRSVUUpCnRlc3QgPSBzdWJzZXQocGFyb2xlLCBzcGxpdCA9PSBGQUxTRSkKCnBhcm9sZUxvZyA8LSBnbG0odmlvbGF0b3Igfi4sIGRhdGEgPSBwYXJvbGUsIGZhbWlseSA9ICJiaW5vbWlhbCIpCmBgYAoK44CQKio0LjIg5b6e5Zue5q245L+C5pW45Lyw6KiI6YKK6Zqb5pWI55SoKirjgJFXaGF0IGNhbiB3ZSBzYXkgYmFzZWQgb24gdGhlIGNvZWZmaWNpZW50IG9mIHRoZSBgbXVsdGlwbGUub2ZmZW5zZXNgIHZhcmlhYmxlPwpgYGB7cn0KIyAoNCkgT3VyIG1vZGVsIHByZWRpY3RzIHRoYXQgYSBwYXJvbGVlIHdobyBjb21taXR0ZWQgbXVsdGlwbGUgb2ZmZW5zZXMgaGFzIDUuMDEgdGltZXMgaGlnaGVyIG9kZHMgb2YgYmVpbmcgYSB2aW9sYXRvciB0aGFuIGEgcGFyb2xlZSB3aG8gZGlkIG5vdCBjb21taXQgbXVsdGlwbGUgb2ZmZW5zZXMgYnV0IGlzIG90aGVyd2lzZSBpZGVudGljYWwuCiMjIyBleHAobG4ob2Rkc0EpKSA9IGV4cChsbihvZGRzQikgKyAxLjYxKQojIyMgZXhwKGxuKG9kZHNBKSkgPSBleHAobG4ob2Rkc0IpKSAqIGV4cCgxLjYxKQojIyMgb2Rkc0EgPSBleHAoMS42MSkgKiBvZGRzQgojIyMgb2Rkc0E9IDUuMDEgKiBvZGRzQgpgYGAKCuOAkCoqNC4zIOW+numgkOa4rOWAvOS8sOioiOWLneeOh+WSjOapn+eOhyoq44CRQWNjb3JkaW5nIHRvIHRoZSBtb2RlbCwgd2hhdCBhcmUgdGhlIG9kZHMgdGhpcyBpbmRpdmlkdWFsIGlzIGEgdmlvbGF0b3I/ICBXaGF0IGlzIHRoZSBwcm9iYWJpbGl0eSB0aGlzIGluZGl2aWR1YWwgaXMgYSB2aW9sYXRvcj8KYGBge3J9Cm9kZHMgPC0gZ2xtKHZpb2xhdG9yIH4gbWFsZSArIHJhY2UgKyBhZ2UgKyBzdGF0ZSArIHRpbWUuc2VydmVkICsgbWF4LnNlbnRlbmNlICsgbXVsdGlwbGUub2ZmZW5zZXMgKyBjcmltZSwgZGF0YSA9IHBhcm9sZSwgZmFtaWx5ID0gYmlub21pYWwpCm9kZHMKCmxvZ29kZHMgPC0gLTQuMDUyMzggKyAwLjI3MDYyKm1hbGUgKyAwLjc1NzI1KnJhY2UgLSAwLjAwNjU1KmFnZSArIDAuMjA4NDAqc3RhdGUyICsgMC44OTM4MSpzdGF0ZTMgLSAzLjI4MDg0KnN0YXRlNCAtIDAuMDc2NTUqdGltZS5zZXJ2ZWQgKyAwLjA1MzI5Km1heC5zZW50ZW5jZSArIDEuNTMxNTUqbXVsdGlwbGUub2ZmZW5zZXMgKyAwLjMzNjg5KmNyaW1lMiAtIDAuMjgwOTkqY3JpbWUzIC0gMC4xNTc4MSpjcmltZTQKbWFsZSA8LSAxCnJhY2UgPC0gMQphZ2UgPC0gNTAKc3RhdGUgPC0gMQp0aW1lLnNlcnZlZCA8LSAzCm1heC5zZW50ZW5jZSA8LSAxMgptdWx0aXBsZS5vZmZlbnNlcyA8LSAwCmNyaW1lIDwtIDIKbG9nb2RkcwpvZGRzIDwtIGV4cChsb2dvZGRzKQpvZGRzCnByb2JWaW9sYXRpb24gPC0gMSAvICgxICsgb2RkcykKcHJvYlZpb2xhdGlvbgpgYGAKPGJyPgoKLSAtIC0KCiMjIyMgNSDpqZforYnmqKHlnosgRXZhbHVhdGluZyB0aGUgTW9kZWwgb24gdGhlIFRlc3RpbmcgU2V0CgrjgJAqKjUuMSDlvp7muKzoqabos4fmlpnpoJDmuKzmqZ/njocqKuOAkVdoYXQgaXMgdGhlIG1heGltdW0gcHJlZGljdGVkIHByb2JhYmlsaXR5IG9mIGEgdmlvbGF0aW9uPwpgYGB7cn0KcHJlZGljdGlvbiA8LSBwcmVkaWN0KHBhcm9sZUxvZywgdHlwZSA9ICJyZXNwb25zZSIsIG5ld2RhdGEgPSB0ZXN0KQpzdW1tYXJ5KHByZWRpY3Rpb24pCmBgYAoK44CQKio1LjIg5b6e5re35reG55+p6Zmj6KiI566X5pWP5oSf5oCn44CB5piO56K65oCn44CB5q2j56K6546HKirjgJFXaGF0IGlzIHRoZSBtb2RlbCdzIGBzZW5zaXRpdml0eWAsIGBzcGVjaWZpY2l0eWAsIGBhY2N1cmFjeWA/CmBgYHtyfQp0YWJsZSh0ZXN0JHZpb2xhdG9yLCBwcmVkaWN0aW9uID4gMC41KQpzZW5zaXRpdml0eSA8LSAxMiAvICgxMSArIDEyKQpzcGVjaWZpY2l0eSA8LSAxNjkgLyAoMTY5ICsxMCkKYWNjdXJhY3kgPC0gKDE2OSArIDEyKSAvICgxNjkgKyAxMSArIDEwICsxMikKc2Vuc2l0aXZpdHkKc3BlY2lmaWNpdHkKYWNjdXJhY3kKYGBgCgrjgJAqKjUuMyDlupXnt5rmqZ/njocqKuOAkVdoYXQgaXMgdGhlIGFjY3VyYWN5IG9mIGEgc2ltcGxlIG1vZGVsIHRoYXQgcHJlZGljdHMgdGhhdCBldmVyeSBwYXJvbGVlIGlzIGEgbm9uLXZpb2xhdG9yPwpgYGB7cn0KdGFibGUodGVzdCR2aW9sYXRvcikKYWNjdXJhY3kgPC0gMTc5IC8gKDE3OSArIDIzKQphY2N1cmFjeQpgYGAKCuOAkCoqNS40IOagueaTmuWgseWEn+efqemZo+iqv+aVtOiHqOeVjOapn+eOhyoq44CRV2hpY2ggb2YgdGhlIGZvbGxvd2luZyBtb3N0IGxpa2VseSBkZXNjcmliZXMgdGhlaXIgcHJlZmVyZW5jZXMgYW5kIGJlc3QgY291cnNlIG9mIGFjdGlvbj8KYGBge3J9CiMgKDIpIFRoZSBib2FyZCBhc3NpZ25zIG1vcmUgY29zdCB0byBhIGZhbHNlIG5lZ2F0aXZlIHRoYW4gYSBmYWxzZSBwb3NpdGl2ZSwgYW5kIHNob3VsZCB0aGVyZWZvcmUgdXNlIGEgbG9naXN0aWMgcmVncmVzc2lvbiBjdXRvZmYgbGVzcyB0aGFuIDAuNS4KIyMjIEZO77ya6KKr5Yak5p6J77yM6KaB55uh6YeP6YG/5YWN5q2k5LiA5oOF5rOB55m855Sf77yM5pWF6Ieo55WM5YC85b+F6aCI6Kq/5L2O77yM5Lul6ZmN5L2O5Z6L5LqM6Yyv6Kqk55m855Sf546HCgpwcmVkIDwtIHByZWRpY3QocGFyb2xlTG9nLCB0eXBlID0gInJlc3BvbnNlIiwgbmV3ZGF0YSA9IHRlc3QpIApzdW1tYXJ5KHByZWQpCmBgYAoK44CQKio1LjUg5q2j56K6546HIHZzIOi+qOitmOeOhyoq44CRV2hpY2ggb2YgdGhlIGZvbGxvd2luZyBpcyB0aGUgbW9zdCBhY2N1cmF0ZSBhc3Nlc3NtZW50IG9mIHRoZSB2YWx1ZSBvZiB0aGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB3aXRoIGEgY3V0b2ZmIDAuNSB0byBhIHBhcm9sZSBib2FyZCwgYmFzZWQgb24gdGhlIG1vZGVsJ3MgYWNjdXJhY3kgYXMgY29tcGFyZWQgdG8gdGhlIHNpbXBsZSBiYXNlbGluZSBtb2RlbD8KYGBge3J9CiMgKDQpIFRoZSBtb2RlbCBpcyBsaWtlbHkgb2YgdmFsdWUgdG8gdGhlIGJvYXJkLCBhbmQgdXNpbmcgYSBkaWZmZXJlbnQgbG9naXN0aWMgcmVncmVzc2lvbiBjdXRvZmYgaXMgbGlrZWx5IHRvIGltcHJvdmUgdGhlIG1vZGVsJ3MgdmFsdWUuCiMjIyBiYXNlbGluZSDlkowg5paw5bu65Ye65L6G55qEIG1vZGVsIOmDveWFt+acieino+mHi+aViOWKm++8jOeEtuiAjOagueaTmnRlc3RpbmcgZGF0YSDmiYDlu7rnva7lh7rkvobnmoTmlrDmqKHlnovop6Pph4vmlYjlipvmm7TkvbMgCgpgYGAKCuOAkCoqNS42IOioiOeul+i+qOitmOeOhyoq44CRVXNpbmcgdGhlIGBST0NSYCBwYWNrYWdlLCB3aGF0IGlzIHRoZSBBVUMgdmFsdWUgZm9yIHRoZSBtb2RlbD8KYGBge3J9CmxpYnJhcnkoUk9DUikKUk9DUnByZWQgPC0gcHJlZGljdGlvbihwcmVkaWN0aW9uLCB0ZXN0JHZpb2xhdG9yKQphcy5udW1lcmljKHBlcmZvcm1hbmNlKFJPQ1JwcmVkLCAiYXVjIilAeS52YWx1ZXMpCmBgYAoK44CQKio1Ljcg6L6o6K2Y546H55qE5a6a576pKirjgJFEZXNjcmliZSB0aGUgbWVhbmluZyBvZiBBVUMgaW4gdGhpcyBjb250ZXh0LgpgYGB7cn0KIyAoMSkgVGhlIHByb2JhYmlsaXR5IHRoZSBtb2RlbCBjYW4gY29ycmVjdGx5IGRpZmZlcmVudGlhdGUgYmV0d2VlbiBhIHJhbmRvbWx5IHNlbGVjdGVkIHBhcm9sZSB2aW9sYXRvciBhbmQgYSByYW5kb21seSBzZWxlY3RlZCBwYXJvbGUgbm9uLXZpb2xhdG9yLgoKIyBBVUMoQXJlYSBVbmRlciBDdXJ2ZSwg6L6o6K2Y5YqbKQpgYGAKPGJyPgoKLSAtIC0KCiMjIyMgNiDmir3mqKPlgY/lt64gSWRlbnRpZnlpbmcgQmlhcyBpbiBPYnNlcnZhdGlvbmFsIERhdGEKCuOAkCoqNi4xIOWmguS9lemBv+WFjeOAgeiouuaWt+OAgeS/ruato+aKveaoo+WBj+W3rioq44CRSG93IGNvdWxkIHdlIGltcHJvdmUgb3VyIGRhdGFzZXQgdG8gYmVzdCBhZGRyZXNzIHNlbGVjdGlvbiBiaWFzPwpgYGB7cn0KIyAoNCkgV2Ugc2hvdWxkIHVzZSBhIGRhdGFzZXQgdHJhY2tpbmcgYSBncm91cCBvZiBwYXJvbGVlcyBmcm9tIHRoZSBzdGFydCBvZiB0aGVpciBwYXJvbGUgdW50aWwgZWl0aGVyIHRoZXkgdmlvbGF0ZWQgcGFyb2xlIG9yIHRoZXkgY29tcGxldGVkIHRoZWlyIHRlcm0uCmBgYAo8YnI+CgotIC0gLQoKPGJyPjxicj48YnI+Cg==