聚類算法之k-means算法圖像像素重新處理

前言:

機器學習----聚類算法的應用很廣泛,屬于非監督學習的它在無法提前定義標簽的前提下將訓練數據聚類。我們今天討論其中比較簡單的一種算法k-means算法。

k-means算法

每當你觀察某些數據源時,很可能會發現數據會以某種形式形成聚類(cluster) 。如下圖:

我們很容易看出來這些數據點可以聚成三類,但具體怎么聚,中心點在哪,數據多了以后還會出現聚幾類的問題。這時我們就改用k-means算法了。

我們先舉幾個生活中的例子:

例如,每個輸入可以是博客文章的標題(我們可以設法用數字向量來表示它) ,那么在這種情況下,我們的目標可能是對相似的文章進行聚類,也可能是了解用戶都在寫什么博客內容。或者,假設我們有一張包含數千種(紅、綠、藍)顏色的圖片,但是我們需要一個5 色版本來進行絲網印刷。這時,聚類分析不僅可以幫助我們選出 5 種顏色,并且還能將“色差”控制在最小的范圍之內。

k-均值算法(k-means)是一種最簡單的聚類分析方法,它通常需要首先選出聚類 k 的數目,然后把輸入劃分為集合 S 1 ,…,S k ,并使得每個數據到其所在聚類的均值(中心對象)的距離的平方之和(即歐式距離)最小化。由于將 n 個點分配到 k 個聚類的方法非常多,所以尋找一個最優聚類方法是一件非常困難的事情。一般情況下,為了找到一個好的聚類方法,我們可以借助于迭代算法。

具體步驟:

1.首先從 d 維空間中選出選擇 k 個數據點作為初始聚類的均值(即中心)。
2.計算每個數據點到這些聚類的均值(即聚類中心)的距離,然后把各個數據點分配給離它最近的那個聚類。
3.如果所有數據點都不再被重新分配,那么就停止并保持現有聚類。
4.如果仍有數據點被重新分配,則重新計算均值,并返回到第 2 步。

寫具體的代碼之前,我們先自己寫個計算向量的工具類,方便以后使用:


from __future__ import division 
import re, math, random

# 
# functions for working with vectors
#

def vector_add(v, w):
    return [int(v_i) + int(w_i) for v_i, w_i in zip(v,w)]

def vector_subtract(v, w):
    return [int(v_i) - int(w_i) for v_i, w_i in zip(v,w)]

def vector_sum(vectors):
    return reduce(vector_add, vectors)

def scalar_multiply(c, v):
    return [c * v_i for v_i in v]

def vector_mean(vectors):
    n = len(vectors)
    return scalar_multiply(1/n, vector_sum(vectors))

def dot(v, w):
    """v_1 * w_1 + ... + v_n * w_n"""
    return sum(int(v_i) * int(w_i) for v_i, w_i in zip(v, w))

def sum_of_squares(v):
    """v_1 * v_1 + ... + v_n * v_n"""
    return dot(v, v)

def magnitude(v):
    return math.sqrt(sum_of_squares(v))

def squared_distance(v, w):
    return sum_of_squares(vector_subtract(v, w))

def distance(v, w):
   return math.sqrt(squared_distance(v, w))

def shape(A):
    num_rows = len(A)
    num_cols = len(A[0]) if A else 0
    return num_rows, num_cols

def get_row(A, i):
    return A[i]
    
def get_column(A, j):
    return [A_i[j] for A_i in A]

def make_matrix(num_rows, num_cols, entry_fn):
    return [[entry_fn(i, j) for j in range(num_cols)]
            for i in range(num_rows)]  

def is_diagonal(i, j):
    return 1 if i == j else 0

這樣我們計算歐氏距離顯得方便了很多。

class KMeans:
    def __init__(self, k):
        self.k = k          
        self.means = None  
    def classify(self, input):
        #求input的值里那個cluster最近,返回下標
        return min(range(self.k),
                   key=lambda i: vector.squared_distance(input, self.means[i]))
                   
    def train(self, inputs):
        self.means = random.sample(inputs, self.k)
        assignments = None
        while True:
            new_assignments = map(self.classify, inputs)
            #結束條件
            if assignments == new_assignments:                
                return
            #繼續訓練
            assignments = new_assignments
            for i in range(self.k):
                i_points = [p for p, a in zip(inputs, assignments) if a == i ]
                if i_points:                                
                    self.means[i] = vector.vector_mean(i_points)

圖片的重新分配底色

有了k-means,我們來簡單實現下上文提到的絲網印刷(5),

def recolor_image(input_file, k=5):
    img = mpimg.imread(input_file)
    pixels = [pixel for row in img 
            for pixel in row]
    clusterer = KMeans(k)
    clusterer.train(pixels)

    def recolor(pixel):
        cluster = clusterer.classify(pixel)
        return clusterer.means[cluster]

    new_img = [[recolor(pixel) for pixel in row]
               for row in img]
    plt.imshow(new_img)
    plt.axis('off')
    plt.show()

if __name__ == '__main__':
    input_file = "image/test.jpg"
    recolor_image(input_file, k=5)

我們來看看效果:

![](http://upload-images.jianshu.io/upload_images/2199772-78ae90909b6b8112.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

效果還行,有點像照片底片~

  • tips:測試時不要上傳太大的圖片哦,我這個是500 X 500的,跑了大概1min30s(4核CPU),筆記本風扇嗡嗡的~ - _ -

結束語

還有很多聚類的算法,這里只寫出一個,歡迎留言給我,求告知除k-means和knn以外的算法哦~共同學習。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容