机器学习:感知机算法(不调库,纯Python代码)

什么是感知机

公式文字我也就不复现了,网上简直多如牛毛

(1)推荐看李航博士的《统计学习方法》
(2)或:https://www.jianshu.com/p/c91087e6e1ea(篇幅略小,简单了解)

第二篇文章篇幅较小,但基本介绍清楚了感知机的原始形式,想要进一步了解感知机的对偶形式,可自行搜索资料

以上两个推荐中,都是以两特征数据来分类的,也就是数据点超平面可以在二维坐标系中呈现
于是我的代码也是如此,代码的可扩展性还是很大的,想要更高维度的数据可以自行修改
但是超过三维特征,将无法结合所有维度同时可视化

——————————————————————————————————————————————————

强烈建议仔细看书学懂了再来看代码!!!不要囫囵吞枣,之前我也是觉得自己看懂了,实际写代码就傻眼
好好想想参数怎么更新,怎么储存数据,怎么利用numpy矩阵…

  1. 全程都采用矩阵来计算,初始化函数中,参数矩阵是一个三维矩阵,其中最后一个是偏置b
  • 创建numpy数组必须声明浮点数类型,否则当学习率不为1时,会导致参数更新自动取整,进而会出现迭代无法完成的情况。
  • 包括收录数据时,也要声明为浮点类型,否则就导致数据自动变为取整型int32
    def __init__(self):
        self.learn_rate = 0.5  # 随意设置
        self.w_b = np.array([[0],
                            [0],
                            [0]], dtype='float32')  # 把三个参数放在一个矩阵中(w1, w2, b)
        self.t_data = None
        self.t_data_c = None  # 因为要三个参数一起更新,观察书上的公式,其实就是y乘x1,x2,1,然后加到参数矩阵就行了所以又建立一个y值全是1的矩阵

  1. 数据收录,每个数据最后一个是1或-1,作为分类标记,然后每个数据以一行存入矩阵
  • 理解参数如何更新后会发现,两个w1,w2是乘以y,然后+=
  • b是直接加y
  • 为了方便更新,我们建立两个train_data矩阵,第二个把y全部改为1,当是误分类点时,用于更新数据
    def collect_data(self):
        collect_1 = []
        collect_2 = []
        while True:
            try:  # 利用异常处理结束输入,别的没想到啥好办法。。
                data = map(float, input("输入数据x1 x2 y(空格隔开)\
                输入任意字母敲回车以结束:").split(' '))
                data = list(data)
                collect_1.append(copy.copy(data))  # 测试发现,下一行的更改会影响上一行,两个指向的变量地址一样,所以得copy一下
                data[2] = 1
                collect_2.append(data)
            except ValueError:
                print("数据收集完毕!")
                break
        self.t_data = np.array(collect_1, dtype='float32')
        self.t_data_c = np.array(collect_2, dtype='float32')

  1. 参数更新迭代
  • 终极目的是误分类点数为0,于是每次检测到一个误分类,便mistake += 1,直到某次更新后为0,跳出while
def gradient_descent(self):
        print("开始迭代...")
        number = 0
        while True:
            number += 1
            # 每次都统计误分类的点数,直到没有为止,跳出循环,迭代结束
            mistake = 0
            for line, line_c in zip(self.t_data, self.t_data_c):  # 一个用来判断是否误分类,一个用来更新参数矩阵
                if line[2] * np.dot(line_c, self.w_b)[0] <= 0:
                    line_c = line_c * line[2] * self.learn_rate
                    print(line_c)
                    mistake += 1
                    self.w_b[0][0] += line_c[0]
                    self.w_b[1][0] += line_c[1]
                    self.w_b[2][0] += line_c[2]
            if mistake == 0:
                break
            print('第{}次迭代\n参数w:{}'.format(number, self.w_b))
        print('-----------------')
        print("迭代完成!")
        print('本次学习率:{},迭代次数:{}'.format(self.learn_rate, number))
  1. 数据可视化
  • 这里注意,在写直线公式的时候,y(其实是x2),可能会出现系数为0的情况,而显然除数不能为0,所以这种情况要单独判断一下
if not self.w_b[1][0]:  # 测试中发现,出现了x_2的系数为0的情况,这样的话绘图时就相当于除数为0了
            x = -1 * self.w_b[2][0] / self.w_b[0][0]
            plt.axvline(x, color='g')
        else:
            x = np.linspace(-10, 10, 10)
            y = -1 * self.w_b[0][0] / self.w_b[1][0] * x + -1 * self.w_b[2][0] / self.w_b[1][0]
            plt.plot(x, y, color='g')

具体分析差不多就是这样

——————————————————————————————————————————————————

完整代码如下

import numpy as np
import copy
import matplotlib.pyplot as plt

# 创建numpy数组必须声明浮点数类型,否则当学习率不为1时,会导致参数更新自动取整,进而会出现迭代无法完成的情况
# 包括收录数据时,也要声明为浮点类型,否则就导致数据自动变为取整型int32


class Perceptron:
    def __init__(self):
        self.learn_rate = 0.5  # 随意设置
        self.w_b = np.array([[0],
                            [0],
                            [0]], dtype='float32')  # 把三个参数放在一个矩阵中(w1, w2, b)
        self.t_data = None
        self.t_data_c = None  # 因为要三个参数一起更新,观察书上的公式,其实就是y乘x1,x2,1,然后加到参数矩阵就行了所以又建立一个y值全是1的矩阵

    def collect_data(self):
        collect_1 = []
        collect_2 = []
        while True:
            try:  # 利用异常处理结束输入,别的没想到啥好办法。。
                data = map(float, input("输入数据x1 x2 y(空格隔开)\
                输入任意字母敲回车以结束:").split(' '))
                data = list(data)
                collect_1.append(copy.copy(data))  # 测试发现,下一行的更改会影响上一行,两个指向的变量地址一样,所以得copy一下
                data[2] = 1
                collect_2.append(data)
            except ValueError:
                print("数据收集完毕!")
                break
        self.t_data = np.array(collect_1, dtype='float32')
        self.t_data_c = np.array(collect_2, dtype='float32')

    def gradient_descent(self):
        print("开始迭代...")
        number = 0
        while True:
            number += 1
            # 每次都统计误分类的点数,直到没有为止,跳出循环,迭代结束
            mistake = 0
            for line, line_c in zip(self.t_data, self.t_data_c):  # 一个用来判断是否误分类,一个用来更新参数矩阵
                if line[2] * np.dot(line_c, self.w_b)[0] <= 0:
                    line_c = line_c * line[2] * self.learn_rate  # 更新方法对应着上面第二条注释
                    print(line_c)
                    mistake += 1
                    self.w_b[0][0] += line_c[0]
                    self.w_b[1][0] += line_c[1]
                    self.w_b[2][0] += line_c[2]
            if mistake == 0:
                break
            print('第{}次迭代\n参数w:{}'.format(number, self.w_b))
        print('-----------------')
        print("迭代完成!")
        print('本次学习率:{},迭代次数:{}'.format(self.learn_rate, number))

    def visualize(self):  # 以下绘图中的y并不是之前的那个y,其实是所谓的x_2。。
        plt.figure(figsize=(8, 4))
        x = 0
        y = 0
        if not self.w_b[1][0]:  # 测试中发现,出现了x_2的系数为0的情况,这样的话绘图时就相当于除数为0了
            x = -1 * self.w_b[2][0] / self.w_b[0][0]
            plt.axvline(x, color='g')
        else:
            x = np.linspace(-10, 10, 10)
            y = -1 * self.w_b[0][0] / self.w_b[1][0] * x + -1 * self.w_b[2][0] / self.w_b[1][0]
            plt.plot(x, y, color='g')
        for i in self.t_data:
            if i[2] == 1:
                plt.scatter(i[0], i[1], c='r', s=5)
            else:
                plt.scatter(i[0], i[1], c='b', s=5)
        plt.xlim(-10, 10)
        plt.ylim(-10, 10)
        plt.xlabel('x(1)')
        plt.ylabel('x(2)')
        plt.show()


p = Perceptron()
p.collect_data()
p.gradient_descent()
p.visualize()

机器学习:感知机算法(不调库,纯Python代码)机器学习:感知机算法(不调库,纯Python代码) CxsGhost 发布了30 篇原创文章 · 获赞 76 · 访问量 2220 私信 关注
上一篇:java8 功能比较强大的两个终止操作 reduce和collect


下一篇:java8-collect操作实例