在這篇文章中,我們提到了集成式學習(Ensemble Learning)在處理非監督式學習(Unsupervised Learning)的難處,並且提出如何使用重設索引來解決問題,也提到了重設索引可能的問題。在這篇文章中,我們將要說明另一個比較強健的方法,可以進行非監督式學習的集成。
一、回顧分群(Clustering)問題
說到分群問題,我們最終想要知道的事情,就是「哪些資料應該要放在同一群」,這是一個比較巨觀的想法。
如果使用微觀的想法,那分群問題即是「第 1 筆資料跟第 2 筆資料要放在同一群嗎?第 1 筆資料跟第 3 筆資料要放在同一群嗎? ... 第 2 筆資料跟第 3 筆資料要放在同一群嗎? ... 第 N-1 筆資料跟第 N 筆資料要放在同一群嗎?」。只要有辦法回答上述全部問題,就做完了分群。
二、共識分群(Consensus Clustering)的概念
現在,我們考慮「第 1 筆資料跟第 2 筆資料要放在同一群嗎」這個問題。假設現在有 3 個非監督式演算法,A 跟 B 演算法將第 1 筆資料跟第 2 筆資料放在同一群,C 演算法將第 1 筆資料跟第 2 筆資料放在不同群。那麼集成這 3 個演算法之後,第 1 筆資料跟第 2 筆資料要放在同一群,比較合理吧!
因此,我們要做的事情是「對任 2 筆資料,看看是不是過半的非監督式演算法,認為這 2 筆資料是屬於同一群」。
共識分群比上週提到的重設索引,最大的好處在於共識分群只管資料是否放在同一群,至於索引是什麼數字,一點也不重要,也就避免了索引的重設、運算中會有的問題。
三、共現矩陣(Co-occurrence Matrix)
用來表現「對任 2 筆資料,看看是不是過半的非監督式演算法,認為這 2 筆資料是屬於同一群」的方法之一,便是使用共現矩陣。
共現矩陣是一個 N 乘 N 的矩陣,N 的大小即為資料數,第 i 行、第 j 列代表「有多少基學習器將第 i 筆資料跟第 j 筆資料放在同一群」。建立好共現矩陣後,就能夠根據矩陣內容來找到最佳的集成結果。
以下是建立共現矩陣的範例程式。X 是一個 2 維的 list ,用來記錄所有基學習器(Base Learner)的分群結果。第 i 行代表第 i 個基學習器的輸出,第 j 列代表該基學習器給第 j 筆資料的索引。
程式中,我們抓出第 i 筆資料,將此資料跟第 j 筆資料比較,看看有多少基學習器將這 2 筆資料放在同一群。
def get_cooccurence_matrix(X):
n_sample = len(X[0])
n_ensemble = len(X)
matrix = np.zeros((n_sample, n_sample))
for i in range(n_sample):
for j in range(n_sample):
for k in range(n_ensemble):
if(X[k][i] == X[k][j]):
matrix[i][j] = matrix[i][j] + 1
return matrix
四、找到共識
有了共現矩陣之後,現在我們要進行集成。首先,我們找有哪些資料跟第 1 筆資料應該要放在同一群,然後換看第 2 筆、第 3 筆 ... 。
for row in range(n_sample):
element = []
for column in range(n_sample):
if(matrix[row][column] > threshold):
element.append(column)
cluster.append(element)
接下來,我們要看交集。舉例來說,如果第 1、2、3 筆資料要放在一起,第 3、4、5 筆資料要放在一起。那集成之後則第 1、2、3、4、5 都要放在一起。
for row in range(n_sample):
for column in range(n_sample):
if(intersection(cluster[row], cluster[column])):
cluster[row] = union(cluster[row], cluster[column])
接著,我們將刪去重複的資訊。
for row in cluster:
if row not in ensemble:
ensemble.append(row)
最後指定索引,就是集成後結果囉。
for row in range(len(ensemble)):
result[np.array(ensemble[row]).astype(int)] = row
事實上,共識分群已經有 Python OpenEnsembles 函式庫可以使用,此函式庫也提供了其他集成非監督式學習的方法。函式庫的使用範例,可以參考「集成式學習:Python 實踐!整合全部技術,打造最強模型」
關於作者
Chia-Hao Li received the M.S. degree in computer science from Durham University, United Kingdom. He engages in computer algorithm, machine learning, and hardware/software codesign. He was former senior engineer in Mediatek, Taiwan. His currently research topic is the application of machine learning techniques for fault detection in the high-performance computing systems.
完整程式
import numpy as np
from sklearn import datasets
from sklearn.metrics import accuracy_scoredef intersection(x, y):
return (len(set(x) & set(y)) > 0)def union(x, y):
return list(set(x) | set(y))def KMeans(X, n_clusters):
dist = np.zeros(n_clusters)
result = np.zeros(len(X))
center = X[np.random.randint(0, len(X), size = 3)]
for iteration in range(20):
for i in range(len(X)):
for j in range(n_clusters):
dist[j] = sum((np.array(X[i]) -
np.array(center[j])) ** 2)
result[i] = np.argmin(dist)
for j in range(n_clusters):
center[j] = np.mean([X[i] for i in range(len(X)) if
result[i] == j], axis = 0)
return result.astype(int)def get_cooccurence_matrix(X):
n_sample = len(X[0])
n_ensemble = len(X)
matrix = np.zeros((n_sample, n_sample))
for i in range(n_sample):
for j in range(n_sample):
for k in range(n_ensemble):
if(X[k][i] == X[k][j]):
matrix[i][j] = matrix[i][j] + 1
return matrixdef get_ensemble_cluster(matrix, n_sample, n_ensemble):
threshold = n_ensemble / 2
ensemble = []
cluster = []
result = np.zeros(n_sample)
for row in range(n_sample):
element = []
for column in range(n_sample):
if(matrix[row][column] > threshold):
element.append(column)
cluster.append(element)
for row in range(n_sample):
for column in range(n_sample):
if(intersection(cluster[row], cluster[column])):
cluster[row] = union(cluster[row], cluster[column])
for row in cluster:
if row not in ensemble:
ensemble.append(row)
for row in range(len(ensemble)):
result[np.array(ensemble[row]).astype(int)] = row
return resultiris = datasets.load_iris()
X = iris.data
Y = iris.targetn_cluster = 3
n_ensemble = 11
p = []for i in range(n_ensemble):
p.append(KMeans(X, n_cluster))
matrix = get_cooccurence_matrix(p)
ensemble = get_ensemble_cluster(matrix, len(Y), n_ensemble)print("Ensemble", accuracy_score(Y, ensemble))