机器学习代码01 Logistic Regression代码实现(分别基于numpy和torch)
机器学习代码01 Logistic Regression简单介绍Logistic Regression是线性回归,但最终是用作分类器:它从样本集中学习拟合参数,将目标值拟合到[0,1]之间,然后对目标值进行离散化,实现分类。Logistic Regression虽然叫逻辑回归,但解决的问题是分类问题通常来说Logistic Regression处理的问题是二分类的问题logistic分类的流程比较
机器学习代码01 Logistic Regression
机器学习算法代码合集 (持续更新中)
简单介绍
Logistic Regression是线性回归,但最终是用作分类器:它从样本集中学习拟合参数,将目标值拟合到[0,1]之间,然后对目标值进行离散化,实现分类。
Logistic Regression虽然叫逻辑回归,但解决的问题是分类问题
通常来说 Logistic Regression处理的问题是二分类
的问题
logistic分类的流程比较简单
- 线性求和
- sigmoid函数激活
- 计算误差
- 修正参数
问题介绍
下图给出的是部分数据集,前两列代表特征,第三列代表标签
可以看到这个是个二分类问题,标签的值是0或1
由于事先已经划分好了train_data.txt和test_data.txt.所以我们可以省略划分数据集的步骤
下面对于train_data.txt,我们就一步一步的利用Logistic Regression来完成对它的训练吧
python实现
这里分为2步骤,第一步是我们基于python的numpy来实现,第二步骤是使用pytorch来实现
Numpy实现
因为没有封装好的模型,所以我们需要自己一步一步的完善它,首先对于Logistic Regression模型,我们需要实现以下几个功能
我从训练和测试两个角度来思考,
- train.py
- 训练集的导入
- 训练集的训练
- 训练结果的保存 (各个训练完最优的参数)
- 其他功能(比如计算损失,正确率,sigmoid函数的实现等等)
- test.py
- 测试集的导入
- 测试集的预测
- 保存预测结果
接下来就一步一步的实现吧
训练模型train.py
训练集导入
因为给出的数据集已经事先划分好了,所以我们省去了数据集划分的阶段,直接读取数据集即可
下面的函数返回的是两个mat
也就是矩阵,一个矩阵存放的是我们要训练的特征,一个矩阵存放的是特征对应的标签
feature_tmp.append(1)
这一步是添加偏置量,我会在下文中详细的介绍原因,为什么要添加个1
np.mat()
是将原来的列表转换为矩阵matrix,因为在np.mat中重载了乘法*
代表了矩阵的乘法
其他的代码没什么好说的,就是将train.txt中的数据分别存放到特征列表和标签列表中
def load_data(file_name):
'''导入训练数据
input: file_name(string)训练数据的位置
output: feature_data(mat)特征
label_data(mat)标签
'''
f = open(file_name) # 打开文件
feature_data = []
label_data = []
for line in f.readlines():
feature_tmp = []
lable_tmp = []
lines = line.strip().split("\t")
feature_tmp.append(1) # 偏置项
for i in range(len(lines) - 1):
feature_tmp.append(float(lines[i]))
lable_tmp.append(float(lines[-1]))
feature_data.append(feature_tmp)
label_data.append(lable_tmp)
f.close() # 关闭文件
return np.mat(feature_data), np.mat(label_data)
看看它运行结束后的效果
这里的1就是偏置量b 后面的数据就是特征(因为特征不止一个,所以是个向量的形式)
可以看出每一个特征都是一个1*3的向量
对于为什么要把偏置放在第一个位置,我稍微做一下解释
对于线性回归的公式,我们一种写法是
y
=
w
x
+
b
y = wx+b
y=wx+b
其实我们知道这里的w和x都是向量,而不是一个值,展开来写其实是
y
=
w
1
x
1
+
w
2
x
2
+
.
.
.
+
w
n
x
n
y=w_1x_1+w_2x_2+...+w_nx_n
y=w1x1+w2x2+...+wnxn
这里的x和w分别是
x
T
=
(
x
1
,
x
2
,
.
.
.
x
n
)
x^T=(x_1,x_2,...x_n)
xT=(x1,x2,...xn)
w
=
(
w
1
,
w
2
,
.
.
.
w
n
)
w=(w_1,w_2,...w_n)
w=(w1,w2,...wn)
其实我们可以把b放到向量中来,把b
看成
b
=
w
0
∗
1
b=w_0*1
b=w0∗1
也就是在x向量前面加个1,w向量前面加个
w
0
w_0
w0
x
T
=
(
1
,
x
1
,
x
2
,
.
.
.
x
n
)
x^T=(1,x_1,x_2,...x_n)
xT=(1,x1,x2,...xn)
w
=
(
w
0
,
w
1
,
w
2
,
.
.
.
w
n
)
w=(w_0,w_1,w_2,...w_n)
w=(w0,w1,w2,...wn)
所以这就是我们这里为什么要给所有的特征向量前面添加一个1的原因
训练集的训练
这一步其实是整个模型里面最为重要的过程,我选择使用梯度下降算法
在这里我们需要实现三个步骤
- 损失值的计算
- 梯度下降算法的实现
- 定义sigmoid函数
因为梯度下降算法通过一轮轮的训练来更新权重,我们需要通过损失值来观察它训练的效果,我们可以通过如下的手段来结束训练 - 事先确定训练的轮数
- 事先给定损失值的接受范围
在这里我使用给定训练轮数的方法,下面我们就开始实践吧
定义sigmoid函数
这步没什么好说的,简化出一个sigmoid函数出来
y
=
1
1
+
e
−
x
y=\frac{1}{1+e^{-x}}
y=1+e−x1
def sig(x):
''' Sigmoid函数
input: x(mat): feature * w
output: sigmoid(x)(mat): Sigmoid值
'''
return 1.0 / (1 + np.exp(-x))
计算损失值
每一个损失值的就是如下图
这里简单的介绍一下为什么要用这个损失
因为y的值要么是0要么是1
因为我们的预测值要越接近真实值越好,也就是
y
^
\hat{y}
y^越接近
y
y
y越好
- y = 1 时,公式只剩下 − y l o g y ^ -ylog\hat{y} −ylogy^ 因为log 对数函数在(0,1)取值为符号,为了使得我们的损失为正,所以在最前面加一个负号,而对数函数很显然在x接近1的时候它的值接近1,所以这里很显然 y ^ \hat{y} y^越接近1,也就是y的值,损失的结果越小
- y=0同理
下面的做法就是将它们损失求和并计算均值
因为预测值和标签值的shape都是m*1
,以对每个预测值我们都要计算它的损失,最后求和,所以需要经过m次循环
def error_rate(h, label_data):
'''计算当前损失函数值
input: h(mat): 预测值
label_data: 实际值
output: err/m(float): 错误率
'''
m = np.shape(h)[0] # 取出预测值的行数
sum_error = 0.0
for i in range(m): # 对每个标签的预测值都计算
if h[i, 0] > 0 and (1 - h[i, 0]) > 0:
sum_error -= (label_data[i, 0] * np.log(h[i, 0]) + \
(1 - label_data[i, 0]) * np.log(1 - h[i, 0]))
else:
sum_error -= 0
return sum_error / m
ps:因为sigmoid函数的范围就是在(0,1),所以我不知道为什么还要加一层if else,不过是老师给的代码,就先放在那边了,也没什么影响,有知道的小伙伴欢迎评论
梯度下降算法的实现
下面就是正题了
梯度下降算法分为下面几步
- 初始化权重w
- 进行若干轮训练
- 计算预测值
- 使用激活函数将预测值压缩到(0,1)中
- 计算损失值:这里采用了每隔100轮输出一次损失的方法
- 更新权重
- 保存训练结果
下面我将一一结合代码来做介绍
首先n的作用就的记录下特征的个数,如下图,我们本次案例特征的个数为3
所以就随机初始化三个w的值,这里选择的初始值是1
在每一次训练的过程中都要更新w的值
w
=
w
−
a
∂
e
r
r
o
r
∂
w
w = w - a\frac{\partial{error}}{\partial{w}}
w=w−a∂w∂error
这里的a就是学习率.作用是控制梯度下降的速率,△w就是根据损失函数对w求导
e
r
r
o
r
=
1
2
(
w
x
−
y
)
2
error = \frac{1}{2}(wx-y)^2
error=21(wx−y)2
∂
e
r
r
o
r
=
w
∗
(
w
x
−
y
)
\partial{error} = w*(wx-y)
∂error=w∗(wx−y)
所以w的更新如下
w
=
w
−
a
∗
w
∗
(
w
x
−
y
)
w = w - a*w*(wx-y)
w=w−a∗w∗(wx−y)
在给定的训练轮数中,每一轮都对w做上述公式的处理
w = w + alpha * feature_data.T * error
这里为什么是加呢,因为在函数里面定义了error = label_data - h
也就是
y
−
w
x
y-wx
y−wx,而feature_data
仍然是上面公式中的w,所以减号就变成了加号
error = label_data - h
注意这里的label_data
和h
的shape都是m*1
的
而feature_data
的shape也是m*3
的,所以为了更新这里的n的特征,我们在最后一步使用了feature_data.T
,这样得出的结果就是一个3*1
的向量了,刚好对应了我们w
的大小
上面的3就是代码中的 n,为了讲解的方便,我就直接用本题的案例来说了
def lr_train_bgd(feature_data, label_data, epoch, alpha):
'''利用梯度下降法训练LR模型
input: feature_data(mat)样例数据
label_data(mat)标签数据
epoch(int)训练的轮数
alpha(float)学习率
output: w(mat):权重
'''
n = np.shape(feature_data)[1] # 特征个数
w = np.mat(np.ones((n, 1))) # 初始化权重
i = 0
while i <= epoch:
i += 1
h = sig(feature_data * w)
error = label_data - h
if i % 100 == 0:
print("\t-----iter = " + str(i) + \
", train error rate = " + str(error_rate(h, label_data)))
w = w + alpha * feature_data.T * error # 权值修正 batch gradient descent
return w
这里我存了点疑惑,如果在训练的过程中发生过拟合该怎么办,是不是应该根据损失的最小值来记录最优的w
下面可以看看效果
这就是我们训练出来的w值
还差最后一步,保存权重
可以随便看看训练过程中损失的变化,只要加个简单的plot就好了,如下图
训练结果的保存
这一步没什么好说的,就是把刚刚计算下来的w保存到文件里,方便到时候test的时候直接取
def save_model(file_name, w):
m = np.shape(w)[0]
f_w = open(file_name, "w")
w_array = []
for i in range(m):
w_array.append(str(w[i, 0]))
f_w.write("\t".join(w_array))
f_w.close()
全代码
'''
Data: 20190327
@Author: Jie Hu
'''
import numpy as np
def load_data(file_name):
'''导入训练数据
input: file_name(string)训练数据的位置
output: feature_data(mat)特征
label_data(mat)标签
'''
f = open(file_name) # 打开文件
feature_data = []
label_data = []
for line in f.readlines():
feature_temp = []
label_temp = []
lines = line.strip().split('\t')
feature_temp.append(1)
for i in range(len(lines) - 1):
feature_temp.append(float(lines[i]))
label_temp.append(float(lines[-1]))
feature_data.append(feature_temp)
label_data.append(label_temp)
f.close()
return np.mat(feature_data), np.mat(label_data)
def sig(x):
''' Sigmoid函数
input: x(mat): feature * w
output: sigmoid(x)(mat): Sigmoid值
'''
return 1.0 / (1 + np.exp(-x))
def error_rate(h, label_data):
'''计算当前损失函数值
input: h(mat): 预测值
label_data: 实际值
output: err/m(float): 错误率
'''
m = np.shape(h)[0]
sum_error = 0.0
for i in range(m):
if h[i, 0] > 0 and (1 - h[i, 0]) > 0:
sum_error -= (label_data[i, 0] * np.log(h[i, 0]) + \
(1 - label_data[i, 0]) * np.log(1 - h[i, 0]))
else:
sum_error -= 0
return sum_error / m
def lr_train_bgd(feature_data, label_data, epoch, alpha):
'''利用梯度下降法训练LR模型
input: feature_data(mat)样例数据
label_data(mat)标签数据
epoch(int)最大迭代次数
alpha(float)学习率
output: w(mat):权重
'''
n = np.shape(feature_data)[1] # 特征个数
w = np.mat(np.ones((n, 1))) # 初始化权重
i = 0
while i <= epoch:
i += 1
h = sig(feature_data * w)
error = label_data - h
if i % 100 == 0:
print("\t-----iter = " + str(i) + \
", train error rate = " + str(error_rate(h, label_data)))
w = w + alpha * feature_data.T * error # 权值修正 batch gradient descent
return w
def save_model(file_name, w):
m = np.shape(w)[0]
f_w = open(file_name, "w")
w_array = []
for i in range(m):
w_array.append(str(w[i, 0]))
f_w.write("\t".join(w_array))
f_w.close()
if __name__ == "__main__":
# 1、导入训练数据
print("---------- 1.load data ------------")
feature_data, label_data = load_data("train_data.txt")
# 2、训练LR模型
print("---------- 2.training ------------")
w = lr_train_bgd(feature_data, label_data, 10000, 0.001)
# 3、保存最终的模型
print("---------- 3.save model ------------")
save_model("weights", w)
测试模型test.py
测试集的导入
没什么好说的,就是把刚刚训练得到的w读一下,然后返回一个w的矩阵
def load_weight(w):
'''导入LR模型
input: w(string)权重所在的文件位置
output: np.mat(w)(mat)权重的矩阵
'''
f = open(w)
w = []
for line in f.readlines():
lines = line.strip().split("\t")
w_tmp = []
for x in lines:
w_tmp.append(float(x))
w.append(w_tmp)
f.close()
return np.mat(w)
这里就是把要测试的数据导入,跟也没什么好说的,测试的数据只有标签值
def load_data(file_name, n):
'''导入测试数据
input: file_name(string)测试集的位置
n(int)特征的个数
output: np.mat(feature_data)(mat)测试集的特征
'''
f = open(file_name)
feature_data = []
for line in f.readlines():
feature_tmp = []
lines = line.strip().split("\t")
if len(lines) != n - 1:
continue
feature_tmp.append(1)
for x in lines:
feature_tmp.append(float(x))
feature_data.append(feature_tmp)
f.close()
return np.mat(feature_data)
测试集的预测
这里也很简单。通过sigmoid函数激活.小于0.5的就分类为0,否则就分类为1
注:在机器学习里一般中间值,在本题中反应为0.5都归为正类,也就是本题的1
def predict(data, w):
'''对测试数据进行预测
input: data(mat)测试数据的特征
w(mat)模型的参数
output: h(mat)最终的预测结果
'''
h = sig(data * w.T) # sig
m = np.shape(h)[0]
for i in range(m):
if h[i, 0] < 0.5:
h[i, 0] = 0.0
else:
h[i, 0] = 1.0
return h
保存预测结果
就是把刚刚预测的结果保存下来
def save_result(file_name, result):
'''保存最终的预测结果
input: file_name(string):预测结果保存的文件名
result(mat):预测的结果
'''
m = np.shape(result)[0]
# 输出预测结果到文件
tmp = []
for i in range(m):
tmp.append(str(result[i, 0]))
f_result = open(file_name, "w")
f_result.write("\t".join(tmp))
f_result.close()
全代码
'''
Date:20180418
@author: zhaozhiyong
'''
import numpy as np
from lr_train_jay import sig
def load_weight(w):
'''导入LR模型
input: w(string)权重所在的文件位置
output: np.mat(w)(mat)权重的矩阵
'''
f = open(w)
w = []
for line in f.readlines():
lines = line.strip().split("\t")
w_tmp = []
for x in lines:
w_tmp.append(float(x))
w.append(w_tmp)
f.close()
return np.mat(w)
def load_data(file_name, n):
'''导入测试数据
input: file_name(string)测试集的位置
n(int)特征的个数
output: np.mat(feature_data)(mat)测试集的特征
'''
f = open(file_name)
feature_data = []
for line in f.readlines():
feature_tmp = []
lines = line.strip().split("\t")
if len(lines) != n - 1:
continue
feature_tmp.append(1)
for x in lines:
feature_tmp.append(float(x))
feature_data.append(feature_tmp)
f.close()
return np.mat(feature_data)
def predict(data, w):
'''对测试数据进行预测
input: data(mat)测试数据的特征
w(mat)模型的参数
output: h(mat)最终的预测结果
'''
h = sig(data * w.T) # sig
m = np.shape(h)[0]
for i in range(m):
if h[i, 0] < 0.5:
h[i, 0] = 0.0
else:
h[i, 0] = 1.0
return h
def save_result(file_name, result):
'''保存最终的预测结果
input: file_name(string):预测结果保存的文件名
result(mat):预测的结果
'''
m = np.shape(result)[0]
# 输出预测结果到文件
tmp = []
for i in range(m):
tmp.append(str(result[i, 0]))
f_result = open(file_name, "w")
f_result.write("\t".join(tmp))
f_result.close()
if __name__ == "__main__":
# 1、导入LR模型
print("---------- 1.load model ------------")
w = load_weight("weights")
n = np.shape(w)[1]
# 2、导入测试数据
print("---------- 2.load data ------------")
testData = load_data("test_data.txt", n)
# 3、对测试数据进行预测
print("---------- 3.get prediction ------------")
h = predict(testData, w) # 进行预测
# 4、保存最终的预测结果
print("---------- 4.save prediction ------------")
save_result("result", h)
Pytorch实现
- 1.准备数据集
- 2.设计模型 (用来计算 y ^ \hat{y} y^)
- 3.构造损失函数和优化器
- 4.训练周期
'''
Date:2020/7/17
@author: Tong Tianyu
'''
import torch
import matplotlib.pyplot as plt
'''
1.准备数据集
2.设计模型 (用来计算 y^)
3.构造损失函数和优化器
4.训练周期
'''
def load_data(file_name):
'''导入训练数据
input: file_name(string)训练数据的位置
output: feature_data(mat)特征
label_data(mat)标签
'''
f = open(file_name) # 打开文件
feature_data = []
label_data = []
for line in f.readlines():
feature_temp = []
label_temp = []
lines = line.strip().split('\t')
for i in range(len(lines) - 1):
feature_temp.append(float(lines[i]))
label_temp.append(float(lines[-1]))
feature_data.append(feature_temp)
label_data.append(label_temp)
f.close()
return torch.tensor(feature_data), torch.tensor(label_data)
# 数据集 加载
x_data, y_data = load_data('train_data.txt')
'''
构造计算图
'''
class LogisticRegressionModel(torch.nn.Module):
def __init__(self):
super(LogisticRegressionModel, self).__init__() # 必须有 第一个参数是你构造的类的名称
self.linear = torch.nn.Linear(2, 1, bias=True)
def forward(self, x): # 实现前绘
y_pred = torch.sigmoid(self.linear(x)) # 实现sigmoid函数
return y_pred
model = LogisticRegressionModel()
# 构造损失函数和优化器
criterion = torch.nn.BCELoss(reduction='mean')
optimizer = torch.optim.SGD(model.parameters(), lr=0.001)
print(*model.parameters())
# 训练周期
epoch_li = [] # 记录轮数
loss_li = [] # 记录损失值
for epoch in range(10000):
y_pred = model(x_data) #预测值
loss = criterion(y_pred, y_data) #计算损失
epoch_li.append(epoch)
loss_li.append(loss)
if (epoch + 1) % 100 == 0:
print('epoch=', epoch + 1, loss.item())
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(model.state_dict())
plt.plot(epoch_li, loss_li)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()
训练的结果跟numpy实现不太一样,不知道哪位大佬知道问题出在哪
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)