import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.font_manager import FontProperties
from sklearn.cluster import KMeans
from mpl_toolkits.mplot3d import Axes3D
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.ensemble import IsolationForest
from sklearn.svm import OneClassSVM
# =========================== 使用 python 进行异常值检测实战(第二个sharing)===========================
# ============ 数据 ========== checked, done with it
# 涉及内容:1.设置中文字体 2.过滤样本 3.选取属性 4.提取排序后头部样本 5.检查样本属性数据类型
# 6.修改属性数据类型 7.样本排序 8.获取样本统计结果 9.画出直方图(针对一个属性)
# 在此设置字体
plt.rcParams['font.sans-serif'] = 'Microsoft YaHei'
df = pd.read_csv('./data/train.csv') # Read a comma-separated values (csv) file into DataFrame.
# # 过滤Id为104517的酒店
df = df.loc[df['prop_id'] == 104517]
# #过滤标间
df = df.loc[df['srch_room_count'] == 1]
# #219表示美国
df = df.loc[df['visitor_location_country_id'] == 219]
df = df[['date_time', 'price_usd', 'srch_booking_window', 'srch_saturday_night_bool']] # We only cares about these four attributes
df.head(10) # show the top 10 rows
df.info() # We can use .info to check all attributes' Dtype (注意!.info 和 .info() 会返回完全不同的结果)
#将date_time属性的类型设置为datetime
df['date_time'] = pd.to_datetime(df['date_time'])
df = df.sort_values('date_time')
print(df['price_usd'].describe()) # describe(): show the statistic result of Dataframe
df['price_usd'].hist()
plt.show()
df = df.loc[df['price_usd'] < 5584]
print(df['price_usd'].describe())
df['price_usd'].hist()
plt.show()
# ============ 时间序列可视化 ========== checked, done with it
# 涉及内容:1.绘制时间序列 2.绘制两个属性的直方图(一个bool类型,一个整数类型)
df.plot(x='date_time', y='price_usd', figsize=(12,6))
plt.xlabel('time')
plt.ylabel('price(usd)')
plt.show()
# first we filter out data according to the 'srch_saturday_night_bool', then only shows price_usd
a = df.loc[df['srch_saturday_night_bool'] == 0, 'price_usd']
b = df.loc[df['srch_saturday_night_bool'] == 1, 'price_usd']
plt.figure(figsize=(10, 6))
plt.hist(a, bins = 50, alpha=0.5, label='不含周六晚上')
plt.hist(b, bins = 50, alpha=0.5, label='含周六晚上')
plt.legend(loc='upper right')
plt.xlabel('价格')
plt.ylabel('数量')
plt.show()
# ============ 基于聚类的异常检测 ========== checked, done with it
# ======= Elbow方法(确认KMEANS的最佳聚类数量)需要肉眼观察 ======= checked, done with it
# 涉及内容:1.手肘方法选取 KMeans 的 K
data = df[['price_usd', 'srch_booking_window', 'srch_saturday_night_bool']]
n_cluster = range(1, 20)
kmeans = [KMeans(n_clusters=i).fit(data) for i in n_cluster] # 猜测:生成了 n_clusters 个 KMeans 对象
scores = [kmeans[i].score(data) for i in range(len(kmeans))] # 猜测:使用 .score(data) 可以获得那个聚类的评分
fig, ax = plt.subplots(figsize=(10,6))
ax.plot(n_cluster, scores) # 以 n_cluster 为 x 轴, scores 为 y 轴,画出图像
plt.xlabel('聚类集群数')
plt.ylabel('分数') # 猜测:这个评分似乎是 distortion(畸变程度) 的取反,分数越大,说明畸变程度越低
plt.title('Elbow 曲线')
plt.show()
# ======= after observing the graph, we select n_cluster = 10, 现在我们想要把数据进行3D可视化 ======= checked, done with it
X = df[['price_usd', 'srch_booking_window', 'srch_saturday_night_bool']]
X = X.reset_index(drop=True) # 把数据样本的下标准重置为 0~样本量
km = KMeans(n_clusters=10)
km.fit(X) # 放入数据,开始进行聚合计算
km.predict(X) # 会返回每个样本属于的聚合下标
# labels of each point(其实就是一个list, 这个list里包含了每个样本所属的聚合的下表,估计要在执行 .predict(X) 之后才能用)
labels = km.labels_
fig = plt.figure(1, figsize=(7,7)) # width: 7 inches, height: 7 inches
ax = fig.add_subplot(projection='3d')
# ax = Axes3D(fig, rect=[0, 0, 0.95, 1], elev=48, azim=134)
# x, y, z, color, edgecolor
# type() 返回数据结构类型(list、dict、numpy.ndarray 等) dtype() 返回数据元素的数据类型(int、float等)
# astype() 改变np.array中所有数据元素的数据类型。
ax.scatter(X.iloc[:,0], X.iloc[:,1], X.iloc[:,2], c=labels.astype(float), edgecolor='k')
ax.set_xlabel("price_usd")
ax.set_ylabel("srch_booking_window")
ax.set_zlabel("srch_saturday_night_bool")
ax.set_title("K Means", fontsize=14)
plt.show()
# =========== 接下来我们要确定需要保留数据中的哪些主要成分(特征) =========== checked, done with it
data = df[['price_usd', 'srch_booking_window', 'srch_saturday_night_bool']]
X = data.to_numpy() # 和 .values 一样,返回 DataFrame 对象的一个 Numpy 表示方法
X_std = StandardScaler().fit_transform(X) #标准化处理,均值为0,标准差为1
mean_vec = np.mean(X_std, axis=0) # 按列算均值,即,每个属性算一个均值
#计算协方差矩阵,要求输入的矩阵的 row 为属性(即,行数为属性数,列数为样本数)
cov_mat = np.cov(X_std.T) # .T 的意思即为‘转置’
#计算特征值和特征向量
eig_vals, eig_vecs = np.linalg.eig(cov_mat)
#每个特征值对应一组特征向量
eig_pairs = [(np.abs(eig_vals[i]),eig_vecs[:,i]) for i in range(len(eig_vals))]
eig_pairs.sort(key = lambda x: x[0], reverse= True) # 根据特征值排序,从大到小
#特征值求和
tot = sum(eig_vals)
#每个主要成分的解释方差
# sorted(eig_vals, reverse=True): 把特征值排序,从大到小
# var_exp: 实际上就是每个特征值占的百分比大小,从大到小排列
var_exp = [(i/tot)*100 for i in sorted(eig_vals, reverse=True)]
#累计的解释方差
cum_var_exp = np.cumsum(var_exp)
plt.figure(figsize=(10, 5))
plt.bar(range(len(var_exp)), var_exp, alpha=0.3, align='center', label='独立的解释方差', color = 'g')
plt.step(range(len(cum_var_exp)), cum_var_exp, where='mid',label='累积解释方差')
plt.ylabel('解释方差率')
plt.xlabel('主成分')
plt.legend(loc='best')
plt.show()
# ============ 因此接下来我们将使用PCA算法进行降维并将设置参数n_components=2。 ======== checked, done with it
data = df[['price_usd', 'srch_booking_window', 'srch_saturday_night_bool']]
X = data.values # 即,获得 DataFrame 对象的 numpy 矩阵
#标准化处理,均值为0,标准差为1
X_std = StandardScaler().fit_transform(X) # NOTE: 这里的正则化处理,会把所有数据变成 -1 ~ 1 的数据, 同时,这里的正则化是 “按列” 处理的
data = pd.DataFrame(X_std) # 从 numpy矩阵 转化成 DataFrame 对象
#将特征维度降到2
pca = PCA(n_components=2)
data = pca.fit_transform(data)
# 降维后将2个新特征进行标准化处理
scaler = StandardScaler()
np_scaled = scaler.fit_transform(data)
data = pd.DataFrame(np_scaled)
kmeans = [KMeans(n_clusters=i).fit(data) for i in n_cluster]
df['cluster'] = kmeans[9].predict(data) # .predict(data) 会返回每个样本属于的聚合下标
df.index = data.index
df['principal_feature1'] = data[0] # data[0] 即 data 列表的第一列属性
df['principal_feature2'] = data[1] # data[0] 即 data 列表的第二列属性
df.head()
# ============== 通过阈值threshold来判定数据是否为异常值, 接着对数据进行可视化 ================ checked, done with it
# 关于 KMeans 和 异常值检测的关系:
# 基于聚类的异常检测中的假设是,如果我们对数据进行聚类,则正常数据将属于聚类,而异常将不属于任何聚类或属于小聚类。 我们使用以下步骤来查找和可视化异常值。
# • 计算每个数据点与其最近的聚类中心之间的距离。 最大的距离被认为是异常的。
# • 设定一个异常值的比例outliers_fraction为1%,这样设置是因为在标准正太分布的情况下(N(0,1))我们一般认定3个标准差以外的数据为异常值,3个标准差以内的数据包含了数据集中99%以上
# 的数据,所以剩下的1%的数据可以视为异常值。
# • 根据异常值比例outliers_fraction,计算异常值的数量number_of_outliers
# • 设定一个判定异常值的阈值threshold
# • 通过阈值threshold来判定数据是否为异常值
# 对数据进行可视化(包含正常数据和异常数据)
# 计算每个数据点到其聚类中心的距离
def getDistanceByPoint(data, model):
distance = pd.Series()
for i in range(0,len(data)): # loop for every element
Xa = np.array(data.loc[i])
Xb = model.cluster_centers_[model.labels_[i]]
distance._set_value(i, np.linalg.norm(Xa-Xb))
return distance
#设置异常值比例
outliers_fraction = 0.01
# 得到每个点到取聚类中心的距离,我们设置了10个聚类中心,kmeans[9]表示有10个聚类中心的模型
distance = getDistanceByPoint(data, kmeans[9])
#根据异常值比例outliers_fraction计算异常值的数量
number_of_outliers = int(outliers_fraction*len(distance))
#设定异常值的阈值
threshold = distance.nlargest(number_of_outliers).min()
#根据阈值来判断是否为异常值
df['anomaly1'] = (distance >= threshold).astype(int)
#数据可视化
fig, ax = plt.subplots(figsize=(10,6))
colors = {0:'blue', 1:'red'}
ax.scatter(df['principal_feature1'], df['principal_feature2'], c=df["anomaly1"].apply(lambda x: colors[x]))
plt.xlabel('principal feature1')
plt.ylabel('principal feature2')
plt.show()
df = df.sort_values('date_time')
df['date_time_int'] = df.date_time.astype(np.int64)
fig, ax = plt.subplots(figsize=(12,6))
a = df.loc[df['anomaly1'] == 1, ['date_time_int', 'price_usd']] #anomaly
ax.plot(df['date_time_int'], df['price_usd'], color='blue', label='正常值') # 线型图
ax.scatter(a['date_time_int'],a['price_usd'], color='red', label='异常值') # 散点图
plt.xlabel('Date Time Integer')
plt.ylabel('价格(美元)')
plt.legend()
plt.show()
# ============== 孤立森林(Isolation Forest)异常检测 ================ checked, done with it
data = df[['price_usd', 'srch_booking_window', 'srch_saturday_night_bool']]
scaler = StandardScaler()
np_scaled = scaler.fit_transform(data)
data = pd.DataFrame(np_scaled)
# 训练孤立森林模型
model = IsolationForest(contamination=outliers_fraction)
model.fit(data)
#返回1表示正常值,-1表示异常值
df['anomaly2'] = pd.Series(model.predict(data)) # 在鼓励森林模型中,异常值用-1表示,正常值用1表示
fig, ax = plt.subplots(figsize=(10,6))
a = df.loc[df['anomaly2'] == -1, ['date_time_int', 'price_usd']] #异常值
ax.plot(df['date_time_int'], df['price_usd'], color='blue', label = '正常值')
ax.scatter(a['date_time_int'],a['price_usd'], color='red', label = '异常值')
plt.legend()
plt.show()
# ============== 8. 基于OneClassSVM的异常检测 ================ checking
data = df[['price_usd', 'srch_booking_window', 'srch_saturday_night_bool']]
scaler = StandardScaler()
np_scaled = scaler.fit_transform(data)
data = pd.DataFrame(np_scaled)
# 训练 oneclassSVM 模型
model = OneClassSVM(nu=outliers_fraction, kernel="rbf", gamma=0.01)
model.fit(data)
df['anomaly3'] = pd.Series(model.predict(data))
fig, ax = plt.subplots(figsize=(10,6))
a = df.loc[df['anomaly3'] == -1, ['date_time_int', 'price_usd']] #anomaly
ax.plot(df['date_time_int'], df['price_usd'], color='blue', label ='正常值')
ax.scatter(a['date_time_int'],a['price_usd'], color='red', label = '异常值')
plt.legend()
plt.show()
# =========== 关于 K-MEANS 以及相关统计、可视化到此为止,接下来是孤立森林异常检测 ===========
# 这里说一下我自己学到的东西
# 1. K-MEANS 算法
# K 为聚类数量,把所有的样本分为 K 个类别,这个类别的划分依据是样本的属性均值
# 一个可行的 KMEANS 算法步骤:
# 1. 随机从数据集中选取 K 个样本作为centroid (TODO: 这里的选取应该是有优化方法的)
# 2. 对于数据集中的每个点,计算它距离每个centroid的距离,并且把它归类为距离最近的哪个cluster
# 3. 更新新的centroid位置
# 4. 重复步骤2,3,直到centroid的位置不再改变(TODO: 或者所有的数据集都分类完毕)
# 优点:
# 1. 非监督学习,不需要样本的标注信
# 缺点:
# 1. 不能利用数据的标注信息,意味着模型的性能不如其它监督学习
# 2. 模型性能对于 K 的取值极为敏感,选一个好的 K 值肥肠重要!
# 3. 对于数据集本身样本的分布也很敏感
# 和 KNN 的区别:
# 1. 训练KNN只需要关注待测样本的局部分布,而训练KMEANS需要关注样本的全局分布
# 2. KNN 是监督学习,而KMEANS是非监督学习
# 参考知识:https://www.bilibili.com/video/BV1ei4y1V7hX/?spm_id_from=333.337.search-card.all.click
# 怎么评估 KEMANS 算法的性能?(用于选取K值)
# 常用方法叫做 ”肘部elbow法则“
# 评估指标:
# 1. SSE(误差平方和): 做完KMEANS后,会产生K个聚类,每个聚类都有自己的质心。一个聚类的SSE就是类内的每个样本点
# 到质心的距离的平方的和,SSE 越小说明这个聚类越紧凑,类内相似度越高。
# 2. 畸变程度(distortion): K 个类的SSE之和
# 肘部法则的核心就是通过比较不同 K 时的 distortion 来选择最佳的 K 值(TODO: 为什么不直接选Distortion最好的?而要选拐点?)
# 回答:畸变程度一定会随着 K 增大减小,比如 K 为样本数时,畸变程度为0,然而这种分类没有任何意义
# 关于 KMeans 和 异常值检测的关系:
# 基于聚类的异常检测中的假设是,如果我们对数据进行聚类,则正常数据将属于聚类,而异常将不属于任何聚类或属于小聚类。 我们使用以下步骤来查找和可视化异常值。
# • 计算每个数据点与其最近的聚类中心之间的距离。 最大的距离被认为是异常的。
# • 设定一个异常值的比例outliers_fraction为1%,这样设置是因为在标准正太分布的情况下(N(0,1))我们一般认定3个标准差以外的数据为异常值,3个标准差以内的数据包含了数据集中99%以上
# 的数据,所以剩下的1%的数据可以视为异常值。
# • 根据异常值比例outliers_fraction,计算异常值的数量number_of_outliers
# • 设定一个判定异常值的阈值threshold
# • 通过阈值threshold来判定数据是否为异常值
# 对数据进行可视化(包含正常数据和异常数据)
# 2. 协方差 参考知识:https://www.bilibili.com/video/BV1gY4y187TL/
# Cov(X, Y): 简单说,就是用来描述两个随机变量X,Y的相关性
# 相关性一般分为三种类别:
# 1.正相关 Cov(X,Y) > 0,此时 X, Y 同增同减
# 2.负相关 Cov(X,Y) < 0,此时 X 增大, Y 减少, X 减少, Y 增加
# 3.不相关(零相关) X,Y 之间的相关关系不明显
# Cov(X, Y) = E[(X - ux)(Y - uY)] 这是数学期望形式
# Cov(X, Y) = sigma_i_1_n(pi(xi - ux)(yi - uy)) 这是概率分布形式
# 其中 pi 是每个样本点出现的概率,xi、yi 是每个样本的属性,ux、uy 是所有样本的两个属性各自的加权平均
# 随时参考那个视频链接,对于协方差的原理讲解很清晰
# 协方差的另一个公式:Cov(X, Y) = sigma_i_1_n((xi - x_mean)(yi - y_mean))/(n-1)
# NOTE: 一个变量自己的协方差就是方差,可以带入上面的公式看看
# NOTE: 在求没有进行过正则化的矩阵的协方差矩阵的时候,所得到的协方差矩阵的对角线是方差
# 3. 特征值和特征向量
# 参考知识:
# 1. https://www.bilibili.com/video/BV1vY4y1J7gd/?spm_id_from=333.337.search-card.all.click
# 2. https://www.bilibili.com/video/BV1XE411b7Lb/?spm_id_from=333.337.search-card.all.click
# 3. https://blog.csdn.net/didi_ya/article/details/109386104
# 补充:矩阵乘法所实现的,实际上是一个向量空间到另一个向量空间的映射
# 举个例子:有矩阵A,y = Ax。设 y 的向量空间为 Y, x 的向量空间为 X,那么 A 就是从 X 到 Y 的映射
# 手动求具体矩阵A的特征值和特征向量步骤:
# NOTE: 以下的 E 矩阵就是 单位矩阵
# 1. 计算方程 | rE - A | = 0 |**| 的意思是行列式,r 实际上是 lambda, 要求让这个行列式计算结果为0。
# 这个算式也有另一个名字:特征多项式 解出这个方程,得到的 r 值,就是特征值
# 2. 由1步得到特征值 r 后,写出方程 (rE - A)x = 0, 解出这个方程的 x 向量,就是特征向量
# 3. 注意,特征值一般不唯一,有多少个特征值,就有多少个对应的特征向量,关于特征向量是否对应特征值唯一,参见下条
# 4. 一个特征值对应的特征向量一般不唯一,但是这些特征向量的‘方向’是一致的,如果只取单位长度的特征向量,那么每个特征值的特征向量就是唯一的
# 特征值表示矩阵A的这个特征的重要性,而特征向量则表示这个特征是什么
# 4. Total variation, Unexplained variation, explained variation 参考知识:https://www.youtube.com/watch?v=6N9bZUy0Iro
# Total variation: SST = sigma_i_1_n((yi - y_mean)^2) # 即,每个样本点 减去 样本均值 的 平方和
# 补充:y_hat 通常指的是 y 的预测值,即,你构建了一个模型,把输入给到这个模型,得到的输入就是 y_hat
# Unexplained variation: SSE = sigma_i_1_n((yi - y_hat)^2) #即,每个样本点 减去 相应的样本预测值 的 平方和
# Explained variation: SSR = sigma_i_1_n((y_hat - y_mean)^2) #即,每个样本预测值 减去 样本均值 的 平方和
# 5. PCA 主成分分析 参考知识:https://www.bilibili.com/video/BV1E5411E71z/
# 关于原数据的假设:每个数据分布都可以由一个所有轴方向上呈正态分布的数据分布拉伸、旋转得来
# 1. 原理:当某几个维度的数据存在高度相关性时(比如极端情况,它们之间的协方差为1),那么这两个维度的数据就可以只用一个维度来表示,减少存储数据的空间,
# 也减少了需要输入模型的数据,从而提高整体的运算速度
# 2. 更加广泛的原理:找到一个新的坐标系(从高维降到低维),使得总体的信息损失最小,一般来说,先找一个轴,作为‘主成分1’, 当所有样本在 '主成分1' 上的
# 投影的 方差最大(数据最分散) 的时候,信息的损失最小,那么说明选择到了最好的 '主成分1'
# 3. 算法步骤:
# 1. 去中心化(把坐标原点放在数据中心)数据中心可以通过均值很轻松计算出来
# 2. 找坐标系(找到方差最大的方向,也可以叫做‘找坐标轴的角度’) <-- 这个是PCA的主要难点
# NOTE:
# 假设 D是原坐标系,D’ 是进行完 PCA 之后(还没降维,只是找到了主成分1)的坐标系,那么 D' = RSD (S是拉伸矩阵,R是旋转矩阵)
# 那么,这个 R 就是我们 PCA 需要寻找的 旋转角度,那个能够让信息损失最小的坐标轴旋转角度
# 怎么算R呢?协方差矩阵的特征向量就是R, 同时,特征值就是坐标轴方向的方差
# 在数据进行拉伸和旋转后,协方差矩阵的数值也会发生改变,具体可以看参考知识的视频
# 根据特征值和特征向量的定义:协方差矩阵 乘以 协方差矩阵的特征向量 = 协方差矩阵的特征值 乘以 协方差矩阵的特征向量
# 再根据视频里的一些变化,就可以看到,协方差矩阵的特征向量的合并实际上就是R矩阵,此外,协方差矩阵的特征值1,实际上就是x方向的拉伸的倍数的平方
# PCA 的缺陷:主成分轴的选择受 '离群点' 的影响很大(可以看视频,在有20个样本点时,仅仅加入一个离群点就可以让选择的轴产生很大摆动)
# 补充:有些别的降维算法可以避免离群点的影响
# 更多补充:SVD(singular value decomposition 奇异值分解) 的右奇异阵V,就是PCA的主成分
# 使用 SVD 求主成分有两个好处:
# 1. 一些SVD的实现算法可以在不求出协方差矩阵的情况下依然求出右奇异矩阵V, 而协方差矩阵的计算量可能很大
# 2. SVD求出的矩阵U有些神秘的作用(暂且不知道,可以看参考知识视频up主关于SVD的介绍)
# 6. 随机森林模型 参考视频:https://www.bilibili.com/video/BV1H5411e73F/?spm_id_from=333.337.search-card.all.click
# 补充:(集成学习)Ensemble Learning, 指的是模型内部由多个弱监督学习(weak learner, 每个weak learner仅在某一方面擅长)集成, 把它们集成之后
# 就可以得到一个在各方面都有一战之力的模型, 随机森林模型就是集成学习的典型代表
# 随机森林的名字解释:森林->模型中包含很多决策树 随机->随机从数据集中采样以训练模型中的每颗决策树
# 模型训练完毕后,数据的输入经过每颗决策树,会得到一系列输出(每颗决策树各有一个输出), 把这些输出总和起来,就能得到整体的、最终的输出
# 随机森林的训练过程:
# 1. 选定超参数 hyperparameter: 比如,几个决策树?分成几层(TODO: 猜测,这里的'层'指的是决策树的层数,所有决策树层数一样)?
# 2. 随机采样,训练每个决策树 DATASET[N * D] => data subset[n * d] N,n 表示样本数量 D,d 表示特征数量 (TODO: 训练决策树的过程是什么?)
# 3. 将待测样本输入到每个树中,再将每个树的结果整合。对于回归问题,通常把每个树的结果的均值作为最终结果。对于分类问题,把所有树的结果的众数作为最终结果。
# 优缺点:
# 优点:
# 1. 由于在训练每个决策树的过程中,训练样本是随机采样的,因此模型随机性强,而且“从多角度看问题”, 不易overfit(过拟合), 抗噪性也强,对异常点outlier不敏感
# 2. 由于每次训练都只从数据集采样一小部分,因此训练速度较快,在处理高维数据时能有不错的性能
# 3. 模型呈树状结果,可解释度高, 可以告诉我们数据集中每个特征的重要性
# 缺点: TODO: 什么是强分类器?和普通分类器有什么区别 回答:强分类器和弱分类器就是字面意思,速度快、准确性高就是强分类器,反之准确性低的就是弱分类器,有时,强分类器可以由多个弱分类器组成
# 1. 模型比较擅长处理 General 的问题,而对于边缘样本则往往无法正确处理。总的来说,模型起点高,但天花板低。
# 更形象的说法是:三个学渣集成在一起,确实能解决更多问题,但是对于困难的问题依然无法处理 (TODO: 集成学习算法中的 Boosting 可以解决这个缺点)
# 6.5. 决策树的定义以及训练过程 参考视频:https://www.bilibili.com/video/BV1Xp4y1U7vW/?spm_id_from=333.999.0.0
# 定义:
# 如果只考虑ID3模型,则每个节点表示一组分类,每一层都需要算法根据判断条件进行一次决策(选择一个分支), 最终达到的叶节点就是模型的输出结果
# 模型性能的关键属性:
# 树的深度,决策条件
# 1. 深度:一般而言,树越深,模型越慢,准确度越高,树越浅,则反之。但是深度过高可能造成过拟合(overfit), 深度过浅也可能造成欠拟合(underfit)
# 但这个是不一定的,在"决策条件"做的好的时候,可能存在深度浅,准确度高的模型
# 2. 决策条件: 决策条件不同时,在当前节点做出决策后走到的子节点所包含的数据是不同的,这个对模型性能影响很大(例子可以看视频的3:40)
# 因此,在使用决策树时,使用优秀的决策条件尤其重要
# 如何选择优秀的决策条件?
# 补充信息论中提到的'熵': 衡量一个节点的不确定性。或者说,低熵表示节点内的数据具有高一致性,低熵表示节点内的数据具有低一致性
# 选择决策条件,可以使用'熵的增益'作为指标:上一层的熵 - 当前一层熵的总和 这个值越大,说明当前一层决策条件选得越好
# (具体公式可以看视频)
# 决策树的训练过程(以ID3算法为例 - NOTE:ID3算法仅适用于分类问题): (TODO: 另一个逻辑 CART(Classification and Regression Tree) 可以被用来处理回归问题)
# 1. 过滤所有可能的决策条件
# 2. 选择使子节点熵最小的决策条件
# 3. 重复1、2,直到:
# 1. 到达了预先设置的最大树的深度
# 2. 每个子节点中的样本都属于一类
# 优缺点:
# 优点:
# 能够根据数据得到结果,也能告诉你“为什么”会有这个结果, 解释一下:
# 1. 可以轻松的打印、可视化决策树的决策过程和分支判断条件
# 2. 易于追溯和倒退一个判断结果产生的原因,方便我们对决策树进行调整和debug
# 缺点:
# 1. 对于树的最大深度这个预制参数很敏感 深大太大,可能overfit, 深度太小,可能underfit
# 2. 在数据量很大,数据维度很高时决策树很慢
# 补充点英文术语:
# 1. ID3 - iterative Dichotomiser 3: 迭代二分器3
# 2. CART - 分类和回归树
# 3. Entropy Gain - 熵增益
# 为了保证一个合理的树的深度,通常会采取交叉验证法(cross-validation)找到哪个设置会保证最优的估算(TODO: 交叉验证法是什么?)
# 7. 孤立森林模型 参考博客: https://blog.csdn.net/m0_37935211/article/details/121864239
# 孤立森林算法是随机森林算法的一个变种,可以用于异常值检测
# 算法的核心思想是:随机地把样本进行分割,异常点相比寻常点更容易因为随机的分割而被分离,于是这些早早就被分离的点就是异常点
# 在决策树中,异常点位于更浅的深度,而寻常点位于更深的深度
# 在孤立森林训练完毕后,可以用来预测一个样本是否是异常点
# 更详细的内容,请参考那篇博客,写得很好!静下心去读就好啦~!
# 7.5. 补充一下SVM的知识 参考视频:https://www.bilibili.com/video/BV16T4y1y7qj/?spm_id_from=333.337.search-card.all.click
# SVM的本质是:量化两类数据差异的方法
# Margin: 间隔。在二类分类问题中,会用一个'平面'把空间分成两半,这个平面就是分类标准,而这个平面到两个类别之间最近的样本的
# 距离的最小值就称为margin(间隔)。越大的间隔意味着两类数据差异越大,那么我们区分起来就越容易
# Margin的一个更加清晰的定义是,两类数据的距离最近的两个点的距离。具体可以看看参考视频
# 还有其它术语,比如决策边界,支持向量,还有支持向量机这个名字的由来,正超平面,负超平面,决策超平面,都请参考视频。
# NOTE: 参考视频并不包括 ‘求解正负超平面的数学公式推导’ , 需要自己看其它视频了解
# 软间隔:考虑到样本中的异常值,把间隔距离当成收入,把考虑异常值会产生的间隔距离损失当成成本,同时考虑收入和成本的‘利润’就是 “软间隔”
# 硬间隔:不考虑异常值,所计算出的间隔
# 升维转换和核技巧(kernel trick):
# 升维转换:面对无法通过一根线/平面把数据分类的样本时,可以对样本数据进行升维, 然后在高维度下进行SVM模型求解,找到对应的分隔超平面
# 缺点:1. 需要明确的维度转换函数 2. 更多的数据存储计算要求
# 核技巧就是为了解决升维转换的缺点而诞生的
# 核技巧:
# 核函数(kernel function) : 能够提供高维度向量相似度的测量,通过选取合适的核公式,可以不用知晓具体的维度转换函数,直接获得数据
# 的高维度差异度, 并以此来进行分类判断
# TODO: 可以看下参考视频的up主的其它视频,有SVM的数学推导,可以深入了解SVM
# 8. 基于SVM的异常检测 参考博客:https://blog.csdn.net/qq_19446965/article/details/118742147
# 更精准的名字:OneClassSVM
# sklearn提供了一些机器学习方法,可用于奇异(Novelty)点或者异常(Outlier)点检测,包括OneClassSVM,Isolation Forest,Local Outlier Factor(LOF)等,
# 其中OneCLassSVM可以用于Novelty Dection,而后两者可用于Outlier Detection。
# 严格来说,OneCLassSVM不是一种outlier detection,而是一种novelty detection方法:它的训练集不应该掺杂异常点,因为模型可能会去匹配这些异常点。但在数据
# 维度很高,或者对相关数据分布没有任何假设的情况下,OneClassSVM也可以作为一种很好的outlier detection方法。
# 在one-class classification中,仅仅只有一类的信息是可以用于训练,其他类别的(总称outlier)信息是缺失的,也就是区分两个类别的边界线是通过仅有的一类数据的
# 信息学习得到的。
# 名词解释:
# novelty detection:当训练数据中没有离群点,我们的目标是用训练好的模型去检测另外发现的新样本
# outlier dection:当训练数据中包含离群点,模型训练时要匹配训练数据的中心样本,忽视训练样本中的其他异常点。
# 8.25. OneClass 与二分类,多分类的区别
# 如果将分类算法进行划分,根据类别个数的不同可以分为单分类,二分类,多分类。常见的分类算法主要解决二分类和多分类问题,预测一封邮件是否是垃圾邮件是一个
# 典型的二分类问题,手写体识别是一个典型的多分类问题,这些算法并不能很好的应用在单分类上,但是单分类问题在工业界广泛存在,由于每个企业刻画用户的数据都
# 是有限的,很多二分类问题很难找到负样本,即使用一些排除法筛选出负样本,负样本也会不纯,不能保证负样本中没有正样本。所以在只能定义正样本不能定义负样本
# 的场景中,使用单分类算法更合适。
# 单分类算法只关注与样本的相似或者匹配程度,对于未知的部分不妄下结论。
# 典型的二类问题:识别邮件是否是垃圾邮件,一类“是”,一类“不是”。
# 典型的多类问题:人脸识别,每个人对应的脸就是一个类,然后把待识别的脸分到对应的类去。
# 而OneClassClassification,它只有一个类,属于该类就返回结果“是”,不属于就返回结果“不是”。
# 其区别就是在二分类问题中,训练集中就由两个类的样本组成,训练出的模型是一个二分类模型;而OneClassClassification中的训练样本只有一类,因此训练出的分
# 类器将不属于该类的所有其他样本判别为“不是”即可,而不是由于属于另一类才返回“不是”的结果。
# 8.5. 关于神经网络比SVM更快的原因 参考博客:https://blog.csdn.net/qq_19446965/article/details/118742147
# One Class Learning 比较经典的算法是One-Class-SVM,这个算法的思路非常简单,就是寻找一个超平面将样本中的正例圈出来,预测就是用这个超平面
# 做决策,在圈内的样本就认为是正样本。由于核函数计算比较耗时,在海量数据的场景用的并不多;
# 由于自编码(一种基于神经网络的 One Class Learning 算法)采用神经网络实现,可以用GPU来进行加速计算,因此比较适合海量数据的场景。