1 Các bước thực hiện mô hình nói chung và MLR nói riêng

2 Tasks

Là phần meta-data gồm các thông tin như: dataset, target variable, loại mô hình TaskClassif hoặc TaskRegr

2.1 Loại tasks

Để tạo 1 task từ a data.frame() or data.table() object, the task type cần phải được chỉ rõ:

Classification Task: The target is a label (stored as character()orfactor()) with only few distinct values. → mlr3::TaskClassif

Regression Task: The target is a numeric quantity (stored as integer() or double()). → mlr3::TaskRegr

Survival Task: The target is the (right-censored) time to an event. → mlr3proba::TaskSurv in add-on package mlr3proba

Ordinal Regression Task: The target is ordinal. → mlr3ordinal::TaskOrdinal in add-on package mlr3ordinal

2.2 Tạo 1 task

Ví dụ tạo 1 task class sử dụng dữ liệu germancredit của package scorecard, biến mục tiêu là “creditability” gồm 2 phân nhóm là good và bad

Đầu tiên, load và prepare the data

data("germancredit", package = "scorecard")
str(germancredit)
## 'data.frame':    1000 obs. of  21 variables:
##  $ status.of.existing.checking.account                     : Factor w/ 4 levels "... < 0 DM","0 <= ... < 200 DM",..: 1 2 4 1 1 4 4 2 4 2 ...
##  $ duration.in.month                                       : num  6 48 12 42 24 36 24 36 12 30 ...
##  $ credit.history                                          : Factor w/ 5 levels "no credits taken/ all credits paid back duly",..: 5 3 5 3 4 3 3 3 3 5 ...
##  $ purpose                                                 : chr  "radio/television" "radio/television" "education" "furniture/equipment" ...
##  $ credit.amount                                           : num  1169 5951 2096 7882 4870 ...
##  $ savings.account.and.bonds                               : Factor w/ 5 levels "... < 100 DM",..: 5 1 1 1 1 5 3 1 4 1 ...
##  $ present.employment.since                                : Factor w/ 5 levels "unemployed","... < 1 year",..: 5 3 4 4 3 3 5 3 4 1 ...
##  $ installment.rate.in.percentage.of.disposable.income     : num  4 2 2 2 3 2 3 2 2 4 ...
##  $ personal.status.and.sex                                 : Factor w/ 5 levels "male : divorced/separated",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ other.debtors.or.guarantors                             : Factor w/ 3 levels "none","co-applicant",..: 1 1 1 3 1 1 1 1 1 1 ...
##  $ present.residence.since                                 : num  4 2 3 4 4 4 4 2 4 2 ...
##  $ property                                                : Factor w/ 4 levels "real estate",..: 1 1 1 2 4 4 2 3 1 3 ...
##  $ age.in.years                                            : num  67 22 49 45 53 35 53 35 61 28 ...
##  $ other.installment.plans                                 : Factor w/ 3 levels "bank","stores",..: 3 3 3 3 3 3 3 3 3 3 ...
##  $ housing                                                 : Factor w/ 3 levels "rent","own","for free": 2 2 2 3 3 3 2 1 2 2 ...
##  $ number.of.existing.credits.at.this.bank                 : num  2 1 1 1 2 1 1 1 1 2 ...
##  $ job                                                     : Factor w/ 4 levels "unemployed/ unskilled - non-resident",..: 3 3 2 3 3 2 3 4 2 4 ...
##  $ number.of.people.being.liable.to.provide.maintenance.for: num  1 1 2 2 2 2 1 1 1 1 ...
##  $ telephone                                               : Factor w/ 2 levels "none","yes, registered under the customers name": 2 1 1 1 1 2 1 2 1 1 ...
##  $ foreign.worker                                          : Factor w/ 2 levels "yes","no": 1 1 1 1 1 1 1 1 1 1 ...
##  $ creditability                                           : Factor w/ 2 levels "bad","good": 2 1 2 2 1 2 2 2 2 1 ...
sel_dt <- germancredit[, c(1:5, 21)]

Tiếp tục ta tạo task theo câu lệnh TaskClassif$new

id: Định danh tùy ý, sử dụng cho các summary. backend: Tham số này cho phép định nghĩa cách sử dụng tập dữ liệu. Ở đây tôi đơn giản cung cấp dataset và nó tự động chuyển sang DataBackendDataTable. target: Tên của biến mục tiêu

library(mlr3verse)
tsk_sc <- TaskClassif$new(id = "sc", backend = sel_dt, target = "creditability")
print(tsk_sc)
## <TaskClassif:sc> (1000 x 6)
## * Target: creditability
## * Properties: twoclass
## * Features (5):
##   - dbl (2): credit.amount, duration.in.month
##   - fct (2): credit.history, status.of.existing.checking.account
##   - chr (1): purpose

print() cung cấp những thông tin cơ bản của task: số biến, số quan sát …

Ngoài ra, ta cũng có thể sử dụng package mlr3viz để đưa ra những graph về các thuộc tính của các features

library(mlr3viz)
autoplot(tsk_sc, type = "pairs")
## Registered S3 method overwritten by 'GGally':
##   method from   
##   +.gg   ggplot2
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

2.3 Định nghĩa trước tasks

mlr3 cung cấp vài tasks định nghĩa trước. Tất cả task được lưu trong R6 Dictionary tên là mlr_tasks.

mlr_tasks
## <DictionaryTask> with 9 stored values
## Keys: boston_housing, german_credit, iris, mtcars, pima, sonar, spam,
##   wine, zoo

Ta cũng có thể chuyển đổi 1 task thành dataset object bằng cách

library(data.table)
as.data.table(mlr_tasks)
##               key task_type nrow ncol lgl int dbl chr fct ord pxc
## 1: boston_housing      regr  506   19   0   3  13   0   2   0   0
## 2:  german_credit   classif 1000   21   0   0   7   0  12   1   0
## 3:           iris   classif  150    5   0   0   4   0   0   0   0
## 4:         mtcars      regr   32   11   0   0  10   0   0   0   0
## 5:           pima   classif  768    9   0   0   8   0   0   0   0
## 6:          sonar   classif  208   61   0   0  60   0   0   0   0
## 7:           spam   classif 4601   58   0   0  57   0   0   0   0
## 8:           wine   classif  178   14   0   2  11   0   0   0   0
## 9:            zoo   classif  101   17  15   1   0   0   0   0   0

Để lấy 1 task tử dictionary, có thể sử dụng phương thức $get() từ mlr_tasks và gán vào object mới. Ví dụ

task_iris <- mlr_tasks$get("iris")
print(task_iris)
## <TaskClassif:iris> (150 x 5)
## * Target: Species
## * Properties: multiclass
## * Features (4):
##   - dbl (4): Petal.Length, Petal.Width, Sepal.Length, Sepal.Width

Cách khác, có thể dụng hàm tsk(), cũng tạo được 1 task từ dictionary

tsk("iris")
## <TaskClassif:iris> (150 x 5)
## * Target: Species
## * Properties: multiclass
## * Features (4):
##   - dbl (4): Petal.Length, Petal.Width, Sepal.Length, Sepal.Width

2.4 Task API

Tất cả thuộc tính và đặc điểm của task có thể được truy vấn sử dụng thuộc tính (fields) public và phương thức

2.4.1 Gọi dữ liệu

Dữ liệu được lưu trong 1 task có thể được lấy trực tiếp từ fields, ví dụ:

task_iris$nrow
## [1] 150
task_iris$ncol
## [1] 5

Thông tin thêm có thể có được qua phương thức của object, ví dụ:

task_iris$data()
##        Species Petal.Length Petal.Width Sepal.Length Sepal.Width
##   1:    setosa          1.4         0.2          5.1         3.5
##   2:    setosa          1.4         0.2          4.9         3.0
##   3:    setosa          1.3         0.2          4.7         3.2
##   4:    setosa          1.5         0.2          4.6         3.1
##   5:    setosa          1.4         0.2          5.0         3.6
##  ---                                                            
## 146: virginica          5.2         2.3          6.7         3.0
## 147: virginica          5.0         1.9          6.3         2.5
## 148: virginica          5.2         2.0          6.5         3.0
## 149: virginica          5.4         2.3          6.2         3.4
## 150: virginica          5.1         1.8          5.9         3.0

Trong mlr3, mỗi dòng là đơn nhất và được xác định băng 1 giá trị integer

iris sư dụng giá trị nguyên cho row_ids

head(task_iris$row_ids)
## [1] 1 2 3 4 5 6

lấy các dòng 1, 51, 101

task_iris$data(rows = c(1, 51, 101))
##       Species Petal.Length Petal.Width Sepal.Length Sepal.Width
## 1:     setosa          1.4         0.2          5.1         3.5
## 2: versicolor          4.7         1.4          7.0         3.2
## 3:  virginica          6.0         2.5          6.3         3.3

mtcars task sử dụng character làm row_ids

task_mtcars <- tsk("mtcars")
head(task_mtcars$row_ids)
## [1] "AMC Javelin"        "Cadillac Fleetwood" "Camaro Z28"        
## [4] "Chrysler Imperial"  "Datsun 710"         "Dodge Challenger"
task_mtcars$data(rows = "Datsun 710")
##     mpg am carb cyl disp drat gear hp  qsec vs   wt
## 1: 22.8  1    1   4  108 3.85    4 93 18.61  1 2.32

Chú ý, method $data() chỉ cho phép đọc, không cho phép modify

Tương tự, mỗi cột cũng được xác định bằng giá trị nguyên hoặc character, tên được lưu thành feature_names và target_names

task_iris$feature_names
## [1] "Petal.Length" "Petal.Width"  "Sepal.Length" "Sepal.Width"
task_iris$target_names
## [1] "Species"

Có thể kết hợp row_ids và column names để chọn tập con của dữ liệu

task_iris$data(rows = c(1, 51, 101), cols = "Species")
##       Species
## 1:     setosa
## 2: versicolor
## 3:  virginica

Để xuất toàn bộ dữ liệu từ task, đơn giản chỉ việc convert sang dạng data.table

summary(as.data.table(task_iris))
##        Species    Petal.Length    Petal.Width     Sepal.Length  
##  setosa    :50   Min.   :1.000   Min.   :0.100   Min.   :4.300  
##  versicolor:50   1st Qu.:1.600   1st Qu.:0.300   1st Qu.:5.100  
##  virginica :50   Median :4.350   Median :1.300   Median :5.800  
##                  Mean   :3.758   Mean   :1.199   Mean   :5.843  
##                  3rd Qu.:5.100   3rd Qu.:1.800   3rd Qu.:6.400  
##                  Max.   :6.900   Max.   :2.500   Max.   :7.900  
##   Sepal.Width   
##  Min.   :2.000  
##  1st Qu.:2.800  
##  Median :3.000  
##  Mean   :3.057  
##  3rd Qu.:3.300  
##  Max.   :4.400

2.4.2 Roles (Rows and Columns)

Có thể gán vai trò cho dòng và cột. Những vai trò ảnh hưởng tới hoạt động của task. Rộng hơn, roles cung cấp thêm thông tin meta-data

Ví dụ

print(task_mtcars$col_roles)
## $feature
##  [1] "am"   "carb" "cyl"  "disp" "drat" "gear" "hp"   "qsec" "vs"   "wt"  
## 
## $target
## [1] "mpg"
## 
## $name
## character(0)
## 
## $order
## character(0)
## 
## $stratum
## character(0)
## 
## $group
## character(0)
## 
## $weight
## character(0)

Để thêm dòng như 1 tính năng bổ sung, t thêm chúng vào data table và tạo lại task

# with `keep.rownames`, data.table stores the row names in an extra column "rn"
data <- as.data.table(mtcars[, 1:3], keep.rownames = TRUE)
task <- TaskRegr$new(id = "cars", backend = data, target = "mpg")

row_ids của task

task$row_ids
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
## [26] 26 27 28 29 30 31 32
task$feature_names
## [1] "cyl"  "disp" "rn"

Feature mới tên là “rn”. Feature này chỉ sử dụng để hiểu thêm về cách add roles, không có nhiều ý nghĩa trong việc dự báo. Để bỏ nó ra khỏi roles feature làm như sau:

task$col_roles$name = "rn"
task$col_roles$feature = setdiff(task$col_roles$feature, "rn")

“rn” không năm trong list feature nữa

task$feature_names
## [1] "cyl"  "disp"

Và cũng không xuất hiện khi tiếp cận data

task$data(rows = 1:2)
##    mpg cyl disp
## 1:  21   6  160
## 2:  21   6  160
task$head(2)
##    mpg cyl disp
## 1:  21   6  160
## 2:  21   6  160

Thay đổi vai trò không làm thay đổi nền tảng dữ liệu, sự thay đổi chỉ làm thay đổi khi view nó. Tức là chỉ thay đổi task

Giống như cột, cũng có thể đặt các roles cho hàng, hàng có thể có 2 loại roles:

Role sử dụng ROWS được sử dụng cho model fitting (mặc dù có thể sử dụng như tập test khi resampling). Role này là role mặc định

Role validation Rows cái mà không sử dụng cho training. Dòng mà bị missing giá trị của biến target trong khi tạo task sẽ tự động chuyển thảnh role validation.

2.4.3 Task Mutators

Như ở trên, việc modifying $col_roles hay $row_roles thay đổi view on the data. Các phương pháp tiện tích $filter(), $select(), chọn dòng hoặc cột

task = tsk("iris")
task$select(c("Sepal.Width", "Sepal.Length")) # keep only these features
task$filter(1:3) # keep only these rows
task$head()
##    Species Sepal.Length Sepal.Width
## 1:  setosa          5.1         3.5
## 2:  setosa          4.9         3.0
## 3:  setosa          4.7         3.2

Ngoài phương pháp subset data đã được thảo luận ở trên, phương pháp $rbind() và $cbind() có phép thêm dòng và cột vào task. Original data không thay đổi, thêm dòng hay cột chỉ ảnh hưởng tới view data trong task

task$cbind(data.table(foo = letters[1:3])) # add column foo
task$head()
##    Species Sepal.Length Sepal.Width foo
## 1:  setosa          5.1         3.5   a
## 2:  setosa          4.9         3.0   b
## 3:  setosa          4.7         3.2   c

2.5 Plotting Tasks

mlr3viz cung cấp các biểu đồ cơ bản để thực hiện mlr3. Loại plot phụ thuộc vào inherited class, nhưng tất cả plot được trả về dưới dạng ggplot2 object, rất dễ để customized

Với classification task (kế thừa từ TaskClassif), xem hướng dẫn mlr3viz::autoplot.TaskClassif. Dưới đây là một vài ví dụ

library(mlr3viz)

# get the pima indians task
task = tsk("pima")

# subset task to only use the 3 first features
task$select(head(task$feature_names, 3))

# default plot: class frequencies
autoplot(task)

# pairs plot (requires package GGally)
autoplot(task, type = "pairs")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

# duo plot (requires package GGally)
autoplot(task, type = "duo")

Tất nhiên, với regression task (inheriting from TaskRegr) xem hướng dẫn mlr3viz::autoplot.TaskRegr

library(mlr3viz)

# get the boston housing task
task = tsk("mtcars")

# subset task to only use the 3 first features
task$select(head(task$feature_names, 3))

# default plot: boxplot of target variable
autoplot(task)

# pairs plot (requires package GGally)
autoplot(task, type = "pairs")

LS0tDQp0aXRsZTogIkLhuq90IMSR4bqndSB24bubaSBNTFIzIC0gUDE6IHRhc2siDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiAiZGVmYXVsdCINCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCiAgICBkZXY6ICdzdmcnDQplZGl0b3Jfb3B0aW9uczoNCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGNvbnNvbGUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmlmICghcmVxdWlyZShtbHIzdmVyc2UpKSBpbnN0YWxsLnBhY2thZ2VzKCJtbHIzdmVyc2UiKQ0KYGBgDQoNCiMgQ8OhYyBixrDhu5tjIHRo4buxYyBoaeG7h24gbcO0IGjDrG5oIG7Ds2kgY2h1bmcgdsOgIE1MUiBuw7NpIHJpw6puZw0KDQohW10oZmlndXJlcy9tbF9hYnN0cmFjdGlvbi5wbmcpDQoNCiMgVGFza3MNCg0KTMOgIHBo4bqnbiBtZXRhLWRhdGEgZ+G7k20gY8OhYyB0aMO0bmcgdGluIG5oxrA6IGRhdGFzZXQsIHRhcmdldCB2YXJpYWJsZSwgbG/huqFpIG3DtCBow6xuaCBUYXNrQ2xhc3NpZiBob+G6t2MgVGFza1JlZ3INCg0KIyMgTG/huqFpIHRhc2tzDQoNCsSQ4buDIHThuqFvIDEgdGFzayB04burIGEgZGF0YS5mcmFtZSgpIG9yIGRhdGEudGFibGUoKSBvYmplY3QsIHRoZSB0YXNrIHR5cGUgY+G6p24gcGjhuqNpIMSRxrDhu6NjIGNo4buJIHLDtToNCg0KQ2xhc3NpZmljYXRpb24gVGFzazogVGhlIHRhcmdldCBpcyBhIGxhYmVsIChzdG9yZWQgYXMgY2hhcmFjdGVyKClvcmZhY3RvcigpKSB3aXRoIG9ubHkgZmV3IGRpc3RpbmN0IHZhbHVlcy4NCuKGkiBtbHIzOjpUYXNrQ2xhc3NpZg0KDQpSZWdyZXNzaW9uIFRhc2s6IFRoZSB0YXJnZXQgaXMgYSBudW1lcmljIHF1YW50aXR5IChzdG9yZWQgYXMgaW50ZWdlcigpIG9yIGRvdWJsZSgpKS4NCuKGkiBtbHIzOjpUYXNrUmVncg0KDQpTdXJ2aXZhbCBUYXNrOiBUaGUgdGFyZ2V0IGlzIHRoZSAocmlnaHQtY2Vuc29yZWQpIHRpbWUgdG8gYW4gZXZlbnQuDQrihpIgbWxyM3Byb2JhOjpUYXNrU3VydiBpbiBhZGQtb24gcGFja2FnZSBtbHIzcHJvYmENCg0KT3JkaW5hbCBSZWdyZXNzaW9uIFRhc2s6IFRoZSB0YXJnZXQgaXMgb3JkaW5hbC4NCuKGkiBtbHIzb3JkaW5hbDo6VGFza09yZGluYWwgaW4gYWRkLW9uIHBhY2thZ2UgbWxyM29yZGluYWwNCg0KIyMgVOG6oW8gMSB0YXNrIA0KVsOtIGThu6UgdOG6oW8gMSB0YXNrIGNsYXNzIHPhu60gZOG7pW5nIGThu68gbGnhu4d1IGdlcm1hbmNyZWRpdCBj4bunYSBwYWNrYWdlIHNjb3JlY2FyZCwgYmnhur9uIG3hu6VjIHRpw6p1IGzDoCAiY3JlZGl0YWJpbGl0eSIgZ+G7k20gMiBwaMOibiBuaMOzbSBsw6AgZ29vZCB2w6AgYmFkDQoNCsSQ4bqndSB0acOqbiwgbG9hZCB2w6AgcHJlcGFyZSB0aGUgZGF0YQ0KDQpgYGB7cn0NCmRhdGEoImdlcm1hbmNyZWRpdCIsIHBhY2thZ2UgPSAic2NvcmVjYXJkIikNCnN0cihnZXJtYW5jcmVkaXQpDQoNCnNlbF9kdCA8LSBnZXJtYW5jcmVkaXRbLCBjKDE6NSwgMjEpXQ0KDQpgYGANCg0KVGnhur9wIHThu6VjIHRhIHThuqFvIHRhc2sgdGhlbyBjw6J1IGzhu4duaCBUYXNrQ2xhc3NpZiRuZXcNCg0KaWQ6IMSQ4buLbmggZGFuaCB0w7l5IMO9LCBz4butIGThu6VuZyBjaG8gY8OhYyBzdW1tYXJ5Lg0KYmFja2VuZDogVGhhbSBz4buRIG7DoHkgY2hvIHBow6lwIMSR4buLbmggbmdoxKlhIGPDoWNoIHPhu60gZOG7pW5nIHThuq1wIGThu68gbGnhu4d1LiDhu54gxJHDonkgdMO0aSDEkcahbiBnaeG6o24gY3VuZyBj4bqlcCBkYXRhc2V0IHbDoCBuw7MgdOG7sSDEkeG7mW5nIGNodXnhu4NuIHNhbmcgKipEYXRhQmFja2VuZERhdGFUYWJsZSoqLiANCnRhcmdldDogVMOqbiBj4bunYSBiaeG6v24gbeG7pWMgdGnDqnUNCg0KDQpgYGB7cn0NCmxpYnJhcnkobWxyM3ZlcnNlKQ0KdHNrX3NjIDwtIFRhc2tDbGFzc2lmJG5ldyhpZCA9ICJzYyIsIGJhY2tlbmQgPSBzZWxfZHQsIHRhcmdldCA9ICJjcmVkaXRhYmlsaXR5IikNCnByaW50KHRza19zYykNCmBgYA0KDQpwcmludCgpIGN1bmcgY+G6pXAgbmjhu69uZyB0aMO0bmcgdGluIGPGoSBi4bqjbiBj4bunYSB0YXNrOiBz4buRIGJp4bq/biwgc+G7kSBxdWFuIHPDoXQgLi4uDQoNCk5nb8OgaSByYSwgdGEgY8WpbmcgY8OzIHRo4buDIHPhu60gZOG7pW5nIHBhY2thZ2UgbWxyM3ZpeiDEkeG7gyDEkcawYSByYSBuaOG7r25nIGdyYXBoIHbhu4EgY8OhYyB0aHXhu5ljIHTDrW5oIGPhu6dhIGPDoWMgZmVhdHVyZXMNCg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkobWxyM3ZpeikNCmF1dG9wbG90KHRza19zYywgdHlwZSA9ICJwYWlycyIpDQpgYGANCg0KIyMgxJDhu4tuaCBuZ2jEqWEgdHLGsOG7m2MgdGFza3MNCg0KbWxyMyBjdW5nIGPhuqVwIHbDoGkgdGFza3MgxJHhu4tuaCBuZ2jEqWEgdHLGsOG7m2MuIFThuqV0IGPhuqMgdGFzayDEkcaw4bujYyBsxrB1IHRyb25nIFI2IERpY3Rpb25hcnkgdMOqbiBsw6AgKiptbHJfdGFza3MqKi4gDQoNCmBgYHtyfQ0KbWxyX3Rhc2tzDQpgYGANCg0KVGEgY8WpbmcgY8OzIHRo4buDIGNodXnhu4NuIMSR4buVaSAxIHRhc2sgdGjDoG5oIGRhdGFzZXQgb2JqZWN0IGLhurFuZyBjw6FjaA0KDQpgYGB7cn0NCmxpYnJhcnkoZGF0YS50YWJsZSkNCmFzLmRhdGEudGFibGUobWxyX3Rhc2tzKQ0KYGBgDQoNCsSQ4buDIGzhuqV5IDEgdGFzayB04butIGRpY3Rpb25hcnksIGPDsyB0aOG7gyBz4butIGThu6VuZyBwaMawxqFuZyB0aOG7qWMgJGdldCgpIHThu6sgbWxyX3Rhc2tzIHbDoCBnw6FuIHbDoG8gb2JqZWN0IG3hu5tpLiBWw60gZOG7pQ0KDQpgYGB7cn0NCnRhc2tfaXJpcyA8LSBtbHJfdGFza3MkZ2V0KCJpcmlzIikNCnByaW50KHRhc2tfaXJpcykNCmBgYA0KDQpDw6FjaCBraMOhYywgY8OzIHRo4buDIGThu6VuZyBow6BtIHRzaygpLCBjxaluZyB04bqhbyDEkcaw4bujYyAxIHRhc2sgdOG7qyBkaWN0aW9uYXJ5DQoNCmBgYHtyfQ0KdHNrKCJpcmlzIikNCmBgYA0KDQoNCiMjIFRhc2sgQVBJDQoNClThuqV0IGPhuqMgdGh14buZYyB0w61uaCB2w6AgxJHhurdjIMSRaeG7g20gY+G7p2EgdGFzayBjw7MgdGjhu4MgxJHGsOG7o2MgdHJ1eSB24bqlbiBz4butIGThu6VuZyB0aHXhu5ljIHTDrW5oIChmaWVsZHMpIHB1YmxpYyB2w6AgcGjGsMahbmcgdGjhu6ljDQoNCiMjIyBH4buNaSBk4buvIGxp4buHdQ0KDQpE4buvIGxp4buHdSDEkcaw4bujYyBsxrB1IHRyb25nIDEgdGFzayBjw7MgdGjhu4MgxJHGsOG7o2MgbOG6pXkgdHLhu7FjIHRp4bq/cCB04burIGZpZWxkcywgdsOtIGThu6U6DQoNCmBgYHtyfQ0KdGFza19pcmlzJG5yb3cNCnRhc2tfaXJpcyRuY29sDQpgYGANCg0KVGjDtG5nIHRpbiB0aMOqbSBjw7MgdGjhu4MgY8OzIMSRxrDhu6NjIHF1YSBwaMawxqFuZyB0aOG7qWMgY+G7p2Egb2JqZWN0LCB2w60gZOG7pToNCg0KYGBge3J9DQp0YXNrX2lyaXMkZGF0YSgpDQpgYGANCg0KVHJvbmcgbWxyMywgbeG7l2kgZMOybmcgbMOgIMSRxqFuIG5o4bqldCB2w6AgxJHGsOG7o2MgeMOhYyDEkeG7i25oIGLEg25nIDEgZ2nDoSB0cuG7iyBpbnRlZ2VyDQoNCmlyaXMgc8awIGThu6VuZyBnacOhIHRy4buLIG5ndXnDqm4gY2hvIHJvd19pZHMNCmBgYHtyfQ0KaGVhZCh0YXNrX2lyaXMkcm93X2lkcykNCmBgYA0KDQps4bqleSBjw6FjIGTDsm5nIDEsIDUxLCAxMDENCmBgYHtyfQ0KdGFza19pcmlzJGRhdGEocm93cyA9IGMoMSwgNTEsIDEwMSkpDQpgYGANCg0KbXRjYXJzIHRhc2sgc+G7rSBk4bulbmcgY2hhcmFjdGVyIGzDoG0gcm93X2lkcw0KDQpgYGB7cn0NCnRhc2tfbXRjYXJzIDwtIHRzaygibXRjYXJzIikNCmhlYWQodGFza19tdGNhcnMkcm93X2lkcykNCnRhc2tfbXRjYXJzJGRhdGEocm93cyA9ICJEYXRzdW4gNzEwIikNCmBgYA0KDQpDaMO6IMO9LCBtZXRob2QgJGRhdGEoKSBjaOG7iSBjaG8gcGjDqXAgxJHhu41jLCBraMO0bmcgY2hvIHBow6lwIG1vZGlmeQ0KDQpUxrDGoW5nIHThu7EsIG3hu5dpIGPhu5l0IGPFqW5nIMSRxrDhu6NjIHjDoWMgxJHhu4tuaCBi4bqxbmcgZ2nDoSB0cuG7iyBuZ3V5w6puIGhv4bq3YyBjaGFyYWN0ZXIsIHTDqm4gxJHGsOG7o2MgbMawdSB0aMOgbmggZmVhdHVyZV9uYW1lcyB2w6AgdGFyZ2V0X25hbWVzDQoNCmBgYHtyfQ0KdGFza19pcmlzJGZlYXR1cmVfbmFtZXMNCnRhc2tfaXJpcyR0YXJnZXRfbmFtZXMNCmBgYA0KDQpDw7MgdGjhu4Mga+G6v3QgaOG7o3Agcm93X2lkcyB2w6AgY29sdW1uIG5hbWVzIMSR4buDIGNo4buNbiB04bqtcCBjb24gY+G7p2EgZOG7ryBsaeG7h3UNCg0KDQpgYGB7cn0NCnRhc2tfaXJpcyRkYXRhKHJvd3MgPSBjKDEsIDUxLCAxMDEpLCBjb2xzID0gIlNwZWNpZXMiKQ0KYGBgDQoNCsSQ4buDIHh14bqldCB0b8OgbiBi4buZIGThu68gbGnhu4d1IHThu6sgdGFzaywgxJHGoW4gZ2nhuqNuIGNo4buJIHZp4buHYyBjb252ZXJ0IHNhbmcgZOG6oW5nIGRhdGEudGFibGUNCg0KYGBge3J9DQpzdW1tYXJ5KGFzLmRhdGEudGFibGUodGFza19pcmlzKSkNCmBgYA0KDQojIyMgUm9sZXMgKFJvd3MgYW5kIENvbHVtbnMpDQoNCkPDsyB0aOG7gyBnw6FuIHZhaSB0csOyIGNobyBkw7JuZyB2w6AgY+G7mXQuIE5o4buvbmcgdmFpIHRyw7Ig4bqjbmggaMaw4bufbmcgdOG7m2kgaG/huqF0IMSR4buZbmcgY+G7p2EgdGFzay4gUuG7mW5nIGjGoW4sIHJvbGVzIGN1bmcgY+G6pXAgdGjDqm0gdGjDtG5nIHRpbiBtZXRhLWRhdGENCg0KVsOtIGThu6UNCg0KYGBge3J9DQpwcmludCh0YXNrX210Y2FycyRjb2xfcm9sZXMpDQpgYGANCg0KxJDhu4MgdGjDqm0gZMOybmcgbmjGsCAxIHTDrW5oIG7Eg25nIGLhu5Ugc3VuZywgdCB0aMOqbSBjaMO6bmcgdsOgbyBkYXRhIHRhYmxlIHbDoCB04bqhbyBs4bqhaSB0YXNrDQoNCmBgYHtyfQ0KIyB3aXRoIGBrZWVwLnJvd25hbWVzYCwgZGF0YS50YWJsZSBzdG9yZXMgdGhlIHJvdyBuYW1lcyBpbiBhbiBleHRyYSBjb2x1bW4gInJuIg0KZGF0YSA8LSBhcy5kYXRhLnRhYmxlKG10Y2Fyc1ssIDE6M10sIGtlZXAucm93bmFtZXMgPSBUUlVFKQ0KdGFzayA8LSBUYXNrUmVnciRuZXcoaWQgPSAiY2FycyIsIGJhY2tlbmQgPSBkYXRhLCB0YXJnZXQgPSAibXBnIikNCmBgYA0KDQpyb3dfaWRzIGPhu6dhIHRhc2sNCg0KYGBge3J9DQp0YXNrJHJvd19pZHMNCmBgYA0KDQoNCmBgYHtyfQ0KdGFzayRmZWF0dXJlX25hbWVzDQpgYGANCg0KRmVhdHVyZSBt4bubaSB0w6puIGzDoCAicm4iLiBGZWF0dXJlIG7DoHkgY2jhu4kgc+G7rSBk4bulbmcgxJHhu4MgaGnhu4N1IHRow6ptIHbhu4EgY8OhY2ggYWRkIHJvbGVzLCBraMO0bmcgY8OzIG5oaeG7gXUgw70gbmdoxKlhIHRyb25nIHZp4buHYyBk4buxIGLDoW8uIMSQ4buDIGLhu48gbsOzIHJhIGto4buPaSByb2xlcyBmZWF0dXJlIGzDoG0gbmjGsCBzYXU6DQoNCmBgYHtyfQ0KdGFzayRjb2xfcm9sZXMkbmFtZSA9ICJybiINCnRhc2skY29sX3JvbGVzJGZlYXR1cmUgPSBzZXRkaWZmKHRhc2skY29sX3JvbGVzJGZlYXR1cmUsICJybiIpDQoNCmBgYA0KDQoNCg0KInJuIiBraMO0bmcgbsSDbSB0cm9uZyBsaXN0IGZlYXR1cmUgbuG7r2ENCg0KYGBge3J9DQp0YXNrJGZlYXR1cmVfbmFtZXMNCmBgYA0KDQpWw6AgY8Wpbmcga2jDtG5nIHh14bqldCBoaeG7h24ga2hpIHRp4bq/cCBj4bqtbiBkYXRhIA0KYGBge3J9DQp0YXNrJGRhdGEocm93cyA9IDE6MikNCnRhc2skaGVhZCgyKQ0KYGBgDQoNClRoYXkgxJHhu5VpIHZhaSB0csOyIGtow7RuZyBsw6BtIHRoYXkgxJHhu5VpIG7hu4FuIHThuqNuZyBk4buvIGxp4buHdSwgc+G7sSB0aGF5IMSR4buVaSBjaOG7iSBsw6BtIHRoYXkgxJHhu5VpIGtoaSB2aWV3IG7Dsy4gVOG7qWMgbMOgIGNo4buJIHRoYXkgxJHhu5VpIHRhc2sNCg0KR2nhu5FuZyBuaMawIGPhu5l0LCBjxaluZyBjw7MgdGjhu4MgxJHhurd0IGPDoWMgcm9sZXMgY2hvIGjDoG5nLCBow6BuZyBjw7MgdGjhu4MgY8OzIDIgbG/huqFpIHJvbGVzOiANCg0KUm9sZSBz4butIGThu6VuZyBST1dTIMSRxrDhu6NjIHPhu60gZOG7pW5nIGNobyBtb2RlbCBmaXR0aW5nICht4bq3YyBkw7kgY8OzIHRo4buDIHPhu60gZOG7pW5nIG5oxrAgdOG6rXAgdGVzdCBraGkgcmVzYW1wbGluZykuIFJvbGUgbsOgeSBsw6Agcm9sZSBt4bq3YyDEkeG7i25oIA0KDQpSb2xlIHZhbGlkYXRpb24gUm93cyBjw6FpIG3DoCBraMO0bmcgc+G7rSBk4bulbmcgY2hvIHRyYWluaW5nLiBEw7JuZyBtw6AgYuG7iyBtaXNzaW5nIGdpw6EgdHLhu4sgY+G7p2EgYmnhur9uIHRhcmdldCB0cm9uZyBraGkgdOG6oW8gdGFzayBz4bq9IHThu7EgxJHhu5luZyBjaHV54buDbiB0aOG6o25oIHJvbGUgdmFsaWRhdGlvbi4NCg0KIyMjIFRhc2sgTXV0YXRvcnMNCg0KTmjGsCDhu58gdHLDqm4sIHZp4buHYyBtb2RpZnlpbmcgJGNvbF9yb2xlcyBoYXkgJHJvd19yb2xlcyB0aGF5IMSR4buVaSB2aWV3IG9uIHRoZSBkYXRhLiBDw6FjIHBoxrDGoW5nIHBow6FwIHRp4buHbiB0w61jaCAkZmlsdGVyKCksICRzZWxlY3QoKSwgY2jhu41uIGTDsm5nIGhv4bq3YyBj4buZdA0KDQpgYGB7cn0NCnRhc2sgPSB0c2soImlyaXMiKQ0KdGFzayRzZWxlY3QoYygiU2VwYWwuV2lkdGgiLCAiU2VwYWwuTGVuZ3RoIikpICMga2VlcCBvbmx5IHRoZXNlIGZlYXR1cmVzDQp0YXNrJGZpbHRlcigxOjMpICMga2VlcCBvbmx5IHRoZXNlIHJvd3MNCnRhc2skaGVhZCgpDQoNCmBgYA0KDQpOZ2/DoGkgcGjGsMahbmcgcGjDoXAgc3Vic2V0IGRhdGEgxJHDoyDEkcaw4bujYyB0aOG6o28gbHXhuq1uIOG7nyB0csOqbiwgcGjGsMahbmcgcGjDoXAgJHJiaW5kKCkgdsOgICRjYmluZCgpIGPDsyBwaMOpcCB0aMOqbSBkw7JuZyB2w6AgY+G7mXQgdsOgbyB0YXNrLiBPcmlnaW5hbCBkYXRhIGtow7RuZyB0aGF5IMSR4buVaSwgdGjDqm0gZMOybmcgaGF5IGPhu5l0IGNo4buJIOG6o25oIGjGsOG7n25nIHThu5tpIHZpZXcgZGF0YSB0cm9uZyB0YXNrDQoNCmBgYHtyfQ0KdGFzayRjYmluZChkYXRhLnRhYmxlKGZvbyA9IGxldHRlcnNbMTozXSkpICMgYWRkIGNvbHVtbiBmb28NCnRhc2skaGVhZCgpDQpgYGANCg0KIyMgUGxvdHRpbmcgVGFza3MNCg0KbWxyM3ZpeiBjdW5nIGPhuqVwIGPDoWMgYmnhu4N1IMSR4buTIGPGoSBi4bqjbiDEkeG7gyB0aOG7sWMgaGnhu4duIG1scjMuIExv4bqhaSBwbG90IHBo4bulIHRodeG7mWMgdsOgbyBpbmhlcml0ZWQgY2xhc3MsIG5oxrBuZyB04bqldCBj4bqjIHBsb3QgxJHGsOG7o2MgdHLhuqMgduG7gSBkxrDhu5tpIGThuqFuZyBnZ3Bsb3QyIG9iamVjdCwgcuG6pXQgZOG7hSDEkeG7gyBjdXN0b21pemVkDQoNClbhu5tpIGNsYXNzaWZpY2F0aW9uIHRhc2sgKGvhur8gdGjhu6thIHThu6sgVGFza0NsYXNzaWYpLCB4ZW0gaMaw4bubbmcgZOG6q24gbWxyM3Zpejo6YXV0b3Bsb3QuVGFza0NsYXNzaWYuIETGsOG7m2kgxJHDonkgbMOgIG3hu5l0IHbDoGkgdsOtIGThu6UgDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShtbHIzdml6KQ0KDQojIGdldCB0aGUgcGltYSBpbmRpYW5zIHRhc2sNCnRhc2sgPSB0c2soInBpbWEiKQ0KDQojIHN1YnNldCB0YXNrIHRvIG9ubHkgdXNlIHRoZSAzIGZpcnN0IGZlYXR1cmVzDQp0YXNrJHNlbGVjdChoZWFkKHRhc2skZmVhdHVyZV9uYW1lcywgMykpDQoNCiMgZGVmYXVsdCBwbG90OiBjbGFzcyBmcmVxdWVuY2llcw0KYXV0b3Bsb3QodGFzaykNCg0KIyBwYWlycyBwbG90IChyZXF1aXJlcyBwYWNrYWdlIEdHYWxseSkNCmF1dG9wbG90KHRhc2ssIHR5cGUgPSAicGFpcnMiKQ0KDQojIGR1byBwbG90IChyZXF1aXJlcyBwYWNrYWdlIEdHYWxseSkNCmF1dG9wbG90KHRhc2ssIHR5cGUgPSAiZHVvIikNCg0KDQpgYGANCg0KVOG6pXQgbmhpw6puLCB24bubaSByZWdyZXNzaW9uIHRhc2sgKGluaGVyaXRpbmcgZnJvbSBUYXNrUmVncikgeGVtIGjGsOG7m25nIGThuqtuIG1scjN2aXo6OmF1dG9wbG90LlRhc2tSZWdyDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShtbHIzdml6KQ0KDQojIGdldCB0aGUgYm9zdG9uIGhvdXNpbmcgdGFzaw0KdGFzayA9IHRzaygibXRjYXJzIikNCg0KIyBzdWJzZXQgdGFzayB0byBvbmx5IHVzZSB0aGUgMyBmaXJzdCBmZWF0dXJlcw0KdGFzayRzZWxlY3QoaGVhZCh0YXNrJGZlYXR1cmVfbmFtZXMsIDMpKQ0KDQojIGRlZmF1bHQgcGxvdDogYm94cGxvdCBvZiB0YXJnZXQgdmFyaWFibGUNCmF1dG9wbG90KHRhc2spDQoNCiMgcGFpcnMgcGxvdCAocmVxdWlyZXMgcGFja2FnZSBHR2FsbHkpDQphdXRvcGxvdCh0YXNrLCB0eXBlID0gInBhaXJzIikNCg0KYGBgDQoNCg0KDQo=