本代码展示的是tensorflow中K均值算法。
代码原始地址在这里
下面这段摘自百度百科:
K-means算法是硬聚类算法,是典型的基于原型的目标函数聚类方法的代表,它是数据点到原型的某种距离作为优化的目标函数,利用函数求极值的方法得到迭代运算的调整规则。
代码里面并没有把K的值直接设为10,而是设为25.然后把这25簇分为10类(对应10个数字)。相当于K均值之后用最近邻再次分类,思路很赞,调整K的值可以无限逼近拟合现实。K值用来调整计算粒度。K与训练样本相同,效果就和最近邻一样了。
经过测试,K值不同最终获得的准确率也不一样:
K值 | 准确率 |
---|---|
10 | 0.52 |
25 | 0.71 |
50 | 0.8 |
100 | 0.86 |
200 | 0.90 |
500 | 0.93 |
1000 | 0.94 |
5000 | 0.96 |
个人感觉,K值越大,分类越细,越是能更多的拟合向量的分布特征。但是综合来看,K=100是性价比较高的数字。K值越大,计算的越慢。K=5000让我的i7-8550U压力山大, 0.96差不多是极限了吧,没有心思测试10000的K值了。
源码
(下文中的图心应该指别人说的质心,不知道怎么翻译,就这么着了)
import numpy as np
import tensorflow as tf
from tensorflow.contrib.factorization import KMeans
# 本代码演示K均值的用法, tensorflow版本必须大于等于V1.1.0
# 代码项目:Project: https://github.com/aymericdamien/TensorFlow-Examples/
# 由于tensorflow实现的K均值算法无法从GPU中获得额外好处,所以我们忽略GPU设备
import os
os.environ['CUDA_VISIBLE_DEVICES'] = ''
# 导入MNIST数据集
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('../data', one_hot=True)
full_data_x = mnist.train.images # shape:(55000, 784),注意记住这个55000,理解后面会用到
# 模型超参数
num_steps = 50 # 训练的总步数
batch_size = 1024 # 每个batch的样本数
k = 25 # K的大小
num_classes = 10 # 十个数字,这也是模型最终分类的个数
num_features = 784 # 每个图片都是28X28,共784个像素
# 输入图片
X = tf.placeholder(tf.float32, shape=[None, num_features])
# 标注
Y = tf.placeholder(tf.float32, shape=[None, num_classes])
# K-Means的参数,其实是从库里使用提前封装好的图
kmeans = KMeans(inputs=X, num_clusters=k, distance_metric='cosine', use_mini_batch=True)
# 构建K-Means的计算图
training_graph = kmeans.training_graph()
if len(training_graph) > 6: # tensorflow 1.4及以上版本
(all_scores, cluster_idx, scores, cluster_cnters_initialized,
cluster_cnters_var, init_op, train_op) = training_graph
else:
(all_scores, cluster_idx, scores, cluster_cnters_initialized,
init_op, train_op) = training_graph
cluster_idx = cluster_idx[0] # 存放所有数据的图心序号
avg_distance = tf.reduce_mean(scores) # 存放平均距离
# 初始化变量
init_vars = tf.global_variables_initializer()
# 建立一个tensorflow会话
sess = tf.Session()
# 运行初始化操作
sess.run(init_vars, feed_dict={X: full_data_x})
sess.run(init_op, feed_dict={X: full_data_x})
# 训练
for i in range(1, num_steps + 1):
_, d, idx = sess.run([train_op, avg_distance, cluster_idx], feed_dict={X: full_data_x})
if i % 10 == 0 or i == 1:
print('步骤 %i, 平均距离是:%f' % (i, d))
# 给每个图心分配一个标签
# 计算每个图心的样本个数,把样本归入离它最近的图心(使用idx)
counts = np.zeros(shape=(k, num_classes)) # counts的shape是(25, 10),用于存放25个图心分类的频率计数
for i in range(len(idx)):
# idx的shape是(55000,),每个成员都是0~24之间的值,对应所属图心的编号
counts[idx[i]] += mnist.train.labels[i]
# mnist.train.labels的shape是(55000, 10), 每个成员都是独热编码,用来标注属于哪个数字
# 将最高频的标注分配给图心。 len(labels_map)是25,也就是每个图心一个成员,记录每个图心所属的数字分类
labels_map = [np.argmax(c) for c in counts]
# 转换前,labels_map的shape为(25,)
labels_map = tf.convert_to_tensor(labels_map)
# 此时labels_map变成了一个const op,输出就是上面(25,)包含的值
# 评估模型。下面开始构建评估计算图
# 注意:centroid_id就是对应label
cluster_label = tf.nn.embedding_lookup(labels_map, cluster_idx)
# cluster_idx输出的tensor,每个成员都映射到labels_map的一个值。
# cluster_label的输出就是映射的label值,后面用来跟标注比较计算准确度
# 计算准确率
correct_prediction = tf.equal(cluster_label, tf.cast(tf.argmax(Y, 1), tf.int32))
accuracy_op = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# 测试模型
test_x, test_y = mnist.test.images, mnist.test.labels
print("测试准确率:", sess.run(accuracy_op, feed_dict={X: test_x, Y: test_y}))
输出:
Connected to pydev debugger (build 173.4301.16)
Extracting ../data\train-images-idx3-ubyte.gz
Extracting ../data\train-labels-idx1-ubyte.gz
Extracting ../data\t10k-images-idx3-ubyte.gz
Extracting ../data\t10k-labels-idx1-ubyte.gz
2018-03-29 10:57:11.388774: I C:\tf_jenkins\home\workspace\rel-win\M\windows\PY\36\tensorflow\core\platform\cpu_feature_guard.cc:137] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX AVX2
步骤 1, 平均距离是:0.341471
步骤 10, 平均距离是:0.221609
步骤 20, 平均距离是:0.220328
步骤 30, 平均距离是:0.219776
步骤 40, 平均距离是:0.219419
步骤 50, 平均距离是:0.219154
测试准确率: 0.7127
进程已结束,退出代码0
本程序牵涉到的知识点:
- convert_to_tensor
- embedding_lookup
tf.convert_to_tensor用于将一个变量转化为计算图中的const op。
使用pycharm调试,查看转化后labels_map的op为:
name: "Const_2"
op: "Const"
attr {
key: "dtype"
value {
type: DT_INT32
}
}
attr {
key: "value"
value {
tensor {
dtype: DT_INT32
tensor_shape {
dim {
size: 25
}
}
tensor_content: "\003\000\000\000\001\000\000\000\010\000\000\000\006\000\000\000\002\000\000\000\006\000\000\000\004\000\000\000\005\000\000\000\007\000\000\000\t\000\000\000\003\000\000\000\003\000\000\000\002\000\000\000\004\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\006\000\000\000\007\000\000\000\001\000\000\000\002\000\000\000\003\000\000\000\006\000\000\000\001\000\000\000\005\000\000\000"
}
}
}
(下面这段来自知乎:https://www.zhihu.com/question/52250059)
embedding_lookup(params, ids)其实就是按照ids顺序返回params中的第ids行。
比如说,ids=[1,3,2],就是返回params中第1,3,2行。返回结果为由params的1,3,2行组成的tensor.