之前讲解了基于中心聚类,采用kmeans聚类算法,下面讲解基于噪声密度聚类算法DBSACN与基于层次聚类算法
(Agglomerative)算法
① 算法定义
噪声密度(Density-Based Spatial Clustering of Applications with Noise, 简写DBSCAN)随机选择一个样本做圆心,以事先给定的半径做圆,凡被该圆圈中的样本都被划为与圆心样本同处一个聚类,再以这些被圈中的样本做圆心,以事先给定的半径继续做圆,不断加入新的样本,扩大聚类的规模,直到再无新的样本加入为止,即完成一个聚类的划分. 以同样的方法,在其余样本中继续划分新的聚类,直到样本空间被耗尽为止,即完成整个聚类划分过程. 示意图如下:当然图中为了显示简洁,圆内每次只选取一个样本继续画圆。下图使用Kmeans效果就不太好
DBSCAN算法中,样本点被分为三类:
边界点(Border point):可以划分到某个聚类,但无法发展出新的样本;
噪声点(Noise):无法划分到某个聚类中的点;被孤立
上图中,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)未知聚簇的数量.
① 算法定义
凝聚层次(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)适合于中心不明显的聚类.
回归问题用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)<
(3)当b(i)=a(i)时,S(i)的值为0,这种情况分类出现了重叠. sklearn提供的计算轮廓系数API: (1)聚类属于无监督学习; (2)聚类是根据数据的特征,将相似度最高的样本划分到一个聚簇中; (3)相似度的度量方式:曼哈顿距离、欧式距离、切比雪夫距离,都可以用闵式距离公式表示; (4)聚类算法 基于原型聚类:k-means算法 基于密度聚类:DBSCAN算法 基于层次聚类:凝聚算法 (5)评价指标:轮廓系数 示例 4:凝聚层次对中心不明显的数据聚类 下面来看一个中心点不明显的凝聚层次聚类示例. 因为是随机产生数据,该程序每次执行结果都不一样. 可以将代码22~24行注释打开,27~30行注释,就是一个非连续凝聚层次聚类. 执行结果: 读取single.txt文件中的样本,定义线性回归模型,并训练,绘制训练的模型,打印模型的R2分数 数据集:一段时期内共享单车使用量,特征:日期、季节、年、月、小时、是否是假期、星期几、是否为工作日、天气、温度、体感温度、湿度、风速;标签:游客使用量、注册用户使用量、总使用量 实现代码: 打印输出 利用支持向量机预测体育场馆周边交通流量。样本特征分别为:星期、时间、对手球队、棒球比赛是否正在进行、通行汽车数量。 执行结果: 数据集:包含两个目录train和test,每个目录下三个类别水果,apple、banana、grape 代码: 执行结果: 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)结构化数据适合用机器学习,例如数据库信息,图像语音非结构化用深度学习score = sm.silhouette_score(x, # 样本
pred_y, # 标签
sample_size=len(x), # 样本数量
metric="euclidean") # 欧式距离度量
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()
机器学习补充练习
示例1:线性回归
# 线性回归示例
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实现图像分类
# -*- 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
机器学习总结
一、基本概念
二、数据预处理
三、回归问题
四、分类
五、聚类
六、评估与优化
七、机器学习的局限性