1.闲扯
由于自己比较喜欢学习算法方面的知识,这得益于本科阶段搞了很多年的ACM,现在是研究生阶段,自然就去学习一些机器学习和深度学习的知识。前段时间从一些渠道知道有TensorFlow这个深度学习框架的存在,就去其官网看了一眼,发现根本看不懂。所以,我按照我学习计算机这些技术的一贯思路(就是去买该技术的实战书籍),就购买了两本关于tensorflow的书籍,一本是姚鹏鹏翻译的《TensorFlow机器学习项目实战》和李嘉璇的《TensorFlow技术解析与实践》,一开始看的是李嘉璇的《TensorFlow技术解析与实践》,这个美女很有意思,在序言之前就说道“恭喜你选择TensorFlow,它是最流行的深度学习框架,没有之一。我相信这是一本能让你坚持看到最后一页的技术书。”然并卵,我看了前三章的时候,确实被吸引到了,特别是tensorflow的一个用于教学目的神经网络在线演示网站PlayGround,但是我看到第四章的时候我就蒙圈了,我太菜了,根本看不懂她在说什么,就暂时没有兴致看下去了。就转去看另外一本书,也就是姚鹏鹏翻译的《TensorFlow机器学习项目实战》。这本书好在介绍了一些简单的tensorflow基础知识就开始上代码了,我个人是比较喜欢实战的。实践是认识的来源嘛。这本书的第一个例子就是用简单的K均值算法来聚类。其算法原理书上有,我就不赘述了。由于它这个例子的源代码我感觉书上解释的不是非常详细,而我为了理解这些源代码也费了不少劲才弄懂,所以写这篇文章的目的在于一方面分享心得,另一方面,巩固一下,温故而知新嘛。
2.开始
这书上展示的源代码少了一行"N=200"。然后,我用自己的方式敲了一下源代码,其实和书上是一样的。源代码如下
3.我的源代码
import tensorflow as tf
import numpy as np
import time
import matplotlib
import matplotlib.pyplot as plt
from sklearn.datasets.samples_generator import make_blobs,make_circles
def bucket_mean(data,bucket_ids,num_num_buckets):
total = tf.unsorted_segment_sum(data,bucket_ids,num_num_buckets)
count = tf.unsorted_segment_sum(tf.ones_like(data),bucket_ids,num_num_buckets)
return total/count
if __name__=='__main__':
DATA_TYPE = 'blobs'
N = 200
if DATA_TYPE == 'circle':
K = 2
else :
K = 4
MAX_ITERS = 100
start = time.time()
centers = [(-2,-2),(-2,1.5),(1.5,-2),(2,1.5)]
if DATA_TYPE == 'circle':
data,features = make_circles(n_samples=200,shuffle=True,noise=0.01,factor=0.4)
else :
data, features = make_blobs(n_samples=200, centers=centers,n_features=2,
cluster_std=0.8,shuffle=False,random_state=42)
fig,ax = plt.subplots()
tmp_x = np.asarray(centers).transpose()[0]
tmp_y = np.asarray(centers).transpose()[1]
ax.scatter(tmp_x,tmp_y,marker='o',s=250)
plt.show()
fig,ax = plt.subplots()
if DATA_TYPE == 'blobs':
ax.scatter(np.asarray(centers).transpose()[0],np.asarray(centers).transpose()[1],marker='o',s=250,c='y')
ax.scatter(data.transpose()[0],data.transpose()[1],marker='o',s=100,c=features,cmap=plt.cm.coolwarm)
plt.plot()
plt.show()
points = tf.Variable(data)
cluster_assigments = tf.Variable(tf.zeros([N],dtype=tf.int64))
centroids = tf.Variable(tf.slice(points.initialized_value(),[0,0],[K,2]))
sess = tf.Session()
sess.run(tf.global_variables_initializer())
sess.run(centroids)
rep_centroids = tf.reshape(tf.tile(centroids,[N,1]),[N,K,2])
rep_points = tf.reshape(tf.tile(points,[1,K]),[N,K,2])
sum_squares = tf.reduce_sum(tf.square(rep_points-rep_centroids),reduction_indices=2)
best_centroids = tf.argmin(sum_squares,1)
did_assigments_change = tf.reduce_any(tf.not_equal(best_centroids,cluster_assigments))
means = bucket_mean(points,best_centroids,K)
with tf.control_dependencies([did_assigments_change]):
do_updates = tf.group(centroids.assign(means),cluster_assigments.assign(best_centroids))
changed = True
iters = 0
fig,ax = plt.subplots()
if DATA_TYPE == 'blobs':
colourindexes = [2,1,4,3]
else :
colourindexes = [2,1]
while changed and iters<MAX_ITERS:
fig,ax = plt.subplots()
iters += 1
[changed,_] = sess.run([did_assigments_change,do_updates])
[centers,assignments] = sess.run([centroids,cluster_assigments])
ax.scatter(sess.run(points).transpose()[0],sess.run(points).transpose()[1],
marker='o',s=200,c=assignments,cmap=plt.cm.coolwarm)
ax.scatter(centers[:,0],centers[:,1],marker='^',s=500,c=colourindexes,cmap=plt.cm.plasma)
ax.set_title('Iteration'+str(iters))
plt.savefig("kmeans"+str(iters)+".png")
ax.scatter(sess.run(points).transpose()[0], sess.run(points).transpose()[1],
marker='o', s=200, c=assignments, cmap=plt.cm.coolwarm)
plt.show()
end = time.time()
print("Found in %.2f seconds"% (end - start) + str(iters) + "iterations" )
print("Centroids:")
print(centers)
print("Cluster assignments:", assignments)
4.解析
首先,代码段:
centers = [(-2,-2),(-2,1.5),(1.5,-2),(2,1.5)]
定义了centers变量并初始化数据,分别代表四个质心的位置。接着代码段:
data, features = make_blobs(n_samples=200, centers=centers,n_features=2,
cluster_std=0.8,shuffle=False,random_state=42)
由于tensorflow没有产生数据集的相关函数,这里使用了机器学习库sklearn中的make_circles来创建数据集。
其中:
n_samples是待生成的样本的总数。centers表示质心数,有两种赋值方法,一种是例如centers=3,就会产生有三个质点的数据集,另一种就是本例中的用法,直接以给定的这四个质点产生数据集。n_features是每个样本的特征数。cluster_std表示每个类别的方差。shuffle表示是否乱序,True则表示打乱,False则不打乱。random_state代表随机数种子。
所以,这条语句就产生了,200*2的数据。相当于200个坐标点。接下来的代码段:
tmp_x = np.asarray(centers).transpose()[0]
tmp_y = np.asarray(centers).transpose()[1]
ax.scatter(tmp_x,tmp_y,marker='o',s=250)
其中np.asarray()是将数据转成array类型,然后np.transpose()可以简单理解为转置。那么原先,200*2的矩阵就变成了2*200.
现在tmp_x存的是2*200矩阵中的第0列,也就是所有原先坐标点的x坐标,同理tmp_y存的是y坐标。接下来的代码段:
points = tf.Variable(data)
cluster_assigments = tf.Variable(tf.zeros([N],dtype=tf.int64))
centroids = tf.Variable(tf.slice(points.initialized_value(),[0,0],[K,2]))
points = tf.Variable(data)的意思是以产生的数据集data直接初始化变量points
cluster_assigments = tf.Variable(tf.zeros([N],dtype=tf.int64))是以一维长度为n的全零张量,并且元素类型为int64初始化变量cluster_assigments
initialized_value()的作用:返回已经初始化变量的值.你应该使用这个函数来代替使用变量本身来初始化依赖这个变量的值的其他变量。那其实points.initialized_value()就是points本身的意思。tf.slice函数是用来切分的,tf.slice(points.initialized_value(),[0,0],[K,2])的意思就是吧points从第0行第0列开始,行数为K,列数为2的部分切取出来。然后用来初始化centroids。这句代码的意思就是先认定最开始的4个点为质心。接下来的代码段:
sess = tf.Session()
sess.run(tf.global_variables_initializer())
sess.run(centroids)
就是开启会话。主要说一下tf.global_variables_initializer(),这个的作用就是初始化,防止受到之前计算的残余值的影响。
接下来再看代码段:
rep_centroids = tf.reshape(tf.tile(centroids,[N,1]),[N,K,2])
rep_points = tf.reshape(tf.tile(points,[1,K]),[N,K,2])
sum_squares = tf.reduce_sum(tf.square(rep_points-rep_centroids),reduction_indices=2)
best_centroids = tf.argmin(sum_squares,1)
did_assigments_change = tf.reduce_any(tf.not_equal(best_centroids,cluster_assigments))
这里先要去理解tf.reduce_sum这个函数,因为它可能比较难以理解。推荐看下知乎再回来reduce_sum()
rep_centroids = tf.reshape(tf.tile(centroids,[N,1]),[N,K,2])和rep_points = tf.reshape(tf.tile(points,[1,K]),[N,K,2])这两段代码是为了每个质点和每个样本点求距离平方的方便而构造的。然后对于sum_squares = tf.reduce_sum(tf.square(rep_points-rep_centroids),reduction_indices=2),相当于每个质点和每个样本点先做差,然后再平方,最后再把和相加。就是两个坐标点(x1,y1)和(x2,y2)求距离平方的公式(x1-x2)^2+(y1-y2)^2一步步的实现。然后由于距离平方求出来了,此时的sum_squares是一个N*K的矩阵,此时best_centroids = tf.argmin(sum_squares,1)就把sum_squares每一行的最小值的索引求出来了,这个索引刚好对应着分类。best_centroids是N*1的矩阵。这些代码的作用就是把样本点归类到相应的质心。did_assigments_change = tf.reduce_any(tf.not_equal(best_centroids,cluster_assigments)),not_equal是让两个张量的对应位判断是否不相等,不相等就返回True,reduce_any是让所有元素求或,best_centroids和cluster_assigments只要有一个元素不相等,就会导致did_assigments_change 变成True,只有完全相等才False,也就是说,did_assigments_change来记录每个元素的分类是否有改变。接下来的代码段:
means = bucket_mean(points,best_centroids,K)
由于已经分出类了,接下来就可以对每一类求出x,y坐标均值,就可以求出每一类的新的质心了。接下来的代码段:
with tf.control_dependencies([did_assigments_change]):
do_updates = tf.group(centroids.assign(means),cluster_assigments.assign(best_centroids))
其中with tf.control_dependencies([a,b,c]):
d
的作用就是确保a,b,c在d之前执行。
在这里就是确保did_assigments_change在do_updates之前执行。而tf.group是为了一次完成多个操作。assign是为变量赋值。centroids.assign(means),cluster_assigments.assign(best_centroids)意思就是每次求出来的新的质点赋给centroids,新的归类赋给cluster_assigments。
至此,建图完成了。tensorflow是符号化编程,目的是先构图可以整合优化性能。
比如我在会话运行某个变量sess.run(do_updates),它就先必须算出means和best_centroids,而要算出这两个又要先算出它们所依赖的,这不就是一张图嘛,未run之前,是没有数据参与的,所以,你就别想用pycharm debug出变量的值了,它显示的只有结构。接下来的代码段:
while changed and iters<MAX_ITERS:
fig,ax = plt.subplots()
iters += 1
[changed,_] = sess.run([did_assigments_change,do_updates])
[centers,assignments] = sess.run([centroids,cluster_assigments])
ax.scatter(sess.run(points).transpose()[0],sess.run(points).transpose()[1],
marker='o',s=200,c=assignments,cmap=plt.cm.coolwarm)
ax.scatter(centers[:,0],centers[:,1],marker='^',s=500,c=colourindexes,cmap=plt.cm.plasma)
ax.set_title('Iteration'+str(iters))
plt.savefig("kmeans"+str(iters)+".png")
这个代码段就很容易理解了,sess.run([did_assigments_change,do_updates]),就可以得到did_assigments_change的值,然后赋给changed,就可以知道有没有改变了,每次[centers,assignments] = sess.run([centroids,cluster_assigments])就可以得到centroids,cluster_assigments的值,分别赋值给centers,assignments,就可以得到新的质心和新的分类。其它画图的代码我就不多说了。