常用机器学习算法汇总(中)

1,113 阅读19分钟

机器学习入门系列(2)--如何构建一个完整的机器学习项目,第八篇!

该系列的前七篇文章:

上一篇文章介绍了线性回归、逻辑回归、决策树和随机森林四种算法,本文会继续介绍四种算法--SVM、朴素贝叶斯、KNN 以及 kmean 算法,其中最后一种是无监督学习的聚类算法,前面三种也是非常常见的算法,特别是 SVM,在 2012 年 AlexNet 网络的成功之前,一直都是图像分类中非常常用的分类算法。


5. 支持向量机(SVM)

简述

定义:SVM 是一种二类分类模型,其基本模型定义为特征空间上的间隔最大的线性分类器,即支持向量机的学习策略便是间隔最大化,最终可转化为一个凸二次规划问题的求解。

或者简单的可以理解为就是在高维空间中寻找一个合理的超平面将数据点分隔开来,其中涉及到非线性数据到高维的映射以达到数据线性可分的目的。

训练数据线性可分时,通过硬间隔最大化,学习一个线性分类器,即线性可分支持向量机,又称为硬间隔支持向量机;训练数据近似线性可分时,通过软间隔最大化,也学习一个线性分类器,即线性支持向量机,也称为软间隔支持向量机;训练数据线性不可分时,通过使用核技巧和软间隔最大化,学习非线性支持向量机。

原始的 SVM 是一个二类分类器,但后续针对多类分类问题,也进行了拓展,有以下几种改进来实现多类分类问题:

1.直接法

直接在目标函数上进行修改,将多个分类面的参数求解合并到一个最优化问题中,通过求解该优化就可以实现多分类。

但是计算复杂度很高,实现起来较为困难。一般很少使用这种方法

2.间接法,间接法又分为以下几种:

  • 一对多:每次训练的时候设置其中某个类为一类,其余所有类为另一个类。

比如有 A,B,C,D 四个类,第一次 A 是一个类,B,C,D 是一个类,训练一个分类器,第二次 B 是一个类,然后 A,C,D 是一个类,训练一个分类器,依次类推。因此,如果总共有 n 个类,最终将训练 n 个分类器。

测试的时候,将测试样本都分别送入所有分类器中,取得到最大值的类别作为其分类结果。这是因为到分类面距离越大,分类越可信。

这种方法的优点是每个优化问题的规模比较小,而且分类速度很快,因为分类器数目和类别数目相同;但是,有时会出现这样两种情况:对一个测试样本,每个分类器都得到它属于分类器所在类别;或者都不属于任意一个分类器的类别。前者称为分类重叠现象,后者叫不可分类现象。前者可以任意选择一个结果或者就按照其到每个超平面的距离来分,哪个远选哪个类别;而后者只能分给新的第 n+1 个类别了。最大的缺点还是由于将 n-1 个类别作为一个类别,其数目会数倍于只有 1 个类的类别,这样会人为造成数据集偏斜的问题

  • 一对一:任意两个类都训练一个分类器,预测的时候通过投票选择最终结果。

这个方法同样会有分类重叠的现象,但不会有不可分类现象,因为不可能所有类别的票数都是 0。这种方法会比较高效,每次训练使用的样本其实就只有两类数据,而且预测会比较稳定,但是缺点是预测时间会很久

优缺点

优点
  1. 使用核函数可以向高维空间进行映射
  2. 使用核函数可以解决非线性的分类
  3. 分类思想很简单,就是将样本与决策面的间隔最大化
  4. 分类效果较好
缺点
  1. 对大规模数据训练比较困难
  2. 无法直接支持多分类,但是可以使用间接的方法来做
  3. 噪声也会影响SVM的性能,因为SVM主要是由少量的支持向量决定的。

代码实现

线性 SVM 的代码实现:

from sklearn import datasets
import numpy as np
from sklearn.cross_validation import train_test_split

iris = datasets.load_iris() # 由于Iris是很有名的数据集,scikit-learn已经原生自带了。
X = iris.data[:, [2, 3]]
y = iris.target # 标签已经转换成0,1,2了
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0) # 为了看模型在没有见过数据集上的表现,随机拿出数据集中30%的部分做测试

# 为了追求机器学习和最优化算法的最佳性能,我们将特征缩放
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
sc.fit(X_train) # 估算每个特征的平均值和标准差
sc.mean_ # 查看特征的平均值,由于Iris我们只用了两个特征,所以结果是array([ 3.82857143,  1.22666667])
sc.scale_ # 查看特征的标准差,这个结果是array([ 1.79595918,  0.77769705])
X_train_std = sc.transform(X_train)
# 注意:这里我们要用同样的参数来标准化测试集,使得测试集和训练集之间有可比性
X_test_std = sc.transform(X_test)
X_combined_std = np.vstack((X_train_std, X_test_std))
y_combined = np.hstack((y_train, y_test))

# 导入SVC
from sklearn.svm import SVC
svm = SVC(kernel='linear', C=1.0, random_state=0) # 用线性核,你也可以通过kernel参数指定其它的核。
svm.fit(X_train_std, y_train)
# 打印决策边界,这个函数是我自己写的,如果你想要的话,我发给你
plot_decision_regions(X_combined_std, y_combined, classifier=svm, test_idx=range(105,150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.show()

接下来是使用非线性 SVM 的代码:

svm = SVC(kernel='rbf', random_state=0, gamma=x, C=1.0) # 令gamma参数中的x分别等于0.2和100.0
svm.fit(X_train_std, y_train) # 这两个参数和上面代码中的训练集一样
plot_decision_regions(X_combined_std, y_combined, classifier=svm, test_idx=range(105,150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.show()

6. 朴素贝叶斯

简述

朴素贝叶斯是基于贝叶斯定理与特征条件独立假设的分类方法。

贝叶斯定理是基于条件概率来计算的,条件概率是在已知事件 B 发生的前提下,求解事件 A 发生的概率,即

P(A|B)=\frac{P(AB)}{P(B)}

所以贝叶斯定理如下所示:

P(B|A) = \frac{P(A|B)P(B)}{P(A)}

朴素贝叶斯分类器可表示为:

f(x)=argmax P(y_{k})\prod_{i=1}^{n}P(x_{i}|y_{k})

这里 P(y_k) 是先验概率,而 P(y_k|x) 则是后验概率,朴素贝叶斯的目标就是最大化后验概率,这等价于期望风险最小化。

朴素贝叶斯根据特征是否离散,分为三种模型,如下所示:

  • 贝叶斯估计/多项式模型:当特征是离散的时候,使用该模型;
  • 高斯模型:特征是连续的时候采用该模型;
  • 伯努利模型:特征是离散的,且取值只能是 0 和 1。

优缺点

优点
  • 对小规模的数据表现很好,适合多分类任务,适合增量式训练。
缺点
  • 对输入数据的表达形式很敏感(离散、连续,值极大极小之类的)。

对比逻辑回归和朴素贝叶斯

相同点
  1. 两者都是对特征的线性表达
  2. 两者建模的都是条件概率,对最终求得的分类结果有很好的解释性
与逻辑回归的不同
  1. 朴素贝叶斯是一个生成模型,在计算 P(y|x) 之前,先要从训练数据中计算 P(x|y) 和 P(y) 的概率,从而利用贝叶斯公式计算 P(y|x)。

    逻辑回归是一个判别模型,它通过在训练数据集上最大化判别函数 P(y|x) 学习得到,不需要知道 P(x|y) 和 P(y) 。

  2. 朴素贝叶斯是建立在条件独立假设基础之上的,设特征 X 含有n个特征属性(X1,X2,...Xn),那么在给定Y的情况下,X1,X2,...Xn是条件独立的。

    逻辑回归的限制则要宽松很多,如果数据满足条件独立假设,能够取得非常好的效果;当数据不满足条件独立假设时,逻辑回归仍然能够通过调整参数让模型最大化的符合数据的分布,从而训练得到在现有数据集下的一个最优模型。

  3. 当数据集比较小的时候,应该选用Naive Bayes,为了能够取得很好的效果,数据的需求量为 O(log n)

    当数据集比较大的时候,应该选用Logistic Regression,为了能够取得很好的效果,数据的需求量为 O( n)

代码实现

下面是使用sklearn的代码例子,分别实现上述三种模型,例子来自 朴素贝叶斯的三个常用模型:高斯、多项式、伯努利

首先是高斯模型的实现:

>>> from sklearn import datasets
>>> iris = datasets.load_iris()
>>> iris.feature_names  # 四个特征的名字
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
>>> iris.data
array([[ 5.1,  3.5,  1.4,  0.2],
       [ 4.9,  3. ,  1.4,  0.2],
       [ 4.7,  3.2,  1.3,  0.2],
       [ 4.6,  3.1,  1.5,  0.2],
       [ 5. ,  3.6,  1.4,  0.2],
       [ 5.4,  3.9,  1.7,  0.4],
       [ 4.6,  3.4,  1.4,  0.3],
       [ 5. ,  3.4,  1.5,  0.2],
       ......
       [ 6.5,  3. ,  5.2,  2. ],
       [ 6.2,  3.4,  5.4,  2.3],
       [ 5.9,  3. ,  5.1,  1.8]]) #类型是numpy.array
>>> iris.data.size  
600  #共600/4=150个样本
>>> iris.target_names
array(['setosa', 'versicolor', 'virginica'], 
      dtype='|S10')
>>> iris.target
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,....., 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ......, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])
>>> iris.target.size
150
>>> from sklearn.naive_bayes import GaussianNB
>>> clf = GaussianNB()
>>> clf.fit(iris.data, iris.target)
>>> clf.predict(iris.data[0])
array([0])   # 预测正确
>>> clf.predict(iris.data[149])
array([2])   # 预测正确
>>> data = numpy.array([6,4,6,2])
>>> clf.predict(data)
array([2])  # 预测结果很合理

接着,多项式模型如下:

>>> import numpy as np
>>> X = np.random.randint(5, size=(6, 100))
>>> y = np.array([1, 2, 3, 4, 5, 6])
>>> from sklearn.naive_bayes import MultinomialNB
>>> clf = MultinomialNB()
>>> clf.fit(X, y)
MultinomialNB(alpha=1.0, class_prior=None, fit_prior=True)
>>> print(clf.predict(X[2]))
[3]

值得注意的是,多项式模型在训练一个数据集结束后可以继续训练其他数据集而无需将两个数据集放在一起进行训练。在 sklearn 中,MultinomialNB() 类的partial_fit() 方法可以进行这种训练。这种方式特别适合于训练集大到内存无法一次性放入的情况。

在第一次调用 partial_fit() 时需要给出所有的分类标号。

>>> import numpy
>>> from sklearn.naive_bayes import MultinomialNB
>>> clf = MultinomialNB() 
>>> clf.partial_fit(numpy.array([1,1]), numpy.array(['aa']), ['aa','bb'])
GaussianNB()
>>> clf.partial_fit(numpy.array([6,1]), numpy.array(['bb']))
GaussianNB()
>>> clf.predict(numpy.array([9,1]))
array(['bb'], 
      dtype='|S2')

伯努利模型如下:

>>> import numpy as np
>>> X = np.random.randint(2, size=(6, 100))
>>> Y = np.array([1, 2, 3, 4, 4, 5])
>>> from sklearn.naive_bayes import BernoulliNB
>>> clf = BernoulliNB()
>>> clf.fit(X, Y)
BernoulliNB(alpha=1.0, binarize=0.0, class_prior=None, fit_prior=True)
>>> print(clf.predict(X[2]))
[3]

7. KNN 算法

简述

k 近邻(KNN)是一种基本分类与回归方法。

其思路如下:给一个训练数据集和一个新的实例,在训练数据集中找出与这个新实例最近的 k 个训练实例,然后统计最近的 k 个训练实例中所属类别计数最多的那个类,就是新实例的类。其流程如下所示:

  1. 计算训练样本和测试样本中每个样本点的距离(常见的距离度量有欧式距离,马氏距离等);
  2. 对上面所有的距离值进行排序;
  3. 选前 k 个最小距离的样本;
  4. 根据这 k 个样本的标签进行投票,得到最后的分类类别;

KNN 的特殊情况是 k=1 的情况,称为最近邻算法。对输入的实例点(特征向量)x,最近邻法将训练数据集中与 x 最近邻点的类作为其类别。

三要素

  1. k 值的选择
  2. 距离的度量(常见的距离度量有欧式距离,马氏距离)
  3. 分类决策规则(多数表决规则)

k 值的选择

  1. k 值越小表明模型越复杂,更加容易过拟合,其偏差小,而方差大
  2. 但是 k 值越大,模型越简单,如果 k=N 的时候就表明无论什么点都是训练集中类别最多的那个类,这种情况,则是偏差大,方差小

所以一般 k 会取一个较小的值,然后用过交叉验证来确定 这里所谓的交叉验证就是将样本划分一部分出来为预测样本,比如 95% 训练,5% 预测,然后 k 分别取1,2,3,4,5 之类的,进行预测,计算最后的分类误差,选择误差最小的 k

距离的度量

KNN 算法使用的距离一般是欧式距离,也可以是更一般的 Lp 距离或者马氏距离,其中 Lp 距离定义如下:

L_p(x_i, x_j) = (\sum_{l=1}^n |x_i^{(l)} - x_j^{(l)} |^p)^{\frac{1}{p}}

KNN的回归

在找到最近的 k 个实例之后,可以计算这 k 个实例的平均值作为预测值。或者还可以给这 k 个实例添加一个权重再求平均值,这个权重与度量距离成反比(越近权重越大)

优缺点

优点

  1. 思想简单,理论成熟,既可以用来做分类也可以用来做回归
  2. 可用于非线性分类
  3. 训练时间复杂度为 O(n);
  4. 准确度高,对数据没有假设,对异常值不敏感;

缺点

  1. 计算量大
  2. 样本不平衡问题(即有些类别的样本数量很多,而其它样本的数量很少);
  3. 需要大量的内存

代码实现

使用sklearn的简单代码例子:

#Import Library
from sklearn.neighbors import KNeighborsClassifier

#Assumed you have, X (predictor) and Y (target) for training data set and x_test(predictor) of test_dataset
# Create KNeighbors classifier object model 

KNeighborsClassifier(n_neighbors=6) # default value for n_neighbors is 5

# Train the model using the training sets and check score
model.fit(X, y)

#Predict Output
predicted= model.predict(x_test)

最后,在用KNN前你需要考虑到:

  • KNN的计算成本很高
  • 所有特征应该标准化数量级,否则数量级大的特征在计算距离上会有偏移。
  • 在进行KNN前预处理数据,例如去除异常值,噪音等。

8. Kmeans 算法

简述

K-均值(Kmeans)是最普及的聚类算法,算法接受一个未标记的数据集,然后将数据集聚类成不同的组。

K-均值是一个迭代算法,假设我们想要将数据聚类成 n 个组,其方法为:

  1. 首先选择 K 个随机的点,称其为聚类中心
  2. 对于数据集中的每一个数据,按照距离 K 个中心点的距离,将其与距离最近的中心点关联起来,与同一个中心点关联的所有点聚成一个类
  3. 计算每一个组的平均值,将该组所关联的中心点移动到平均值的位置
  4. 重复步骤 2-3,直到中心点不再变化

这个过程中分两个主要步骤,第一个就是第二步,将训练集中的样本点根据其与聚类中心的距离,分配到距离最近的聚类中心处,接着第二个就是第三步,更新类中心,做法是计算每个类的所有样本的平均值,然后将这个平均值作为新的类中心值,接着继续这两个步骤,直到达到终止条件,一般是指达到设定好的迭代次数。

当然在这个过程中可能遇到有聚类中心是没有分配数据点给它的,通常的一个做法是删除这种聚类中心,或者是重新选择聚类中心,保证聚类中心数还是初始设定的 K 个

随机初始化

在运行 K-均值算法之前,首先需要随机初始化所有的聚类中心点,做法如下:

  1. 首先应该选择 K<m ,即聚类中心点的个数要小于所有训练集实例的数量
  2. 随机选择 K 个训练实例,然后令 K 个聚类中心分别和这 K 个训练实例相等

K-均值的一个问题在于,它有可能会停留在一个局部最小值处,而这取决于初始化的情况。

为了解决这个问题,通常需要**多次运行 K-均值算法,每一次都重新进行随机初始化,最后再比较多次运行 K-均值的结果,选择代价函数最小的结果。这种方法在 K 较小(2-10)**的时候还是可行的,但是如果 K 较大,这种做法可能不会有明显地改善。

优缺点

优点

  1. k-means 算法是解决聚类问题的一种经典算法,算法简单、快速
  2. 对处理大数据集,该算法是相对可伸缩的和高效率的,因为它的复杂度大约是 O(nkt),其中 n 是所有对象的数目,k 是簇的数目, t 是迭代的次数。通常 k<<n。这个算法通常局部收敛
  3. 算法尝试找出使平方误差函数值最小的k个划分。当簇是密集的、球状或团状的,且簇与簇之间区别明显时,聚类效果较好。

缺点

  1. k-平均方法只有在簇的平均值被定义的情况下才能使用,且对有些分类属性的数据不适合。
  2. 要求用户必须事先给出要生成的簇的数目 k。
  3. 对初值敏感,对于不同的初始值,可能会导致不同的聚类结果。
  4. 不适合于发现非凸面形状的簇,或者大小差别很大的簇
  5. 对于**"噪声"和孤立点数据敏感**,少量的该类数据能够对平均值产生极大影响。

代码实现

代码参考自K-Means Clustering

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time    : 2016/10/21 16:35
@Author  : cai

实现 K-Means 聚类算法
"""

import numpy as np
import matplotlib.pyplot as plt
from scipy.io import loadmat
import os

# 寻址最近的中心点
def find_closest_centroids(X, centroids):
    m = X.shape[0]
    k = centroids.shape[0]
    idx = np.zeros(m)

    for i in range(m):
        min_dist = 1000000
        for j in range(k):
            # 计算每个训练样本和中心点的距离
            dist = np.sum((X[i, :] - centroids[j, :]) ** 2)
            if dist < min_dist:
                # 记录当前最短距离和其中心的索引值
                min_dist = dist
                idx[i] = j

    return idx

# 计算聚类中心
def compute_centroids(X, idx, k):
    m, n = X.shape
    centroids = np.zeros((k, n))

    for i in range(k):
        indices = np.where(idx == i)
        # 计算下一个聚类中心,这里简单的将该类中心的所有数值求平均值作为新的类中心
        centroids[i, :] = (np.sum(X[indices, :], axis=1) / len(indices[0])).ravel()

    return centroids

# 初始化聚类中心
def init_centroids(X, k):
    m, n = X.shape
    centroids = np.zeros((k, n))
    # 随机初始化 k 个 [0,m]的整数
    idx = np.random.randint(0, m, k)

    for i in range(k):
        centroids[i, :] = X[idx[i], :]

    return centroids

# 实现 kmeans 算法
def run_k_means(X, initial_centroids, max_iters):
    m, n = X.shape
    # 聚类中心的数目
    k = initial_centroids.shape[0]
    idx = np.zeros(m)
    centroids = initial_centroids

    for i in range(max_iters):
        idx = find_closest_centroids(X, centroids)
        centroids = compute_centroids(X, idx, k)

    return idx, centroids

dataPath = os.path.join('data', 'ex7data2.mat')
data = loadmat(dataPath)
X = data['X']

initial_centroids = init_centroids(X, 3)
# print(initial_centroids)
# idx = find_closest_centroids(X, initial_centroids)
# print(idx)

# print(compute_centroids(X, idx, 3))

idx, centroids = run_k_means(X, initial_centroids, 10)
# 可视化聚类结果
cluster1 = X[np.where(idx == 0)[0], :]
cluster2 = X[np.where(idx == 1)[0], :]
cluster3 = X[np.where(idx == 2)[0], :]

fig, ax = plt.subplots(figsize=(12, 8))
ax.scatter(cluster1[:, 0], cluster1[:, 1], s=30, color='r', label='Cluster 1')
ax.scatter(cluster2[:, 0], cluster2[:, 1], s=30, color='g', label='Cluster 2')
ax.scatter(cluster3[:, 0], cluster3[:, 1], s=30, color='b', label='Cluster 3')
ax.legend()
plt.show()

# 载入一张测试图片,进行测试
imageDataPath = os.path.join('data', 'bird_small.mat')
image = loadmat(imageDataPath)
# print(image)

A = image['A']
print(A.shape)

# 对图片进行归一化
A = A / 255.

# 重新调整数组的尺寸
X = np.reshape(A, (A.shape[0] * A.shape[1], A.shape[2]))
# 随机初始化聚类中心
initial_centroids = init_centroids(X, 16)
# 运行聚类算法
idx, centroids = run_k_means(X, initial_centroids, 10)

# 得到最后一次的最近中心点
idx = find_closest_centroids(X, centroids)
# map each pixel to the centroid value
X_recovered = centroids[idx.astype(int), :]
# reshape to the original dimensions
X_recovered = np.reshape(X_recovered, (A.shape[0], A.shape[1], A.shape[2]))

# plt.imshow(X_recovered)
# plt.show()

完整代码例子和数据可以查看Kmeans练习代码


小结

这四种算法就简单介绍这么多内容,下一篇会介绍最后几种常见的算法,包括目前非常常用的深度学习网络--卷积神经网络。

如果对本文有任何建议或者想法,都可以留言给我,谢谢!


参考


欢迎关注我的微信公众号--机器学习与计算机视觉,或者扫描下方的二维码,大家一起交流,学习和进步!

往期精彩推荐

机器学习系列
Github项目 & 资源教程推荐