之前讲解了基于中心聚类,采用kmeans聚类算法,下面讲解基于噪声密度聚类算法DBSACN与基于层次聚类算法
(Agglomerative)算法
2)噪声密度
① 算法定义
噪声密度(Density-Based Spatial Clustering of Applications with Noise, 简写DBSCAN)随机选择一个样本做圆心,以事先给定的半径做圆,凡被该圆圈中的样本都被划为与圆心样本同处一个聚类,再以这些被圈中的样本做圆心,以事先给定的半径继续做圆,不断加入新的样本,扩大聚类的规模,直到再无新的样本加入为止,即完成一个聚类的划分. 以同样的方法,在其余样本中继续划分新的聚类,直到样本空间被耗尽为止,即完成整个聚类划分过程. 示意图如下:当然图中为了显示简洁,圆内每次只选取一个样本继续画圆。下图使用Kmeans效果就不太好
DBSCAN算法中,样本点被分为三类:
-
边界点(Border point):可以划分到某个聚类,但无法发展出新的样本;
-
噪声点(Noise):无法划分到某个聚类中的点;被孤立
-
核心点(Core point):除了噪声和边界点以外的样本都是核心点;
上图中,A和B为核心点,C为边界点,D为噪声点. 此外,DBSCAN还有两个重要参数:
-
邻域半径:设置邻域半径大小;
-
最少样本数目:邻域内最小样本数量,某个样本邻域内的样本超过该数,才认为是核心点.否者认为是边界点,不再扩展。
② 实现
sklearn提供了DBSCAN模型来实现噪声密度聚类,原型如下:
model = sc.DBSCAN(eps, # 半径 min_samples) # 最小样本数
示例代码:
# 噪声密度聚类示例 import numpy as np import sklearn.cluster as sc import matplotlib.pyplot as mp import sklearn.metrics as sm # 读取样本 x = [] with open("../data/perf.txt", "r") as f: for line in f.readlines(): line = line.replace("\n", "") data = [float(substr) for substr in line.split(",")] x.append(data) x = np.array(x) epsilon = 0.8 # 邻域半径 min_samples = 5 # 最小样本数 # 创建噪声密度聚类器 model = sc.DBSCAN(eps=epsilon, # 半径 min_samples=min_samples) # 最小样本数 model.fit(x) score = sm.silhouette_score(x, model.labels_, sample_size=len(x), metric='euclidean') # 计算轮廓系数 pred_y = model.labels_ print(pred_y) # 打印所有样本类别 # print(model.core_sample_indices_) # 打印所有核心样本索引 # 区分样本 core_mask = np.zeros(len(x), dtype=bool) core_mask[model.core_sample_indices_] = True # 核心样本下标 offset_mask = (pred_y == -1) # 孤立样本 periphery_mask = ~(core_mask | offset_mask) # 核心样本、孤立样本之外的样本 # 可视化 mp.figure('DBSCAN Cluster', facecolor='lightgray') mp.title('DBSCAN Cluster', fontsize=20) mp.xlabel('x', fontsize=14) mp.ylabel('y', fontsize=14) mp.tick_params(labelsize=14) mp.grid(linestyle=':') labels = set(pred_y) print(labels) cs = mp.get_cmap('brg', len(labels))(range(len(labels))) print("cs:", cs) # 核心点 mp.scatter(x[core_mask][:, 0], # x坐标值数组 x[core_mask][:, 1], # y坐标值数组 c=cs[pred_y[core_mask]], s=80, label='Core') # 边界点 mp.scatter(x[periphery_mask][:, 0], x[periphery_mask][:, 1], edgecolor=cs[pred_y[periphery_mask]], facecolor='none', s=80, label='Periphery') # 噪声点 mp.scatter(x[offset_mask][:, 0], x[offset_mask][:, 1], marker='D', c=cs[pred_y[offset_mask]], s=80, label='Offset') mp.legend() mp.show()
执行图像:
③ 特点及使用
-
算法优点
(1)不用人为提前确定聚类类别数K; (2)聚类速度快; (3)能够有效处理噪声点(因为异常点不会被包含于任意一个簇,则认为是噪声),对噪声不敏感; (4)能够应对任意形状的空间聚类.
-
算法缺点
(1)当数据量过大时,要求较大的内存支持I/O消耗很大; (2)当空间聚类的密度不均匀、聚类间距差别很大时、聚类效果有偏差; (3)邻域半径和最少样本数量两个参数对聚类结果影响较大.
-
何时选择噪声密度聚类方法
(1)数据稠密、没有明显中心;
(2)噪声数据较多;
(3)未知聚簇的数量.
3)凝聚层次聚类
① 算法定义
凝聚层次(Agglomerative)算法,首先将每个样本看做独立的聚类,如果聚类数大于预期,则合并两个距离最近的样本作为一个新的聚类,如此反复迭代,不断扩大聚类规模的同时,减少聚类的总数,直到聚类数减少到预期值为止. 这里的关键问题是如何计算聚类之间的距离.
依据对距离的不同定义,将Agglomerative Clustering的聚类方法分为三种:
-
ward:默认选项,挑选两个簇来合并,是的所有簇中的方差增加最小。这通常会得到大小差不多相等的簇。
-
average链接:将簇中所有点之间平均距离最小的两个簇合并。
-
complete链接:也称为最大链接,将簇中点之间最大距离最小的两个簇合并。
ward适用于大多数数据集。如果簇中的成员个数非常不同(比如其中一个比其他所有都大得多),那么average或complete可能效果更好。
② 实现
sklearn提供了AgglomerativeClustering聚类器来实现凝聚层次聚类,示例代码如下:
# 凝聚层次聚类示例 import numpy as np import sklearn.cluster as sc import matplotlib.pyplot as mp x = [] with open("../data/multiple3.txt", "r") as f: for line in f.readlines(): line = line.replace("\n", "") data = [float(substr) for substr in line.split(",")] x.append(data) x = np.array(x) # 凝聚聚类 model = sc.AgglomerativeClustering(n_clusters=4) # n_cluster为聚类数量 model.fit(x) # 训练 pred_y = model.labels_ # 聚类标签(聚类结果) # 可视化 mp.figure("Agglomerative", facecolor="lightgray") mp.title("Agglomerative") mp.xlabel("x", fontsize=14) mp.ylabel("y", fontsize=14) mp.tick_params(labelsize=10) mp.scatter(x[:, 0], x[:, 1], s=80, c=pred_y, cmap="brg") mp.show()
执行结果:
③ 特点及使用
(1)需要事先给定期望划分的聚类数(k),来自业务或指标优化;
(2)没有聚类中心,无法进行聚类预测,因为不依赖于中心的划分,所以对于中心特征不明显的样本,划分效果更佳稳定.
(3)适合于中心不明显的聚类.
3. 聚类的评价指标
回归问题用R2,均方误差来评价;分类问题用准确率,错误率,查准率,召回率,F1评价;聚类用轮廓系数S(i)评价
理想的聚类可以用四个字概况:内密外疏,即同一聚类内部足够紧密,聚类之间足够疏远. 学科中使用“轮廓系数”来进行度量,见下图:
假设我们已经通过一定算法,将待分类数据进行了聚类,对于簇中的每个样本,分别计算它们的轮廓系数。对于其中的一个点 i 来说: a(i) = average(i向量到所有它属于的簇中其它点的距离) b(i) = min (i向量到各个非本身所在簇的所有点的平均距离) 那么 i 向量轮廓系数就为:
$$
S(i)=\frac{b(i)-a(i)}{max(b(i), a(i))}
$$
由公式可以得出:
(1)当b(i)>>a(i)时,S(i)越接近于1,这种情况聚类效果最好;
(2)当b(i)<<a(i)时,S(i)越接近于-1,这种情况聚类效果最差;
(3)当b(i)=a(i)时,S(i)的值为0,这种情况分类出现了重叠.
sklearn提供的计算轮廓系数API:
score = sm.silhouette_score(x, # 样本 pred_y, # 标签 sample_size=len(x), # 样本数量 metric="euclidean") # 欧式距离度量
4. 聚类问题总结
(1)聚类属于无监督学习;
(2)聚类是根据数据的特征,将相似度最高的样本划分到一个聚簇中;
(3)相似度的度量方式:曼哈顿距离、欧式距离、切比雪夫距离,都可以用闵式距离公式表示;
(4)聚类算法
-
基于原型聚类:k-means算法
-
基于密度聚类:DBSCAN算法
-
基于层次聚类:凝聚算法
(5)评价指标:轮廓系数
示例 4:凝聚层次对中心不明显的数据聚类
下面来看一个中心点不明显的凝聚层次聚类示例.
# 凝聚层次聚类示例 import numpy as np import sklearn.cluster as sc import matplotlib.pyplot as mp import sklearn.neighbors as nb n_sample = 500 t = 2.5 * np.pi * (1 + 2 * np.random.rand(n_sample, 1)) # 产生随机角度 # 产生数据样本(阿基米德螺线) x = 0.05 * t * np.cos(t) y = 0.05 * t * np.sin(t) n = 0.05 * np.random.rand(n_sample, 2) # 产生随机噪声 x = np.hstack((x, y)) + n # 水平合并 # 无连续性凝聚层次聚类器 # model = sc.AgglomerativeClustering(n_clusters=3, linkage="average") # model.fit(x) # 训练 # pred_y1 = model.labels_ # 聚类标签(聚类结果) # 有连续性凝聚层次聚类器 conn = nb.kneighbors_graph(x, 10, include_self=False) # 创建每个样本的近邻集合 model = sc.AgglomerativeClustering(n_clusters=3, linkage="average", connectivity=conn) # 在凝聚过程中优先选择近邻中连续性最好的样本,优先凝聚 model.fit(x) # 训练 pred_y1 = model.labels_ # 聚类标签(聚类结果) # 可视化 mp.figure("AgglomerativeClustering Cluster", facecolor="lightgray") mp.title("AgglomerativeClustering Cluster") mp.xlabel("x", fontsize=14) mp.ylabel("y", fontsize=14) mp.tick_params(labelsize=10) mp.grid(linestyle=":") mp.axis("equal") mp.scatter(x[:, 0], x[:, 1], c=pred_y1, cmap="brg", s=80, alpha=0.5) mp.show()
执行结果(有连续层次):
因为是随机产生数据,该程序每次执行结果都不一样. 可以将代码22~24行注释打开,27~30行注释,就是一个非连续凝聚层次聚类. 执行结果:
机器学习补充练习
示例1:线性回归
读取single.txt文件中的样本,定义线性回归模型,并训练,绘制训练的模型,打印模型的R2分数
# 线性回归示例 import numpy as np # 线性模型 import sklearn.linear_model as lm # 模型性能评价模块 import sklearn.metrics as sm import matplotlib.pyplot as mp x, y = [], [] # 输入、输出样本 with open("single.txt", "rt") as f: for line in f.readlines(): data = [float(substr) for substr in line.split(",")] x.append(data[:-1]) y.append(data[-1]) x = np.array(x) # 二维数据形式的输入矩阵,一行一样本,一列一特征 y = np.array(y) # 一维数组形式的输出序列,每个元素对应一个输入样本 print(x) print(y) # 创建线性回归器 model = lm.LinearRegression() # 用已知输入、输出数据集训练回归器 model.fit(x, y) # 根据训练模型预测输出 pred_y = model.predict(x) # 评估指标 err = sm.mean_absolute_error(y, pred_y) # 评价绝对值误差 print(err) err2 = sm.mean_squared_error(y, pred_y) # 平均平方误差 print(err2) err3 = sm.median_absolute_error(y, pred_y) # 中位绝对值误差 print(err3) err4 = sm.r2_score(y, pred_y) # R2得分, 范围[0, 1], 分值越大越好 print(err4) # 可视化回归曲线 mp.figure('Linear Regression', facecolor='lightgray') mp.title('Linear Regression', fontsize=20) mp.xlabel('x', fontsize=14) mp.ylabel('y', fontsize=14) mp.tick_params(labelsize=10) mp.grid(linestyle=':') # 绘制样本点 mp.scatter(x, y, c='dodgerblue', alpha=0.8, s=60, label='Sample') # 绘制拟合直线 sorted_indices = x.T[0].argsort() mp.plot(x[sorted_indices], pred_y[sorted_indices], c='orangered', label='Regression') mp.legend() mp.show()
示例2:利用随机森林实现共享单车投放量预测
-
数据集:一段时期内共享单车使用量,特征:日期、季节、年、月、小时、是否是假期、星期几、是否为工作日、天气、温度、体感温度、湿度、风速;标签:游客使用量、注册用户使用量、总使用量
-
实现代码:
# -*- coding: utf-8 -*- # 使用随机森林实现共享单车使用量预测 import csv import numpy as np import sklearn.utils as su import sklearn.ensemble as se import sklearn.metrics as sm import matplotlib.pyplot as mp # 读取共享单车使用率文件中的数据 ############### 基于天的数据训练与预测 ############### with open("bike_day.csv", "r") as f: reader = csv.reader(f) x, y = [], [] for row in reader: x.append(row[2:13]) # 第1列序号掐掉, 挑出其中的输入 y.append(row[-1]) # 最后一列是输出 fn_dy = np.array(x[0]) # 保存特征名称 x = np.array(x[1:], dtype=float) # 去掉第1行标题部分 y = np.array(y[1:], dtype=float) # 去掉第1行标题部分 # 将矩阵打乱 x = su.shuffle(x, random_state=7) y = su.shuffle(y, random_state=7) # 计算训练数据的笔数,创建训练集、测试集 train_size = int(len(x) * 0.9) # 用90%的数据来训练模型 train_x = x[:train_size] # 训练输入 train_y = y[:train_size] # 训练输出 test_x = x[train_size:] # 测试输入 test_y = y[train_size:] # 测试输出 # 创建随机森林回归器,并进行训练 model = se.RandomForestRegressor(max_depth=10, #最大深度 n_estimators=1000, #树数量 min_samples_split=2) #最小样本数量,小于该数就不再划分子节点 model.fit(train_x, train_y) # 训练 # 基于天统计数据的特征重要性 fi_dy = model.feature_importances_ # print(fi_dy) pre_test_y = model.predict(test_x) print(sm.r2_score(test_y, pre_test_y)) #打印r2得分 # 可视化 mp.figure('Bike', facecolor='lightgray') mp.subplot(211) mp.title('Day', fontsize=16) mp.ylabel('Importance', fontsize=12) mp.tick_params(labelsize=10) mp.grid(axis='y', linestyle=':') sorted_idx = fi_dy.argsort()[::-1] pos = np.arange(sorted_idx.size) mp.bar(pos, fi_dy[sorted_idx], facecolor='deepskyblue', edgecolor='steelblue') mp.xticks(pos, fn_dy[sorted_idx], rotation=30) ############### 基于小时的数据训练与预测 ############### with open("bike_hour.csv", "r") as f_hr: reader = csv.reader(f_hr) x, y = [], [] for row in reader: x.append(row[2:13]) # 第1列序号掐掉, 挑出其中的输入 y.append(row[-1]) # 输出 fn_hr = np.array(x[0]) x = np.array(x[1:], dtype=float) y = np.array(y[1:], dtype=float) x = su.shuffle(x, random_state=7) y = su.shuffle(y, random_state=7) # 计算训练数据的笔数,创建训练集、测试集 train_size = int(len(x) * 0.9) train_x = x[:train_size] # 训练输入 train_y = y[:train_size] # 训练输出 test_x = x[train_size:] # 测试输入 test_y = y[train_size:] # 测试输出 # 创建随机森林回归器,并进行训练 model = se.RandomForestRegressor(max_depth=10, n_estimators=1000, min_samples_split=2) model.fit(train_x, train_y) fi_hr = model.feature_importances_ # 基于小时数据的特征重要性 pre_test_y = model.predict(test_x) print(sm.r2_score(test_y, pre_test_y)) #打印r2得分 #可视化 mp.subplot(212) mp.title('Houre', fontsize=16) mp.ylabel('Importance', fontsize=12) mp.tick_params(labelsize=10) mp.grid(axis='y', linestyle=':') sorted_idx = fi_hr.argsort()[::-1] pos = np.arange(sorted_idx.size) mp.bar(pos, fi_hr[sorted_idx], facecolor='deepskyblue', edgecolor='steelblue') mp.xticks(pos, fn_hr[sorted_idx], rotation=30) mp.tight_layout() mp.show()
-
打印输出
0.8915180372559434 0.9185448658002986
-
特征重要性可视化
示例3:利用SVM预测交通流量
利用支持向量机预测体育场馆周边交通流量。样本特征分别为:星期、时间、对手球队、棒球比赛是否正在进行、通行汽车数量。
# 利用支持向量机实现交通流量预测 # 数据集:17568笔样本 # 特征分别为星期、时间、对手球队、棒球比赛是否正在进行,标签为通行汽车数量 import numpy as np import sklearn.model_selection as ms import sklearn.svm as svm import sklearn.metrics as sm import matplotlib.pyplot as mp import sklearn.preprocessing as sp # 自定义编码器 class DigitEncoder(): def fit_transform(self, x): return x.astype(int) def transform(self, x): return x.astype(int) def inverse_transform(self, x): return x.astype(str) data = [] with open("../data/traffic.txt", "r") as f: for line in f.readlines(): line = line.replace("\n", "") data.append(line.split(",")) data = np.array(data).T encoders, x = [], [] for row in range(len(data)): if data[row, 0].isdigit(): # 数值,使用自定义编码器 encoder = DigitEncoder() else: # 字符串,使用标签编码器 encoder = sp.LabelEncoder() if row < len(data) - 1: # 不是最后一行:特征 x.append(encoder.fit_transform(data[row])) else: # 最后一行:标签 y = encoder.fit_transform(data[row]) encoders.append(encoder) # 记录编码器 x = np.array(x).T # 转置还原 # 划分训练集、测试集 train_x, test_x, train_y, test_y = ms.train_test_split( x, y, test_size=0.25, random_state=5) # 基于径向基核函数的支持向量机回归器 model = svm.SVR(kernel="rbf", C=10, epsilon=0.2) model.fit(train_x, train_y) pred_test_y = model.predict(test_x) print("r2_score:", sm.r2_score(test_y, pred_test_y)) data = [["Tuesday", "13:35", "San Francisco", "yes"]] # 待预测数据 data = np.array(data).T x = [] # 对样本进行编码 for row in range(len(data)): encoder = encoders[row] x.append(encoder.transform(data[row])) x = np.array(x).T pred_y = model.predict(x) print(int(pred_y))
执行结果:
r2_score: 0.6379517119380995 27
示例5:利用SVM实现图像分类
-
数据集:包含两个目录train和test,每个目录下三个类别水果,apple、banana、grape
-
代码:
# -*- coding: utf-8 -*- import os import numpy as np import cv2 as cv import sklearn.metrics as sm import sklearn.preprocessing as sp import sklearn.svm as svm name_dict = {"apple": 0, "banana": 1, "grape": 2} # 读取图片、类别,并且存入字典 def search_samples(dir_path): img_samples = {} dirs = os.listdir(dir_path) for d in dirs: sub_dir_path = dir_path + "/" + d # 拼接子目录完整路径 if not os.path.isdir(sub_dir_path): # 不是子目录 continue imgs = os.listdir(sub_dir_path) # 列出子目录中所有文件 for img_file in imgs: img_path = sub_dir_path + "/" + img_file # 拼接完整路径 if d in img_samples: # 该类别已经在字典中 img_samples[d].append(img_path) else: img_list = [] # 定义空列表 img_list.append(img_path) # 将图像加入列表 img_samples[d] = img_list return img_samples train_samples = search_samples('../data/fruits_tiny/train') # 搜索所有图像样本 train_x, train_y = [], [] # 加载训练集样本数据,训练模型,模型存储 for label, img_list in train_samples.items(): descs = np.array([]) for img_file in img_list: # 读取原始图像,并转为灰度图像 print("读取样本:", img_file) im = cv.imread(img_file) im_gray = cv.cvtColor(im, cv.COLOR_BGR2GRAY) # 调整大小 h, w = im_gray.shape[:2] # 取出高度、宽度 f = 200 / min(h, w) # 计算缩放比率 im_gray = cv.resize(im_gray, None, fx=f, fy=f) # 图像缩放 # 计算特征矩阵 sift = cv.xfeatures2d.SIFT_create() keypoints = sift.detect(im_gray) _, desc = sift.compute(im_gray, keypoints) # 添加到样本、输出数组 # print("desc.shape:", desc.shape) desc = np.sum(desc, axis=0) # 0-列方向 train_x.append(desc) # 图像数据特征 train_y.append(name_dict[label]) # 标签 train_x = np.array(train_x) train_y = np.array(train_y) # print("train_y.shape:", train_y.shape) # 定义模型、训练 print("开始训练......") model = svm.SVC(kernel='poly', degree=2) model.fit(train_x, train_y) print("训练结束.") # 测试模型 test_samples = search_samples('../data/fruits_tiny/test') test_x, test_y = [], [] # 读取测试数据,并计算特征值 for label, filenames in test_samples.items(): descs = np.array([]) for img_file in filenames: print("读取测试样本:", img_file) # 读取原始图像,并转为灰度图像 image = cv.imread(img_file) gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY) # 调整大小 h, w = gray.shape[:2] f = 200 / min(h, w) gray = cv.resize(gray, None, fx=f, fy=f) # 计算特征矩阵 sift = cv.xfeatures2d.SIFT_create() keypoints = sift.detect(gray) _, desc = sift.compute(gray, keypoints) # 添加测试输入、输出数组 desc = np.sum(desc, axis=0)# 0-列方向 test_x.append(desc) test_y.append(name_dict[label]) # 标签 # 执行预测 print("开始预测......") pred_test_y = model.predict(test_x) print("预测结束.") # 打印分类报告 print(sm.classification_report(test_y, pred_test_y))
执行结果:
中间打印省略...... precision recall f1-score support 0 1.00 0.80 0.89 10 1 1.00 1.00 1.00 10 2 0.83 1.00 0.91 10 accuracy 0.93 30 macro avg 0.94 0.93 0.93 30 weighted avg 0.94 0.93 0.93 30
机器学习总结
一、基本概念
1)有监督学习、无监督学习
2)批量学习、增量学习
3)基于模型(从数据找规律建立模型学习)、基于实例的学习(从数据中找相似类别答案,例如决策树)
4)基本问题:回归问题、分类问题、聚类问题、降维问题
5)机器学习一般过程:准备数据 --> 数据清洗 --> 选择模型 --> 训练 --> 评估 --> 测试 --> 应用及维护
二、数据预处理
1)标准化:处理后每列均值为0,标准差为1
2)范围缩放:将每列最大值转换为1,最小值转换为0
3)归一化:将每行的值转换为百分比
4)二值化:转换为0/1其中一个
5)独热编码:将特征值转换为一个1和一串0的表示
6)标签编码:字符串转换数值
三、回归问题
1)线性回归:线性模型、损失函数最小值求解、梯度下降
2)多项回归:多项式模型、欠拟合、过拟合、正则化
3)Lasso回归、Ridge回归
4)决策树回归:信息熵、信息增益、增益率、基尼系数、集成学习
5)评价指标:R2、均方误差
四、分类
1)逻辑回归:Sigmoid函数、交叉熵、利用二分类模型实现多分类
2)决策树分类
3)支持向量机分类
-
二分类;线性分类边界;只考虑离分类边界最近的样本;间隔最大化;线性不可分问题,通过核函数转换为线性可分
-
核函数:线性核函数、多项式核函数、径向基核函数
4)朴素贝叶斯:贝叶斯定理、朴素贝叶斯分类器
五、聚类
1)聚类:无监督学习、聚类(欧氏距离、曼哈顿距离、闵式距离、切比雪夫距离)
2)基于原型聚类、基于密度聚类、基于层次的聚类
3)对应算法:k-means,DBSCAN,凝聚层次
4)评价指标:轮廓系数
六、评估与优化
1)分类模型评估指标:准确率、错误率、查准率、召回率、F1
2)混淆矩阵
3)测试集、训练集划分;交叉验证法
4)验证曲线(评估某一个参数)、学习曲线(评估不同大小训练集对模型影响)
5)超参数选择
6)最优超参数组合选择方式:网格搜索、随机搜索
七、机器学习的局限性
1)特征提取(依赖于人工算法提取特征),过于复杂模型例如猫狗分类人们很难以描述,建不了模型。
2)精度、准确度低。
3)结构化数据适合用机器学习,例如数据库信息,图像语音非结构化用深度学习