9. Unsupervised Learning

‘비지도 학습은’ 8장의 지도 학습과는 달리 응답변수 Y가 없는 학습 기법이다.
단순히 관측치인 X집합만 가지고 있으며, 그들 사이의 관계를 이해하고자 한다.

9.1. Clustering

9.1.1. Hierarchical clustering, 계층적 군집화

: 개체에 대한 설명이 숫자변수로 구성된 경우

분류에서 Tree를 구성하기 위한 두 단계:

  1. 데카르트 공간의 점으로 나타낸다.
  2. 거리를 측정하여 거리에 따라 분기(branches)를 결정한다.


변수마다 다른 단위에 있을 경우, 분류에 반영하기 어렵다. 변수의 크기를 조정할 필요가 생기는데, 이 경우 도메인 지식에 따라 최적의 방법을 결정해야 한다. 계층적 군집화에선 간단히 dist()함수를 사용할 수 있다. dist()함수는 각 개별 entity에서 모든 entities까지의 거리를 제공한다. 거리는 전체 트리가 구성될 때 까지 가까운 항목 사이에 분기를 구성한 다음 분기 간에 연결하는데 사용할 수 있고, 이를 계층적 군집화라고 한다.




9.1.2. k-means

: 계층 구조의 구성없이, 각 사례를 여러 그룹 중 하나에 할당하는 것을 말한다.

EX: WorldCities 데이터를 사용한다. 4,000개 대도시의 위도와 경도 정보는 있지만 대륙에 대한 정보는 없는 상황이다.
위치만을 기준으로 k개 군집으로 분류해보고, 대륙이 옳게 분류되었는지 알아보자.

BigCities <- world_cities %>%
  arrange(desc(population)) %>%
  head(4000) %>%
  select(longitude, latitude)

glimpse(BigCities)
Rows: 4,000
Columns: 2
$ longitude <dbl> 121.45806, 28.94966, -58.37723, 72.88261, -99.12766, 116.39723, 67.01040, 117.17667, 113.25000, 7…
$ latitude  <dbl> 31.22222, 41.01384, -34.61315, 19.07283, 19.42847, 39.90750, 24.86080, 39.14222, 23.11667, 28.651…

인구 수로 정렬한 상위 4,000개 대도시의 위도와 경도 정보를 살펴볼 수 있다.

set.seed(15)

city_clusts <- BigCities %>%
  kmeans(centers = 6) %>%
  fitted("classes") %>%
  as.character()

BigCities <- BigCities %>% mutate(cluster = city_clusts)
BigCities %>% ggplot(aes(x = longitude, y = latitude)) +
  geom_point(aes(color = cluster), alpha = 0.5)

위치만을 기준으로 하여 k=6, 즉 6개 군집으로 분류해본 결과이다. 실제와 거의 유사하게 분류한 것을 확인할 수 있다.


9.2. Dimension reduction

: 종종 데이터에 구하고자 하는 task에 도움되는 정보가 없는 변수, 또는 유사한 패턴을 가지거나 동일한 정보의 변수가 존재할 수 있다. 이들은 알고리즘의 학습을 방해하는 노이즈로 취급되므로 제거해 줄 필요가 있다.

EX: 2008년 스코틀랜드 의회의 투표 데이터

찬/반 투표 패턴을 통해 투표인의 소속 정당을 클러스터링 할 수 있는지 알아보자.

Votes %>%
  mutate(Vote = factor(vote, labels = c("Nay","Abstain","Aye"))) %>%
  ggplot(aes(x = bill, y = name, fill = Vote)) +
  geom_tile() + xlab("Ballot") + ylab("Member of Parliament") +
  scale_fill_manual(values = c("darkgray", "white", "goldenrod")) +
  scale_x_discrete(breaks = NULL, labels = NULL) +
  scale_y_discrete(breaks = NULL, labels = NULL)

마치 “스코티쉬 타탄” 패턴이 보이는 듯 하지만, 많은 패턴을 식별하긴 아직 어렵다.
스코티쉬 타탄이란 다음과 같은 문양을 말한다.




9.2.1. Intuitive approaches, 직관적인 접근법

Votes %>% filter(bill %in% c("S1M-240.2", "S1M-639.1")) %>%
  tidyr::spread(key = bill, value = vote) %>%
  ggplot(aes(x = `S1M-240.2`, y = `S1M-639.1`)) +
  geom_point(alpha = 0.7, position = position_jitter(width = 0.1, height = 0.1)) +
  geom_point(alpha = 0.01, size = 10, color = "red" )

위 결과는 SIM-240.2와 SIM-639.1 두 투표에 대한 결과를 보여준다. 결과는 (예X아니오X기권)으로 3X3=9, 9가지 가능한 경우가 있고, 현재 8그룹이 나타나있다. 본 결과는 과연 8개의 군집을 의미할까?

Votes %>% mutate(set_num = as.numeric(factor(bill)),
                 set = 
                   ifelse(set_num < max(set_num) / 2, "First_Half", "Second_Half")) %>%
  group_by(name, set) %>%
  summarize(Ayes = sum(vote)) %>%
  tidyr::spread(key = set, value = Ayes) %>%
  ggplot(aes(x = First_Half, y = Second_Half)) +
  geom_point(alpha = 0.7, size = 5)

두 개의 투표 결과가 아니라 모든 투표 데이터를 사용하니, 구성원이 두 군집으로 나타날 수 있음을 보였다.



9.2.2. Singular value decomposition (=SVD)

앞서 n=134 명의 p=773회 투표데이터 시각화에서 x에 투표 전반부, y에 후반부를 지정하였는데, 이보다 더 나은 지정 방법이 존재할 수도 있다. SVD는 이러한 투표-유권자 간 최상의 근사치를 행렬을 통해 찾을 수 있는 수학적 접근 방식이다. SVD는 좌표축의 회전을 의미하므로 주성분 몇 가지로 많은 변동을 설명할 수 있다.

Votes_wide <- Votes %>%
  pivot_wider(names_from = bill, values_from = vote)
vote_svd <- Votes_wide %>% 
  select(-name) %>% 
  svd()

num_clusters <- 5

vote_svd_tidy <- vote_svd %>%
  tidy(matrix = "u") %>%
  filter(PC < num_clusters) %>%
  mutate(PC = paste0("pc_", PC)) %>%
  pivot_wider(names_from = PC, values_from = value) %>%
  select(-row)
New names:
clusts <- vote_svd_tidy %>% 
  kmeans(centers = num_clusters)

tidy(clusts)

voters <- clusts %>%
  augment(vote_svd_tidy)

ggplot(data = voters, aes(x = pc_1, y = pc_2)) +
  geom_point(aes(x = 0, y = 0), color = "red", shape = 1, size = 7) + 
  geom_point(size = 5, alpha = 0.6, aes(color = .cluster)) +
  xlab("Best Vector from SVD") + 
  ylab("Second Best Vector from SVD") +
  ggtitle("Political Positions of Members of Parliament") + 
  scale_color_brewer(palette = "Set2")

※Clustering members of Scottish Parliament based on SVD along the members

5개의 클러스터를 입력하였으나, 최상의 SVD합계를 통해 분할된 군집은 3개로 보인다. Confusion Matrix를 통해 실제 정당이 얼마나 잘 분류되었는지 확인해볼 수 있다.

voters <- voters %>% 
  mutate(name = Votes_wide$name) %>%
  left_join(Parties, by = c("name" = "name"))

mosaic::tally(party ~ .cluster, data = voters)
                                          .cluster
party                                       1  2  3  4  5
  Member for Falkirk West                   0  1  0  0  0
  Scottish Conservative and Unionist Party  0  0  0 20  0
  Scottish Green Party                      0  1  0  0  0
  Scottish Labour                           0  1  0  0 57
  Scottish Liberal Democrats                0 16  0  0  1
  Scottish National Party                  26  0 10  0  0
  Scottish Socialist Party                  0  1  0  0  0


ballots <- vote_svd %>%
  tidy(matrix = "v") %>%
  filter(PC < num_clusters) %>%
  mutate(PC = paste0("pc_", PC)) %>%
  pivot_wider(names_from = PC, values_from = value) %>%
  select(-column)
New names:
clust_ballots <- kmeans(ballots, centers = num_clusters)
ballots <- clust_ballots %>% 
  augment(ballots) %>%
  mutate(bill = names(select(Votes_wide, -name)))

ggplot(data = ballots, aes(x = pc_1, y = pc_2)) +
  geom_point(aes(x = 0, y = 0), color = "red", shape = 1, size = 7) + 
  geom_point(size = 5, alpha = 0.6, aes(color = .cluster)) +
  xlab("Best Vector from SVD") + 
  ylab("Second Best Vector from SVD") +
  ggtitle("Influential Ballots") + 
  scale_color_brewer(palette = "Set2")

※Clustering of Scottish Parliament ballots based on SVD along the ballots

위의 클러스터링 된 이미지에선 군집이 중앙의 빨간 점을 기준으로 대칭되어 사회적 진보대 보수, 경제적 진보 대 보수로 해석될 수도 있다. 투표 데이터에서 정치적 군집 외에도 사회적 효과, 경제적 효과 등 얻을 수 있는 정보가 더 존재하고, 주성분은 투표-유권자를 재배치하는 데 사용할 수 있다.

Votes_svd <- Votes %>%
  mutate(Vote = factor(vote, labels = c("Nay", "Abstain", "Aye"))) %>%
  inner_join(ballots, by = "bill") %>%
  inner_join(voters, by = "name")

ggplot(data = Votes_svd, 
  aes(x = reorder(bill, pc_1.x), y = reorder(name, pc_1.y), fill = Vote)) +
  geom_tile() + 
  xlab("Ballot") + 
  ylab("Member of Parliament") +
  scale_fill_manual(values = c("darkgray", "white", "goldenrod")) + 
  scale_x_discrete(breaks = NULL, labels = NULL) + 
  scale_y_discrete(breaks = NULL, labels = NULL)

데이터가 상하로 나뉘었는데, 상단을 보면 두 개의 주요 정당으로 데이터가 명확하게 분류된 것을 볼 수 있다. 배경을 고려하면 국민당과 노동당이 구분된 것으로 유추할 수 있고, 나머지 작은 정당들은 하단의 절반에서 덜 명확하게 분류되고 있다.

기계학습이 데이터에서 의미있는 패턴을 식별하였지만, 추출한 패턴을 해석하기 위해선 인간의 도메인 지식을 문제에 적용하는 것이 중요하다.






LS0tDQp0aXRsZTogIk1vZGVybiBEYXRhIFNjaWVuY2Ugd2l0aCBSX0NwdDkiDQphdXRob3I6IHNlb25nc3UsIGtpbQ0KZGF0ZTogMjAyMy0wMS0xOA0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCmBgYHtyIGluY2x1ZGU9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobW9zYWljKQ0KbGlicmFyeShtY2x1c3QpDQpsaWJyYXJ5KGJyb29tKQ0KYGBgDQoNCiMgOS4gVW5zdXBlcnZpc2VkIExlYXJuaW5nDQon67mE7KeA64+EIO2VmeyKteydgCcgOOyepeydmCDsp4Drj4Qg7ZWZ7Iq16rO864qUIOuLrOumrCDsnZHri7Xrs4DsiJggWeqwgCDsl4bripQg7ZWZ7Iq1IOq4sOuyleydtOuLpC4NClwNCuuLqOyInO2eiCDqtIDsuKHsuZjsnbggWOynke2VqeunjCDqsIDsp4Dqs6Ag7J6I7Jy866mwLCDqt7jrk6Qg7IKs7J207J2YIOq0gOqzhOulvCDsnbTtlbTtlZjqs6DsnpAg7ZWc64ukLg0KXA0KXA0KDQojIyA5LjEuIENsdXN0ZXJpbmcNCg0KIyMjIDkuMS4xLiBIaWVyYXJjaGljYWwgY2x1c3RlcmluZywg6rOE7Li17KCBIOq1sOynke2ZlA0KOiDqsJzssrTsl5Ag64yA7ZWcIOyEpOuqheydtCDsiKvsnpDrs4DsiJjroZwg6rWs7ISx65CcIOqyveyasA0KXA0KXA0K67aE66WY7JeQ7IScIFRyZWXrpbwg6rWs7ISx7ZWY6riwIOychO2VnCDrkZAg64uo6rOEOg0KXA0KDQoxLiDrjbDsubTrpbTtirgg6rO16rCE7J2YIOygkOycvOuhnCDrgpjtg4Drgrjri6QuXA0KMi4g6rGw66as66W8IOy4oeygle2VmOyXrCDqsbDrpqzsl5Ag65Sw6528IOu2hOq4sChicmFuY2hlcynrpbwg6rKw7KCV7ZWc64ukLlwNClwNClwNCg0K67OA7IiY66eI64ukIOuLpOuluCDri6jsnITsl5Ag7J6I7J2EIOqyveyasCwg67aE66WY7JeQIOuwmOyYge2VmOq4sCDslrTroLXri6QuIOuzgOyImOydmCDtgazquLDrpbwg7KGw7KCV7ZWgIO2VhOyalOqwgCDsg53quLDripTrjbAsIOydtCDqsr3smrAg64+E66mU7J24IOyngOyLneyXkCDrlLDrnbwg7LWc7KCB7J2YIOuwqeuyleydhCDqsrDsoJXtlbTslbwg7ZWc64ukLiDqs4TsuLXsoIEg6rWw7KeR7ZmU7JeQ7ISgIOqwhOuLqO2eiCBkaXN0KCntlajsiJjrpbwg7IKs7Jqp7ZWgIOyImCDsnojri6QuIGRpc3QoKe2VqOyImOuKlCDqsIEg6rCc67OEIGVudGl0eeyXkOyEnCDrqqjrk6AgZW50aXRpZXPquYzsp4DsnZgg6rGw66as66W8IOygnOqzte2VnOuLpC4g6rGw66as64qUIOyghOyytCDtirjrpqzqsIAg6rWs7ISx65CgIOuVjCDquYzsp4Ag6rCA6rmM7Jq0IO2VreuqqSDsgqzsnbTsl5Ag67aE6riw66W8IOq1rOyEse2VnCDri6TsnYwg67aE6riwIOqwhOyXkCDsl7DqsrDtlZjripTrjbAg7IKs7Jqp7ZWgIOyImCDsnojqs6AsIOydtOulvCDqs4TsuLXsoIEg6rWw7KeR7ZmU65286rOgIO2VnOuLpC4NClwNClwNCiFbXShDOi9Vc2Vycy91c2VyL0Rlc2t0b3AvTGFiX2RyaXZlL1JfcHJvamVjdC9bMjAyM11Nb2Rlcm4gRGF0YSBTY2llbmNlIFdpdGggUi9pbWFnZS85LTNfaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcucG5nKQ0KXA0KXA0KXA0KDQojIyMgOS4xLjIuIGstbWVhbnMNCjog6rOE7Li1IOq1rOyhsOydmCDqtazshLHsl4bsnbQsIOqwgSDsgqzroYDrpbwg7Jes65+sIOq3uOujuSDspJEg7ZWY64KY7JeQIO2VoOuLue2VmOuKlCDqsoPsnYQg66eQ7ZWc64ukLg0KXA0KXA0KDQpFWDogV29ybGRDaXRpZXMg642w7J207YSw66W8IOyCrOyaqe2VnOuLpC4gNCwwMDDqsJwg64yA64+E7Iuc7J2YIOychOuPhOyZgCDqsr3rj4Qg7KCV67O064qUIOyeiOyngOunjCDrjIDrpZnsl5Ag64yA7ZWcIOygleuztOuKlCDsl4bripQg7IOB7Zmp7J2064ukLg0KXA0K7JyE7LmY66eM7J2EIOq4sOykgOycvOuhnCBr6rCcIOq1sOynkeycvOuhnCDrtoTrpZjtlbTrs7Tqs6AsIOuMgOulmeydtCDsmLPqsowg67aE66WY65CY7JeI64qU7KeAIOyVjOyVhOuztOyekC4NClwNCmBgYHtyfQ0KQmlnQ2l0aWVzIDwtIHdvcmxkX2NpdGllcyAlPiUNCiAgYXJyYW5nZShkZXNjKHBvcHVsYXRpb24pKSAlPiUNCiAgaGVhZCg0MDAwKSAlPiUNCiAgc2VsZWN0KGxvbmdpdHVkZSwgbGF0aXR1ZGUpDQoNCmdsaW1wc2UoQmlnQ2l0aWVzKQ0KYGBgDQrsnbjqtawg7IiY66GcIOygleugrO2VnCDsg4HsnIQgNCwwMDDqsJwg64yA64+E7Iuc7J2YIOychOuPhOyZgCDqsr3rj4Qg7KCV67O066W8IOyCtO2OtOuzvCDsiJgg7J6I64ukLg0KXA0KYGBge3J9DQpzZXQuc2VlZCgxNSkNCg0KY2l0eV9jbHVzdHMgPC0gQmlnQ2l0aWVzICU+JQ0KICBrbWVhbnMoY2VudGVycyA9IDYpICU+JQ0KICBmaXR0ZWQoImNsYXNzZXMiKSAlPiUNCiAgYXMuY2hhcmFjdGVyKCkNCg0KQmlnQ2l0aWVzIDwtIEJpZ0NpdGllcyAlPiUgbXV0YXRlKGNsdXN0ZXIgPSBjaXR5X2NsdXN0cykNCkJpZ0NpdGllcyAlPiUgZ2dwbG90KGFlcyh4ID0gbG9uZ2l0dWRlLCB5ID0gbGF0aXR1ZGUpKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gY2x1c3RlciksIGFscGhhID0gMC41KQ0KYGBgDQrsnITsuZjrp4zsnYQg6riw7KSA7Jy866GcIO2VmOyXrCBrPTYsIOymiSA26rCcIOq1sOynkeycvOuhnCDrtoTrpZjtlbTrs7gg6rKw6rO87J2064ukLiDsi6TsoJzsmYAg6rGw7J2YIOycoOyCrO2VmOqyjCDrtoTrpZjtlZwg6rKD7J2EIO2ZleyduO2VoCDsiJgg7J6I64ukLg0KXA0KXA0KXA0KDQojIyA5LjIuIERpbWVuc2lvbiByZWR1Y3Rpb24NCjog7KKF7KKFIOuNsOydtO2EsOyXkCDqtaztlZjqs6DsnpAg7ZWY64qUIHRhc2vsl5Ag64+E7JuA65CY64qUIOygleuztOqwgCDsl4bripQg67OA7IiYLCDrmJDripQg7Jyg7IKs7ZWcIO2MqO2EtOydhCDqsIDsp4DqsbDrgpgg64+Z7J287ZWcIOygleuztOydmCDrs4DsiJjqsIAg7KG07J6s7ZWgIOyImCDsnojri6QuIOydtOuTpOydgCDslYzqs6DrpqzsppjsnZgg7ZWZ7Iq17J2EIOuwqe2VtO2VmOuKlCDrhbjsnbTspojroZwg7Leo6riJ65CY66+A66GcIOygnOqxsO2VtCDspIQg7ZWE7JqU6rCAIOyeiOuLpC4NClwNClwNCg0KKipFWDogMjAwOOuFhCDsiqTsvZTti4Drnpzrk5wg7J2Y7ZqM7J2YIO2IrO2RnCDrjbDsnbTthLAqKg0KXA0KDQrssKwv67CYIO2IrO2RnCDtjKjthLTsnYQg7Ya17ZW0IO2IrO2RnOyduOydmCDshozsho0g7KCV64u57J2EIO2BtOufrOyKpO2EsOungSDtlaAg7IiYIOyeiOuKlOyngCDslYzslYTrs7TsnpAuDQpgYGB7ciBlY2hvPVRSVUV9DQpWb3RlcyAlPiUNCiAgbXV0YXRlKFZvdGUgPSBmYWN0b3Iodm90ZSwgbGFiZWxzID0gYygiTmF5IiwiQWJzdGFpbiIsIkF5ZSIpKSkgJT4lDQogIGdncGxvdChhZXMoeCA9IGJpbGwsIHkgPSBuYW1lLCBmaWxsID0gVm90ZSkpICsNCiAgZ2VvbV90aWxlKCkgKyB4bGFiKCJCYWxsb3QiKSArIHlsYWIoIk1lbWJlciBvZiBQYXJsaWFtZW50IikgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJkYXJrZ3JheSIsICJ3aGl0ZSIsICJnb2xkZW5yb2QiKSkgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGJyZWFrcyA9IE5VTEwsIGxhYmVscyA9IE5VTEwpICsNCiAgc2NhbGVfeV9kaXNjcmV0ZShicmVha3MgPSBOVUxMLCBsYWJlbHMgPSBOVUxMKQ0KYGBgDQrrp4jsuZggIuyKpOy9lO2LsOyJrCDtg4Dtg4QiIO2MqO2EtOydtCDrs7TsnbTripQg65OvIO2VmOyngOunjCwg66eO7J2AIO2MqO2EtOydhCDsi53rs4TtlZjquLQg7JWE7KeBIOyWtOugteuLpC4NClwNCuyKpOy9lO2LsOyJrCDtg4Dtg4TsnbTrnoAg64uk7J2M6rO8IOqwmeydgCDrrLjslpHsnYQg66eQ7ZWc64ukLg0KXA0KIVtdKEM6L1VzZXJzL3VzZXIvRGVza3RvcC9MYWJfZHJpdmUvUl9wcm9qZWN0L1syMDIzXU1vZGVybiBEYXRhIFNjaWVuY2UgV2l0aCBSL2ltYWdlL3Njb3R0aXNoIHRhcnRhbiBwYXR0ZXJuLmpwZWcpDQpcDQpcDQpcDQpcDQoNCiMjIyA5LjIuMS4gSW50dWl0aXZlIGFwcHJvYWNoZXMsIOyngeq0gOyggeyduCDsoJHqt7zrspUNCmBgYHtyIGVjaG89VFJVRX0NClZvdGVzICU+JSBmaWx0ZXIoYmlsbCAlaW4lIGMoIlMxTS0yNDAuMiIsICJTMU0tNjM5LjEiKSkgJT4lDQogIHRpZHlyOjpzcHJlYWQoa2V5ID0gYmlsbCwgdmFsdWUgPSB2b3RlKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gYFMxTS0yNDAuMmAsIHkgPSBgUzFNLTYzOS4xYCkpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNywgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjEsIGhlaWdodCA9IDAuMSkpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMDEsIHNpemUgPSAxMCwgY29sb3IgPSAicmVkIiApDQpgYGANCuychCDqsrDqs7zripQgU0lNLTI0MC4y7JmAIFNJTS02MzkuMSDrkZAg7Yis7ZGc7JeQIOuMgO2VnCDqsrDqs7zrpbwg67O07Jes7KSA64ukLiDqsrDqs7zripQgKOyYiFjslYTri4jsmKRY6riw6raMKeycvOuhnCAzWDM9OSwgOeqwgOyngCDqsIDriqXtlZwg6rK97Jqw6rCAIOyeiOqzoCwg7ZiE7J6sIDjqt7jro7nsnbQg64KY7YOA64KY7J6I64ukLiDrs7gg6rKw6rO864qUIOqzvOyXsCA46rCc7J2YIOq1sOynkeydhCDsnZjrr7jtlaDquYw/DQpcDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpWb3RlcyAlPiUgbXV0YXRlKHNldF9udW0gPSBhcy5udW1lcmljKGZhY3RvcihiaWxsKSksDQogICAgICAgICAgICAgICAgIHNldCA9IA0KICAgICAgICAgICAgICAgICAgIGlmZWxzZShzZXRfbnVtIDwgbWF4KHNldF9udW0pIC8gMiwgIkZpcnN0X0hhbGYiLCAiU2Vjb25kX0hhbGYiKSkgJT4lDQogIGdyb3VwX2J5KG5hbWUsIHNldCkgJT4lDQogIHN1bW1hcml6ZShBeWVzID0gc3VtKHZvdGUpKSAlPiUNCiAgdGlkeXI6OnNwcmVhZChrZXkgPSBzZXQsIHZhbHVlID0gQXllcykgJT4lDQogIGdncGxvdChhZXMoeCA9IEZpcnN0X0hhbGYsIHkgPSBTZWNvbmRfSGFsZikpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNywgc2l6ZSA9IDUpDQpgYGANCuuRkCDqsJzsnZgg7Yis7ZGcIOqysOqzvOqwgCDslYTri4jrnbwg66qo65OgIO2IrO2RnCDrjbDsnbTthLDrpbwg7IKs7Jqp7ZWY64uILCDqtazshLHsm5DsnbQg65GQIOq1sOynkeycvOuhnCDrgpjtg4DrgqAg7IiYIOyeiOydjOydhCDrs7TsmIDri6QuDQpcDQpcDQpcDQpcDQoNCiMjIyA5LjIuMi4gU2luZ3VsYXIgdmFsdWUgZGVjb21wb3NpdGlvbiAoPVNWRCkNCuyVnuyEnCBuPTEzNCDrqoXsnZggcD03NzPtmowg7Yis7ZGc642w7J207YSwIOyLnOqwge2ZlOyXkOyEnCB47JeQIO2IrO2RnCDsoITrsJjrtoAsIHnsl5Ag7ZuE67CY67aA66W8IOyngOygle2VmOyYgOuKlOuNsCwg7J2067O064ukIOuNlCDrgpjsnYAg7KeA7KCVIOuwqeuyleydtCDsobTsnqztlaAg7IiY64+EIOyeiOuLpC4gU1ZE64qUIOydtOufrO2VnCDtiKztkZwt7Jyg6raM7J6QIOqwhCDstZzsg4HsnZgg6re87IKs7LmY66W8IO2WieugrOydhCDthrXtlbQg7LC+7J2EIOyImCDsnojripQg7IiY7ZWZ7KCBIOygkeq3vCDrsKnsi53snbTri6QuIFNWROuKlCDsooztkZzstpXsnZgg7ZqM7KCE7J2EIOydmOuvuO2VmOuvgOuhnCDso7zshLHrtoQg66qHIOqwgOyngOuhnCDrp47snYAg67OA64+Z7J2EIOyEpOuqhe2VoCDsiJgg7J6I64ukLg0KYGBge3J9DQpWb3Rlc193aWRlIDwtIFZvdGVzICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gYmlsbCwgdmFsdWVzX2Zyb20gPSB2b3RlKQ0Kdm90ZV9zdmQgPC0gVm90ZXNfd2lkZSAlPiUgDQogIHNlbGVjdCgtbmFtZSkgJT4lIA0KICBzdmQoKQ0KDQpudW1fY2x1c3RlcnMgPC0gNQ0KDQp2b3RlX3N2ZF90aWR5IDwtIHZvdGVfc3ZkICU+JQ0KICB0aWR5KG1hdHJpeCA9ICJ1IikgJT4lDQogIGZpbHRlcihQQyA8IG51bV9jbHVzdGVycykgJT4lDQogIG11dGF0ZShQQyA9IHBhc3RlMCgicGNfIiwgUEMpKSAlPiUNCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IFBDLCB2YWx1ZXNfZnJvbSA9IHZhbHVlKSAlPiUNCiAgc2VsZWN0KC1yb3cpDQoNCmNsdXN0cyA8LSB2b3RlX3N2ZF90aWR5ICU+JSANCiAga21lYW5zKGNlbnRlcnMgPSBudW1fY2x1c3RlcnMpDQoNCnRpZHkoY2x1c3RzKQ0KDQp2b3RlcnMgPC0gY2x1c3RzICU+JQ0KICBhdWdtZW50KHZvdGVfc3ZkX3RpZHkpDQoNCmdncGxvdChkYXRhID0gdm90ZXJzLCBhZXMoeCA9IHBjXzEsIHkgPSBwY18yKSkgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gMCwgeSA9IDApLCBjb2xvciA9ICJyZWQiLCBzaGFwZSA9IDEsIHNpemUgPSA3KSArIA0KICBnZW9tX3BvaW50KHNpemUgPSA1LCBhbHBoYSA9IDAuNiwgYWVzKGNvbG9yID0gLmNsdXN0ZXIpKSArDQogIHhsYWIoIkJlc3QgVmVjdG9yIGZyb20gU1ZEIikgKyANCiAgeWxhYigiU2Vjb25kIEJlc3QgVmVjdG9yIGZyb20gU1ZEIikgKw0KICBnZ3RpdGxlKCJQb2xpdGljYWwgUG9zaXRpb25zIG9mIE1lbWJlcnMgb2YgUGFybGlhbWVudCIpICsgDQogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDIiKQ0KYGBgDQoqKuKAu0NsdXN0ZXJpbmcgbWVtYmVycyBvZiBTY290dGlzaCBQYXJsaWFtZW50IGJhc2VkIG9uIFNWRCBhbG9uZyB0aGUgbWVtYmVycyoqDQpcDQpcDQoNCjXqsJzsnZgg7YG065+s7Iqk7YSw66W8IOyeheugpe2VmOyYgOycvOuCmCwg7LWc7IOB7J2YIFNWRO2VqeqzhOulvCDthrXtlbQg67aE7ZWg65CcIOq1sOynkeydgCAz6rCc66GcIOuztOyduOuLpC4gQ29uZnVzaW9uIE1hdHJpeOulvCDthrXtlbQg7Iuk7KCcIOygleuLueydtCDslrzrp4jrgpgg7J6YIOu2hOulmOuQmOyXiOuKlOyngCDtmZXsnbjtlbTrs7wg7IiYIOyeiOuLpC4NClwNCmBgYHtyfQ0Kdm90ZXJzIDwtIHZvdGVycyAlPiUgDQogIG11dGF0ZShuYW1lID0gVm90ZXNfd2lkZSRuYW1lKSAlPiUNCiAgbGVmdF9qb2luKFBhcnRpZXMsIGJ5ID0gYygibmFtZSIgPSAibmFtZSIpKQ0KDQptb3NhaWM6OnRhbGx5KHBhcnR5IH4gLmNsdXN0ZXIsIGRhdGEgPSB2b3RlcnMpDQpgYGANClwNCmBgYHtyfQ0KYmFsbG90cyA8LSB2b3RlX3N2ZCAlPiUNCiAgdGlkeShtYXRyaXggPSAidiIpICU+JQ0KICBmaWx0ZXIoUEMgPCBudW1fY2x1c3RlcnMpICU+JQ0KICBtdXRhdGUoUEMgPSBwYXN0ZTAoInBjXyIsIFBDKSkgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBQQywgdmFsdWVzX2Zyb20gPSB2YWx1ZSkgJT4lDQogIHNlbGVjdCgtY29sdW1uKQ0KY2x1c3RfYmFsbG90cyA8LSBrbWVhbnMoYmFsbG90cywgY2VudGVycyA9IG51bV9jbHVzdGVycykNCmJhbGxvdHMgPC0gY2x1c3RfYmFsbG90cyAlPiUgDQogIGF1Z21lbnQoYmFsbG90cykgJT4lDQogIG11dGF0ZShiaWxsID0gbmFtZXMoc2VsZWN0KFZvdGVzX3dpZGUsIC1uYW1lKSkpDQoNCmdncGxvdChkYXRhID0gYmFsbG90cywgYWVzKHggPSBwY18xLCB5ID0gcGNfMikpICsNCiAgZ2VvbV9wb2ludChhZXMoeCA9IDAsIHkgPSAwKSwgY29sb3IgPSAicmVkIiwgc2hhcGUgPSAxLCBzaXplID0gNykgKyANCiAgZ2VvbV9wb2ludChzaXplID0gNSwgYWxwaGEgPSAwLjYsIGFlcyhjb2xvciA9IC5jbHVzdGVyKSkgKw0KICB4bGFiKCJCZXN0IFZlY3RvciBmcm9tIFNWRCIpICsgDQogIHlsYWIoIlNlY29uZCBCZXN0IFZlY3RvciBmcm9tIFNWRCIpICsNCiAgZ2d0aXRsZSgiSW5mbHVlbnRpYWwgQmFsbG90cyIpICsgDQogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDIiKQ0KYGBgDQoqKuKAu0NsdXN0ZXJpbmcgb2YgU2NvdHRpc2ggUGFybGlhbWVudCBiYWxsb3RzIGJhc2VkIG9uIFNWRCBhbG9uZyB0aGUgYmFsbG90cyoqDQpcDQpcDQoNCuychOydmCDtgbTrn6zsiqTthLDrp4Eg65CcIOydtOuvuOyngOyXkOyEoCDqtbDsp5HsnbQg7KSR7JWZ7J2YIOu5qOqwhCDsoJDsnYQg6riw7KSA7Jy866GcIOuMgOy5reuQmOyWtCDsgqztmozsoIEg7KeE67O064yAIOuztOyImCwg6rK97KCc7KCBIOynhOuztCDrjIAg67O07IiY66GcIO2VtOyEneuQoCDsiJjrj4Qg7J6I64ukLiDtiKztkZwg642w7J207YSw7JeQ7IScIOygley5mOyggSDqtbDsp5Eg7Jm47JeQ64+EIOyCrO2ajOyggSDtmqjqs7wsIOqyveygnOyggSDtmqjqs7wg65OxIOyWu+ydhCDsiJgg7J6I64qUIOygleuztOqwgCDrjZQg7KG07J6s7ZWY6rOgLCDso7zshLHrtoTsnYAg7Yis7ZGcLeycoOq2jOyekOulvCDsnqzrsLDsuZjtlZjripQg642wIOyCrOyaqe2VoCDsiJgg7J6I64ukLg0KXA0KYGBge3J9DQpWb3Rlc19zdmQgPC0gVm90ZXMgJT4lDQogIG11dGF0ZShWb3RlID0gZmFjdG9yKHZvdGUsIGxhYmVscyA9IGMoIk5heSIsICJBYnN0YWluIiwgIkF5ZSIpKSkgJT4lDQogIGlubmVyX2pvaW4oYmFsbG90cywgYnkgPSAiYmlsbCIpICU+JQ0KICBpbm5lcl9qb2luKHZvdGVycywgYnkgPSAibmFtZSIpDQoNCmdncGxvdChkYXRhID0gVm90ZXNfc3ZkLCANCiAgYWVzKHggPSByZW9yZGVyKGJpbGwsIHBjXzEueCksIHkgPSByZW9yZGVyKG5hbWUsIHBjXzEueSksIGZpbGwgPSBWb3RlKSkgKw0KICBnZW9tX3RpbGUoKSArIA0KICB4bGFiKCJCYWxsb3QiKSArIA0KICB5bGFiKCJNZW1iZXIgb2YgUGFybGlhbWVudCIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiZGFya2dyYXkiLCAid2hpdGUiLCAiZ29sZGVucm9kIikpICsgDQogIHNjYWxlX3hfZGlzY3JldGUoYnJlYWtzID0gTlVMTCwgbGFiZWxzID0gTlVMTCkgKyANCiAgc2NhbGVfeV9kaXNjcmV0ZShicmVha3MgPSBOVUxMLCBsYWJlbHMgPSBOVUxMKQ0KYGBgDQrrjbDsnbTthLDqsIAg7IOB7ZWY66GcIOuCmOuJmOyXiOuKlOuNsCwg7IOB64uo7J2EIOuztOuptCDrkZAg6rCc7J2YIOyjvOyalCDsoJXri7nsnLzroZwg642w7J207YSw6rCAIOuqhe2Zle2VmOqyjCDrtoTrpZjrkJwg6rKD7J2EIOuzvCDsiJgg7J6I64ukLiDrsLDqsr3snYQg6rOg66Ck7ZWY66m0IOq1reuvvOuLueqzvCDrhbjrj5nri7nsnbQg6rWs67aE65CcIOqyg+ycvOuhnCDsnKDstpTtlaAg7IiYIOyeiOqzoCwg64KY66i47KeAIOyekeydgCDsoJXri7nrk6TsnYAg7ZWY64uo7J2YIOygiOuwmOyXkOyEnCDrjZwg66qF7ZmV7ZWY6rKMIOu2hOulmOuQmOqzoCDsnojri6QuDQpcDQpcDQoqKuq4sOqzhO2VmeyKteydtCDrjbDsnbTthLDsl5DshJwg7J2Y66+47J6I64qUIO2MqO2EtOydhCDsi53rs4TtlZjsmIDsp4Drp4wsIOy2lOy2nO2VnCDtjKjthLTsnYQg7ZW07ISd7ZWY6riwIOychO2VtOyEoCDsnbjqsITsnZgg64+E66mU7J24IOyngOyLneydhCDrrLjsoJzsl5Ag7KCB7Jqp7ZWY64qUIOqyg+ydtCDspJHsmpTtlZjri6QuKioNCg0KXA0KXA0KXA0KXA0KXA0K