感知机与逻辑斯蒂回归的Python实现
实验目的
深入理解感知机与logistic回归的统计数学原理,掌握其算法过程,提高代码能力
实验准备
使用到的第三方库有:
numpy——用于记录样本数据的张量,方便运算
csv——用于导入csv格式的数据
matplotlib.pyplot、Ipython.display——用于为二维特征数据画图
下图为数据样式
实验结果
感知机
二维特征数据
样本的两个特征值和真实类别由正态分布函数随机生成
- 学习率0.0001,训练轮数2000。以下为每500轮训练后的权重变化与分类面的位置变化
最终分类结果如下,预测准确率为83.333%(误分类了一个样本)
- 如下二图,由于生成的数据具有随机性,在固定的学习率和训练轮数下,预测准确率往往不固定,可能是100%、50%、66.67%等等,依赖于样本
- 实际上,学习率和训练轮数的设置也会影响训练准确率和预测准确率,但这里只有6个样本,这个问题不是很突出,因此放在cancer数据集中讨论
cancer数据集
由于是给定的数据集,消除了随机性,这里按照试验要求将整个数据集取前4/5作为训练集,后1/5作为预测集。数据共569个样本,30个特征维度,二分类
- 学习率0.0001,训练轮数2000。以下为每500轮训练后的第一个权重和偏置的变化
the epoch: 1
w[0], b of this epoch: [-4.39230000e-03][-0.0083]
the epoch: 501
w[0], b of this epoch: [-1.12009642e+01][-4.1583]
the epoch: 1001
w[0], b of this epoch: [-1.40914858e+01][-8.3083]
the epoch: 1501
w[0], b of this epoch: [-1.37060803e+01][-12.4583]
最终的预测准确率为92.04%
- 学习率0.0001,训练轮数1000
The accuary of prediction: 82.30088495575221%
- 学习率0.0001,训练轮数1000
The accuary of prediction: 86.72566371681415%
- 学习率0.01,训练轮数2000
The accuary of prediction: 92.03539823008849%
由上可见,在这个感知机的实现中,训练轮数对固定样本的分类训练影响最大,学习率开始可能影响较大,但对最终结果影响不大
逻辑斯蒂回归
二维特征数据
这里的样本生成方法与感知机一样,不再赘述,详见代码分析一节
- 学习率0.0001,训练轮数2000。以下为每500轮训练后的权重变化与分类面的位置变化
最终分类结果如下,预测准确率为50%(误分类了三个样本)
- 如下二图,由于生成的数据具有随机性,在固定的学习率和训练轮数下,预测准确率往往不固定,可能是100%、50%、66.67%等等,依赖于样本
- 学习率0.0001,训练轮数2000
- 学习率0.00001,训练轮数2000
经测试,在固定2000轮训练下,学习率为0.0001时对数据的分类拟合较好,可以达到100%,而学习率更大或更小时,由于数据的随机性,准确率常在66.67%或83.33%,甚至50%,由此可见学习率对于模型训练也有不小影响
cancer数据集
由于是给定的数据集,消除了随机性,这里按照试验要求将整个数据集取前4/5作为训练集,后1/5作为预测集。数据共569个样本,30个特征维度,二分类
- 学习率0.001,训练轮数2000。以下为每500轮训练后的第一个权重和偏置的变化
the epoch: 1
w[0], b of this epoch: [-2.19615000e-02][-0.0415]
the epoch: 501
w[0], b of this epoch: [-1.11730504e+02][-14.50546454]
the epoch: 1001
w[0], b of this epoch: [-1.39643444e+02][-18.37636951]
the epoch: 1501
w[0], b of this epoch: [-1.55486049e+02][-20.38613226]
最终的预测准确率为91.15%
- 学习率0.0001,训练轮数2000
The accuary of prediction: 89.38053097345133%
- 学习率0.001,训练轮数1000
The accuary of prediction: 82.30088495575221%
- 学习率0.001,训练轮数3000
The accuary of prediction: 82.30088495575221%
由上可见,在逻辑斯蒂回归的实现中,学习率和训练轮数对固定样本的分类训练都有不小的影响
代码分析
感知机
import numpy as np
import csv
import matplotlib.pyplot as plt
from IPython import display
# 给二维特征数据画图
def plot2d(sample, idx_right, idx_false, w, b):
# 新建画布
display.set_matplotlib_formats('svg')
plt.rcParams['figure.figsize'] = (8, 4)
# 利用分类索引画散点,对绿错红,0.4透明度
plt.scatter(sample[idx_right, 0], sample[idx_right, 1], c='green', alpha=0.4)
plt.scatter(sample[idx_false, 0], sample[idx_false, 1], c='red', alpha=0.4)
# 利用样本数据的两个最值寻找分类线上的端点
x0min, x0max = sample[:, 0].min(), sample[:, 0].max()
x1min = -(b+x0min*w[0, 0])/w[1, 0]
x1max = -(b+x0max*w[0, 0])/w[1, 0]
# 画线
plt.plot([x0min, x0max], [x1min, x1max], 'yv-', label='line', linewidth=1)
plt.xlabel('x1') # 打标签
plt.ylabel('x2')
plt.show()
# 加载数据,数据样式参看实验准备一节
def load_data(data_path, train_rate=0.8):
with open(data_path) as f:
reader = csv.reader(f)
ans = np.array([list for list in reader])
ans = np.delete(ans, 0, axis=0) # 删表头
ans = np.delete(ans, 0, axis=1) # 删id
label = ans[:, 0].reshape(-1, 1) # 拿第二列的分类值
ans = np.delete(ans, 0, axis=1) # 删分类值
ans = ans.astype(np.float)
label[label == 'M'] = 1 # 转为数值
label[label == 'B'] = -1
label = label.astype(np.int)
rate = int(ans.shape[0]*train_rate) # 按预设比例划分数据集
return ans[0:rate, :], label[0:rate, :], ans[rate:-1, :], label[rate:-1, :]
# 计算准确率并绘制图形
def accuary(pre, label, data, w, b):
res = np.multiply(pre, label) > 0
# 对pre和label按位相乘,1*1或-1*-1或1*-1,同号结果大于零即预测成功
idx_right = np.argwhere(res.squeeze() == True)
# 取预测成功者索引,即>0为True的
idx_false = np.argwhere(res.squeeze() == False) # 取失败者索引
# 若采用二维特征数据需要画图则取消注释,高维数据使用会出错
# plot2d(data, idx_right.reshape(1, -1).squeeze(), idx_false.reshape(1, -1).squeeze(), w, b)
return len(idx_right) / len(label) * 100 # 返回准确率
# 返回(样本数*1)的1/-1预测分类矩阵
def predict(w, x, b):
res = np.sign(np.dot(x, w) + b) # 线性运算后取符号
res[res == 0] = -1 # 在分类线上者(即为0者)归为-1类
return res # 返回预测类的矩阵
# 训练,迭代参数
def train(w, x, b, y, lr, epoch):
for i in range(epoch):
print("the epoch: {}".format(i+1))
y_pre = np.dot(x, w) + b # 线性运算
res = np.multiply(y_pre, y) > 0 # 按位乘
idx_right = np.argwhere(res.squeeze() == True) # 取同号之积
temp = x.copy()
# 不能写temp = x,否则会引用传递改变x!!!!!
temp[idx_right.squeeze()] = 0
# 正确预测则不参与权值迭代,其特征值暂时归零
# 迭代参数,最小化损失函数
w += lr*np.dot(temp.transpose(), y) # lr*x*y累和
b += lr*y.sum() # lr*y
# if i % 500 == 0:
# print("w, b of this epoch: \n{}\n{}".format(w, b))
# temp_accuary = accuary(y_pre, y, x, w, b)
# print("the accuary of epoch: {}%".format(temp_accuary))
# if (temp_accuary > train_accuary) and (lr > 0.01):
# lr -= 0.01
if __name__ == '__main__':
# 加载cancer数据集
data_path = 'data.csv'
train_d, train_l, pre_d, pre_l = load_data(data_path, train_rate=0.8)
# 随机生成二维特征样本,使用该样本需要把上一行注释掉
'''train_d = np.random.randn(6, 2)
train_l = np.sign(np.random.randn(6, 1))
train_l[train_l == 0] = -1
pre_d = train_d
pre_l = train_l'''
# 初始化权重、偏置,以及超参数
w = np.zeros((train_d.shape[1], 1), dtype=float)
b = np.zeros(1, dtype=float)
lr = 0.0001 # 学习率
epoch = 2000 # 训练轮数
# 训练并预测
train(w, train_d, b, train_l, lr, epoch)
p = predict(w, pre_d, b)
print("The accuary of prediction: {}%".format(accuary(p, pre_l, pre_d, w, b)))
逻辑斯蒂回归
该实现大部分与感知机相同,重复之处不再注释
import numpy as np
import csv
import matplotlib.pyplot as plt
from IPython import display
def plot2d(sample, idx_right, idx_false, w, b):
display.set_matplotlib_formats('svg')
plt.rcParams['figure.figsize'] = (8, 4)
plt.scatter(sample[idx_right, 0], sample[idx_right, 1], c='green', alpha=0.4)
plt.scatter(sample[idx_false, 0], sample[idx_false, 1], c='red', alpha=0.4)
x0min, x0max = sample[:, 0].min(), sample[:, 0].max()
x1min = -(b+x0min*w[0, 0])/w[1, 0]
x1max = -(b+x0max*w[0, 0])/w[1, 0]
plt.plot([x0min, x0max], [x1min, x1max], 'yv-', label='line', linewidth=1)
plt.xlabel('x1')
plt.ylabel('x2')
plt.show()
def load_data(data_path, train_rate=0.8):
with open(data_path) as f:
reader = csv.reader(f)
ans = np.array([list for list in reader])
ans = np.delete(ans, 0, axis=0)
ans = np.delete(ans, 0, axis=1)
label = ans[:, 0].reshape(-1, 1)
ans = np.delete(ans, 0, axis=1)
ans = ans.astype(np.float64)
label[label == 'M'] = 1
label[label == 'B'] = 0 # 此处类值为0/1,切勿按感知机代码加载数据
label = label.astype(np.int)
rate = int(ans.shape[0] * train_rate)
return ans[0:rate, :], label[0:rate, :], ans[rate:-1, :], label[rate:-1, :]
def accuary(pre, label, data, w, b):
res = np.logical_xor(pre, label)
# 此处由于类别值为0/1,采取异或来观测分类正确与否,
# 正确则相同返回False,错误则相异返回True
idx_right = np.argwhere(res.squeeze() == False)
idx_false = np.argwhere(res.squeeze() == True)
# 要画图就取消注释
# plot2d(data, idx_right.reshape(1, -1).squeeze(), idx_false.reshape(1, -1).squeeze(), w, b)
return len(idx_right) / len(label) * 100
def predict(w, x, b):
linear = np.dot(x, w) + b
inlinear = 1.0 / (1.0 + np.exp(linear)) # logistic变换
inlinear = 1.0 - inlinear
idx = np.argwhere(inlinear.squeeze() > 0.5)
res = np.zeros(linear.shape)
res[idx] = 1
return res
def train(w, x, b, y, lr, epoch):
for i in range(epoch):
print("the epoch: {}".format(i+1))
linear = np.dot(x, w) + b
inlinear = 1.0 / (1.0 + np.exp(linear))
# print(inlinear)
inlinear = 1.0 - inlinear
# 为下面算准确率准备
idx = np.argwhere(inlinear.reshape(1, -1).squeeze() > 0.5)
res = np.zeros(inlinear.shape)
res[idx] = 1
# 迭代参数,最大化似然函数
w += lr * np.dot(x.transpose(), (y-inlinear))
b += lr * (y-inlinear).sum()
# if i % 500 == 0:
# print("w, b of this epoch: \n{}\n{}".format(w, b))
# print("the accuary: {}%".format(accuary(res, y, x, w, b)))
if __name__ == '__main__':
# 加载cancer数据集
data_path = 'data.csv'
train_d, train_l, pre_d, pre_l = load_data(data_path, train_rate=0.8)
# 随机生成二维特征样本,若使用则注释掉上一行
'''train_d = np.random.randn(6, 2)
train_l = np.sign(np.random.randn(6, 1))
train_l[train_l < 0] = 0
pre_d = train_d
pre_l = train_l'''
# 初始化权重、偏置,以及超参数
w = np.zeros((train_d.shape[1], 1), dtype=np.float)
b = np.zeros(1, dtype=np.float)
lr = 0.001
epoch = 3000
# 训练并预测
train(w, train_d, b, train_l, lr, epoch)
p = predict(w, pre_d, b)
print("The accuary of prediction: {}%".format(accuary(p, pre_l, pre_d, w, b)))
另附
卑微带学生第一次写博客,贴上了写的还算美观的一次课程作业报告,希望讨个开门红吧!
以后多多学习记录!