군집화 k-means Clustering (ver.R)
출처
http://jorditorres.org/first-contact-with-tensorflow/#cap3 (First Contact with tensorflow)
https://tensorflow.rstudio.com/ (TensorFlow™ for R)
http://motioninsocial.com/tufte/ (Tufte in R)
k-means 군집화 (ver. R)
k-means Clustering (ver. Python)
1. 좌표값 생성
num_puntos <- 2000
temp_logic <- sample(0:1, num_puntos, T)
temp_x1 <- rnorm(num_puntos, 0, .9)
temp_y1 <- rnorm(num_puntos, 0, .9)
temp_x2 <- rnorm(num_puntos, 3, .5)
temp_y2 <- rnorm(num_puntos, 1, .5)
x <- temp_logic * temp_x1 + (1 - temp_logic) * temp_x2
y <- temp_logic * temp_y1 + (1 - temp_logic) * temp_y2
plot(y ~ x, col = 2 * temp_logic + 2)
require(ggplot2);require(ggthemes)
## Loading required package: ggplot2
## Loading required package: ggthemes
ggplot(data.frame(x, y, col = factor(temp_logic)) , aes(x, y, col = col)) + theme_tufte() + geom_point(size = 2, alpha = .3)
2. k-means clustering algorithm
라이브러리 tensorflow
require(tensorflow)
## Loading required package: tensorflow
data를 텐서형태로 만들기
vectors <- cbind(x,y)
나눌 그룹 수 : 4개 ( k = 4 )
initial centroids 설정 : 2000개의 data를 무작위로 4개의 그룹으로 나눈 후 평균을 계산.
k <- 4
centroides <- tf$Variable(tf$slice(tf$random_shuffle(vectors),as.integer(c(0,0)),as.integer(c(k,-1))))
r에서 0이나 -1을 치면 기본적으로 float64로 들어간다. 그래서 integer로 바꿔줘야만 tf$slice가 돌아간다. 미쳤다…
print(dim(vectors)) ; centroides$get_shape()
## [1] 2000 2
## (4, 2)
k-means algorithm은 반복해가면서 주어진 데이터와 centroides의 유클리디안 거리를 계산하고 그 거리의 합을 작게하는 방향으로 centroides를 조절하는 알고리즘이다.
이 값을 계산하기 위해서 tensorflow에서는 tf$subtract(vectors, centroides) 를 쓴다. (tf$sub 가 tf$subract 로 rename 되었다.) (하지만 R 에서는 그냥 빼기 “-”를 써도 된다.)
아래에서 하는 과정은 vectors와 centroides의 차원을 맞춰서 연산하기 가능하도록 하기 위한 것이다.
expanded_vectors <- tf$expand_dims(vectors,as.integer(0))
expanded_centroides <- tf$expand_dims(centroides, as.integer(1))
print(expanded_vectors$get_shape());print(expanded_centroides$get_shape())
## (1, 2000, 2)
## (4, 1, 2)
원래는 연산할 수 없는 tensor 2개지만 tensorflow은 broadcasting을 따르기 때문에, tf$subtract 함수를 쓰면 알아서 차원을 맞춰준다. (크기가 1인 차원은 텐서 연산 시 다른 텐서의 해당 차원 크기에 맞게 계산을 반복해준다.) (R에서는 그냥 “-” 빼기를 써도 tf$subtract 연산을 해준다.)
아래 코드는 연산코드이다.
diff <- expanded_vectors - expanded_centroides # diff <- tf$subtract(expanded_vectors, expanded_centroides)
sqr <- diff^2 # sqr <- tf$square(diff)
distances <- tf$reduce_sum(sqr, as.integer(2))
assignments <- tf$argmin(distances, as.integer(0))
print(diff$get_shape());print(sqr$get_shape());print(distances$get_shape());print(assignments$get_shape())
## (4, 2000, 2)
## (4, 2000, 2)
## (4, 2000)
## (2000,)
expanded_vectors에 expanded_centroides를 빼서 diff에 assign, diff를 제곱해서 sqr에 assign, sqr을 더해서 distances에 assign, distances에 minimum 값에 해당하는 centroides의 번호를 assignments에 assign 한다.
이후 assignments에 저장된 번호를 기준으로 centroides를 다시 계산해서 mean에 assign 하고, centroides를 갱신하게된다.
temp_ft<-function(vectors, assignments, k){
temp_list<-list()
for(i in 1:k){
temp_list[[i]]<-tf$reduce_mean(tf$gather(vectors,tf$reshape(tf$where(tf$equal(assignments,i)),as.integer(c(1,-1)))),as.integer(1))
}
return(temp_list)
}
means <- tf$concat(temp_ft(vectors, assignments, k),as.integer(0))
코드를 뜯어보자면
i가 1부터 4까지 바뀌면서 tf.equal로 assignments가 이 I 인지 아닌지 bool 값으로 표시하고, tf.where로 True 인 값의 index를 찾아낸다. tf.reshape는 그 index의 tensor 모양을 바꿔서 궁극적으로는 tf.gather로 index에 해당되는 좌표값(vectors에 저장 되어있는 데이터값)만을 뽑아내게된다. 이 뽑아낸 값을 tf.reduce_mean으로 평균을 계산해서 새로운 centroide를 만들어내게된다.
I가 1부터 4까지 바뀌면서 생성된 4개의 centroid를 tf.concat 을 써서 하나의 텐서로 만들어서 means에 assign하는 과정이다.
이를 python 버전에서는
means = tf.concat([tf.reduce_mean(tf.gather(vectors,tf.reshape(tf.where(tf.equal(assignments,c)),[1,-1])),1) for c in range(k)],0)
로 c를 0부터 3까지 바꾸면서 간단하게 표현하지만, R에서는 어찌 표현해야 할지 몰라 for문을 썼고, 함수로 만들었다.
update_centroides <- tf$assign(centroides, means)
init_op <- tf$global_variables_initializer()
sess <- tf$Session()
sess$run(init_op)
num_steps <- 100
for(step in 1:num_steps){
result <- sess$run(list(update_centroides, centroides, assignments))
}
centroid_values <- result[[2]] ; assignment_values <- result[[3]]
print(centroid_values)
## [,1] [,2]
## [1,] NaN NaN
## [2,] NaN NaN
## [3,] NaN NaN
## [4,] NaN NaN
뭔가 잘못 되었다. 이유를 찬찬히 생각해보니, “index 차이”였다.
무슨 말인가 하면, Python은 index가 0에서 시작하고, R은 index가 1에서 시작한다!
그래서 적절히 보고 index를 잘 설정해줘야한다는 것을 알았다. 그리고 앞으로 tensorflow를 R로 쓸 때의 험난한 길이 보였다!!!!!!!!
그래서 바꾼 부분은 다음과 같다. (tf$equal 안에 i를 as.integer(i-1)로 고쳤다.)
temp_ft<-function(vectors, assignments, k){
temp_list<-list()
for(i in 1:k){
temp_list[[i]]<-tf$reduce_mean(tf$gather(vectors,tf$reshape(tf$where(tf$equal(assignments,as.integer(i-1))),as.integer(c(1,-1)))),as.integer(1))
}
return(temp_list)
}
means <- tf$concat(temp_ft(vectors, assignments, k),as.integer(0))
update_centroides <- tf$assign(centroides, means)
그리고 나서 다시 돌리게 되면
init_op <- tf$global_variables_initializer()
sess <- tf$Session()
sess$run(init_op)
num_steps <- 100
for(step in 1:num_steps){
result <- sess$run(list(update_centroides, centroides, assignments))
}
centroid_values <- result[[2]] ; assignment_values <- result[[3]]
print(centroid_values)
## [,1] [,2]
## [1,] 3.0126321 0.9878420
## [2,] -0.3047442 -0.8658527
## [3,] -0.5894321 0.7145909
## [4,] 0.9147840 0.1380691
제대로 나온다.
Graph (Scatter plot)
require(ggplot2);require(ggthemes)
data <- data.frame(x = x, y = y, cluster = factor(assignment_values))
ggplot(data , aes(x, y, col = cluster)) + theme_tufte() + geom_point(size = 2, alpha = .3)