【机器学习】人像识别(三)——K-Means聚类

简介

  K-Means聚类是一种非监督的聚类方式,原理参看数据挖掘十大算法 | k-means。
  

代码

import sys
import random
import numpy as np
from sklearn.decomposition import IncrementalPCA

imgNum = 10 # 几张图片
KNum = 2 # 分成几类
n = 2 # 每张图片都是n×n
dimension = 2016 # 每张图片的维数
dst_dimension = 10 # 想降到的维数
bound = 10 # 前后两次迭代结果之差小于这个时可以停止
maxRecurseTime = 10 # 最多迭代次数
centroids = [] # 存放KNum个质心的n维坐标向量
last_centroids = [] # 上一次递归得到的质心坐标们
ori_dots = [] # 存放每个点的n维坐标向量
dots  = [] # 降维之后的点的坐标
clusters = [] # 存放每个类中有哪些点, clusters[i]中存放的是第i类中的点的下标,第i类的中心是centroids[i]
selected = []

Distance = lambda v: np.linalg.norm(v)

# 初始化点的坐标,并进行降维,返回降维后的向量集
def InitDots():
    # 读入imgNum个点的坐标,存放在dots当中
    for i in range(imgNum):
        ori_dots.append([])

    # 读入向量集并降维
    ReadFiles(ori_dots)
    return PCA(ori_dots)

# 初始化质心信息
def InitCentroids():
    # 初始化质心信息
    for i in range(KNum):
        centroids.append([])
        centroids[i] = np.array([float(0)] * dimension) # 初始化为全零
        clusters.append([])

    # 随机挑选初始时的‘质心’坐标
    for i in range(KNum):
        _ = random.randrange(imgNum)
        while _ in selected:
            _ = random.randrange(imgNum)
        selected.append(_)
    selected.sort()
    print('selected:' , selected)

    for i in range(KNum):
        centroids[i] = dots[selected[i]]
    print('centroids 0:', centroids)

# 读入imgNum个图的坐标向量
def ReadFiles(dots):  
    path = r'C:\Users\Owner\Documents\Visual Studio 2015\Projects\Python\K-Means\K-Means\\'
    fd = open(path + 'input.txt', 'r')
    _ = fd.read() # 一次读进所有
    fd.close()
    _ = _.split()
    for i in range(len(_)):
        dots[i % imgNum].append(255 - float(_[i]))
    tmp = [2, 5]
    for i in tmp:
        for j in range(len(dots[i])):
            dots[i][j] *= 0.9

# 降维操作
def PCA(dots):
    X = np.array(dots)
    ipca = IncrementalPCA(n_components = dst_dimension)
    ipca.fit(X)
    Y = ipca.transform(X) 
    print('y = ', Y, '\n')
    for i in range(len(Y)):
        Y[i] = np.array(Y[i]) 
    return Y

# 对于每个cluster,计算质心
def CalcCentroids(KNum, dimension, centroids, dots, clusters):
    # 先把上一次得到的质心存放到last_centroids当中
    last_centroids = centroids
    # centroids = [] 这会导致传不回去

    for i in range(KNum):
        v = np.array([float(0)] * dst_dimension)
        for _ in clusters[i]:
            v += dots[_]
        l = len(clusters[i])
        centroids[i] = (v / l)

# 聚类,判断每个点属于哪个类
def Cluster(imgNum, KNum, dots, clusters):
    # 清空原有数据
    for i in range(KNum):
        clusters[i] = []

    # 计算每个点到每个质心的距离,并将他们放到相应的cluster中
    for i in range(imgNum):
        store = [] # 存放当前的点到每个质心的距离
        for j in range(KNum):
            store.append(Distance(dots[i] - centroids[j]))
        cluster_index = store.index(min(store)) # store中最小的数是min(store),找这个最小数的下标用store.index()
        clusters[cluster_index].append(i)

dots = InitDots()
InitCentroids()

temp = sys.stdout
log_root = r'C:\Users\Owner\Documents\Visual Studio 2015\Projects\Python\K-Means\K-Means\Log_'+str(dst_dimension)+'\\'
sys.stdout = open(log_root + str(selected) + '.txt','w')

for i in range(bound):
    print('ROUND ' + str(i) + ': ')
    print('centroids: ', centroids)
    Cluster(imgNum, KNum, dots, clusters)
    print('clusters',  clusters, '\n')
    CalcCentroids(KNum, dimension, centroids, dots, clusters)

print('centroids: ', centroids)
print('clusters: ', clusters)

sys.stdout = temp

遇到的问题

  1. 计算两点之间的欧氏距离可以用numpy库中的函数:
    dist = numpy.linalg.norm(vec1 - vec2)

  2. 经常发现ROUND 2之后,质心就不再变了。对此,我心中不太踏实,踟蹰于聚类速度是否当真如此之快。不过数据集这样小,结果也正确,姑且将此归功于K-Means的有效性吧。

  3. 在阅读log文档时,发现编号为2和5的图总是被归到一组,然而恢复出灰度图后,发现这两张图并不是一个人,但有一个共同点——色调较暗。因此在读入数据后,将这两张图的灰度值乘了系数0.9,之后聚类结果就基本稳定了。

你可能感兴趣的:(Python,机器学习)