Sys.setlocale("LC_ALL","C")
[1] "C"
packages = c(
"dplyr","ggplot2","d3heatmap","googleVis","devtools","plotly", "xgboost",
"magrittr","caTools","ROCR","corrplot", "rpart", "rpart.plot",
"doParallel", "caret", "glmnet", "Matrix", "e1071", "randomForest",
"flexclust", "FactoMineR", "factoextra"
)
existing = as.character(installed.packages()[,1])
for(pkg in packages[!(packages %in% existing)]) install.packages(pkg)
rm(list=ls(all=T))
options(digits=4, scipen=12)
library(dplyr)
library(ggplot2)
library(flexclust)
library(FactoMineR)
library(factoextra)
A. 集群分析與尺度縮減
A1. 批發交易資料
W = read.csv('data/wholesales.csv')
W$Channel = factor( paste0("Ch",W$Channel) ) #地區
W$Region = factor( paste0("Reg",W$Region) ) #通路
W[3:8] = lapply(W[3:6], log, base=10) #區隔變數:其他六個變數,用他們買東西的數量來區隔出有哪些類型的客戶
summary(W)
Channel Region Fresh Milk Grocery Frozen
Ch1:298 Reg1: 77 Min. :0.477 Min. :1.74 Min. :0.477 Min. :1.40
Ch2:142 Reg2: 47 1st Qu.:3.495 1st Qu.:3.19 1st Qu.:3.333 1st Qu.:2.87
Reg3:316 Median :3.930 Median :3.56 Median :3.677 Median :3.18
Mean :3.792 Mean :3.53 Mean :3.666 Mean :3.17
3rd Qu.:4.229 3rd Qu.:3.86 3rd Qu.:4.028 3rd Qu.:3.55
Max. :5.050 Max. :4.87 Max. :4.967 Max. :4.78
Detergents_Paper Delicassen
Min. :0.477 Min. :1.74
1st Qu.:3.495 1st Qu.:3.19
Median :3.930 Median :3.56
Mean :3.792 Mean :3.53
3rd Qu.:4.229 3rd Qu.:3.86
Max. :5.050 Max. :4.87
A2. 兩個區隔變數
hc = W[,3:4] %>% scale %>% dist %>% hclust
plot(hc)
rect.hclust(hc, k=5, border="red")

1.做集群分析前要先做標準化的動作(scale),讓平均值等於0標準差等於1(例子:像血壓跟薪水的幅度不一樣,投射到空間會相差太遠)
2.hclust必須要丟距離矩陣的資料,所以要先做dist
3.依照自己專業能力判斷要切幾群,最好切在垂直線距離較長的位子
W$group = cutree(hc, k=5) %>% factor
ggplot(W, aes(x=Fresh, y=Milk, col=group)) +
geom_point(size=3, alpha=0.5) +
theme_light()

A3. 六個區隔變數
hc = W[,3:7] %>% scale %>% dist %>% hclust
plot(hc)
W$group = factor(cutree(hc, k=8))
rect.hclust(hc, k=8, border="red")

library(FactoMineR)
library(factoextra)
fviz_dend(
hc, k=8, show_labels=F, rect=T, rect_fill=T,
labels_track_height=0,
palette="ucscgb", rect_border="ucscgb")
A4. 尺度縮減
Dimension Reduction with PCA (Principle Component Analysis, 主成分分析)
W[,3:8] %>% PCA(graph=F) %>% fviz_pca_biplot(
label="var", col.ind=W$group,
pointshape=19, mean.point=F,
addEllipses=T, ellipse.level=0.7,
ellipse.type = "convex", palette="ucscgb",
repel=T
)

1. Cluster Analysis for Movies
主要議題:依類型(Genre)對電影分類
學習重點:
- 集群分析的基本觀念
- 距離矩陣:Distance Matrix
- 層級式集群分析:Hierarchical Cluster Analysis
- 樹狀圖(Dendrogram)的判讀
- 依據樹狀圖決定要分多少群
- 以群組平均值檢視各族群的屬性
1.1 整理資料
M = read.table("data/movieLens.txt", header=FALSE, sep="|",quote="\"")
# Assign column names
colnames(M) = c(
"ID", "Title", "ReleaseDate", "VideoReleaseDate", "IMDB",
"Unknown", "Action", "Adventure", "Animation", "Childrens",
"Comedy", "Crime", "Documentary", "Drama", "Fantasy", "FilmNoir",
"Horror", "Musical", "Mystery", "Romance", "SciFi", "Thriller",
"War", "Western")
# Remove unnecessary variables
M$ID = NULL
M$ReleaseDate = NULL
M$VideoReleaseDate = NULL
M$IMDB = NULL
# Remove duplicates
M = unique(M)
1.2 檢視資料
head(M, 5)
sum(M$Comedy) # 喜劇片
[1] 502
sum(M$Western) # 西部片
[1] 27
sum(M$Romance | M$Drama) # 浪漫劇情片
[1] 863
1.3 距離矩陣
dmx= dist(M[2:20], method="euclidean")
dmx %>% as.matrix %>% dim
[1] 1664 1664
1.4 層級式集群分析
hclust1 = hclust(dmx, method = "ward.D")
1.5 檢視樹狀圖
plot(hclust1)
rect.hclust(hclust1, k=5, border="red")

1.6 切割群組
grp = cutree(hclust1, k = 5)
table(grp)
grp
1 2 3 4 5
824 370 209 196 65
1.7 檢查群組屬性
tapply(M$Action, grp, mean) #bygroup統計action的平均值,基本上是在統計個群集為動作片的比率
1 2 3 4 5
0.28641 0.00000 0.00000 0.06633 0.00000
tapply(M$Romance, grp, mean)
1 2 3 4 5
0.05825 0.00000 0.00000 1.00000 0.00000
1.8 The sapply-split-... Combo:
sapply(split(M[,2:20], grp), colMeans) %>% round(3) #round:小數點後三位
1 2 3 4 5
Unknown 0.002 0 0 0.000 0
Action 0.286 0 0 0.066 0
Adventure 0.161 0 0 0.000 0
Animation 0.051 0 0 0.000 0
Childrens 0.146 0 0 0.000 0
Comedy 0.177 0 1 0.418 1
Crime 0.123 0 0 0.031 0
Documentary 0.061 0 0 0.000 0
Drama 0.238 1 0 0.434 1
Fantasy 0.027 0 0 0.000 0
FilmNoir 0.028 0 0 0.005 0
Horror 0.107 0 0 0.010 0
Musical 0.068 0 0 0.000 0
Mystery 0.073 0 0 0.000 0
Romance 0.058 0 0 1.000 0
SciFi 0.121 0 0 0.000 0
Thriller 0.279 0 0 0.092 0
War 0.086 0 0 0.000 0
Western 0.033 0 0 0.000 0
#對資料框根據group切分成五個資料框,並呼叫collumn by group做平均
#對一個資料框根據他的類別切成若干個資料框,重複做means
1.9 資料視覺化
layout(matrix(c(1,2,2), 3, 1))
par(mar=c(2,3,1,1), cex=0.8)
table(grp) %>% barplot(col=3:7, names.arg=paste0("Group-",1:5))
par(mar=c(6,3,2,1))
sapply(split(M[,2:20], grp), colMeans) %>% t %>%
barplot(beside=T, col=3:7, las=2)

【問題討論】
從管理的角度來看,我們為甚麼要分群?
- 把顧客做分群才能針對不同類型的顧客給予適當的行銷策略
- 分群等同於把市場顧客分成各種小區塊,才能創造出差異化行銷
我們為甚麼要做尺度縮減?
- 當區隔變數很多時,我們一般很難在二維空間上看出多維度的變化
- 因此尺度縮減+資料視覺化可以幫助我們找到一個壓下來資料損失最小的角度,以便我們解釋區隔變數之間的差異
我們要如何把集群分析的結果轉化為策略呢?
- 分群後的結果會顯示出哪些類型的顧客通常常買哪些商品、不常買哪些產品,就可以依據顧客類型做差異化行銷
- 針對銷量較少的產品做出改善方案
2. Flower Image
2.1 整理資料
# Read data
flower = read.csv("data/flower.csv", header=FALSE)
# Change the data type to matrix
flowerMatrix = as.matrix(flower)
dim(flowerMatrix)
[1] 50 50
# Turn matrix into a vector
flowerVector = as.vector(flowerMatrix)
length(flowerVector)
[1] 2500
2.2 距離矩陣
# Compute distances
distance = dist(flowerVector, method = "euclidean")
2.3 層級式集群分析
# Hierarchical clustering
clusterIntensity = hclust(distance, method="ward.D")
2.4 樹狀圖
# Plot the dendrogram
plot(clusterIntensity)
# Select 3 clusters
rect.hclust(clusterIntensity, k = 3, border = "red")

切割群組
flowerClusters = cutree(clusterIntensity, k = 3)
table(flowerClusters)
flowerClusters
1 2 3
1634 272 594
# flowerClusters
族群平均(畫素顏色深淺度)
# Find mean intensity values
tapply(flowerVector, flowerClusters, mean)
1 2 3
0.08574 0.50826 0.93148
圖像比較
# Plot the image and the clusters
dim(flowerClusters) = c(50,50)
par(mfrow=c(1,2), mar=c(2,2,2,2))
# Original image
image(flowerMatrix,axes=FALSE,col=grey(seq(0,1,length=256)),main="Original")
# New image
image(flowerClusters, axes = FALSE, main="3 Cluster")

3. MRI Image
3.1 整理資料
# Read data
healthy = read.csv("data/healthy.csv", header=FALSE)
healthyMatrix = as.matrix(healthy)
dim(healthyMatrix)
[1] 566 646
3.2 畫出圖形
# Plot image
par(mar=c(1,1,1,1))
image(healthyMatrix,axes=FALSE,col=grey(seq(0,1,length=256)))

3.3 距離矩陣
# Compute distances
healthyVector = as.vector(healthyMatrix)
distance = dist(healthyVector, method = "euclidean")
Error: cannot allocate vector of size 498.0 Gb
【Q】 What is the problem?
- 原本要用層級式集群分析,但轉成vector時資料太大無法做出距離矩陣
- 因此改用kmeans
3.4 KMeans集群分析
# Run k-means
k = 5
set.seed(1)
KMC = kmeans(healthyVector, centers = k, iter.max = 1000)
3.5 檢查分群結果
# View(KMC)
table(KMC$cluster)
1 2 3 4 5
20556 101085 133162 31555 79278
KMC$centers
[,1]
1 0.48177
2 0.10619
3 0.01962
4 0.30943
5 0.18421
3.6 畫出分群結果
# Extract clusters
X = KMC$cluster
# Plot the image with the clusters
dim(X) = c(nrow(healthyMatrix), ncol(healthyMatrix))
# Plot image
par(mar=c(1,1,1,1))
image(X, axes = FALSE, col=rainbow(k))

3.7 讀進、轉換測試圖形
tumor = read.csv("data/tumor.csv", header=FALSE)
tumorMatrix = as.matrix(tumor)
dim(tumorMatrix)
[1] 571 512
tumorVector = as.vector(tumorMatrix)
length(tumorVector)
[1] 292352
3.8 將原圖形之分群規則套用到測試圖形
# Apply clusters from before to new image, using the flexclust package
library(flexclust)
t0 = Sys.time()
KMC.kcca = flexclust::as.kcca(KMC, healthyVector) # 建立模型
Found more than one class "kcca" in cache; using the first, from namespace 'flexclust'
Also defined by 'kernlab'
Found more than one class "kcca" in cache; using the first, from namespace 'flexclust'
Also defined by 'kernlab'
tumorClusters = predict(KMC.kcca, newdata = tumorVector) # 進行預測(轉換)
Found more than one class "kcca" in cache; using the first, from namespace 'flexclust'
Also defined by 'kernlab'
Sys.time() - t0
Time difference of 1.212 mins
3.9 圖像比較
# Visualize the clusters
dim(tumorClusters) = c(nrow(tumorMatrix), ncol(tumorMatrix))
par(mfrow=c(1,2), mar=c(1,1,2,1))
image(X, axes = FALSE, col=rainbow(k), main="Healthy")
image(t(tumorClusters)[,571:1], axes = FALSE, col=rainbow(k), main="Tumor")

【學習重點】
- 集群分析在圖像處理的應用
- 單區隔變數的集群分析
- 集群分析模型
【問題討論】
層級式和K-Means集群分析有什麼差異? 它們分別用在什麼狀況?
|
kmeans
|
層級式
|
|
一開始必須先說要設幾群
|
把資料丟進去,直到長出樹才決定分多少群
|
|
適用於任何大小的資料
|
資料點太多會做得太慢
|
|
彈性低
|
彈性高
|
集群分析模型和普通的集群分析有什麼差異?
- 集群分析:把x值看作資料點的屬性,根據屬性把資料點分成若干群。分群的結果一開始在資料裡面是看不到,分群的結果基本上是透過分析既有的欄位而產生的新欄位。
- 集群分析模型則會建立許多不同的模型加以評估分析,看出各個模型的差異
- 集群分析的目的在於使群內距離最小、群間距離越大
什麼時候需要建集群分析模型? 集群分析模型的用法?
- 當我們擁有許多欄位的x值,但沒有標的值y可以參考,必須透過分析所有資料之後才會產生的新欄位
- 層級式 & kmeans
圖像處理和圖像辨識有什麼差異?
- 圖像處理:對圖像進行分析、加工和處理
- 圖像辨識:讓機器對圖像進行分析、處理和學習,使機器可以辨識出不同圖像之間的差異
分析方法比較

LS0tDQp0aXRsZTogIkFTNi0wIOmbhue+pOWIhuaekCINCmF1dGhvcjogIuesrOS4gOe1hCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCjxicj4NCg0KYGBge3J9DQpTeXMuc2V0bG9jYWxlKCJMQ19BTEwiLCJDIikNCnBhY2thZ2VzID0gYygNCiAgImRwbHlyIiwiZ2dwbG90MiIsImQzaGVhdG1hcCIsImdvb2dsZVZpcyIsImRldnRvb2xzIiwicGxvdGx5IiwgInhnYm9vc3QiLA0KICAibWFncml0dHIiLCJjYVRvb2xzIiwiUk9DUiIsImNvcnJwbG90IiwgInJwYXJ0IiwgInJwYXJ0LnBsb3QiLA0KICAiZG9QYXJhbGxlbCIsICJjYXJldCIsICJnbG1uZXQiLCAiTWF0cml4IiwgImUxMDcxIiwgInJhbmRvbUZvcmVzdCIsDQogICJmbGV4Y2x1c3QiLCAiRmFjdG9NaW5lUiIsICJmYWN0b2V4dHJhIg0KICApDQpleGlzdGluZyA9IGFzLmNoYXJhY3RlcihpbnN0YWxsZWQucGFja2FnZXMoKVssMV0pDQpmb3IocGtnIGluIHBhY2thZ2VzWyEocGFja2FnZXMgJWluJSBleGlzdGluZyldKSBpbnN0YWxsLnBhY2thZ2VzKHBrZykNCmBgYA0KDQpgYGB7ciBlY2hvPVQsIG1lc3NhZ2U9RiwgY2FjaGU9Riwgd2FybmluZz1GfQ0Kcm0obGlzdD1scyhhbGw9VCkpDQpvcHRpb25zKGRpZ2l0cz00LCBzY2lwZW49MTIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShmbGV4Y2x1c3QpDQpsaWJyYXJ5KEZhY3RvTWluZVIpDQpsaWJyYXJ5KGZhY3RvZXh0cmEpDQpgYGANCg0KLSAtIC0NCg0KIyMjIEEuIOmbhue+pOWIhuaekOiIh+WwuuW6pue4rua4mw0KDQojIyMjIyBBMS4g5om555m85Lqk5piT6LOH5paZDQpgYGB7cn0NClcgPSByZWFkLmNzdignZGF0YS93aG9sZXNhbGVzLmNzdicpDQpXJENoYW5uZWwgPSBmYWN0b3IoIHBhc3RlMCgiQ2giLFckQ2hhbm5lbCkgKSAj5Zyw5Y2ADQpXJFJlZ2lvbiA9IGZhY3RvciggcGFzdGUwKCJSZWciLFckUmVnaW9uKSApICPpgJrot68NCldbMzo4XSA9IGxhcHBseShXWzM6Nl0sIGxvZywgYmFzZT0xMCkgI+WNgOmalOiuiuaVuDrlhbbku5blha3lgIvorormlbjvvIznlKjku5blgJHosrfmnbHopb/nmoTmlbjph4/kvobljYDpmpTlh7rmnInlk6rkupvpoZ7lnovnmoTlrqLmiLYNCnN1bW1hcnkoVykNCmBgYA0KDQojIyMjIyBBMi4g5YWp5YCL5Y2A6ZqU6K6K5pW4DQpgYGB7cn0NCmhjID0gV1ssMzo0XSAlPiUgc2NhbGUgJT4lIGRpc3QgJT4lIGhjbHVzdCANCnBsb3QoaGMpDQpyZWN0LmhjbHVzdChoYywgaz01LCBib3JkZXI9InJlZCIpDQpgYGANCjxwPjEu5YGa6ZuG576k5YiG5p6Q5YmN6KaB5YWI5YGa5qiZ5rqW5YyW55qE5YuV5L2cKHNjYWxlKe+8jOiuk+W5s+Wdh+WAvOetieaWvDDmqJnmupblt67nrYnmlrwxKOS+i+WtkO+8muWDj+ihgOWjk+i3n+iWquawtOeahOW5heW6puS4jeS4gOaoo++8jOaKleWwhOWIsOepuumWk+acg+ebuOW3ruWkqumBoCk8L3A+DQo8cD4yLmhjbHVzdOW/hemgiOimgeS4n+i3nembouefqemZo+eahOizh+aWme+8jOaJgOS7peimgeWFiOWBmmRpc3Q8L3A+DQo8cD4zLuS+neeFp+iHquW3seWwiOalreiDveWKm+WIpOaWt+imgeWIh+W5vue+pO+8jOacgOWlveWIh+WcqOWeguebtOe3mui3nemboui8g+mVt+eahOS9jeWtkDwvcD4NCg0KYGBge3J9DQpXJGdyb3VwID0gY3V0cmVlKGhjLCBrPTUpICU+JSBmYWN0b3IgI+aKiuWIhue+pOe1kOaenOaUvuWcqGdyb3Vw6YCZ5qyE5L2NDQpnZ3Bsb3QoVywgYWVzKHg9RnJlc2gsIHk9TWlsaywgY29sPWdyb3VwKSkgKw0KICBnZW9tX3BvaW50KHNpemU9MywgYWxwaGE9MC41KSArIA0KICB0aGVtZV9saWdodCgpDQpgYGANCg0KIyMjIyMgQTMuIOWFreWAi+WNgOmalOiuiuaVuA0KYGBge3J9DQpoYyA9IFdbLDM6N10gJT4lIHNjYWxlICU+JSBkaXN0ICU+JSBoY2x1c3QNCnBsb3QoaGMpDQpXJGdyb3VwID0gZmFjdG9yKGN1dHJlZShoYywgaz04KSkNCnJlY3QuaGNsdXN0KGhjLCBrPTgsIGJvcmRlcj0icmVkIikNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkoRmFjdG9NaW5lUikNCmxpYnJhcnkoZmFjdG9leHRyYSkNCmZ2aXpfZGVuZCgNCiAgaGMsIGs9OCwgc2hvd19sYWJlbHM9RiwgcmVjdD1ULCByZWN0X2ZpbGw9VCwNCiAgbGFiZWxzX3RyYWNrX2hlaWdodD0wLA0KICBwYWxldHRlPSJ1Y3NjZ2IiLCByZWN0X2JvcmRlcj0idWNzY2diIikNCmBgYA0KDQojIyMjIyBBNC4g5bC65bqm57iu5ribIA0KRGltZW5zaW9uIFJlZHVjdGlvbiB3aXRoIFBDQSAoUHJpbmNpcGxlIENvbXBvbmVudCBBbmFseXNpcywg5Li75oiQ5YiG5YiG5p6QKQ0KYGBge3IgZmlnLmhlaWdodD03LCBmaWcud2lkdGg9OX0NCldbLDM6OF0gJT4lIFBDQShncmFwaD1GKSAlPiUgZnZpel9wY2FfYmlwbG90KA0KICBsYWJlbD0idmFyIiwgY29sLmluZD1XJGdyb3VwLA0KICBwb2ludHNoYXBlPTE5LCBtZWFuLnBvaW50PUYsDQogIGFkZEVsbGlwc2VzPVQsIGVsbGlwc2UubGV2ZWw9MC43LA0KICBlbGxpcHNlLnR5cGUgPSAiY29udmV4IiwgcGFsZXR0ZT0idWNzY2diIiwNCiAgcmVwZWw9VA0KICApDQpgYGANCjxicj4NCg0KLSAtIC0NCg0KIyMjIDEuIENsdXN0ZXIgQW5hbHlzaXMgZm9yIE1vdmllcyAgDQoNCioq5Li76KaB6K2w6aGM77ya5L6d6aGe5Z6LKEdlbnJlKeWwjembu+W9seWIhumhnioqDQoNCioq5a2457+S6YeN6bue77yaKioNCg0KKyDpm4bnvqTliIbmnpDnmoTln7rmnKzop4Dlv7UNCisg6Led6Zui55+p6Zmj77yaRGlzdGFuY2UgTWF0cml4DQorIOWxpOe0muW8j+mbhue+pOWIhuaekO+8mkhpZXJhcmNoaWNhbCBDbHVzdGVyIEFuYWx5c2lzDQorIOaoueeLgOWclihEZW5kcm9ncmFtKeeahOWIpOiugA0KKyDkvp3mk5rmqLnni4DlnJbmsbrlrpropoHliIblpJrlsJHnvqQNCisg5Lul576k57WE5bmz5Z2H5YC85qqi6KaW5ZCE5peP576k55qE5bGs5oCnDQoNCjxicj4NCg0KIyMjIyMgMS4xIOaVtOeQhuizh+aWmQ0KYGBge3J9DQpNID0gcmVhZC50YWJsZSgiZGF0YS9tb3ZpZUxlbnMudHh0IiwgaGVhZGVyPUZBTFNFLCBzZXA9InwiLHF1b3RlPSJcIiIpDQoNCiMgQXNzaWduIGNvbHVtbiBuYW1lcw0KY29sbmFtZXMoTSkgPSBjKA0KICAiSUQiLCAiVGl0bGUiLCAiUmVsZWFzZURhdGUiLCAiVmlkZW9SZWxlYXNlRGF0ZSIsICJJTURCIiwgDQogICJVbmtub3duIiwgIkFjdGlvbiIsICJBZHZlbnR1cmUiLCAiQW5pbWF0aW9uIiwgIkNoaWxkcmVucyIsIA0KICAiQ29tZWR5IiwgIkNyaW1lIiwgIkRvY3VtZW50YXJ5IiwgIkRyYW1hIiwgIkZhbnRhc3kiLCAiRmlsbU5vaXIiLCANCiAgIkhvcnJvciIsICJNdXNpY2FsIiwgIk15c3RlcnkiLCAiUm9tYW5jZSIsICJTY2lGaSIsICJUaHJpbGxlciIsDQogICJXYXIiLCAiV2VzdGVybiIpDQoNCiMgUmVtb3ZlIHVubmVjZXNzYXJ5IHZhcmlhYmxlcw0KTSRJRCA9IE5VTEwNCk0kUmVsZWFzZURhdGUgPSBOVUxMDQpNJFZpZGVvUmVsZWFzZURhdGUgPSBOVUxMDQpNJElNREIgPSBOVUxMDQoNCiMgUmVtb3ZlIGR1cGxpY2F0ZXMNCk0gPSB1bmlxdWUoTSkNCmBgYA0KDQojIyMjIyAxLjIg5qqi6KaW6LOH5paZDQpgYGB7cn0NCmhlYWQoTSwgNSkNCmBgYA0KDQpgYGB7cn0NCnN1bShNJENvbWVkeSkgICAgICAgICAgICAgIyDllpzliofniYcNCnN1bShNJFdlc3Rlcm4pICAgICAgICAgICAgIyDopb/pg6jniYcNCnN1bShNJFJvbWFuY2UgfCBNJERyYW1hKSAgIyDmtarmvKvliofmg4XniYcNCmBgYA0KDQojIyMjIyAxLjMg6Led6Zui55+p6ZmjDQpgYGB7cn0NCmRteD0gZGlzdChNWzI6MjBdLCBtZXRob2Q9ImV1Y2xpZGVhbiIpDQpkbXggJT4lIGFzLm1hdHJpeCAlPiUgZGltDQpgYGANCg0KIyMjIyMgMS40IOWxpOe0muW8j+mbhue+pOWIhuaekA0KYGBge3J9DQpoY2x1c3QxID0gaGNsdXN0KGRteCwgbWV0aG9kID0gIndhcmQuRCIpIA0KYGBgDQoNCiMjIyMjIDEuNSDmqqLoppbmqLnni4DlnJYNCmBgYHtyfQ0KcGxvdChoY2x1c3QxKQ0KcmVjdC5oY2x1c3QoaGNsdXN0MSwgaz01LCBib3JkZXI9InJlZCIpDQpgYGANCg0KIyMjIyMgMS42IOWIh+WJsue+pOe1hA0KYGBge3J9DQpncnAgPSBjdXRyZWUoaGNsdXN0MSwgayA9IDUpDQp0YWJsZShncnApDQpgYGANCg0KIyMjIyMgMS43IOaqouafpee+pOe1hOWxrOaApw0KYGBge3J9DQp0YXBwbHkoTSRBY3Rpb24sIGdycCwgbWVhbikgI2J5Z3JvdXDntbHoqIhhY3Rpb27nmoTlubPlnYflgLzvvIzln7rmnKzkuIrmmK/lnKjntbHoqIjlgIvnvqTpm4bngrrli5XkvZzniYfnmoTmr5TnjocNCnRhcHBseShNJFJvbWFuY2UsIGdycCwgbWVhbikNCmBgYA0KDQojIyMjIyAxLjggVGhlIGBzYXBwbHlgLWBzcGxpdGAtYC4uLmAgQ29tYm/vvJoNCmBgYHtyfQ0Kc2FwcGx5KHNwbGl0KE1bLDI6MjBdLCBncnApLCBjb2xNZWFucykgJT4lIHJvdW5kKDMpICNyb3VuZDrlsI/mlbjpu57lvozkuInkvY0NCiPlsI3os4fmlpnmoYbmoLnmk5pncm91cOWIh+WIhuaIkOS6lOWAi+izh+aWmeahhizkuKblkbzlj6tjb2xsdW1uIGJ5IGdyb3Vw5YGa5bmz5Z2HDQoj5bCN5LiA5YCL6LOH5paZ5qGG5qC55pOa5LuW55qE6aGe5Yil5YiH5oiQ6Iul5bmy5YCL6LOH5paZ5qGG77yM6YeN6KSH5YGabWVhbnMNCmBgYA0KDQojIyMjIyAxLjkg6LOH5paZ6KaW6Ka65YyWDQpgYGB7cn0NCmxheW91dChtYXRyaXgoYygxLDIsMiksIDMsIDEpKQ0KcGFyKG1hcj1jKDIsMywxLDEpLCBjZXg9MC44KQ0KdGFibGUoZ3JwKSAlPiUgYmFycGxvdChjb2w9Mzo3LCBuYW1lcy5hcmc9cGFzdGUwKCJHcm91cC0iLDE6NSkpDQpwYXIobWFyPWMoNiwzLDIsMSkpDQpzYXBwbHkoc3BsaXQoTVssMjoyMF0sIGdycCksIGNvbE1lYW5zKSAlPiUgdCAlPiUgDQogIGJhcnBsb3QoYmVzaWRlPVQsIGNvbD0zOjcsIGxhcz0yKQ0KDQpgYGANCg0KIyMjIyMg44CQ5ZWP6aGM6KiO6KuW44CRDQoNCuW+nueuoeeQhueahOinkuW6puS+hueci++8jOaIkeWAkeeCuueUmum6vOimgeWIhue+pO+8nyANCg0KKyDmiorpoaflrqLlgZrliIbnvqTmiY3og73ph53lsI3kuI3lkIzpoZ7lnovnmoTpoaflrqLntabkuojpgannlbbnmoTooYzpirfnrZbnlaUNCisg5YiG576k562J5ZCM5pa85oqK5biC5aC06aGn5a6i5YiG5oiQ5ZCE56iu5bCP5Y2A5aGK77yM5omN6IO95Ym16YCg5Ye65beu55Ww5YyW6KGM6Yq3DQoNCg0K5oiR5YCR54K655Sa6bq86KaB5YGa5bC65bqm57iu5rib77yfIA0KDQorIOeVtuWNgOmalOiuiuaVuOW+iOWkmuaZgu+8jOaIkeWAkeS4gOiIrOW+iOmbo+WcqOS6jOe2reepuumWk+S4iueci+WHuuWkmue2reW6pueahOiuiuWMlg0KKyDlm6DmraTlsLrluqbnuK7muJsr6LOH5paZ6KaW6Ka65YyW5Y+v5Lul5bmr5Yqp5oiR5YCR5om+5Yiw5LiA5YCL5aOT5LiL5L6G6LOH5paZ5pCN5aSx5pyA5bCP55qE6KeS5bqm77yM5Lul5L6/5oiR5YCR6Kej6YeL5Y2A6ZqU6K6K5pW45LmL6ZaT55qE5beu55WwDQoNCg0K5oiR5YCR6KaB5aaC5L2V5oqK6ZuG576k5YiG5p6Q55qE57WQ5p6c6L2J5YyW54K6562W55Wl5ZGi77yfIA0KDQorIOWIhue+pOW+jOeahOe1kOaenOacg+mhr+ekuuWHuuWTquS6m+mhnuWei+eahOmhp+WuoumAmuW4uOW4uOiyt+WTquS6m+WVhuWTgeOAgeS4jeW4uOiyt+WTquS6m+eUouWTge+8jOWwseWPr+S7peS+neaTmumhp+WuoumhnuWei+WBmuW3rueVsOWMluihjOmKtw0KKyDph53lsI3pirfph4/ovIPlsJHnmoTnlKLlk4HlgZrlh7rmlLnlloTmlrnmoYgNCg0KPGJyPg0KDQotIC0gLQ0KDQojIyMgMi4gRmxvd2VyIEltYWdlDQoNCiMjIyMjIDIuMSDmlbTnkIbos4fmlpkNCmBgYHtyfQ0KIyBSZWFkIGRhdGENCmZsb3dlciA9IHJlYWQuY3N2KCJkYXRhL2Zsb3dlci5jc3YiLCBoZWFkZXI9RkFMU0UpDQoNCiMgQ2hhbmdlIHRoZSBkYXRhIHR5cGUgdG8gbWF0cml4DQpmbG93ZXJNYXRyaXggPSBhcy5tYXRyaXgoZmxvd2VyKQ0KZGltKGZsb3dlck1hdHJpeCkNCg0KIyBUdXJuIG1hdHJpeCBpbnRvIGEgdmVjdG9yDQpmbG93ZXJWZWN0b3IgPSBhcy52ZWN0b3IoZmxvd2VyTWF0cml4KQ0KbGVuZ3RoKGZsb3dlclZlY3RvcikNCmBgYA0KDQojIyMjIyAyLjIg6Led6Zui55+p6ZmjDQpgYGB7cn0NCiMgQ29tcHV0ZSBkaXN0YW5jZXMNCmRpc3RhbmNlID0gZGlzdChmbG93ZXJWZWN0b3IsIG1ldGhvZCA9ICJldWNsaWRlYW4iKQ0KYGBgDQoNCiMjIyMjIDIuMyDlsaTntJrlvI/pm4bnvqTliIbmnpANCmBgYHtyfQ0KIyBIaWVyYXJjaGljYWwgY2x1c3RlcmluZw0KY2x1c3RlckludGVuc2l0eSA9IGhjbHVzdChkaXN0YW5jZSwgbWV0aG9kPSJ3YXJkLkQiKQ0KYGBgDQoNCiMjIyMjIDIuNCDmqLnni4DlnJYNCmBgYHtyfQ0KIyBQbG90IHRoZSBkZW5kcm9ncmFtDQpwbG90KGNsdXN0ZXJJbnRlbnNpdHkpDQojIFNlbGVjdCAzIGNsdXN0ZXJzDQpyZWN0LmhjbHVzdChjbHVzdGVySW50ZW5zaXR5LCBrID0gMywgYm9yZGVyID0gInJlZCIpDQpgYGANCg0KIyMjIyMg5YiH5Ymy576k57WEDQpgYGB7cn0NCmZsb3dlckNsdXN0ZXJzID0gY3V0cmVlKGNsdXN0ZXJJbnRlbnNpdHksIGsgPSAzKQ0KdGFibGUoZmxvd2VyQ2x1c3RlcnMpDQojIGZsb3dlckNsdXN0ZXJzDQpgYGANCg0KIyMjIyMg5peP576k5bmz5Z2HKOeVq+e0oOmhj+iJsua3sea3uuW6pikNCmBgYHtyfQ0KIyBGaW5kIG1lYW4gaW50ZW5zaXR5IHZhbHVlcw0KdGFwcGx5KGZsb3dlclZlY3RvciwgZmxvd2VyQ2x1c3RlcnMsIG1lYW4pDQpgYGANCg0KIyMjIyMg5ZyW5YOP5q+U6LyDDQpgYGB7ciBmaWcuaGVpZ2h0PTMuMiwgZmlnLndpZHRoPTYuNH0NCiMgUGxvdCB0aGUgaW1hZ2UgYW5kIHRoZSBjbHVzdGVycw0KZGltKGZsb3dlckNsdXN0ZXJzKSA9IGMoNTAsNTApDQpwYXIobWZyb3c9YygxLDIpLCBtYXI9YygyLDIsMiwyKSkNCg0KIyBPcmlnaW5hbCBpbWFnZQ0KaW1hZ2UoZmxvd2VyTWF0cml4LGF4ZXM9RkFMU0UsY29sPWdyZXkoc2VxKDAsMSxsZW5ndGg9MjU2KSksbWFpbj0iT3JpZ2luYWwiKQ0KDQojIE5ldyBpbWFnZQ0KaW1hZ2UoZmxvd2VyQ2x1c3RlcnMsIGF4ZXMgPSBGQUxTRSwgbWFpbj0iMyBDbHVzdGVyIikNCmBgYA0KPGJyPg0KDQotIC0gLQ0KDQojIyMgMy4gTVJJIEltYWdlDQoNCiMjIyMjIDMuMSDmlbTnkIbos4fmlpkNCmBgYHtyfQ0KIyBSZWFkIGRhdGENCmhlYWx0aHkgPSByZWFkLmNzdigiZGF0YS9oZWFsdGh5LmNzdiIsIGhlYWRlcj1GQUxTRSkNCmhlYWx0aHlNYXRyaXggPSBhcy5tYXRyaXgoaGVhbHRoeSkNCmRpbShoZWFsdGh5TWF0cml4KQ0KYGBgDQoNCiMjIyMjIDMuMiDnlavlh7rlnJblvaINCmBgYHtyIGZpZy53aWR0aD0yLjgzLCBmaWcuaGVpZ2h0PTMuMjN9DQojIFBsb3QgaW1hZ2UNCnBhcihtYXI9YygxLDEsMSwxKSkNCmltYWdlKGhlYWx0aHlNYXRyaXgsYXhlcz1GQUxTRSxjb2w9Z3JleShzZXEoMCwxLGxlbmd0aD0yNTYpKSkNCmBgYA0KDQojIyMjIyAzLjMg6Led6Zui55+p6ZmjDQpgYGB7cn0NCiMgQ29tcHV0ZSBkaXN0YW5jZXMNCmhlYWx0aHlWZWN0b3IgPSBhcy52ZWN0b3IoaGVhbHRoeU1hdHJpeCkNCmRpc3RhbmNlID0gZGlzdChoZWFsdGh5VmVjdG9yLCBtZXRob2QgPSAiZXVjbGlkZWFuIikNCmBgYA0KDQoqKuOAkFHjgJEqKiBXaGF0IGlzIHRoZSBwcm9ibGVtPw0KDQorIOWOn+acrOimgeeUqOWxpOe0muW8j+mbhue+pOWIhuaekO+8jOS9hui9ieaIkHZlY3RvcuaZguizh+aWmeWkquWkp+eEoeazleWBmuWHuui3nembouefqemZow0KKyDlm6DmraTmlLnnlKhrbWVhbnMNCg0KDQojIyMjIyAzLjQgS01lYW5z6ZuG576k5YiG5p6QDQpgYGB7cn0NCiMgUnVuIGstbWVhbnMNCmsgPSA1DQpzZXQuc2VlZCgxKQ0KS01DID0ga21lYW5zKGhlYWx0aHlWZWN0b3IsIGNlbnRlcnMgPSBrLCBpdGVyLm1heCA9IDEwMDApDQpgYGANCg0KIyMjIyMgMy41IOaqouafpeWIhue+pOe1kOaenA0KYGBge3J9DQojIFZpZXcoS01DKQ0KdGFibGUoS01DJGNsdXN0ZXIpDQpLTUMkY2VudGVycw0KYGBgDQoNCiMjIyMjIDMuNiDnlavlh7rliIbnvqTntZDmnpwNCmBgYHtyIGZpZy53aWR0aD0yLjgzLCBmaWcuaGVpZ2h0PTMuMjN9DQojIEV4dHJhY3QgY2x1c3RlcnMNClggPSBLTUMkY2x1c3Rlcg0KDQojIFBsb3QgdGhlIGltYWdlIHdpdGggdGhlIGNsdXN0ZXJzDQpkaW0oWCkgPSBjKG5yb3coaGVhbHRoeU1hdHJpeCksIG5jb2woaGVhbHRoeU1hdHJpeCkpDQoNCiMgUGxvdCBpbWFnZQ0KcGFyKG1hcj1jKDEsMSwxLDEpKQ0KaW1hZ2UoWCwgYXhlcyA9IEZBTFNFLCBjb2w9cmFpbmJvdyhrKSkNCmBgYA0KDQojIyMjIyAzLjcg6K6A6YCy44CB6L2J5o+b5ris6Kmm5ZyW5b2iDQpgYGB7cn0NCnR1bW9yID0gcmVhZC5jc3YoImRhdGEvdHVtb3IuY3N2IiwgaGVhZGVyPUZBTFNFKQ0KdHVtb3JNYXRyaXggPSBhcy5tYXRyaXgodHVtb3IpDQpkaW0odHVtb3JNYXRyaXgpDQp0dW1vclZlY3RvciA9IGFzLnZlY3Rvcih0dW1vck1hdHJpeCkNCmxlbmd0aCh0dW1vclZlY3RvcikNCmBgYA0KDQojIyMjIyAzLjgg5bCH5Y6f5ZyW5b2i5LmL5YiG576k6KaP5YmH5aWX55So5Yiw5ris6Kmm5ZyW5b2iDQpgYGB7cn0NCiMgQXBwbHkgY2x1c3RlcnMgZnJvbSBiZWZvcmUgdG8gbmV3IGltYWdlLCB1c2luZyB0aGUgZmxleGNsdXN0IHBhY2thZ2UNCmxpYnJhcnkoZmxleGNsdXN0KQ0KdDAgPSBTeXMudGltZSgpDQpLTUMua2NjYSA9IGZsZXhjbHVzdDo6YXMua2NjYShLTUMsIGhlYWx0aHlWZWN0b3IpICAgICAgICAjIOW7uueri+aooeWeiw0KdHVtb3JDbHVzdGVycyA9IHByZWRpY3QoS01DLmtjY2EsIG5ld2RhdGEgPSB0dW1vclZlY3RvcikgIyDpgLLooYzpoJDmuKwo6L2J5o+bKQ0KU3lzLnRpbWUoKSAtIHQwDQpgYGANCg0KIyMjIyMgMy45IOWcluWDj+avlOi8gw0KYGBge3IgZmlnLmhlaWdodD0zLjIsIGZpZy53aWR0aD02fQ0KIyBWaXN1YWxpemUgdGhlIGNsdXN0ZXJzDQpkaW0odHVtb3JDbHVzdGVycykgPSBjKG5yb3codHVtb3JNYXRyaXgpLCBuY29sKHR1bW9yTWF0cml4KSkNCg0KcGFyKG1mcm93PWMoMSwyKSwgbWFyPWMoMSwxLDIsMSkpDQppbWFnZShYLCBheGVzID0gRkFMU0UsIGNvbD1yYWluYm93KGspLCBtYWluPSJIZWFsdGh5IikNCmltYWdlKHQodHVtb3JDbHVzdGVycylbLDU3MToxXSwgYXhlcyA9IEZBTFNFLCBjb2w9cmFpbmJvdyhrKSwgbWFpbj0iVHVtb3IiKQ0KYGBgDQoNCiMjIyMjIOOAkOWtuOe/kumHjem7nuOAkQ0KDQorIOmbhue+pOWIhuaekOWcqOWcluWDj+iZleeQhueahOaHieeUqA0KKyDllq7ljYDpmpTorormlbjnmoTpm4bnvqTliIbmnpANCisg6ZuG576k5YiG5p6Q5qih5Z6LDQoNCiMjIyMjIOOAkOWVj+mhjOiojuirluOAkQ0KDQrlsaTntJrlvI/lkoxLLU1lYW5z6ZuG576k5YiG5p6Q5pyJ5LuA6bq85beu55Ww77yfIOWug+WAkeWIhuWIpeeUqOWcqOS7gOm6vOeLgOazge+8nw0KDQo8dGFibGUgd2lkdGg9IjQ1MCIsYm9yZGVyPSIxIj4NCjx0cj4NCjx0ZD5rbWVhbnM8L3RkPg0KPHRkPuWxpOe0muW8jzwvdGQ+DQo8L3RyPg0KPHRyPg0KPHRkPuS4gOmWi+Wni+W/hemgiOWFiOiqquimgeioreW5vue+pDwvdGQ+DQo8dGQ+5oqK6LOH5paZ5Lif6YCy5Y6777yM55u05Yiw6ZW35Ye65qi55omN5rG65a6a5YiG5aSa5bCR576kPC90ZD4NCjwvdHI+DQo8dHI+DQo8dGQ+6YGp55So5pa85Lu75L2V5aSn5bCP55qE6LOH5paZPC90ZD4NCjx0ZD7os4fmlpnpu57lpKrlpJrmnIPlgZrlvpflpKrmhaI8L3RkPg0KPC90cj4NCjx0cj4NCjx0ZD7lvYjmgKfkvY48L3RkPg0KPHRkPuW9iOaAp+mrmDwvdGQ+DQo8L3RyPg0KPC90YWJsZT4NCjxicj4NCg0K6ZuG576k5YiG5p6Q5qih5Z6L5ZKM5pmu6YCa55qE6ZuG576k5YiG5p6Q5pyJ5LuA6bq85beu55Ww77yfIA0KDQorIOmbhue+pOWIhuaekDrmiop45YC855yL5L2c6LOH5paZ6bue55qE5bGs5oCn77yM5qC55pOa5bGs5oCn5oqK6LOH5paZ6bue5YiG5oiQ6Iul5bmy576k44CC5YiG576k55qE57WQ5p6c5LiA6ZaL5aeL5Zyo6LOH5paZ6KOh6Z2i5piv55yL5LiN5Yiw77yM5YiG576k55qE57WQ5p6c5Z+65pys5LiK5piv6YCP6YGO5YiG5p6Q5pei5pyJ55qE5qyE5L2N6ICM55Si55Sf55qE5paw5qyE5L2N44CCDQorIOmbhue+pOWIhuaekOaooeWei+WJh+acg+W7uueri+ioseWkmuS4jeWQjOeahOaooeWei+WKoOS7peipleS8sOWIhuaekO+8jOeci+WHuuWQhOWAi+aooeWei+eahOW3rueVsA0KKyDpm4bnvqTliIbmnpDnmoTnm67nmoTlnKjmlrzkvb/nvqTlhafot53pm6LmnIDlsI/jgIHnvqTplpPot53pm6LotorlpKcNCg0KDQrku4DpurzmmYLlgJnpnIDopoHlu7rpm4bnvqTliIbmnpDmqKHlnovvvJ8g6ZuG576k5YiG5p6Q5qih5Z6L55qE55So5rOV77yfDQoNCisg55W25oiR5YCR5pOB5pyJ6Kix5aSa5qyE5L2N55qEeOWAvO+8jOS9huaykuacieaomeeahOWAvHnlj6/ku6Xlj4PogIPvvIzlv4XpoIjpgI/pgY7liIbmnpDmiYDmnInos4fmlpnkuYvlvozmiY3mnIPnlKLnlJ/nmoTmlrDmrITkvY0NCisg5bGk57Sa5byPICYga21lYW5zDQoNCuWcluWDj+iZleeQhuWSjOWcluWDj+i+qOitmOacieS7gOm6vOW3rueVsO+8nw0KDQorIOWcluWDj+iZleeQhjrlsI3lnJblg4/pgLLooYzliIbmnpDjgIHliqDlt6XlkozomZXnkIYNCisg5ZyW5YOP6L6o6K2YOuiuk+apn+WZqOWwjeWcluWDj+mAsuihjOWIhuaekOOAgeiZleeQhuWSjOWtuOe/ku+8jOS9v+apn+WZqOWPr+S7pei+qOitmOWHuuS4jeWQjOWcluWDj+S5i+mWk+eahOW3rueVsA0KDQo8YnI+DQoNCjxwPuWIhuaekOaWueazleavlOi8gzwvcD4NCjxpbWcgc3JjPSJDOi9Vc2Vycy9VU0VSL0Rvd25sb2Fkcy9tb2RlbC5qcGciPg0KDQotIC0gLQ0KDQo8YnI+PGJyPjxicj48YnI+PGJyPg0KDQo8c3R5bGU+DQouY2FwdGlvbiB7DQogIGNvbG9yOiAjNzc3Ow0KICBtYXJnaW4tdG9wOiAxMHB4Ow0KfQ0KcCBjb2RlIHsNCiAgd2hpdGUtc3BhY2U6IGluaGVyaXQ7DQp9DQpwcmUgew0KICB3b3JkLWJyZWFrOiBub3JtYWw7DQogIHdvcmQtd3JhcDogbm9ybWFsOw0KICBsaW5lLWhlaWdodDogMTsNCn0NCnByZSBjb2RlIHsNCiAgd2hpdGUtc3BhY2U6IGluaGVyaXQ7DQp9DQpwLGxpIHsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCi5yew0KICBsaW5lLWhlaWdodDogMS4yOw0KfQ0KDQp0aXRsZXsNCiAgY29sb3I6ICNjYzAwMDA7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQpib2R5ew0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KaDEsaDIsaDMsaDQsaDV7DQogIGNvbG9yOiAjMDA4ODAwOw0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KaDN7DQogIGNvbG9yOiAjYjM2YjAwOw0KICBiYWNrZ3JvdW5kOiAjZmZlMGIzOw0KICBsaW5lLWhlaWdodDogMjsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQoNCmg1ew0KICBjb2xvcjogIzAwNjAwMDsNCiAgYmFja2dyb3VuZDogI2ZmZmZlMDsNCiAgbGluZS1oZWlnaHQ6IDI7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KDQplbXsNCiAgY29sb3I6ICMwMDAwYzA7DQogIGJhY2tncm91bmQ6ICNmMGYwZjA7DQogIH0NCiAgDQp0YWJsZSx0aCx0ZHsNCiAgYm9yZGVyOjFweCBzb2xpZCBibGFjazsNCn0NCn0NCjwvc3R5bGU+DQoNCg==