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")

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)對電影分類

學習重點:


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)
              Title Unknown Action Adventure Animation Childrens Comedy Crime Documentary
1  Toy Story (1995)       0      0         0         1         1      1     0           0
2  GoldenEye (1995)       0      1         1         0         0      0     0           0
3 Four Rooms (1995)       0      0         0         0         0      0     0           0
4 Get Shorty (1995)       0      1         0         0         0      1     0           0
5    Copycat (1995)       0      0         0         0         0      0     1           0
  Drama Fantasy FilmNoir Horror Musical Mystery Romance SciFi Thriller War Western
1     0       0        0      0       0       0       0     0        0   0       0
2     0       0        0      0       0       0       0     0        1   0       0
3     0       0        0      0       0       0       0     0        1   0       0
4     1       0        0      0       0       0       0     0        0   0       0
5     1       0        0      0       0       0       0     0        1   0       0
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)
      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)
                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
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?

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)        # 建立模型
tumorClusters = predict(KMC.kcca, newdata = tumorVector) # 進行預測(轉換)
Sys.time() - t0
Time difference of 31.98 secs
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集群分析有什麼差異? 它們分別用在什麼狀況?

集群分析模型和普通的集群分析有什麼差異?

什麼時候需要建集群分析模型? 集群分析模型的用法?

圖像處理和圖像辨識有什麼差異?








LS0tDQp0aXRsZTogIkFTNi0wIOmbhue+pOWIhuaekCINCmF1dGhvcjogIuWNk+mbjeeEtiBEOTk0MDEwMDAxLCAyMDE4LzA3LzIyIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KPGJyPg0KDQpgYGB7cn0NClN5cy5zZXRsb2NhbGUoIkxDX0FMTCIsIkMiKQ0KcGFja2FnZXMgPSBjKA0KICAiZHBseXIiLCJnZ3Bsb3QyIiwiZDNoZWF0bWFwIiwiZ29vZ2xlVmlzIiwiZGV2dG9vbHMiLCJwbG90bHkiLCAieGdib29zdCIsDQogICJtYWdyaXR0ciIsImNhVG9vbHMiLCJST0NSIiwiY29ycnBsb3QiLCAicnBhcnQiLCAicnBhcnQucGxvdCIsDQogICJkb1BhcmFsbGVsIiwgImNhcmV0IiwgImdsbW5ldCIsICJNYXRyaXgiLCAiZTEwNzEiLCAicmFuZG9tRm9yZXN0IiwNCiAgImZsZXhjbHVzdCIsICJGYWN0b01pbmVSIiwgImZhY3RvZXh0cmEiDQogICkNCmV4aXN0aW5nID0gYXMuY2hhcmFjdGVyKGluc3RhbGxlZC5wYWNrYWdlcygpWywxXSkNCmZvcihwa2cgaW4gcGFja2FnZXNbIShwYWNrYWdlcyAlaW4lIGV4aXN0aW5nKV0pIGluc3RhbGwucGFja2FnZXMocGtnKQ0KYGBgDQoNCmBgYHtyIGVjaG89VCwgbWVzc2FnZT1GLCBjYWNoZT1GLCB3YXJuaW5nPUZ9DQpybShsaXN0PWxzKGFsbD1UKSkNCm9wdGlvbnMoZGlnaXRzPTQsIHNjaXBlbj0xMikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGZsZXhjbHVzdCkNCmxpYnJhcnkoRmFjdG9NaW5lUikNCmxpYnJhcnkoZmFjdG9leHRyYSkNCmBgYA0KDQotIC0gLQ0KDQojIyMgQS4g6ZuG576k5YiG5p6Q6IiH5bC65bqm57iu5ribDQoNCiMjIyMjIEExLiDmibnnmbzkuqTmmJPos4fmlpkNCmBgYHtyfQ0KVyA9IHJlYWQuY3N2KCdkYXRhL3dob2xlc2FsZXMuY3N2JykNClckQ2hhbm5lbCA9IGZhY3RvciggcGFzdGUwKCJDaCIsVyRDaGFubmVsKSApDQpXJFJlZ2lvbiA9IGZhY3RvciggcGFzdGUwKCJSZWciLFckUmVnaW9uKSApDQpXWzM6OF0gPSBsYXBwbHkoV1szOjZdLCBsb2csIGJhc2U9MTApDQpzdW1tYXJ5KFcpDQpgYGANCg0KIyMjIyMgQTIuIOWFqeWAi+WNgOmalOiuiuaVuA0KYGBge3J9DQpoYyA9IFdbLDM6NF0gJT4lIHNjYWxlICU+JSBkaXN0ICU+JSBoY2x1c3QNCnBsb3QoaGMpDQpyZWN0LmhjbHVzdChoYywgaz01LCBib3JkZXI9InJlZCIpDQpgYGANCg0KYGBge3J9DQpXJGdyb3VwID0gY3V0cmVlKGhjLCBrPTUpICU+JSBmYWN0b3INCmdncGxvdChXLCBhZXMoeD1GcmVzaCwgeT1NaWxrLCBjb2w9Z3JvdXApKSArDQogIGdlb21fcG9pbnQoc2l6ZT0zLCBhbHBoYT0wLjUpICsgDQogIHRoZW1lX2xpZ2h0KCkNCmBgYA0KDQojIyMjIyBBMy4g5YWt5YCL5Y2A6ZqU6K6K5pW4DQpgYGB7cn0NCmhjID0gV1ssMzo3XSAlPiUgc2NhbGUgJT4lIGRpc3QgJT4lIGhjbHVzdA0KcGxvdChoYykNClckZ3JvdXAgPSBmYWN0b3IoY3V0cmVlKGhjLCBrPTgpKQ0KcmVjdC5oY2x1c3QoaGMsIGs9OCwgYm9yZGVyPSJyZWQiKQ0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeShGYWN0b01pbmVSKQ0KbGlicmFyeShmYWN0b2V4dHJhKQ0KZnZpel9kZW5kKA0KICBoYywgaz04LCBzaG93X2xhYmVscz1GLCByZWN0PVQsIHJlY3RfZmlsbD1ULA0KICBsYWJlbHNfdHJhY2tfaGVpZ2h0PTAsDQogIHBhbGV0dGU9InVjc2NnYiIsIHJlY3RfYm9yZGVyPSJ1Y3NjZ2IiKQ0KYGBgDQoNCiMjIyMjIEE0LiDlsLrluqbnuK7muJsgDQpEaW1lbnNpb24gUmVkdWN0aW9uIHdpdGggUENBIChQcmluY2lwbGUgQ29tcG9uZW50IEFuYWx5c2lzLCDkuLvmiJDliIbliIbmnpApDQpgYGB7ciBmaWcuaGVpZ2h0PTcsIGZpZy53aWR0aD05fQ0KV1ssMzo4XSAlPiUgUENBKGdyYXBoPUYpICU+JSBmdml6X3BjYV9iaXBsb3QoDQogIGxhYmVsPSJ2YXIiLCBjb2wuaW5kPVckZ3JvdXAsDQogIHBvaW50c2hhcGU9MTksIG1lYW4ucG9pbnQ9RiwNCiAgYWRkRWxsaXBzZXM9VCwgZWxsaXBzZS5sZXZlbD0wLjcsDQogIGVsbGlwc2UudHlwZSA9ICJjb252ZXgiLCBwYWxldHRlPSJ1Y3NjZ2IiLA0KICByZXBlbD1UDQogICkNCmBgYA0KPGJyPg0KDQotIC0gLQ0KDQojIyMgMS4gQ2x1c3RlciBBbmFseXNpcyBmb3IgTW92aWVzICANCg0KKirkuLvopoHorbDpoYzvvJrkvp3poZ7lnosoR2VucmUp5bCN6Zu75b2x5YiG6aGeKioNCg0KKirlrbjnv5Lph43pu57vvJoqKg0KDQorIOmbhue+pOWIhuaekOeahOWfuuacrOingOW/tQ0KKyDot53pm6Lnn6npmaPvvJpEaXN0YW5jZSBNYXRyaXgNCisg5bGk57Sa5byP6ZuG576k5YiG5p6Q77yaSGllcmFyY2hpY2FsIENsdXN0ZXIgQW5hbHlzaXMNCisg5qi554uA5ZyWKERlbmRyb2dyYW0p55qE5Yik6K6ADQorIOS+neaTmuaoueeLgOWcluaxuuWumuimgeWIhuWkmuWwkee+pA0KKyDku6XnvqTntYTlubPlnYflgLzmqqLoppblkITml4/nvqTnmoTlsazmgKcNCg0KPGJyPg0KDQojIyMjIyAxLjEg5pW055CG6LOH5paZDQpgYGB7cn0NCk0gPSByZWFkLnRhYmxlKCJkYXRhL21vdmllTGVucy50eHQiLCBoZWFkZXI9RkFMU0UsIHNlcD0ifCIscXVvdGU9IlwiIikNCg0KIyBBc3NpZ24gY29sdW1uIG5hbWVzDQpjb2xuYW1lcyhNKSA9IGMoDQogICJJRCIsICJUaXRsZSIsICJSZWxlYXNlRGF0ZSIsICJWaWRlb1JlbGVhc2VEYXRlIiwgIklNREIiLCANCiAgIlVua25vd24iLCAiQWN0aW9uIiwgIkFkdmVudHVyZSIsICJBbmltYXRpb24iLCAiQ2hpbGRyZW5zIiwgDQogICJDb21lZHkiLCAiQ3JpbWUiLCAiRG9jdW1lbnRhcnkiLCAiRHJhbWEiLCAiRmFudGFzeSIsICJGaWxtTm9pciIsIA0KICAiSG9ycm9yIiwgIk11c2ljYWwiLCAiTXlzdGVyeSIsICJSb21hbmNlIiwgIlNjaUZpIiwgIlRocmlsbGVyIiwNCiAgIldhciIsICJXZXN0ZXJuIikNCg0KIyBSZW1vdmUgdW5uZWNlc3NhcnkgdmFyaWFibGVzDQpNJElEID0gTlVMTA0KTSRSZWxlYXNlRGF0ZSA9IE5VTEwNCk0kVmlkZW9SZWxlYXNlRGF0ZSA9IE5VTEwNCk0kSU1EQiA9IE5VTEwNCg0KIyBSZW1vdmUgZHVwbGljYXRlcw0KTSA9IHVuaXF1ZShNKQ0KYGBgDQoNCiMjIyMjIDEuMiDmqqLoppbos4fmlpkNCmBgYHtyfQ0KaGVhZChNLCA1KQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtKE0kQ29tZWR5KSAgICAgICAgICAgICAjIOWWnOWKh+eJhw0Kc3VtKE0kV2VzdGVybikgICAgICAgICAgICAjIOilv+mDqOeJhw0Kc3VtKE0kUm9tYW5jZSB8IE0kRHJhbWEpICAjIOa1qua8q+WKh+aDheeJhw0KYGBgDQoNCiMjIyMjIDEuMyDot53pm6Lnn6npmaMNCmBgYHtyfQ0KZG14PSBkaXN0KE1bMjoyMF0sIG1ldGhvZD0iZXVjbGlkZWFuIikNCmRteCAlPiUgYXMubWF0cml4ICU+JSBkaW0NCmBgYA0KDQojIyMjIyAxLjQg5bGk57Sa5byP6ZuG576k5YiG5p6QDQpgYGB7cn0NCmhjbHVzdDEgPSBoY2x1c3QoZG14LCBtZXRob2QgPSAid2FyZC5EIikgDQpgYGANCg0KIyMjIyMgMS41IOaqouimluaoueeLgOWclg0KYGBge3J9DQpwbG90KGhjbHVzdDEpDQpyZWN0LmhjbHVzdChoY2x1c3QxLCBrPTUsIGJvcmRlcj0icmVkIikNCmBgYA0KDQojIyMjIyAxLjYg5YiH5Ymy576k57WEDQpgYGB7cn0NCmdycCA9IGN1dHJlZShoY2x1c3QxLCBrID0gNSkNCnRhYmxlKGdycCkNCmBgYA0KDQojIyMjIyAxLjcg5qqi5p+l576k57WE5bGs5oCnDQpgYGB7cn0NCnRhcHBseShNJEFjdGlvbiwgZ3JwLCBtZWFuKQ0KdGFwcGx5KE0kUm9tYW5jZSwgZ3JwLCBtZWFuKQ0KYGBgDQoNCiMjIyMjIDEuOCBUaGUgYHNhcHBseWAtYHNwbGl0YC1gLi4uYCBDb21ib++8mg0KYGBge3J9DQpzYXBwbHkoc3BsaXQoTVssMjoyMF0sIGdycCksIGNvbE1lYW5zKSAlPiUgcm91bmQoMykNCmBgYA0KDQojIyMjIyAxLjkg6LOH5paZ6KaW6Ka65YyWDQpgYGB7cn0NCmxheW91dChtYXRyaXgoYygxLDIsMiksIDMsIDEpKQ0KcGFyKG1hcj1jKDIsMywxLDEpLCBjZXg9MC44KQ0KdGFibGUoZ3JwKSAlPiUgYmFycGxvdChjb2w9Mzo3LCBuYW1lcy5hcmc9cGFzdGUwKCJHcm91cC0iLDE6NSkpDQpwYXIobWFyPWMoNiwzLDIsMSkpDQpzYXBwbHkoc3BsaXQoTVssMjoyMF0sIGdycCksIGNvbE1lYW5zKSAlPiUgdCAlPiUgDQogIGJhcnBsb3QoYmVzaWRlPVQsIGNvbD0zOjcsIGxhcz0yKQ0KDQpgYGANCg0KIyMjIyMg44CQ5ZWP6aGM6KiO6KuW44CRDQoNCuW+nueuoeeQhueahOinkuW6puS+hueci++8jOaIkeWAkeeCuueUmum6vOimgeWIhue+pO+8nyANCg0KKw0KKw0KDQoNCuaIkeWAkeeCuueUmum6vOimgeWBmuWwuuW6pue4rua4m++8nyANCg0KKw0KKw0KDQoNCuaIkeWAkeimgeWmguS9leaKiumbhue+pOWIhuaekOeahOe1kOaenOi9ieWMlueCuuetlueVpeWRou+8nyANCg0KKw0KKw0KDQo8YnI+DQoNCi0gLSAtDQoNCiMjIyAyLiBGbG93ZXIgSW1hZ2UNCg0KIyMjIyMgMi4xIOaVtOeQhuizh+aWmQ0KYGBge3J9DQojIFJlYWQgZGF0YQ0KZmxvd2VyID0gcmVhZC5jc3YoImRhdGEvZmxvd2VyLmNzdiIsIGhlYWRlcj1GQUxTRSkNCg0KIyBDaGFuZ2UgdGhlIGRhdGEgdHlwZSB0byBtYXRyaXgNCmZsb3dlck1hdHJpeCA9IGFzLm1hdHJpeChmbG93ZXIpDQpkaW0oZmxvd2VyTWF0cml4KQ0KDQojIFR1cm4gbWF0cml4IGludG8gYSB2ZWN0b3INCmZsb3dlclZlY3RvciA9IGFzLnZlY3RvcihmbG93ZXJNYXRyaXgpDQpsZW5ndGgoZmxvd2VyVmVjdG9yKQ0KYGBgDQoNCiMjIyMjIDIuMiDot53pm6Lnn6npmaMNCmBgYHtyfQ0KIyBDb21wdXRlIGRpc3RhbmNlcw0KZGlzdGFuY2UgPSBkaXN0KGZsb3dlclZlY3RvciwgbWV0aG9kID0gImV1Y2xpZGVhbiIpDQpgYGANCg0KIyMjIyMgMi4zIOWxpOe0muW8j+mbhue+pOWIhuaekA0KYGBge3J9DQojIEhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nDQpjbHVzdGVySW50ZW5zaXR5ID0gaGNsdXN0KGRpc3RhbmNlLCBtZXRob2Q9IndhcmQuRCIpDQpgYGANCg0KIyMjIyMgMi40IOaoueeLgOWclg0KYGBge3J9DQojIFBsb3QgdGhlIGRlbmRyb2dyYW0NCnBsb3QoY2x1c3RlckludGVuc2l0eSkNCiMgU2VsZWN0IDMgY2x1c3RlcnMNCnJlY3QuaGNsdXN0KGNsdXN0ZXJJbnRlbnNpdHksIGsgPSAzLCBib3JkZXIgPSAicmVkIikNCmBgYA0KDQojIyMjIyDliIflibLnvqTntYQNCmBgYHtyfQ0KZmxvd2VyQ2x1c3RlcnMgPSBjdXRyZWUoY2x1c3RlckludGVuc2l0eSwgayA9IDMpDQp0YWJsZShmbG93ZXJDbHVzdGVycykNCiMgZmxvd2VyQ2x1c3RlcnMNCmBgYA0KDQojIyMjIyDml4/nvqTlubPlnYco55Wr57Sg6aGP6Imy5rex5re65bqmKQ0KYGBge3J9DQojIEZpbmQgbWVhbiBpbnRlbnNpdHkgdmFsdWVzDQp0YXBwbHkoZmxvd2VyVmVjdG9yLCBmbG93ZXJDbHVzdGVycywgbWVhbikNCmBgYA0KDQojIyMjIyDlnJblg4/mr5TovIMNCmBgYHtyIGZpZy5oZWlnaHQ9My4yLCBmaWcud2lkdGg9Ni40fQ0KIyBQbG90IHRoZSBpbWFnZSBhbmQgdGhlIGNsdXN0ZXJzDQpkaW0oZmxvd2VyQ2x1c3RlcnMpID0gYyg1MCw1MCkNCnBhcihtZnJvdz1jKDEsMiksIG1hcj1jKDIsMiwyLDIpKQ0KDQojIE9yaWdpbmFsIGltYWdlDQppbWFnZShmbG93ZXJNYXRyaXgsYXhlcz1GQUxTRSxjb2w9Z3JleShzZXEoMCwxLGxlbmd0aD0yNTYpKSxtYWluPSJPcmlnaW5hbCIpDQoNCiMgTmV3IGltYWdlDQppbWFnZShmbG93ZXJDbHVzdGVycywgYXhlcyA9IEZBTFNFLCBtYWluPSIzIENsdXN0ZXIiKQ0KYGBgDQo8YnI+DQoNCi0gLSAtDQoNCiMjIyAzLiBNUkkgSW1hZ2UNCg0KIyMjIyMgMy4xIOaVtOeQhuizh+aWmQ0KYGBge3J9DQojIFJlYWQgZGF0YQ0KaGVhbHRoeSA9IHJlYWQuY3N2KCJkYXRhL2hlYWx0aHkuY3N2IiwgaGVhZGVyPUZBTFNFKQ0KaGVhbHRoeU1hdHJpeCA9IGFzLm1hdHJpeChoZWFsdGh5KQ0KZGltKGhlYWx0aHlNYXRyaXgpDQpgYGANCg0KIyMjIyMgMy4yIOeVq+WHuuWcluW9og0KYGBge3IgZmlnLndpZHRoPTIuODMsIGZpZy5oZWlnaHQ9My4yM30NCiMgUGxvdCBpbWFnZQ0KcGFyKG1hcj1jKDEsMSwxLDEpKQ0KaW1hZ2UoaGVhbHRoeU1hdHJpeCxheGVzPUZBTFNFLGNvbD1ncmV5KHNlcSgwLDEsbGVuZ3RoPTI1NikpKQ0KYGBgDQoNCiMjIyMjIDMuMyDot53pm6Lnn6npmaMNCmBgYHtyfQ0KIyBDb21wdXRlIGRpc3RhbmNlcw0KaGVhbHRoeVZlY3RvciA9IGFzLnZlY3RvcihoZWFsdGh5TWF0cml4KQ0KZGlzdGFuY2UgPSBkaXN0KGhlYWx0aHlWZWN0b3IsIG1ldGhvZCA9ICJldWNsaWRlYW4iKQ0KYGBgDQoNCioq44CQUeOAkSoqIFdoYXQgaXMgdGhlIHByb2JsZW0/DQoNCisNCisgDQorDQoNCiMjIyMjIDMuNCBLTWVhbnPpm4bnvqTliIbmnpANCmBgYHtyfQ0KIyBSdW4gay1tZWFucw0KayA9IDUNCnNldC5zZWVkKDEpDQpLTUMgPSBrbWVhbnMoaGVhbHRoeVZlY3RvciwgY2VudGVycyA9IGssIGl0ZXIubWF4ID0gMTAwMCkNCmBgYA0KDQojIyMjIyAzLjUg5qqi5p+l5YiG576k57WQ5p6cDQpgYGB7cn0NCiMgVmlldyhLTUMpDQp0YWJsZShLTUMkY2x1c3RlcikNCktNQyRjZW50ZXJzDQpgYGANCg0KIyMjIyMgMy42IOeVq+WHuuWIhue+pOe1kOaenA0KYGBge3IgZmlnLndpZHRoPTIuODMsIGZpZy5oZWlnaHQ9My4yM30NCiMgRXh0cmFjdCBjbHVzdGVycw0KWCA9IEtNQyRjbHVzdGVyDQoNCiMgUGxvdCB0aGUgaW1hZ2Ugd2l0aCB0aGUgY2x1c3RlcnMNCmRpbShYKSA9IGMobnJvdyhoZWFsdGh5TWF0cml4KSwgbmNvbChoZWFsdGh5TWF0cml4KSkNCg0KIyBQbG90IGltYWdlDQpwYXIobWFyPWMoMSwxLDEsMSkpDQppbWFnZShYLCBheGVzID0gRkFMU0UsIGNvbD1yYWluYm93KGspKQ0KYGBgDQoNCiMjIyMjIDMuNyDoroDpgLLjgIHovYnmj5vmuKzoqablnJblvaINCmBgYHtyfQ0KdHVtb3IgPSByZWFkLmNzdigiZGF0YS90dW1vci5jc3YiLCBoZWFkZXI9RkFMU0UpDQp0dW1vck1hdHJpeCA9IGFzLm1hdHJpeCh0dW1vcikNCmRpbSh0dW1vck1hdHJpeCkNCnR1bW9yVmVjdG9yID0gYXMudmVjdG9yKHR1bW9yTWF0cml4KQ0KbGVuZ3RoKHR1bW9yVmVjdG9yKQ0KYGBgDQoNCiMjIyMjIDMuOCDlsIfljp/lnJblvaLkuYvliIbnvqTopo/liYflpZfnlKjliLDmuKzoqablnJblvaINCmBgYHtyfQ0KIyBBcHBseSBjbHVzdGVycyBmcm9tIGJlZm9yZSB0byBuZXcgaW1hZ2UsIHVzaW5nIHRoZSBmbGV4Y2x1c3QgcGFja2FnZQ0KbGlicmFyeShmbGV4Y2x1c3QpDQp0MCA9IFN5cy50aW1lKCkNCktNQy5rY2NhID0gZmxleGNsdXN0Ojphcy5rY2NhKEtNQywgaGVhbHRoeVZlY3RvcikgICAgICAgICMg5bu656uL5qih5Z6LDQp0dW1vckNsdXN0ZXJzID0gcHJlZGljdChLTUMua2NjYSwgbmV3ZGF0YSA9IHR1bW9yVmVjdG9yKSAjIOmAsuihjOmgkOa4rCjovYnmj5spDQpTeXMudGltZSgpIC0gdDANCmBgYA0KDQojIyMjIyAzLjkg5ZyW5YOP5q+U6LyDDQpgYGB7ciBmaWcuaGVpZ2h0PTMuMiwgZmlnLndpZHRoPTZ9DQojIFZpc3VhbGl6ZSB0aGUgY2x1c3RlcnMNCmRpbSh0dW1vckNsdXN0ZXJzKSA9IGMobnJvdyh0dW1vck1hdHJpeCksIG5jb2wodHVtb3JNYXRyaXgpKQ0KDQpwYXIobWZyb3c9YygxLDIpLCBtYXI9YygxLDEsMiwxKSkNCmltYWdlKFgsIGF4ZXMgPSBGQUxTRSwgY29sPXJhaW5ib3coayksIG1haW49IkhlYWx0aHkiKQ0KaW1hZ2UodCh0dW1vckNsdXN0ZXJzKVssNTcxOjFdLCBheGVzID0gRkFMU0UsIGNvbD1yYWluYm93KGspLCBtYWluPSJUdW1vciIpDQpgYGANCg0KIyMjIyMg44CQ5a2457+S6YeN6bue44CRDQoNCisg6ZuG576k5YiG5p6Q5Zyo5ZyW5YOP6JmV55CG55qE5oeJ55SoDQorIOWWruWNgOmalOiuiuaVuOeahOmbhue+pOWIhuaekA0KKyDpm4bnvqTliIbmnpDmqKHlnosNCg0KIyMjIyMg44CQ5ZWP6aGM6KiO6KuW44CRDQoNCuWxpOe0muW8j+WSjEstTWVhbnPpm4bnvqTliIbmnpDmnInku4Dpurzlt67nlbDvvJ8g5a6D5YCR5YiG5Yil55So5Zyo5LuA6bq854uA5rOB77yfDQoNCisNCisNCg0K6ZuG576k5YiG5p6Q5qih5Z6L5ZKM5pmu6YCa55qE6ZuG576k5YiG5p6Q5pyJ5LuA6bq85beu55Ww77yfIA0KDQorDQorDQoNCuS7gOm6vOaZguWAmemcgOimgeW7uumbhue+pOWIhuaekOaooeWei++8nyDpm4bnvqTliIbmnpDmqKHlnovnmoTnlKjms5XvvJ8NCg0KKw0KKw0KDQrlnJblg4/omZXnkIblkozlnJblg4/ovqjorZjmnInku4Dpurzlt67nlbDvvJ8NCg0KKw0KKw0KDQo8YnI+DQoNCi0gLSAtDQoNCjxicj48YnI+PGJyPjxicj48YnI+DQoNCjxzdHlsZT4NCi5jYXB0aW9uIHsNCiAgY29sb3I6ICM3Nzc7DQogIG1hcmdpbi10b3A6IDEwcHg7DQp9DQpwIGNvZGUgew0KICB3aGl0ZS1zcGFjZTogaW5oZXJpdDsNCn0NCnByZSB7DQogIHdvcmQtYnJlYWs6IG5vcm1hbDsNCiAgd29yZC13cmFwOiBub3JtYWw7DQogIGxpbmUtaGVpZ2h0OiAxOw0KfQ0KcHJlIGNvZGUgew0KICB3aGl0ZS1zcGFjZTogaW5oZXJpdDsNCn0NCnAsbGkgew0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KLnJ7DQogIGxpbmUtaGVpZ2h0OiAxLjI7DQp9DQoNCnRpdGxlew0KICBjb2xvcjogI2NjMDAwMDsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCmJvZHl7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQpoMSxoMixoMyxoNCxoNXsNCiAgY29sb3I6ICMwMDg4MDA7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQpoM3sNCiAgY29sb3I6ICNiMzZiMDA7DQogIGJhY2tncm91bmQ6ICNmZmUwYjM7DQogIGxpbmUtaGVpZ2h0OiAyOw0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCg0KaDV7DQogIGNvbG9yOiAjMDA2MDAwOw0KICBiYWNrZ3JvdW5kOiAjZmZmZmUwOw0KICBsaW5lLWhlaWdodDogMjsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQoNCmVtew0KICBjb2xvcjogIzAwMDBjMDsNCiAgYmFja2dyb3VuZDogI2YwZjBmMDsNCiAgfQ0KPC9zdHlsZT4NCg0K