This notebook has examples of multiple One-Class Classification for outlier detection. All algorithms are tested with Spambase dataset and each algorithm is tested with an additional dataset.

Outlier Detection

  • Outlier samples → sparse, minoritary, category
  • Outlier scenario → multi-class scenario + more than one class
  • One-class classifification → single class
  • Single class → model its boundary + isolate the “rest”
  • Non-single-class samples → non-modeled
# Package with benchmark datasets
library(mlbench)
# Package with IsolationForest implementation
library(solitude)
# Package with SVM implementation
library(e1071)
# Package with LOF implementation
library(DDoutlier)
# Package with autoencoder implementation
library(h2o)

1 Isolation Forest

  • Compute “isolation score” per sample
  • Construct a tree per sample
  • Random splits on attribute values
  • → isolates the sample from the rest
  • → “outliers easy to isolate…”
  • Path length from root to node
  • ~ “isolation score” = “outlierness”
  • “low path length” ~ “high outlierness”
  • → easy to isolate point
  • → graph “outlierness” values → threshold

Isolation Forest

1.1 Spambase

  • Choose a supervised dataset (e.g. spambase)
  • Choose one of its classes (e.g. “non-spam e-mails”)
  • Apply the one-class method over it
  • Be careful, methods may only work with numerical features. Remove the class!
  • Graph “outlierness” distribution → cut-off point to decide outliers
  • Are there suspicious outliers within this class e-mails?

Spam email

Non spam email

spambase <- read.csv(file = "../data/spambase.csv", header = TRUE, sep = ",")
spambase$class <- as.factor(spambase$class)
spambaseNONSPAM <- spambase[spambase$class == 0, ]
spambaseNONSPAM <- spambaseNONSPAM[, -58]
print(spambaseNONSPAM)
# Empty tree structure
iso <- isolationForest$new()

# Learn the IsolationForest for our data
iso$fit(spambaseNONSPAM)
INFO  [22:09:01.654] dataset has duplicated rows 
INFO  [22:09:01.762] Building Isolation Forest ...  
INFO  [22:09:03.892] done 
INFO  [22:09:03.924] Computing depth of terminal nodes ...  
INFO  [22:09:05.164] done 
INFO  [22:09:05.393] Completed growing isolation forest 
p <- iso$predict(spambaseNONSPAM)
print(p)
# sort(p$anomaly_score)
plot(density(p$anomaly_score), main = "Anomaly Score Density")


# Based on the plot, decide the cut-off point (e.g > 0.63)
which(p$anomaly_score > 0.63)
[1]  152  762  818  884 1105 1162 1433 1460 1551

1.2 BostonHousing

# Census data for 506 Boston houses
data("BostonHousing", package = "mlbench")
print(BostonHousing)
# Empty tree structure
iso <- isolationForest$new()

# Learn the IsolationForest for our data
iso$fit(BostonHousing)
INFO  [22:09:07.862] Building Isolation Forest ...  
INFO  [22:09:08.038] done 
INFO  [22:09:08.067] Computing depth of terminal nodes ...  
INFO  [22:09:08.706] done 
INFO  [22:09:08.764] Completed growing isolation forest 
p <- iso$predict(BostonHousing)
print(p)
# sort(p$anomaly_score)
plot(density(p$anomaly_score), main = "Anomaly Score Density")


# Based on the plot, decide the cut-off point (e.g > 0.63)
which(p$anomaly_score > 0.63)
 [1] 164 198 205 254 255 256 283 284 287 354 355 356 373 491

2 OneClass SVM (OCSVM)

  • Learn a SVM with single-class samples
  • Map to higher dimension space
  • Separating hyperplane
  • Maximize margin between origin and data
  • Outliers → points outside boundary

OneClass SVMs (OCSVM)

2.1 Spambase

# train a SVM one-classification model
model <- svm(spambaseNONSPAM, y = NULL, type = "one-classification")
summary(model)

Call:
svm.default(x = spambaseNONSPAM, y = NULL, type = "one-classification")


Parameters:
   SVM-Type:  one-classification 
 SVM-Kernel:  radial 
      gamma:  0.01754386 
         nu:  0.5 

Number of Support Vectors:  1395




Number of Classes: 1
# CAUTION: testing on the same training set
# TRUE values mean suspect outliers
pred <- predict(model, spambaseNONSPAM)
# which(pred == TRUE)
table(pred)
pred
FALSE  TRUE 
 1413  1375 

2.2 Airquality

# Daily air quality measurements in New York, May to September 1973
data(airquality)
print(airquality)
# Daily air quality measurements in New York, May to September 1973
data(airquality)

# train a SVM one-classification model
model <- svm(airquality, y = NULL, type = "one-classification")
summary(model)

Call:
svm.default(x = airquality, y = NULL, type = "one-classification")


Parameters:
   SVM-Type:  one-classification 
 SVM-Kernel:  radial 
      gamma:  0.1666667 
         nu:  0.5 

Number of Support Vectors:  58




Number of Classes: 1
# CAUTION: testing on the same training set
# TRUE values mean suspect outliers
pred <- predict(model, airquality)
# which(pred == TRUE)
table(pred)
pred
FALSE  TRUE 
   51    60 

3 Local Outlier Factor (LOF)

  • Distance-based algorithm
  • To decide “outlier”
  • → by local neighborhood
  • → by local density
  • Parameter → k, number of neighbours
  • Calculate the neighborhood
  • Outlier → defifined “locally”
  • Outlierness → compute density of its local k-neighborhood

Local Outlier Factor (LOF)

3.1 Spambase

# calculate "outlierness" score, by LOF
outlierness <- LOF(dataset = spambaseNONSPAM, k = 5)

# assign an index to outlierness values
names(outlierness) <- seq_len(nrow(spambaseNONSPAM))
# sort(outlierness, decreasing = TRUE)
hist(outlierness)

which(outlierness > 20.0)
  88  152  277  313  410  652  724  753  780  784  801  857  980 1217 1237 
  88  152  277  313  410  652  724  753  780  784  801  857  980 1217 1237 
1387 1416 1426 1434 1591 1726 1790 1794 1799 1827 1832 1864 1867 1994 2022 
1387 1416 1426 1434 1591 1726 1790 1794 1799 1827 1832 1864 1867 1994 2022 
2025 2100 2492 2581 2583 2721 2725 2734 
2025 2100 2492 2581 2583 2721 2725 2734 

3.2 EuStockMarkets

# 1860 daily Closing Prices of Major European Stock Indices
data("EuStockMarkets")
EuStockMarkets[sample(nrow(EuStockMarkets), 10), ]
          DAX    SMI    CAC   FTSE
 [1,] 1729.96 1840.5 1963.3 2591.0
 [2,] 1617.78 1761.9 1755.4 2348.0
 [3,] 2182.47 3150.1 1852.6 3541.6
 [4,] 1437.65 1878.4 1657.3 2541.2
 [5,] 2449.09 3309.9 1974.5 3715.9
 [6,] 3018.58 4209.1 2503.1 4228.4
 [7,] 2764.00 3829.8 2229.1 3934.3
 [8,] 5870.49 7816.9 4215.7 5877.4
 [9,] 5774.38 8139.2 4095.0 5809.7
[10,] 1617.18 2247.5 1890.4 2846.9
# calculate "outlierness" score, by LOF
outlierness <- LOF(dataset = EuStockMarkets, k = 5)

# assign an index to outlierness values
names(outlierness) <- seq_len(nrow(EuStockMarkets))
# sort(outlierness, decreasing = TRUE)
hist(outlierness)

which(outlierness > 2.0)
 36  37 331 332 
 36  37 331 332 

4 Autoencoder

  • Learn representation of data
  • Reducing to non-linear dimensions in hidden layers
  • {Encode + Decode} 1-class data
  • Check for anomalies
  • Does the autoencoder “reconstruct” the input data in the output?
  • → “reconstruction error”
  • → high value indicative of outlierness
  • Hidden layers’ features
  • Compact, non-linear representation
  • → learn with them a supervised model?

Autoencoder

4.1 Spambase

h2o.init(port = 50001)
 Connection successful!

R is connected to the H2O cluster: 
    H2O cluster uptime:         1 hours 48 minutes 
    H2O cluster timezone:       Europe/Madrid 
    H2O data parsing timezone:  UTC 
    H2O cluster version:        3.34.0.3 
    H2O cluster version age:    1 month and 26 days  
    H2O cluster name:           H2O_started_from_R_julet_nys902 
    H2O cluster total nodes:    1 
    H2O cluster total memory:   1.46 GB 
    H2O cluster total cores:    8 
    H2O cluster allowed cores:  8 
    H2O cluster healthy:        TRUE 
    H2O Connection ip:          localhost 
    H2O Connection port:        50001 
    H2O Connection proxy:       NA 
    H2O Internal Security:      FALSE 
    H2O API Extensions:         Amazon S3, Algos, AutoML, Core V3, TargetEncoder, Core V4 
    R Version:                  R version 4.1.2 (2021-11-01) 
spambase <- h2o.importFile(path = "../data/spambase.csv")

  |                                                                          
  |                                                                    |   0%
  |                                                                          
  |=======================                                             |  34%
  |                                                                          
  |====================================================================| 100%
spambaseNONSPAM <- spambase[spambase$C58 == 0, ]
spambaseNONSPAM <- spambaseNONSPAM[, -58]
# learn autoencoder with 2 hidden layers of 10 units each
autoencoder_model <- h2o.deeplearning(
    x = 1:57,
    training_frame = spambaseNONSPAM,
    autoencoder = TRUE,
    hidden = c(10, 10),
    epochs = 5
)

  |                                                                          
  |                                                                    |   0%
  |                                                                          
  |====================================================================| 100%
# features in the autoencoder's first hidden layer
deep_features_layer1 <- h2o.deepfeatures(autoencoder_model, spambaseNONSPAM, layer = 1)

  |                                                                          
  |                                                                    |   0%
  |                                                                          
  |====================================================================| 100%
# further supervised models can be trained with these features
head(deep_features_layer1)

# reconstruction error per sample ~ outlierness indicative
reconstruction_error <- h2o.anomaly(autoencoder_model, spambaseNONSPAM)
head(reconstruction_error)
reconstruction_error <- as.data.frame(reconstruction_error)
plot(sort(reconstruction_error$Reconstruction.MSE), main = "Reconstruction Error")

which(reconstruction_error > 0.02)
 [1]   66  110  152  202  241  458  466  515  671  673  742  762  768  802
[15]  818  861  884  910  990 1005 1036 1103 1139 1141 1162 1163 1193 1220
[29] 1366 1413 1433 1434 1438 1460 1491 1500 1551 1596 1611 1625 1662 1678
[43] 1719 1723 1749 1750 1755 1953 1976 2010 2043 2067 2094 2100 2207 2208
[57] 2227 2453 2454 2455 2456 2457 2461 2463 2490 2496 2497 2500 2507 2566
[71] 2651

4.2 Prostate

h2o.init(port = 50001)
 Connection successful!

R is connected to the H2O cluster: 
    H2O cluster uptime:         1 hours 49 minutes 
    H2O cluster timezone:       Europe/Madrid 
    H2O data parsing timezone:  UTC 
    H2O cluster version:        3.34.0.3 
    H2O cluster version age:    1 month and 26 days  
    H2O cluster name:           H2O_started_from_R_julet_nys902 
    H2O cluster total nodes:    1 
    H2O cluster total memory:   1.46 GB 
    H2O cluster total cores:    8 
    H2O cluster allowed cores:  8 
    H2O cluster healthy:        TRUE 
    H2O Connection ip:          localhost 
    H2O Connection port:        50001 
    H2O Connection proxy:       NA 
    H2O Internal Security:      FALSE 
    H2O API Extensions:         Amazon S3, Algos, AutoML, Core V3, TargetEncoder, Core V4 
    R Version:                  R version 4.1.2 (2021-11-01) 
prostate_path <- system.file("extdata", "prostate.csv", package = "h2o")
prostate <- h2o.importFile(path = prostate_path)

  |                                                                          
  |                                                                    |   0%
  |                                                                          
  |====================================================================| 100%
print(prostate)

[380 rows x 9 columns] 
# learn autoencoder with 2 hidden layers of 10 units each
autoencoder_model <- h2o.deeplearning(
    x = 3:9,
    training_frame = prostate,
    autoencoder = TRUE,
    hidden = c(10, 10),
    epochs = 5
)

  |                                                                          
  |                                                                    |   0%
  |                                                                          
  |====================================================================| 100%
# features in the autoencoder's first hidden layer
deep_features_layer1 <- h2o.deepfeatures(autoencoder_model, prostate, layer = 1)

  |                                                                          
  |                                                                    |   0%
  |                                                                          
  |====================================================================| 100%
# further supervised models can be trained with these features
head(deep_features_layer1)

# reconstruction error per sample ~ outlierness indicative
reconstruction_error <- h2o.anomaly(autoencoder_model, prostate)
head(reconstruction_error)
reconstruction_error <- as.data.frame(reconstruction_error)
plot(sort(reconstruction_error$Reconstruction.MSE), main = "Reconstruction Error")

which(reconstruction_error > 0.15)
 [1]  10  19  20  38  46  52  88 115 139 297 303
LS0tDQp0aXRsZTogIk91dGxpZXIgRGV0ZWN0aW9uIC0gT25lIGNsYXNzIENsYXNzaWZpY2F0aW9uIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOiANCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCi0tLQ0KDQpUaGlzIG5vdGVib29rIGhhcyBleGFtcGxlcyBvZiBtdWx0aXBsZSBPbmUtQ2xhc3MgQ2xhc3NpZmljYXRpb24gZm9yIG91dGxpZXIgZGV0ZWN0aW9uLiBBbGwgYWxnb3JpdGhtcyBhcmUgdGVzdGVkIHdpdGggU3BhbWJhc2UgZGF0YXNldCBhbmQgZWFjaCBhbGdvcml0aG0gaXMgdGVzdGVkIHdpdGggYW4gYWRkaXRpb25hbCBkYXRhc2V0Lg0KDQohW091dGxpZXIgRGV0ZWN0aW9uXSguLi9pbWFnZXMvb3V0bGllcl9kZXRlY3Rpb24ucG5nKQ0KDQoqIE91dGxpZXIgc2FtcGxlcyDihpIgc3BhcnNlLCBtaW5vcml0YXJ5LCBjYXRlZ29yeQ0KKiBPdXRsaWVyIHNjZW5hcmlvIOKGkiBtdWx0aS1jbGFzcyBzY2VuYXJpbyArIG1vcmUgdGhhbiBvbmUgY2xhc3MNCiogT25lLWNsYXNzIGNsYXNzaWZpZmljYXRpb24g4oaSIHNpbmdsZSBjbGFzcw0KKiBTaW5nbGUgY2xhc3Mg4oaSIG1vZGVsIGl0cyBib3VuZGFyeSArIGlzb2xhdGUgdGhlIOKAnHJlc3TigJ0NCiogTm9uLXNpbmdsZS1jbGFzcyBzYW1wbGVzIOKGkiBub24tbW9kZWxlZA0KDQpgYGB7cn0NCiMgUGFja2FnZSB3aXRoIGJlbmNobWFyayBkYXRhc2V0cw0KbGlicmFyeShtbGJlbmNoKQ0KIyBQYWNrYWdlIHdpdGggSXNvbGF0aW9uRm9yZXN0IGltcGxlbWVudGF0aW9uDQpsaWJyYXJ5KHNvbGl0dWRlKQ0KIyBQYWNrYWdlIHdpdGggU1ZNIGltcGxlbWVudGF0aW9uDQpsaWJyYXJ5KGUxMDcxKQ0KIyBQYWNrYWdlIHdpdGggTE9GIGltcGxlbWVudGF0aW9uDQpsaWJyYXJ5KEREb3V0bGllcikNCiMgUGFja2FnZSB3aXRoIGF1dG9lbmNvZGVyIGltcGxlbWVudGF0aW9uDQpsaWJyYXJ5KGgybykNCmBgYA0KDQoNCiMgSXNvbGF0aW9uIEZvcmVzdA0KDQoqIENvbXB1dGUg4oCcaXNvbGF0aW9uIHNjb3Jl4oCdIHBlciBzYW1wbGUNCiogQ29uc3RydWN0IGEgdHJlZSBwZXIgc2FtcGxlDQoqIFJhbmRvbSBzcGxpdHMgb24gYXR0cmlidXRlIHZhbHVlcw0KKiDihpIgaXNvbGF0ZXMgdGhlIHNhbXBsZSBmcm9tIHRoZSByZXN0DQoqIOKGkiDigJxvdXRsaWVycyBlYXN5IHRvIGlzb2xhdGUuLi7igJ0NCiogUGF0aCBsZW5ndGggZnJvbSByb290IHRvIG5vZGUNCiogfiDigJxpc29sYXRpb24gc2NvcmXigJ0gPSDigJxvdXRsaWVybmVzc+KAnQ0KKiDigJxsb3cgcGF0aCBsZW5ndGjigJ0gfiDigJxoaWdoIG91dGxpZXJuZXNz4oCdDQoqIOKGkiBlYXN5IHRvIGlzb2xhdGUgcG9pbnQNCiog4oaSIGdyYXBoIOKAnG91dGxpZXJuZXNz4oCdIHZhbHVlcyDihpIgdGhyZXNob2xkDQoNCiFbSXNvbGF0aW9uIEZvcmVzdF0oLi4vaW1hZ2VzL2lzb2xhdGlvbl9mb3Jlc3QucG5nKQ0KDQojIyBTcGFtYmFzZQ0KDQoqIENob29zZSBhIHN1cGVydmlzZWQgZGF0YXNldCAoZS5nLiBzcGFtYmFzZSkNCiogQ2hvb3NlIG9uZSBvZiBpdHMgY2xhc3NlcyAoZS5nLiDigJxub24tc3BhbSBlLW1haWxz4oCdKQ0KKiBBcHBseSB0aGUgb25lLWNsYXNzIG1ldGhvZCBvdmVyIGl0DQoqIEJlIGNhcmVmdWwsIG1ldGhvZHMgbWF5IG9ubHkgd29yayB3aXRoIG51bWVyaWNhbCBmZWF0dXJlcy4gUmVtb3ZlIHRoZSBjbGFzcyENCiogR3JhcGgg4oCcb3V0bGllcm5lc3PigJ0gZGlzdHJpYnV0aW9uIOKGkiBjdXQtb2ZmIHBvaW50IHRvIGRlY2lkZSBvdXRsaWVycw0KKiBBcmUgdGhlcmUgc3VzcGljaW91cyBvdXRsaWVycyB3aXRoaW4gdGhpcyBjbGFzcyBlLW1haWxzPw0KDQohW1NwYW0gZW1haWxdKC4uL2ltYWdlcy9zcGFtLnBuZykNCg0KIVtOb24gc3BhbSBlbWFpbF0oLi4vaW1hZ2VzL25vbnNwYW0ucG5nKQ0KDQpgYGB7cn0NCnNwYW1iYXNlIDwtIHJlYWQuY3N2KGZpbGUgPSAiLi4vZGF0YS9zcGFtYmFzZS5jc3YiLCBoZWFkZXIgPSBUUlVFLCBzZXAgPSAiLCIpDQpzcGFtYmFzZSRjbGFzcyA8LSBhcy5mYWN0b3Ioc3BhbWJhc2UkY2xhc3MpDQpzcGFtYmFzZU5PTlNQQU0gPC0gc3BhbWJhc2Vbc3BhbWJhc2UkY2xhc3MgPT0gMCwgXQ0Kc3BhbWJhc2VOT05TUEFNIDwtIHNwYW1iYXNlTk9OU1BBTVssIC01OF0NCnByaW50KHNwYW1iYXNlTk9OU1BBTSkNCmBgYA0KDQpgYGB7cn0NCiMgRW1wdHkgdHJlZSBzdHJ1Y3R1cmUNCmlzbyA8LSBpc29sYXRpb25Gb3Jlc3QkbmV3KCkNCg0KIyBMZWFybiB0aGUgSXNvbGF0aW9uRm9yZXN0IGZvciBvdXIgZGF0YQ0KaXNvJGZpdChzcGFtYmFzZU5PTlNQQU0pDQpwIDwtIGlzbyRwcmVkaWN0KHNwYW1iYXNlTk9OU1BBTSkNCnByaW50KHApDQojIHNvcnQocCRhbm9tYWx5X3Njb3JlKQ0KcGxvdChkZW5zaXR5KHAkYW5vbWFseV9zY29yZSksIG1haW4gPSAiQW5vbWFseSBTY29yZSBEZW5zaXR5IikNCg0KIyBCYXNlZCBvbiB0aGUgcGxvdCwgZGVjaWRlIHRoZSBjdXQtb2ZmIHBvaW50IChlLmcgPiAwLjYzKQ0Kd2hpY2gocCRhbm9tYWx5X3Njb3JlID4gMC42MykNCmBgYA0KDQojIyBCb3N0b25Ib3VzaW5nDQoNCmBgYHtyfQ0KIyBDZW5zdXMgZGF0YSBmb3IgNTA2IEJvc3RvbiBob3VzZXMNCmRhdGEoIkJvc3RvbkhvdXNpbmciLCBwYWNrYWdlID0gIm1sYmVuY2giKQ0KcHJpbnQoQm9zdG9uSG91c2luZykNCmBgYA0KDQpgYGB7cn0NCiMgRW1wdHkgdHJlZSBzdHJ1Y3R1cmUNCmlzbyA8LSBpc29sYXRpb25Gb3Jlc3QkbmV3KCkNCg0KIyBMZWFybiB0aGUgSXNvbGF0aW9uRm9yZXN0IGZvciBvdXIgZGF0YQ0KaXNvJGZpdChCb3N0b25Ib3VzaW5nKQ0KcCA8LSBpc28kcHJlZGljdChCb3N0b25Ib3VzaW5nKQ0KcHJpbnQocCkNCiMgc29ydChwJGFub21hbHlfc2NvcmUpDQpwbG90KGRlbnNpdHkocCRhbm9tYWx5X3Njb3JlKSwgbWFpbiA9ICJBbm9tYWx5IFNjb3JlIERlbnNpdHkiKQ0KDQojIEJhc2VkIG9uIHRoZSBwbG90LCBkZWNpZGUgdGhlIGN1dC1vZmYgcG9pbnQgKGUuZyA+IDAuNjMpDQp3aGljaChwJGFub21hbHlfc2NvcmUgPiAwLjYzKQ0KYGBgDQoNCiMgT25lQ2xhc3MgU1ZNIChPQ1NWTSkNCg0KKiBMZWFybiBhIFNWTSB3aXRoIHNpbmdsZS1jbGFzcyBzYW1wbGVzDQoqIE1hcCB0byBoaWdoZXIgZGltZW5zaW9uIHNwYWNlDQoqIFNlcGFyYXRpbmcgaHlwZXJwbGFuZQ0KKiBNYXhpbWl6ZSBtYXJnaW4gYmV0d2VlbiBvcmlnaW4gYW5kIGRhdGENCiogT3V0bGllcnMg4oaSIHBvaW50cyBvdXRzaWRlIGJvdW5kYXJ5DQoNCiFbT25lQ2xhc3MgU1ZNcyAoT0NTVk0pXSguLi9pbWFnZXMvb25lY2xhc3Nfc3ZtLnBuZykNCg0KIyMgU3BhbWJhc2UNCg0KYGBge3J9DQojIHRyYWluIGEgU1ZNIG9uZS1jbGFzc2lmaWNhdGlvbiBtb2RlbA0KbW9kZWwgPC0gc3ZtKHNwYW1iYXNlTk9OU1BBTSwgeSA9IE5VTEwsIHR5cGUgPSAib25lLWNsYXNzaWZpY2F0aW9uIikNCnN1bW1hcnkobW9kZWwpDQoNCiMgQ0FVVElPTjogdGVzdGluZyBvbiB0aGUgc2FtZSB0cmFpbmluZyBzZXQNCiMgVFJVRSB2YWx1ZXMgbWVhbiBzdXNwZWN0IG91dGxpZXJzDQpwcmVkIDwtIHByZWRpY3QobW9kZWwsIHNwYW1iYXNlTk9OU1BBTSkNCiMgd2hpY2gocHJlZCA9PSBUUlVFKQ0KdGFibGUocHJlZCkNCmBgYA0KDQojIyBBaXJxdWFsaXR5DQoNCmBgYHtyfQ0KIyBEYWlseSBhaXIgcXVhbGl0eSBtZWFzdXJlbWVudHMgaW4gTmV3IFlvcmssIE1heSB0byBTZXB0ZW1iZXIgMTk3Mw0KZGF0YShhaXJxdWFsaXR5KQ0KcHJpbnQoYWlycXVhbGl0eSkNCmBgYA0KDQpgYGB7cn0NCiMgRGFpbHkgYWlyIHF1YWxpdHkgbWVhc3VyZW1lbnRzIGluIE5ldyBZb3JrLCBNYXkgdG8gU2VwdGVtYmVyIDE5NzMNCmRhdGEoYWlycXVhbGl0eSkNCg0KIyB0cmFpbiBhIFNWTSBvbmUtY2xhc3NpZmljYXRpb24gbW9kZWwNCm1vZGVsIDwtIHN2bShhaXJxdWFsaXR5LCB5ID0gTlVMTCwgdHlwZSA9ICJvbmUtY2xhc3NpZmljYXRpb24iKQ0Kc3VtbWFyeShtb2RlbCkNCg0KIyBDQVVUSU9OOiB0ZXN0aW5nIG9uIHRoZSBzYW1lIHRyYWluaW5nIHNldA0KIyBUUlVFIHZhbHVlcyBtZWFuIHN1c3BlY3Qgb3V0bGllcnMNCnByZWQgPC0gcHJlZGljdChtb2RlbCwgYWlycXVhbGl0eSkNCiMgd2hpY2gocHJlZCA9PSBUUlVFKQ0KdGFibGUocHJlZCkNCmBgYA0KDQojIExvY2FsIE91dGxpZXIgRmFjdG9yIChMT0YpDQoNCiogRGlzdGFuY2UtYmFzZWQgYWxnb3JpdGhtDQoqIFRvIGRlY2lkZSDigJxvdXRsaWVy4oCdDQoqIOKGkiBieSBsb2NhbCBuZWlnaGJvcmhvb2QNCiog4oaSIGJ5IGxvY2FsIGRlbnNpdHkNCiogUGFyYW1ldGVyIOKGkiBrLCBudW1iZXIgb2YgbmVpZ2hib3Vycw0KKiBDYWxjdWxhdGUgdGhlIG5laWdoYm9yaG9vZA0KKiBPdXRsaWVyIOKGkiBkZWZpZmluZWQg4oCcbG9jYWxseeKAnQ0KKiBPdXRsaWVybmVzcyDihpIgY29tcHV0ZSBkZW5zaXR5IG9mIGl0cyBsb2NhbCBrLW5laWdoYm9yaG9vZA0KDQohW0xvY2FsIE91dGxpZXIgRmFjdG9yIChMT0YpXSguLi9pbWFnZXMvbG9jYWxfb3V0bGllcl9mYWN0b3IucG5nKQ0KDQojIyBTcGFtYmFzZQ0KDQpgYGB7cn0NCiMgY2FsY3VsYXRlICJvdXRsaWVybmVzcyIgc2NvcmUsIGJ5IExPRg0Kb3V0bGllcm5lc3MgPC0gTE9GKGRhdGFzZXQgPSBzcGFtYmFzZU5PTlNQQU0sIGsgPSA1KQ0KDQojIGFzc2lnbiBhbiBpbmRleCB0byBvdXRsaWVybmVzcyB2YWx1ZXMNCm5hbWVzKG91dGxpZXJuZXNzKSA8LSBzZXFfbGVuKG5yb3coc3BhbWJhc2VOT05TUEFNKSkNCiMgc29ydChvdXRsaWVybmVzcywgZGVjcmVhc2luZyA9IFRSVUUpDQpoaXN0KG91dGxpZXJuZXNzKQ0Kd2hpY2gob3V0bGllcm5lc3MgPiAyMC4wKQ0KYGBgDQoNCiMjIEV1U3RvY2tNYXJrZXRzDQoNCmBgYHtyfQ0KIyAxODYwIGRhaWx5IENsb3NpbmcgUHJpY2VzIG9mIE1ham9yIEV1cm9wZWFuIFN0b2NrIEluZGljZXMNCmRhdGEoIkV1U3RvY2tNYXJrZXRzIikNCkV1U3RvY2tNYXJrZXRzW3NhbXBsZShucm93KEV1U3RvY2tNYXJrZXRzKSwgMTApLCBdDQpgYGANCg0KYGBge3J9DQojIGNhbGN1bGF0ZSAib3V0bGllcm5lc3MiIHNjb3JlLCBieSBMT0YNCm91dGxpZXJuZXNzIDwtIExPRihkYXRhc2V0ID0gRXVTdG9ja01hcmtldHMsIGsgPSA1KQ0KDQojIGFzc2lnbiBhbiBpbmRleCB0byBvdXRsaWVybmVzcyB2YWx1ZXMNCm5hbWVzKG91dGxpZXJuZXNzKSA8LSBzZXFfbGVuKG5yb3coRXVTdG9ja01hcmtldHMpKQ0KIyBzb3J0KG91dGxpZXJuZXNzLCBkZWNyZWFzaW5nID0gVFJVRSkNCmhpc3Qob3V0bGllcm5lc3MpDQp3aGljaChvdXRsaWVybmVzcyA+IDIuMCkNCmBgYA0KDQojIEF1dG9lbmNvZGVyDQoNCiogTGVhcm4gcmVwcmVzZW50YXRpb24gb2YgZGF0YQ0KKiBSZWR1Y2luZyB0byBub24tbGluZWFyIGRpbWVuc2lvbnMgaW4gaGlkZGVuIGxheWVycw0KKiB7RW5jb2RlICsgRGVjb2RlfSAxLWNsYXNzIGRhdGENCiogQ2hlY2sgZm9yIGFub21hbGllcw0KKiBEb2VzIHRoZSBhdXRvZW5jb2RlciDigJxyZWNvbnN0cnVjdOKAnSB0aGUgaW5wdXQgZGF0YSBpbiB0aGUgb3V0cHV0Pw0KKiDihpIg4oCccmVjb25zdHJ1Y3Rpb24gZXJyb3LigJ0NCiog4oaSIGhpZ2ggdmFsdWUgaW5kaWNhdGl2ZSBvZiBvdXRsaWVybmVzcw0KKiBIaWRkZW4gbGF5ZXJzJyBmZWF0dXJlcw0KKiBDb21wYWN0LCBub24tbGluZWFyIHJlcHJlc2VudGF0aW9uDQoqIOKGkiBsZWFybiB3aXRoIHRoZW0gYSBzdXBlcnZpc2VkIG1vZGVsPw0KDQohW0F1dG9lbmNvZGVyXSguLi9pbWFnZXMvYXV0b2VuY29kZXIucG5nKQ0KDQojIyBTcGFtYmFzZQ0KDQpgYGB7cn0NCmgyby5pbml0KHBvcnQgPSA1MDAwMSkNCnNwYW1iYXNlIDwtIGgyby5pbXBvcnRGaWxlKHBhdGggPSAiLi4vZGF0YS9zcGFtYmFzZS5jc3YiKQ0Kc3BhbWJhc2VOT05TUEFNIDwtIHNwYW1iYXNlW3NwYW1iYXNlJEM1OCA9PSAwLCBdDQpzcGFtYmFzZU5PTlNQQU0gPC0gc3BhbWJhc2VOT05TUEFNWywgLTU4XQ0KYGBgDQoNCmBgYHtyfQ0KIyBsZWFybiBhdXRvZW5jb2RlciB3aXRoIDIgaGlkZGVuIGxheWVycyBvZiAxMCB1bml0cyBlYWNoDQphdXRvZW5jb2Rlcl9tb2RlbCA8LSBoMm8uZGVlcGxlYXJuaW5nKA0KICAgIHggPSAxOjU3LA0KICAgIHRyYWluaW5nX2ZyYW1lID0gc3BhbWJhc2VOT05TUEFNLA0KICAgIGF1dG9lbmNvZGVyID0gVFJVRSwNCiAgICBoaWRkZW4gPSBjKDEwLCAxMCksDQogICAgZXBvY2hzID0gNQ0KKQ0KDQojIGZlYXR1cmVzIGluIHRoZSBhdXRvZW5jb2RlcidzIGZpcnN0IGhpZGRlbiBsYXllcg0KZGVlcF9mZWF0dXJlc19sYXllcjEgPC0gaDJvLmRlZXBmZWF0dXJlcyhhdXRvZW5jb2Rlcl9tb2RlbCwgc3BhbWJhc2VOT05TUEFNLCBsYXllciA9IDEpDQoNCiMgZnVydGhlciBzdXBlcnZpc2VkIG1vZGVscyBjYW4gYmUgdHJhaW5lZCB3aXRoIHRoZXNlIGZlYXR1cmVzDQpoZWFkKGRlZXBfZmVhdHVyZXNfbGF5ZXIxKQ0KDQojIHJlY29uc3RydWN0aW9uIGVycm9yIHBlciBzYW1wbGUgfiBvdXRsaWVybmVzcyBpbmRpY2F0aXZlDQpyZWNvbnN0cnVjdGlvbl9lcnJvciA8LSBoMm8uYW5vbWFseShhdXRvZW5jb2Rlcl9tb2RlbCwgc3BhbWJhc2VOT05TUEFNKQ0KaGVhZChyZWNvbnN0cnVjdGlvbl9lcnJvcikNCnJlY29uc3RydWN0aW9uX2Vycm9yIDwtIGFzLmRhdGEuZnJhbWUocmVjb25zdHJ1Y3Rpb25fZXJyb3IpDQpwbG90KHNvcnQocmVjb25zdHJ1Y3Rpb25fZXJyb3IkUmVjb25zdHJ1Y3Rpb24uTVNFKSwgbWFpbiA9ICJSZWNvbnN0cnVjdGlvbiBFcnJvciIpDQp3aGljaChyZWNvbnN0cnVjdGlvbl9lcnJvciA+IDAuMDIpDQpgYGANCg0KIyMgUHJvc3RhdGUNCg0KYGBge3J9DQpoMm8uaW5pdChwb3J0ID0gNTAwMDEpDQpwcm9zdGF0ZV9wYXRoIDwtIHN5c3RlbS5maWxlKCJleHRkYXRhIiwgInByb3N0YXRlLmNzdiIsIHBhY2thZ2UgPSAiaDJvIikNCnByb3N0YXRlIDwtIGgyby5pbXBvcnRGaWxlKHBhdGggPSBwcm9zdGF0ZV9wYXRoKQ0KcHJpbnQocHJvc3RhdGUpDQpgYGANCg0KYGBge3J9DQojIGxlYXJuIGF1dG9lbmNvZGVyIHdpdGggMiBoaWRkZW4gbGF5ZXJzIG9mIDEwIHVuaXRzIGVhY2gNCmF1dG9lbmNvZGVyX21vZGVsIDwtIGgyby5kZWVwbGVhcm5pbmcoDQogICAgeCA9IDM6OSwNCiAgICB0cmFpbmluZ19mcmFtZSA9IHByb3N0YXRlLA0KICAgIGF1dG9lbmNvZGVyID0gVFJVRSwNCiAgICBoaWRkZW4gPSBjKDEwLCAxMCksDQogICAgZXBvY2hzID0gNQ0KKQ0KDQojIGZlYXR1cmVzIGluIHRoZSBhdXRvZW5jb2RlcidzIGZpcnN0IGhpZGRlbiBsYXllcg0KZGVlcF9mZWF0dXJlc19sYXllcjEgPC0gaDJvLmRlZXBmZWF0dXJlcyhhdXRvZW5jb2Rlcl9tb2RlbCwgcHJvc3RhdGUsIGxheWVyID0gMSkNCg0KIyBmdXJ0aGVyIHN1cGVydmlzZWQgbW9kZWxzIGNhbiBiZSB0cmFpbmVkIHdpdGggdGhlc2UgZmVhdHVyZXMNCmhlYWQoZGVlcF9mZWF0dXJlc19sYXllcjEpDQoNCiMgcmVjb25zdHJ1Y3Rpb24gZXJyb3IgcGVyIHNhbXBsZSB+IG91dGxpZXJuZXNzIGluZGljYXRpdmUNCnJlY29uc3RydWN0aW9uX2Vycm9yIDwtIGgyby5hbm9tYWx5KGF1dG9lbmNvZGVyX21vZGVsLCBwcm9zdGF0ZSkNCmhlYWQocmVjb25zdHJ1Y3Rpb25fZXJyb3IpDQpyZWNvbnN0cnVjdGlvbl9lcnJvciA8LSBhcy5kYXRhLmZyYW1lKHJlY29uc3RydWN0aW9uX2Vycm9yKQ0KcGxvdChzb3J0KHJlY29uc3RydWN0aW9uX2Vycm9yJFJlY29uc3RydWN0aW9uLk1TRSksIG1haW4gPSAiUmVjb25zdHJ1Y3Rpb24gRXJyb3IiKQ0Kd2hpY2gocmVjb25zdHJ1Y3Rpb25fZXJyb3IgPiAwLjE1KQ0KYGBg