https://yourfreetemplates.com/free-machine-learning-diagram/
专案中,常会遇到 Imbalanced Data 不平衡资料。
如:乳癌患者、恐怖份子查验、诈欺犯预测...等,我们关注的是"少数"样本是否能被准确预测?
以蛋白质范例,可以发现决策边界完全无法将少数资料分离。
sns.set(style='white')
ds = fetch_datasets()['protein_homo']
X = PCA(n_components=2).fit_transform(ds.data)
X = MinMaxScaler().fit_transform(X)
y = ds.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42, shuffle=True)
# print('X shape:', X.shape)
# print('y shape:', y.shape)
# print('Positive Ratio:', np.count_nonzero(y==1) / y.shape[0])
lr = LogisticRegression().fit(X_train, y_train)
y_pred = lr.predict(X_test)
# print('Report :', classification_report(y_test, y_pred))
# print('ROC :', roc_auc_score(y_test, y_pred))
plot_x = np.linspace(0, 1, 1000)
plot_y = (-lr.coef_[0][0] * plot_x - lr.intercept_) / lr.coef_[0][1]
# 作图
sns.scatterplot(X_train[:, 0], X_train[:, 1], hue=y_train)
plt.plot(plot_x, plot_y)
plt.title('Imblanced Data: Original')
plt.ylim(0, 1.5)
plt.show()
一般来说 Accuracy 准确度是一个直觉性高的指标。
但单纯的准确度并没办法精准衡量模型是好是坏,因此这里介绍几种更常见的评估方式:
下图为上述资料的混淆矩阵,可发现准确率达到 47700 / (47700+398) = 99.2%
from sklearn.metrics import plot_confusion_matrix
cm = plot_confusion_matrix(
lr,
X_test, y_test,
cmap=plt.cm.Blues
)
plt.show()
甚麽是精确率?
Precision 精确率:被"预测正确样本"中,是"实际正确样本"有多少比例。
Recall 召回率:"实际正确样本"中,被"预测正确样本"有多少比例。
有了精确率与召回率,统计学家进一步定义了:
化简後可得:
也有学者提出精确率 & 召回率不同权重的算法:
全名 Receiver Operating Characteristic,而曲线下面积称 Area Under Curve (AUC)。
首先定义:
有了这两个指标,再配合模型的"阈值",我们可以画出一条 ROC 曲线。
图左:
蓝色为负样本,红色为正样本,横轴则是模型预测的机率。
很直觉的理解:正样本集中於预测机率高的分段上,负样本则较低。
此时我们可以设一个"阈值"(通常预设 0.5),以上判定为正,以下判定为负。
图右:
将横轴设为 FPR,纵轴设为 TPR,配上不同阈值可画出一条曲线。
若选 A 点作阈值,则大部分负样本都被剔除,但正样本也留下较少(TPR/FPR 皆下降)。
约有一半的正样本被判定为正,TPR ≈ 0.5 ,少部分负样本被判定为正,FPR ≈ 0.2 ,
最终得到 A 点在右图曲线上的位置。
蛋白质范例的 ROC(AUC):
from sklearn.metrics import roc_curve, roc_auc_score
fpr, tpr, threshold = roc_curve(y_test, y_pred)
auc = roc_auc_score(y_test, y_pred)
plt.title('Receiver Operating Characteristic')
plt.plot(fpr, tpr, c='b', label=f'AUC = {auc:0.2f}')
plt.plot([0, 1], [0, 1], 'r--')
plt.ylabel('True Positive Rate')
plt.xlabel('False Positive Rate')
plt.legend(loc='best')
plt.show()
但问题仍没解决:样本比例差异过大的情况下,总会使训练的模型判断能力差。
因此,接下来我们会尝试透过采样技术来克服它。
将少数样本用某种方式重复抽样或合成新样本,称过采样。
相反,将多数样本中较不具代表性的移除以免造成杂讯,称欠采样。
全名 Synthetic Minority Oversampling Technique 合成少数过采样技术。
概念是在少数样本位置近的地方,人工合成一些样本。
A. 挑一个少数派(红点),并将邻近的 k 个(k=3)点找出。(Pic1)
B. 从 k 个近邻点中随机选取一个,透过公式合成 N 个(N=3)样本点。(Pic2)
C. 接着对所有的少数点做同样的操作。
蛋白质范例操作 SMOTE:
from imblearn.over_sampling import SMOTE
from sklearn.metrics import roc_auc_score, classification_report
X_re, y_re = SMOTE(random_state=42).fit_resample(X_train, y_train)
lr = LogisticRegression().fit(X_re, y_re)
y_pred = lr.predict(X_test)
plot_x = np.linspace(0, 1, 1000)
plot_y = (-lr.coef_[0][0] * plot_base - lr.intercept_) / lr.coef_[0][1]
# 作图
sns.scatterplot(X_re[:, 0], X_re[:, 1], hue=y_re)
plt.plot(plot_x, plot_y)
plt.title('Positive Sample')
plt.ylim(0, 1.5)
plt.show()
结合刚刚的 ROC(AUC)曲线:
from sklearn.metrics import roc_curve, roc_auc_score
fpr, tpr, threshold = roc_curve(y_test, y_pred)
auc = roc_auc_score(y_test, y_pred)
plt.title('Receiver Operating Characteristic')
plt.plot(fpr, tpr, c='b', label=f'AUC = {auc:0.2f}')
plt.plot([0, 1], [0, 1], 'r--')
plt.ylabel('True Positive Rate')
plt.xlabel('False Positive Rate')
plt.legend(loc='best')
plt.show()
print(auc)
>> 0.7254869736523286
SMOTE 虽不错,但有一个明显缺点:对"所有少数样本"都做过采样。
大多时候并不是所有少数样本都无监别度,真正无监别度的是与多数样本混合在一起的少数样本。
靠近边界的少数样本因与多数样本混合在一起,易产生杂讯。若对边界样本学习,可能将多数样本误判为少数。
因此,对 SMOTE 算法做出改进的算法,即 SMOTE Border Line。
from imblearn.over_sampling import BorderlineSMOTE
blsmote = BorderlineSMOTE(random_state=42, kind=’borderline-2')
X_re, y_re = blsmote.fit_resample(X_train, y_train)
实作上,其实还是更常使用 SMOTE,毕竟 Borderline 方法的计算复杂,且阈值设定也缺乏公定的标准。
需要花更多时间调参,然而跑分进步幅度却不大。
相对过采样,欠采样是将多数样本进行 Scale Down,使模型的权重改变,少考虑一些多数样本。
最简单的做法是随机排除掉一些多数样本。但有可能排除掉边界样本,
使没监别度少数样本也被模型考虑,虽使监别度上升,却增加过拟合风险。
会针对所有样本去遍历一次。
令两个样本点 x, y 分属不同的 class,一个为多数样本,另一为少数,可计算样本间距 d(x, y)。
若找不到第三个样本点 z,使得任一样本点到 z 的距离比样本点间距还小,则删去其。
核心理念:找出边界监别度不高的样本,认为这些样本属杂讯应该剔除(类似 Borderline SMOTE)。
蛋白质范例操作 SMOTE + TomekLinks:
X_re, y_re = SMOTE(random_state=42).fit_resample(X_train, y_train)
X_rere, y_rere = TomekLinks().fit_resample(X_re, y_re)
lr = LogisticRegression().fit(X_rere, y_rere)
y_pred = lr.predict(X_test)
from sklearn.metrics import roc_curve, roc_auc_score
fpr, tpr, threshold = roc_curve(y_test, y_pred)
auc = roc_auc_score(y_test, y_pred)
print(auc)
>> 0.7332799742949548
与 Tomek Links 观念相同,也是透过某种方式来剔除监别度低的样本。
ENN 改成对多数样本寻找 K 个近邻点,若一半以上(门槛可自设)不属於多数样本,就将该样本剔除。
实作上,其实很常同时使用过采样 + 欠采样来做资料重组。如下图:
重新采样的目的是让模型产生监别度,而不是让模型学习错误资讯。若先采样才切分,可能使测试资料偏离了原资料,导致模型学习到一堆杂讯。
不管哪种采样,都会大幅增加过拟合程度(如:样本数少,又做欠采样)。
即使模型区分出来,由於欠采样後多数样本过少,导致模型只侧重学习某部分样本,无法反映资料全貌。
此时,交叉验证、建立多模型做集成学习,都会是好的解决方式。
蛋白质范例是因为少数样本与多数样本看上去还能分离,实际运行很有可能碰到完全分不开的例子。
若少数样本杂乱地散落在多数样本之间,此时就不要考虑采样问题。
可以优先评估是否资料本身的分布有问题,像是一开始回收数据错误,或样本并非欧几里得分布等情况。
使用内建 wine,试着用 pipeline、Cross Validation,写个回圈以操作演示过的演算法。
import numpy as np
import pandas as pd
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
ds = load_wine()
X = pd.DataFrame(ds.data, columns=ds.feature_names)
y = pd.DataFrame(ds.target, columns=['Wine'])
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
# 把要用的 model 整理出
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import GaussianNB
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.svm import SVC
from sklearn.neural_network import MLPClassifier
from sklearn.ensemble import RandomForestClassifier
models = []
models.append(("Logistic Regression", LogisticRegression()))
models.append(("Naive Bayes", GaussianNB()))
models.append(("K-Nearest Neighbour", KNeighborsClassifier(n_neighbors=3)))
models.append(("Decision Tree", DecisionTreeClassifier()))
models.append(("Support Vector Machine-linear", SVC(kernel="linear")))
models.append(("Support Vector Machine-rbf", SVC(kernel="rbf")))
models.append(("Random Forest", RandomForestClassifier(n_estimators=7)))
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import StratifiedKFold
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
scores = []
names = []
for name, model in models:
kfold = StratifiedKFold(n_splits=10).split(X_train, y_train)
rfc_PL = make_pipeline(
StandardScaler(),
PCA(n_components=2),
model
)
cv = cross_val_score(rfc_PL, X_train, y_train, cv=kfold, scoring = "accuracy")
names.append(name)
scores.append(cv)
for i in range(len(names)):
print(f'{names[i]:<30}: {scores[i].mean()*100:.3f}')
>> Logistic Regression : 96.090
Naive Bayes : 96.090
K-Nearest Neighbour : 92.949
Decision Tree : 94.551
Support Vector Machine-linear : 95.321
Support Vector Machine-rbf : 96.090
Random Forest : 96.090
在进入Pattern的介绍之前,我觉得要先让大家认识一下UML这个东西,尤其是Class Dia...
开始学习 JavaScript 之後遇到的变数五花八门,不理解用法或是不懂的回传的型态,就很容易会卡...
太空狗闪躲陨石 教学原文参考:太空狗闪躲陨石 这篇文章会介绍,如何在 Scratch 3 里使用键盘...
为了将来可能做DNS负载均衡、或故障转移等,先快速建一个简单的DNS服务,本次安装OS为Centos...
课程目标 本课程将简介两种常见敏捷方法 (Scrum 与 Kanban) 如何在专案中要如何处理问题...