1. KNN算法-分类
1.1 样本距离判断
明可夫斯基距离:欧式距离 ,明可夫斯基距离的特殊情况;曼哈顿距离 ,明可夫斯基距离的特殊情况。
两个样本的距离公式可以通过如下公式进行计算,又称为欧式距离。

(1)欧式距离:

(2)曼哈顿距离:


1.2 KNN 算法原理
K-近邻算法(K-Nearest Neighbors,简称KNN) ,根据K个邻居样本的类别来判断当前样本的类别;如果一个样本在特征空间中的k个最相似(最邻近)样本中的大多数属于某个类别,则该类本也属于这个类别,比如: 有10000个样本,选出7个到样本A的距离最近的,然后这7个样本中假设:类别1有2个,类别2有3个,类别3有2个,那么就认为A样本属于类别2,因为它的7个邻居中类别2最多(近朱者赤近墨者黑)。
示例:

使用KNN算法预测《唐人街探案》电影属于哪种类型?分别计算每个电影和预测电影的距离然后求解:

1.3 KNN缺点
对于大规模数据集,计算量大,因为需要计算测试样本与所有训练样本的距离。对于高维数据,距离度量可能变得不那么有意义,这就是所谓的"维度灾难"需要选择合适的k值和距离度量,这可能需要一些实验和调整。
1.4 API 介绍
class sklearn.neighbors.KNeighborsClassifier(n_neighbors=5, algorithm='auto')
参数:
(1)n_neighbors:
int, default=5,默认情况下用于kneighbors查询的近邻数,就是K。
(2)algorithm:
{'auto', 'ball_tree', 'kd_tree', 'brute'}, default='auto'。找到近邻的方式,注意不是计算距离的方式,与机器学习算法没有什么关系,开发中请使用默认值'auto'。
方法:
(1) fit(x,y) : 使用X作为训练数据和y作为目标数据。
(2) predict(x): 预测提供的数据,得到预测数据。
示例:
python
# 用KNN算法对鸢尾花进行分类
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
# 1)获取数据
iris = load_iris()
# 只有4个特征, 150个样本
print(iris.data.shape) # (150,4)
# 4个特征的描述 ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
print(iris.feature_names)
# 150个目标,对应150个样本的类别
print(iris.target.shape) # (150,)
# 目标值只有0 1 2这三种值,说明150个样本属于三类中的其中一种
print(iris.target) # [0 0 0...1 1 1 ...2 2 2]
# 目标值三种值代表的三种类型的描述。
print(iris.target_names) # ['setosa' 'versicolor' 'virginica']
# 2)划分数据集
# x_train训练特征,y_train训练目标, x_test测试特征,y_test测试目标
x_train, x_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=22)
# print(x_train.shape, x_test.shape, y_train.shape, y_test.shape) #(112, 4) (38, 4) (112,) (38,)
# 3)特征工程:标准化, 只有4个特征
transfer = StandardScaler()
# 对训练特征做标准化, 对测试特征做相同的标准化,因为fit_transform中已经有fit进行计算了,所以对x_test只需要做transform了
# 训练用的什么数据,模式就只能识别什么样的数据。
x_train = transfer.fit_transform(x_train)
x_test = transfer.transform(x_test)
# 4)KNN算法预估器, k=7表示找7个邻近来判断自身类型.
estimator = KNeighborsClassifier(n_neighbors=7)
estimator.fit(x_train, y_train) # 该步骤就是estimator根据训练特征和训练目标在自己学习,让它自己变聪敏
# 5)模型评估 测试一下聪敏的estimator能力
# 方法1:直接比对真实值和预测值,
y_predict = estimator.predict(x_test) # y_predict预测的目标结果
print("y_predict:\n", y_predict)
print("直接比对真实值和预测值:\n", y_test == y_predict)
# 方法2:计算准确率,
score = estimator.score(x_test, y_test)
print("准确率为:\n", score) # 0.9473684210526315
模型保存与加载:
python
import joblib
# 保存模型
joblib.dump(estimator, "my_ridge.pkl")
# 加载模型
estimator = joblib.load("my_ridge.pkl")
#使用模型预测
y_test=estimator.predict([[0.4,0.2,0.4,0.7]])
print(y_test)
2. 模型选择与调优
2.1 交叉验证
2.1.1 保留交叉验证HoldOut
HoldOut Cross-validation(Train-Test Split)在这种交叉验证技术中,整个数据集被随机地划分为训练集和验证集。根据经验法则,整个数据集的近70%被用作训练集,其余30%被用作验证集。也就是我们最常使用的,直接划分数据集的方法。
优点: 很简单很容易执行。
**缺点:**不适用于不平衡的数据集。假设我们有一个不平衡的数据集,有0类和1类。假设80%的数据属于 "0 "类,其余20%的数据属于 "1 "类。这种情况下,训练集的大小为80%,测试数据的大小为数据集的20%。可能发生的情况是,所有80%的 "0 "类数据都在训练集中,而所有 "1 "类数据都在测试集中。因此,我们的模型将不能很好地概括我们的测试数据,因为它之前没有见过 "1 "类的数据;一大块数据被剥夺了训练模型的机会。
在小数据集的情况下,有一部分数据将被保留下来用于测试模型,这些数据可能具有重要的特征,而我们的模型可能会因为没有在这些数据上进行训练而错过。
2.1.2 K-折交叉验证(K-fold)
(K-fold Cross Validation,记为K-CV或K-fold): K-Fold交叉验证技术中,整个数据集被划分为K个大小相同的部分。每个分区被称为 一个"Fold"。所以我们有K个部分,我们称之为K-Fold。一个Fold被用作验证集,其余的K-1个Fold被用作训练集。
该技术重复K次,直到每个Fold都被用作验证集,其余的作为训练集,模型的最终准确度是通过取k个模型验证数据的平均准确度来计算的。

2.1.3 分层k-折交叉验证Stratified k-fold
**Stratified k-fold cross validation:**K-折交叉验证的变种, 分层的意思是说在每一折中都保持着原始数据中各个类别的比例关系,比如说:原始数据有3类,比例为1:2:1,采用3折分层交叉验证,那么划分的3折中,每一折中的数据类别保持着1:2:1的比例,这样的验证结果更加可信。

2.1.4 API 介绍
from sklearn.model_selection import StratifiedKFold: 普通K折交叉验证和分层K折交叉验证的使用是一样的 只是引入的类不同。
from sklearn.model_selection import KFold: 使用时只是KFold这个类名不一样其他代码完全一样。
strat_k_fold=sklearn.model_selection.StratifiedKFold(n_splits=5, shuffle=True, random_state=42) :n_splits划分为几个折叠,shuffle是否在拆分之前被打乱(随机化),False则按照顺序拆分,random_state随机因子
indexs=strat_k_fold.split(X,y): 返回一个可迭代对象,一共有5个折叠,每个折叠对应的是训练集和测试集的下标,然后可以用for循环取出每一个折叠对应的X和y下标来访问到对应的测试数据集和训练数据集 以及测试目标集和训练目标集。
for train_index, test_index in indexs:
X[train_index],y[train_index],X[test_index ],y[test_index ]
示例:
python
from sklearn.datasets import load_iris
from sklearn.model_selection import StratifiedKFold
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
# 加载数据
iris = load_iris()
X = iris.data
y = iris.target
# 初始化分层k-折交叉验证器
#n_splits划分为几个折叠
#shuffle是否在拆分之前被打乱(随机化),False则按照顺序拆分
#random_state随机因子
strat_k_fold = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
# 创建一个K近邻分类器实例
knn = KNeighborsClassifier(n_neighbors=7)
# 进行交叉验证
accuracies = []
for train_index, test_index in strat_k_fold.split(X, y):
print(train_index, test_index)
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
# 数据预处理(标准化)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 使用K近邻分类器进行训练
knn.fit(X_train_scaled, y_train)
# 输出每次折叠的准确性得分
score = knn.score(X_test_scaled ,y_test)
print(score)
accuracies.append(score)#把分数添加到外面列表中
print(sum(accuracies)/len(accuracies))#平均得分
#使用StratifiedKFold来创建5个折叠,每个折叠中鸢尾花数据集的类别分布与整体数据集的分布一致。然后我们对每个折叠进行了训练和测试,计算了分类器的准确性。
2.2 超参数搜索
超参数搜索也叫网格搜索(Grid Search),比如在KNN算法中,n_neighbors是一个可以人为设置的参数,所以就是一个超参数。网格搜索能自动的帮助我们找到最好的超参数值。
2.2.1 API 介绍
class sklearn.model_selection.GridSearchCV(estimator, param_grid)
说明:
同时进行交叉验证(CV)、和网格搜索(GridSearch),GridSearchCV实际上也是一个估计器(estimator),同时它有几个重要属性:
best_params_ 最佳参数
best_score_ 在训练集中的准确率
best_estimator_ 最佳估计器
cv_results_ 交叉验证过程描述
best_index_最佳k在列表中的下标
参数:
estimator: scikit-learn估计器实例
param_grid:以参数名称(str)作为键,将参数设置列表尝试作为值的字典
示例: {"n_neighbors": [1, 3, 5, 7, 9, 11]}
cv: 确定交叉验证切分策略,值为:
(1)None 默认5折
(2)integer 设置多少折
如果估计器是分类器,使用"分层k-折交叉验证(StratifiedKFold)"。在所有其他情况下,使用KFold。
示例:
python
# 用KNN算法对鸢尾花进行分类,添加网格搜索和交叉验证
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
def knn_iris_gscv():
# 1)获取数据
iris = load_iris()
# 2)划分数据集
x_train, x_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=22)
# 3)特征工程:标准化
transfer = StandardScaler()
x_train = transfer.fit_transform(x_train)
x_test = transfer.transform(x_test)
# 4)KNN算法预估器, 这里就不传参数n_neighbors了,交给GridSearchCV来传递
estimator = KNeighborsClassifier()
# 加入网格搜索与交叉验证, GridSearchCV会让k分别等于1,2,5,7,9,11进行网格搜索偿试。cv=10表示进行10次交叉验证
estimator = GridSearchCV(estimator, param_grid={"n_neighbors": [1, 3, 5, 7, 9, 11]}, cv=10)
estimator.fit(x_train, y_train)
# 5)模型评估
# 方法1:直接比对真实值和预测值
y_predict = estimator.predict(x_test)
print("y_predict:\n", y_predict)
print("直接比对真实值和预测值:\n", y_test == y_predict)
# 方法2:计算准确率
score = estimator.score(x_test, y_test)
print("在测试集中的准确率为:\n", score) #0.9736842105263158
# 最佳参数:best_params_
print("最佳参数:\n", estimator.best_params_) #{'n_neighbors': 3}, 说明k=3时最好
# 最佳结果:best_score_
print("在训练集中的准确率:\n", estimator.best_score_) #0.9553030303030303
# 最佳估计器:best_estimator_
print("最佳估计器:\n", estimator.best_estimator_) # KNeighborsClassifier(n_neighbors=3)
# 交叉验证结果:cv_results_
print("交叉验证过程描述:\n", estimator.cv_results_)
#最佳参数组合的索引:最佳k在列表中的下标
print("最佳参数组合的索引:\n",estimator.best_index_)
#通常情况下,直接使用best_params_更为方便
return None
knn_iris_gscv()
3. 朴素贝叶斯分类
3.1 贝叶斯分类理论
假设现在我们有一个数据集,它由两类数据组成,数据分布如下图所示:

我们现在用p1(x,y)表示数据点(x,y)属于类别1(图中红色圆点表示的类别)的概率,用p2(x,y)表示数据点(x,y)属于类别2(图中蓝色三角形表示的类别)的概率,那么对于一个新数据点(x,y),可以用下面的规则来判断它的类别:
-
如果p1(x,y)>p2(x,y),那么类别为1;
-
如果p1(x,y)<p2(x,y),那么类别为2。
也就是说,我们会选择高概率对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策。已经了解了贝叶斯决策理论的核心思想,那么接下来,就是学习如何计算p1和p2概率。
3.2 条件概率
在学习计算p1 和p2概率之前,我们需要了解什么是条件概率(Conditional probability),就是指在事件B发生的情况下,事件A发生的概率,用P(A|B)来表示。

根据文氏图,可以很清楚地看到在事件B发生的情况下,事件A发生的概率就是P(A∩B)除以P(B)。
𝑃(𝐴|𝐵)=𝑃(𝐴∩𝐵)/𝑃(𝐵), 因此:𝑃(𝐴∩𝐵)=𝑃(𝐴|𝐵)*𝑃(𝐵), 同理可得:
𝑃(𝐴∩𝐵)=𝑃(𝐵|𝐴)*𝑃(𝐴), 即:
𝑃(𝐴|𝐵)=𝑃(B|A)*𝑃(𝐴)/𝑃(𝐵)
这就是条件概率的计算公式。
3.3 全概率公式
除了条件概率以外,在计算p1和p2的时候,还要用到全概率公式,因此,这里继续推导全概率公式。假定样本空间S,是两个事件A与A'的和。

上图中,红色部分是事件A,绿色部分是事件A',它们共同构成了样本空间S。在这种情况下,事件B可以划分成两个部分。

即:𝑃(𝐵)=𝑃(𝐵∩𝐴)+𝑃(𝐵∩𝐴′), 在上面的推导当中,我们已知:𝑃(𝐵∩𝐴)=𝑃(𝐵|𝐴)𝑃(𝐴), 所以:𝑃(𝐵)=𝑃(𝐵|𝐴)𝑃(𝐴)+𝑃(𝐵|𝐴′)𝑃(𝐴′),
这就是全概率公式。它的含义是,如果A和A'构成样本空间的一个划分,那么事件B的概率,就等于A和A'的概率分别乘以B对这两个事件的条件概率之和。
将这个公式代入上一节的条件概率公式,就得到了条件概率的另一种写法:
3.4 贝叶斯推断
对条件概率公式进行变形,可以得到如下形式:

我们把P(A)称为"先验概率"(Prior probability) ,即在B事件发生之前,我们对A事件概率的一个判断。
P(A|B)称为"后验概率"(Posterior probability) ,即在B事件发生之后,我们对A事件概率的重新评估。
P(B|A)/P(B)称为"可能性函数"(Likelyhood) ,这是一个调整因子,使得预估概率更接近真实概率。
所以,条件概率可以理解成的式子:后验概率 = 先验概率x调整因子
这就是贝叶斯推断的含义。我们先预估一个"先验概率",然后加入实验结果,看这个实验到底是增强还是削弱了"先验概率",由此得到更接近事实的"后验概率"。
3.5 朴素贝叶斯推断
理解了贝叶斯推断,那么让我们继续看看朴素贝叶斯。贝叶斯和朴素贝叶斯的概念是不同的,区别就在于"朴素"二字,朴素贝叶斯对条件概率分布做了**条件独立**性的假设。 比如下面的公式,假设有n个特征:
根据贝叶斯定理,后验概率 P(a|X) 可以表示为:
其中:
-
P(X|a) 是给定类别 ( a ) 下观测到特征向量 X=(x_1, x_2, ..., x_n) 的概率;
-
P(a) 是类别 a 的先验概率;
-
P(X) 是观测到特征向量 X 的边缘概率,通常作为归一化常数处理。
朴素贝叶斯分类器的关键假设是特征之间的条件独立性,即给定类别 a ,特征和
(其中
相互独立。)
因此,我们可以将联合概率 P(X|a) 分解为各个特征的概率乘积:
将这个条件独立性假设应用于贝叶斯公式,我们得到:
这样,朴素贝叶斯分类器就可以通过计算每种可能类别的条件概率和先验概率,然后选择具有最高概率的类别作为预测结果。
这样我们就可以进行计算了。如果有些迷糊,让我们从一个例子开始讲起,你会看到贝叶斯分类器很好懂,一点都不难。
|----|--------|--------|--------|--------|
| | 纹理 | 色泽 | 鼔声 | 类别 |
| 1 | 清晰 | 清绿 | 清脆 | 好瓜 |
| 2 | 模糊 | 乌黑 | 浊响 | 坏瓜 |
| 3 | 模糊 | 清绿 | 浊响 | 坏瓜 |
| 4 | 清晰 | 乌黑 | 沉闷 | 好瓜 |
| 5 | 清晰 | 清绿 | 浊响 | 好瓜 |
| 6 | 模糊 | 乌黑 | 沉闷 | 坏瓜 |
| 7 | 清晰 | 乌黑 | 清脆 | 好瓜 |
| 8 | 模糊 | 清绿 | 沉闷 | 好瓜 |
| 9 | 清晰 | 乌黑 | 浊响 | 坏瓜 |
| 10 | 模糊 | 清绿 | 清脆 | 好瓜 |
| 11 | 清晰 | 清绿 | 沉闷 | ? |
| 12 | 模糊 | 乌黑 | 浊响 | ? |
python
示例:
p(a|X) = p(X|a)* p(a)/p(X) #贝叶斯公式
p(X|a) = p(x1,x2,x3...xn|a) = p(x1|a)*p(x2|a)*p(x3|a)...p(xn|a)
p(X) = p(x1,x2,x3...xn) = p(x1)*p(x2)*p(x3)...p(xn)
p(a|X) = p(x1|a)*p(x2|a)*p(x3|a)...p(xn|a) * p(a) / p(x1)*p(x2)*p(x3)...p(xn) #朴素贝叶斯公式
P(好瓜)=(好瓜数量)/所有瓜
P(坏瓜)=(坏瓜数量)/所有瓜
p(纹理清晰)=(纹理清晰数量)/所有瓜
p(纹理清晰|好瓜)= 好瓜中纹理清晰数量/好瓜数量
p(纹理清晰|坏瓜)= 坏瓜中纹理清晰数量/坏瓜数量
p(好瓜|纹理清晰,色泽清绿,鼓声沉闷)
=【p(好瓜)】*【p(纹理清晰,色泽清绿,鼓声沉闷|好瓜)】/【p(纹理清晰,色泽清绿,鼓声沉闷)】
=【p(好瓜)】*【p(纹理清晰|好瓜)*p(色泽清绿|好瓜)*p(鼓声沉闷|好瓜)】/【p(纹理清晰)*p(色泽清绿)*p(鼓声沉闷)】
p(坏瓜|纹理清晰,色泽清绿,鼓声沉闷)
=【p(坏瓜)*p(纹理清晰|坏瓜)*p(色泽清绿|坏瓜)*p(鼓声沉闷|坏瓜)】/【p(纹理清晰)*p(色泽清绿)*p(鼓声沉闷)】
从公式中判断"p(好瓜|纹理清晰,色泽清绿,鼓声沉闷)"和"p(坏瓜|纹理清晰,色泽清绿,鼓声沉闷)"时,因为它们的分母
值是相同的,[值都是p(纹理清晰)*p(色泽清绿)*p(鼓声沉闷)],所以只要计算它们的分子就可以判断是"好瓜"还是"坏瓜"之间谁大谁小了,所以没有必要计算分母
p(好瓜) = 6/10
p(坏瓜)=4/10
p(纹理清晰|好瓜) = 4/6
p(色泽清绿|好瓜) = 4/6
p(鼓声沉闷|好瓜) = 2/6
p(纹理清晰|坏瓜) = 1/4
p(色泽清绿|坏瓜) = 1/4
p(鼓声沉闷|坏瓜) = 1/4
把以上计算代入公式的分子
p(好瓜)*p(纹理清晰|好瓜)*p(色泽清绿|好瓜)*p(鼓声沉闷|好瓜) = 4/45
p(坏瓜)*p(纹理清晰|坏瓜)*p(色泽清绿|坏瓜)*p(鼓声沉闷|坏瓜) = 1/160
所以
p(好瓜|纹理清晰,色泽清绿,鼓声沉闷) > p(坏瓜|纹理清晰,色泽清绿,鼓声沉闷),
所以把(纹理清晰,色泽清绿,鼓声沉闷)的样本归类为好瓜
3.6 拉普拉斯平滑系数
某些事件或特征可能从未出现过,这会导致它们的概率被估计为零。然而,在实际应用中,即使某个事件或特征没有出现在训练集中,也不能完全排除它在未来样本中出现的可能性。拉普拉斯平滑技术可以避免这种"零概率陷阱",公式为:

一般α取值1,m的值为总特征数量,通过这种方法,即使某个特征在训练集中从未出现过,它的概率也不会被估计为零,而是会被赋予一个很小但非零的值,从而避免了模型在面对新数据时可能出现的过拟合或预测错误。比如计算判断新瓜(纹理清晰,色泽淡白,鼓声沉闷)是好和坏时,因为在样本中色泽淡白没有出现,导致出现0值,会影响计算结果,要采用拉普拉斯平滑系数。
python
p(好瓜|纹理清晰,色泽淡白,鼓声沉闷)
=【p(好瓜)】*【p(纹理清晰|好瓜)*p(色泽淡白|好瓜)*p(鼓声沉闷|好瓜)】/【p(纹理清晰)*p(色泽淡白)*p(鼓声沉闷)】
p(坏瓜|纹理清晰,色泽淡白,鼓声沉闷)
=【p(坏瓜)】*【p(纹理清晰|坏瓜)*p(色泽淡白|坏瓜)*p(鼓声沉闷|坏瓜)】/【p(纹理清晰)*p(色泽淡白)*p(鼓声沉闷)】
p(纹理清晰|好瓜)= (4+1)/(6+3) # +1是因为防止零概率 +3是因为有3个特征(纹理,色泽,鼓声)
p(色泽淡白|好瓜)= (0+1)/(6+3)
p(鼓声沉闷|好瓜) = (2+1)/(6+3)
p(纹理清晰|坏瓜)= (1+1)/(4+3)
p(色泽淡白|坏瓜)= (0+1)/(4+3)
p(鼓声沉闷|坏瓜) = (1+1)/(4+3)
示例:
python
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB
# 1)获取数据
news =load_iris()
# 2)划分数据集
x_train, x_test, y_train, y_test = train_test_split(news.data, news.target)
# 3)特征工程:不用做标准化
# 4)朴素贝叶斯算法预估器流程
estimator = MultinomialNB()
estimator.fit(x_train, y_train)
# 5)模型评估
score = estimator.score(x_test, y_test)
print("准确率为:\n", score)
# 6)预测
index=estimator.predict([[2,2,3,1]])
print("预测:\n",index,news.target_names,news.target_names[index])