周志华机器学习P69 3.3 使用对率回归对3.0α西瓜数据集实现分类

周志华机器学习P69 3.3 使用对率回归对3.0α西瓜数据集实现分类

数据集的加载

编号,密度,含糖率,好瓜
1,0.697,0.46,1
2,0.774,0.376,1
3,0.634,0.264,1
4,0.608,0.318,1
5,0.556,0.215,1
6,0.403,0.237,1
7,0.481,0.149,1
8,0.437,0.211,1
9,0.666,0.091,0
10,0.243,0.0267,0
11,0.245,0.057,0
12,0.343,0.099,0
13,0.639,0.161,0
14,0.657,0.198,0
15,0.36,0.37,0
16,0.593,0.042,0
17,0.719,0.103,0

首先我们观察数据集,发现好瓜一般都是含糖率高的,而坏瓜一般含糖率不高~~,所以我们可以直接设置一个阈值,小于此值的都是坏瓜反之好瓜就行了~~

仿照pytorch的DataLoader风格,我仿写了一个相似的DataLoader

import pandas as pd

class DataLoader:
    def __init__(self, csv: str or pd.DataFrame, x_label: list, y_label: list) -> None:
        if type(csv) == str:
            csv = pd.read_csv(csv)
        elif type(csv) == pd.DataFrame:
            pass
        self.xs = csv[x_label].values           # 含有所有样本特征值的特征矩阵
        self.ys = csv[y_label].values           # 含有所有样本真实标签的标签矩阵

    def __len__(self):
        return len(self.xs)                     # 返回数据集的长度
    
    def __getitem__(self, idx):
        return self.xs[idx], self.ys[idx]       # 这个方法用于从外部获取数据集中的数据

这样我们可以每一次取一个数据,然后把它喂到我们的模型里。

if __name__ == '__main__':
    data = DataLoader(pd.read_csv('./3.0a.csv'), ['密度', '含糖率'], ['好瓜'])
    for x, label in data:
        print(x, label)
[0.697 0.46 ] [1]
[0.774 0.376] [1]
[0.634 0.264] [1]
[0.608 0.318] [1]
[0.556 0.215] [1]
[0.403 0.237] [1]
[0.481 0.149] [1]
[0.437 0.211] [1]
[0.666 0.091] [0]
[0.243  0.0267] [0]
[0.245 0.057] [0]
[0.343 0.099] [0]
[0.639 0.161] [0]
[0.657 0.198] [0]
[0.36 0.37] [0]
[0.593 0.042] [0]
[0.719 0.103] [0]

模型

按照西瓜书上的定义,我们的模型定义为 y = 1 1 + e − ( w T x + b ) y=\frac{1}{1+e^{-(w^Tx + b)}} y=1+e(wTx+b)1
当y值大于0.5时,结果为正类,结果小于0.5时为负类
这里专门定义了一个类用于储存模型以及参数,对应下面代码里的forward函数
需要注意的是,在实际编写程序的时候,没有必要专门定义一个b变量用于储存,只需把b添加在w的末尾即可,然后在x最后添加一列1,这样
算出来的 x T w x^Tw xTw就是 w T x + b w^Tx+b wTx+b

from typing import Any
import numpy as np

class logit_regrassion:
    def __init__(self, alpha=1) -> None:
        self.w = np.array([0, 0, 1])        # 两个特征w加一个b
        self.alpha = alpha
        

    def forward(self, x):                   # 推导,即上面那个模型的公式
        x = np.append(x, 1)                 # 添加一个1以计算b
        y = 1 / (1 + np.power(np.e, -(self.w.T @ x)))
        # print(self.w)
        if y >= 0.5:
            return 1
        else:
            return 0
    
    def backward(self, x: np.ndarray, label):
        x = np.append(x, 1)     # 添加一个1以计算b
        print(self.w)
        if label == 1:
            self.w = (self.w.T + self.alpha * x * (np.power(np.e, -(self.w.T @ x)) / np.power(np.power(np.e, -(self.w.T @ x)) + 1, 2)))
        else:
            self.w = (self.w.T - self.alpha * x * (np.power(np.e, -(self.w.T @ x)) / np.power(np.power(np.e, -(self.w.T @ x)) + 1, 2)))

    def get_w(self):
        return self.w

要使得我们的推导函数尽可能的正确的分类,我们要像线性回归那样去定义一个目标函数,不过这里不能像线性回归那样直接使用最小二乘法,
因为这样定义出来的目标函数对w求偏导时并不是一个凸函数,所以不能用最小二乘法,书上用的是极大似然估计,所以我们这里用的也是极大似然估计法,
L = y 1 1 + e ( w T x ) + ( 1 − y ) ( 1 − 1 1 + e ( w T x ) ) L=y\frac{1}{1+e^(w^Tx)} + (1-y)(1-\frac{1}{1+e^(w^Tx)}) L=y1+e(wTx)1+(1y)(11+e(wTx)1)
(这里省略了b,因为把b放进w里了)
用样本的真实值乘以推导出来是此样本的概率最后求和,即为极大似然估计法的公式
然后我们对这个函数求w的偏导即可得到这个函数的梯度,然后按照梯度的正方向去更新迭代参数即可,因为这里我们要求目标函数的最大值,故和线性回归更新参数的
方向相反,是要让函数的参数上升到最大值点,而不是下降,求导的过程我就不写了,相信大家都会求吧。因为这里只有两类,所以我就分情况讨论了。
对应上面的backward函数

训练

首先把以上我们写好的两个类引入,还有一些需要用到的包(这里每个类我都新建了一个文件)

from dataloader import DataLoader
from net import logit_regrassion
import pandas as pd
from tqdm import tqdm
import matplotlib.pyplot as plt
import numpy as np

然后这里用到了留一法进行训练,因为数据量较小,每一次取其中的一个样本为验证样本,其余为训练样本

# 当数据量很小的时候我们使用留一法 每一次取一个样本作为测试样本 其它为训练样本
raw_data = pd.read_csv('./3.0a.csv')
indexs = list(range(len(raw_data) - 1))         
indexs = [x + 1 for x in indexs]        # [0, 1, 2, 3 ... 16]
x_label = ['密度', '含糖率']            # 特征值对应csv文件里的列名
y_label = ['好瓜']                      # 标签对应的列明
net = logit_regrassion()                # 网络的构建
epoch = 100                             # 循环的次数
total_num = 0                           # 这两个参数用于统计准确率
right_num = 0

然后就是训练,每一个小epoch中先用训练样本对目标函数进行参数更新,然后剩下的一个样本用于测试,
这里还用到了进度条库tqdm 对于这个库的用法大家可以百度

with tqdm(total=epoch * 16) as pbar:
    for i in range(epoch):
        for index in indexs:
            train_data = pd.concat([raw_data.loc[:index - 2], raw_data.loc[index:]])
            val_data = pd.DataFrame(raw_data.loc[index - 1]).T
            # 训练
            for train_x, label in DataLoader(train_data, x_label, y_label):
                net.backward(train_x, label)

            # 测试
            for val_x, label in DataLoader(val_data, x_label, y_label):
                # print(net.forward(val_x), label[0])
                if net.forward(val_x) == label:
                    right_num += 1
                total_num += 1

            pbar.update(1)

最后打印准确率并保存参数

print(right_num / total_num)

# 得到参数并写入
w = net.get_w()
np.save('w', w)

测试

画图以显示结果

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import mpl_toolkits.axisartist as axisartist #导入坐标轴加工模块
import math

w = np.load('w.npy')
raw_data = pd.read_csv('./3.0a.csv')
positive_x = np.append(raw_data[raw_data['好瓜'] == 1][['密度', '含糖率']].values, np.ones(shape=(raw_data[raw_data['好瓜'] == 1][['密度', '含糖率']].values.shape[0], 1)), axis=1)
nagetive_x = np.append(raw_data[raw_data['好瓜'] == 0][['密度', '含糖率']].values, np.ones(shape=(raw_data[raw_data['好瓜'] == 0][['密度', '含糖率']].values.shape[0], 1)), axis=1)
positive_z = positive_x @ w
nagetive_z = nagetive_x @ w
print(positive_z, nagetive_z)

# 
plt.rcParams['font.sans-serif'] = 'SimHei' ## 设置中文显示
plt.rcParams['axes.unicode_minus'] = False
fig=plt.figure(figsize=(4,2)) #新建画布
ax=axisartist.Subplot(fig,111) #使用axisartist.Subplot方法创建一个绘图区对象ax
fig.add_axes(ax) #将绘图区对象添加到画布中
 
ax.axis[:].set_visible(False) #隐藏原来的实线矩形
 
ax.axis["x"]=ax.new_floating_axis(0,0,axis_direction="bottom") #添加x轴
ax.axis["y"]=ax.new_floating_axis(1,0,axis_direction="bottom") #添加y轴
 
ax.axis["x"].set_axisline_style("->",size=1.0) #给x坐标轴加箭头
ax.axis["y"].set_axisline_style("->",size=1.0) #给y坐标轴加箭头
ax.annotate(s='z' ,xy=(2*math.pi,0) ,xytext=(2*math.pi,0.1)) #标注x轴
ax.annotate(s='y' ,xy=(0,1.0) ,xytext=(-0.5,1.0)) #标注y轴
 
plt.xlim(-15, 15) #设置横坐标范围
plt.ylim(0, 2) #设置纵坐标范围
ax.set_xticks(range(-15, 16, 1)) #设置x轴刻度
ax.set_yticks([-1,1]) #设置y轴刻度

# sigmoid 函数 
sigmoid_x = np.linspace(-15, 15, 1000)
sigmoid_y = 1 / (1 + np.power(np.e, -sigmoid_x))
plt.plot(sigmoid_x, sigmoid_y, color="black") #描点连线

# z = wx
positive_dot = plt.scatter(positive_z, 1 / (1 + np.power(np.e, -positive_z)), c='red', label='正样本')
nagetive_dot = plt.scatter(nagetive_z, 1 / (1 + np.power(np.e, -nagetive_z)), c='blue', label='负样本')

# 
plt.legend(handles=[positive_dot, nagetive_dot])
plt.show() #出图

piPBzE4.md.png
完整代码

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值