机器学习代码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=w01
也就是在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+ex1

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=wawerror
这里的a就是学习率.作用是控制梯度下降的速率,△w就是根据损失函数对w求导
e r r o r = 1 2 ( w x − y ) 2 error = \frac{1}{2}(wx-y)^2 error=21(wxy)2
∂ e r r o r = w ∗ ( w x − y ) \partial{error} = w*(wx-y) error=w(wxy)
所以w的更新如下
w = w − a ∗ w ∗ ( w x − y ) w = w - a*w*(wx-y) w=waw(wxy)

在给定的训练轮数中,每一轮都对w做上述公式的处理
w = w + alpha * feature_data.T * error
这里为什么是加呢,因为在函数里面定义了error = label_data - h 也就是 y − w x y-wx ywx,而feature_data仍然是上面公式中的w,所以减号就变成了加号

error = label_data - h 注意这里的label_datah的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实现不太一样,不知道哪位大佬知道问题出在哪

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐