KNN&多项式分类器的设计

KNN 分类器构造

一、KNN 算法的思路:

存在一个样本数据集合,称为训练样本集,且样本集中每个数据都存在标签,即样本集中每一数据与所属分类的对应关系。
输入没有标签的数据后,将新数据中的每个特征与样本集中数据对应的特征进行比较,提取出样本集中特征最相似数据(最近邻)的分类标签。选择 k 个最相似数据中出现次数最多的分类作为新数据的分类。

二、算法步骤:

  1. 计算未知实例到所有已知实例的距离;
  2. 选择参数 K;
  3. 根据多数表决( majority-voting )规则,将未知实例归类为样本中最多数的类别。

算法流程图

graph TD 输入 --> 计算测试数据与各训练数据之间的距离 --> 对距离进行排序 --> 选取距离最小的K个点-->确定前K个点的类别频率--> 将频率最高的分类作为预测分类

距离的衡量方法

欧拉距离 这种测量方式就是简单的平面几何中两点之间的直线距离。
上述方法延伸至三维或更多维的情况,总结公式为:

\[d(x, y)=\sqrt{\sum_{i=0}^{n}\left(x_{i}-y_{i}\right)^{2}} \]

曼哈顿距离 ,街区的距离

\[d(x, y)=\sum_{i=0}^{n}\left|x_{i}-y_{i}\right| \]

K 值的选择
K 值的选择会影响结果,如下图:

cs9KbV.png

图中的数据集都打好了 label ,一类是蓝色正方形,一类是红色三角形,绿色圆形是待分类的数据。
K = 3 时,范围内红色三角形多,这个待分类点属于红色三角形。
K = 5 时,范围内蓝色正方形多,这个待分类点属于蓝色正方形。
如何选择一个最佳的 K 值取决于数据。一般较大 K 值能减小噪声的影响,但会使类别之间的界限变得模糊。因此 K 的取值一般比较小 ( K < 20 )。

改进:

cs9KbV.png

在点 Y 的预测中,范围内三角形类数量占优,因此将 Y 点归为三角形。但从视觉上观测,分为圆形类更合理。根据这种情况,可以在距离测量中加入权重,如 1/d (d: 距离)。

from __future__ import print_function
import sys
import os
import math
import numpy as np
from sklearn import datasets
import matplotlib.pyplot as plt
from collections import Counter
from sklearn.datasets import make_classification


def shuffle_data(X, y, seed=None):
    if seed:
        np.random.seed(seed)
    idx = np.arange(X.shape[0])
    np.random.shuffle(idx)
    return X[idx], y[idx]


# 正规化数据集 X
def normalize(X, axis=-1, p=2):
    lp_norm = np.atleast_1d(np.linalg.norm(X, p, axis))
    lp_norm[lp_norm == 0] = 1
    return X / np.expand_dims(lp_norm, axis)


# 标准化数据集 X
def standardize(X):
    X_std = np.zeros(X.shape)
    mean = X.mean(axis=0)
    std = X.std(axis=0)
    # 分母不能等于 0 的情形
    # X_std = (X - X.mean(axis=0)) / X.std(axis=0)
    for col in range(np.shape(X)[1]):
        if std[col]:
            X_std[:, col] = (X_std[:, col] - mean[col]) / std[col]
    return X_std


# 划分数据集为训练集和测试集
def train_test_split(X, y, test_size=0.2, shuffle=True, seed=None):
    if shuffle:
        X, y = shuffle_data(X, y, seed)
    n_train_samples = int(X.shape[0] * (1 - test_size))
    x_train, x_test = X[:n_train_samples], X[n_train_samples:]
    y_train, y_test = y[:n_train_samples], y[n_train_samples:]
    return x_train, x_test, y_train, y_test


def accuracy(y, y_pred):
    y = y.reshape(y.shape[0], -1)
    y_pred = y_pred.reshape(y_pred.shape[0], -1)
    return np.sum(y == y_pred) / len(y)


class KNN():
    def __init__(self, k=5):
        self.k = k

    # 计算一个样本与训练集中所有样本的欧氏距离的平方
    def euclidean_distance(self, one_sample, X_train):
        one_sample = one_sample.reshape(1, -1)
        X_train = X_train.reshape(X_train.shape[0], -1)
        distances = np.power(np.tile(one_sample, (X_train.shape[0], 1)) - X_train, 2).sum(axis=1)
        return distances

    # 获取 k 个近邻的类别标签
    def get_k_neighbor_labels(self, distances, y_train, k):
        k_neighbor_labels = []
        for distance in np.sort(distances)[:k]:
            label = y_train[distances == distance]
            k_neighbor_labels.append(label)
        return np.array(k_neighbor_labels).reshape(-1, )

    # 进行标签统计,得票最多的标签就是该测试样本的预测标签
    def vote(self, one_sample, X_train, y_train, k):
        distances = self.euclidean_distance(one_sample, X_train)  # print(distances.shape)
        y_train = y_train.reshape(y_train.shape[0], 1)
        k_neighbor_labels = self.get_k_neighbor_labels(distances, y_train, k)  # print(k_neighbor_labels.shape)
        find_label, find_count = 0, 0
        for label, count in Counter(k_neighbor_labels).items():
            if count > find_count:
                find_count = count
                find_label = label
        return find_label

    # 对测试集进行预测
    def predict(self, X_test, X_train, y_train):
        y_pred = []
        for sample in X_test:
            label = self.vote(sample, X_train, y_train, self.k)
            y_pred.append(label)  # print(y_pred)
        return np.array(y_pred)


def main():
    data = make_classification(n_samples=200, n_features=4, n_informative=2,
                               n_redundant=2, n_repeated=0, n_classes=2)
    X, y = data[0], data[1]
    for i in range(2, 200, 3):
        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, shuffle=True)
        clf = KNN(k=i)
        y_pred = clf.predict(X_test, X_train, y_train)
        accu = accuracy(y_test, y_pred)
        print("k=", i, "\tAccuracy:", accu)


if __name__ == "__main__": main()
cs9KbV.png
csC6FU.png

三、实验结论

由上图可知,随着 KNN 算法中 k 的值增大,算法的分类精度有显著的下降。
原因:较大的k值,相当于用较大的邻域中的训练实例进行预测,使模型简化。会减少噪音的影响,但是会使类别之间的界限变模糊,降低预测准确度;造成欠拟合。

感知机模型的构造

一、感知机原理

感知机是一个二类分类的线性分类器,是支持向量机和神经网络的基础。假设数据是线性可分的,目标是通过梯度下降法,极小化损失函数,最后找到一个分割超平面,可以将数据划分成两个类别。

  • 模型流程图
csC6FU.png


import numpy as np

class Perceptron(object):
    '''
    eta:学习率
    n_iter:权重向量的训练次数
    w_:神经分叉权重向量
    error_:记录神经元判断出错次数
    '''

    def __init__(self, eta=0.01, n_iter=10):
        self.eta = eta
        self.n_iter = n_iter
        pass

    def fit(self, x, y):
        '''
        输入训练数据,训练神经元,x输入样本向量,y对应样本分类
        x:shape(n_samples,n_features)
        n_samples:样本量
        n_feature:神经元分叉数量
        例:x:[[1,2,3],[4,5,6]]
        n_samples:2
        n_feature:3
        y:[1,-1]
        '''
        '''
        初始化权重向量为0
        加一是因为将前面算法提到的w0,也就是步调函数阈值
        w_:权重向量
        error_:错误记录向量
        '''
        self.errors_ = []
        self.w_ = np.zeros(1 + x.shape[1])
        for _ in range(self.n_iter):
            errors = 0
            for xi, target in zip(x, y):
                '''
                zip(x,y):[[1,2,3,1],[4,5,6,-1]]
                '''
                update = (self.eta) * (target - self.predict(xi))
                self.w_[1:] += update * xi
                self.w_[0] += update
                '''
                ▽w(n) = update * x[n]
                '''
                errors += int(update != 0.0)
                self.errors_.append(errors)
                pass
            pass

    def net_input(self, x):
        '''
        z = w0 * 1 + w1 * x1 +... +wn * xn
        '''
        z = np.dot(x, self.w_[1:]) + self.w_[0]
        return z
        pass

    def predict(self, x):
        return np.where(self.net_input(x) >= 0.0, 1, -1)
        pass

import matplotlib.pyplot as plt
import matplotlib as mat
import pandas as pd
import numpy as np

df = pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data", header=None)
# 输出最后20行的数据,并观察数据结构 萼片长度(sepal length),萼片宽度(),
# 花瓣长度(petal length),花瓣宽度,种类
print(df.tail(n=20))
print(df.shape)

# 0到100行,第5列
y = df.iloc[0:100, 4].values
# 将target值转数字化 Iris-setosa为-1,否则值为1
y = np.where(y == "Iris-setosa", -1, 1)
# 取出0到100行,第1,第三列的值
x = df.iloc[0:100, [0, 2]].values

""" 鸢尾花散点图 """

# scatter绘制点图
plt.scatter(x[0:50, 0], x[0:50, 1], color="red", marker="o", label="setosa")
plt.scatter(x[50:100, 0], x[50:100, 1], color="blue", marker="x", label="versicolor")
# 防止中文乱码 下面分别是windows系统,mac系统解决中文乱码方案
# zhfont1 = mat.font_manager.FontProperties(fname='C:\Windows\Fonts\simsun.ttc')
# zhfont1 = mat.font_manager.FontProperties(fname='/System/Library/Fonts/PingFang.ttc')
plt.title("鸢尾花散点图")
plt.xlabel(u"萼片宽度")
plt.ylabel(u"花瓣宽度")
plt.legend(loc="upper left")
plt.show()
csC6FU.png
import matplotlib.pyplot as plt
import matplotlib as mat
import pandas as pd
import numpy as np

"""
    训练模型并且记录错误次数,观察错误次数的变化
"""
print(__doc__)
# 加载鸢尾花数据
df = pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data", header=None)

# 数据真实值
y = df.iloc[0:100, 4].values
y = np.where(y == "Iris-setosa", -1, 1)
x = df.iloc[0:100, [1, 3]].values
"""
    误差数折线图 
    @:param eta: 0.1 学习速率
    @:param n_iter:0.1 迭代次数
"""
ppn = Perceptron(eta=0.1, n_iter=10)
ppn.fit(x, y)
# plot绘制折线图
plt.plot(range(1, len(ppn.errors_) + 1), ppn.errors_, marker="o")
# 防止中文乱码
# zhfont1 = mat.font_manager.FontProperties(fname='C:\Windows\Fonts\simsun.ttc')
plt.xlabel("迭代次数(n_iter)")
plt.ylabel("错误分类次数(error_number)")
plt.show()
csC6FU.png
import pandas as pd
import matplotlib as mat

from matplotlib.colors import ListedColormap
import numpy as np
import matplotlib.pyplot as plt


def plot_decision_regions(x, y, classifier, resolution=0.2):
    """
    二维数据集决策边界可视化
    :parameter
    -----------------------------
    :param self: 将鸢尾花花萼长度、花瓣长度进行可视化及分类
    :param x: list 被分类的样本
    :param y: list 样本对应的真实分类
    :param classifier: method  分类器:感知器
    :param resolution:
    :return:
    -----------------------------
    """
    markers = ('s', 'x', 'o', '^', 'v')
    colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
    # y去重之后的种类
    listedColormap = ListedColormap(colors[:len(np.unique(y))])

    # 花萼长度最小值-1,最大值+1
    x1_min, x1_max = x[:, 0].min() - 1, x[:, 0].max() + 1
    # 花瓣长度最小值-1,最大值+1
    x2_min, x2_max = x[:, 1].min() - 1, x[:, 1].max() + 1

    # 将最大值,最小值向量生成二维数组xx1,xx2
    # np.arange(x1_min, x1_max, resolution)  最小值最大值中间,步长为resolution
    new_x1 = np.arange(x1_min, x1_max, resolution)
    new_x2 = np.arange(x2_min, x2_max, resolution)
    xx1, xx2 = np.meshgrid(new_x1, new_x2)

    # 预测值
    # z = classifier.predict([xx1, xx2])
    z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
    z = z.reshape(xx1.shape)
    plt.contourf(xx1, xx2, z, alpha=0.4)
    plt.xlim(xx1.min(), xx1.max())
    plt.ylim(xx2.min(), xx2.max())

    for idx, c1 in enumerate(np.unique(y)):
        plt.scatter(x=x[y == c1, 0], y=x[y == c1, 1], alpha=0.8, c=listedColormap(idx), marker=markers[idx], label=c1)


df = pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data", header=None)

# 0到100行,第5列
y = df.iloc[0:100, 4].values
# 将target值转数字化 Iris-setosa为-1,否则值为1,相当于激活函数-在此处表现为分段函数
y = np.where(y == "Iris-setosa", -1, 1)
# 取出0到100行,第1,第三列的值
x = df.iloc[0:100, [1, 3]].values
ppn = Perceptron(eta=0.1, n_iter=10)
ppn.fit(x, y)
plot_decision_regions(x, y, classifier=ppn)
# 防止中文乱码
# zhfont1 = mat.font_manager.FontProperties(fname='C:\Windows\Fonts\simsun.ttc')
plt.title("鸢尾花花瓣、花萼边界分割")
plt.xlabel("萼片宽度 [cm]")
plt.ylabel("花瓣宽度 [cm]")

# plt.legend(loc="uper left")
plt.show()
csC6FU.png

二、结论

使用 Iris 数据集的前 100 个记录,根据花萼宽度(第 2 列)、花瓣宽度(第 4 列)这两个特征对花进行分类,显示结果并分析能否正确分类?为什么?

根据第二列和第四列的属性值和对应的标签可视化如下,可以看到数据在二维平面上是线性可分的,因此感知机是完全可以正确分类的。

posted @ 2021-04-13 16:52  Dallas98  阅读(442)  评论(0)    收藏  举报
蜀ICP备20020397号