【自学笔记】之Python机器学习算法(2)——利用梯度下降法训练Logistic Regression模型

PS:参考书籍《Python机器学习算法》——赵志勇  @电子工业出版社

我们从最终代码反过来看计算过程

1.首先导入模块

import numpy as np

 

2.然后下面是主训练函数

def lr_train_bgd(feature, label, maxCycle, alpha):
    '''
    利用梯度下降法训练LR模型
    :param feature: 特征矩阵(mat)
    :param label: 标签矩阵(mat)
    :param maxCycle: 最大迭代次数(int)
    :param alpha: 学习率(float)
    :return: w:权重矩阵(mat)
    '''
    n = np.shape(feature)[1]  # 特征个数(多少列)
    w = np.mat(np.ones((n, 1)))  # 初始化权重矩阵
    for i in range(maxCycle):
        h = sigmoid(feature * w)  # 计算Sigmoid值
        err = label - h  # 误差值矩阵等于标签矩阵减去Sigmoid值矩阵(这里计算的并非是误差率)
        if (i + 1) % 100 == 0:
            print("\t----------迭代次数:{},训练误差率为:{}".format(i + 1, error_rate(h, label)))
        # 权重修正,权重初始化时全部值都是0, 通过不断迭代依次修正
        w = w + alpha * feature.T * err  # 修正公式为:原权重矩阵+学习率*特征转置矩阵*误差值矩阵
    return w

        ①获取特征矩阵feature的特征个数,也就是这个矩阵的列数,赋值给n

        ②初始化权重矩阵,最开始的权重矩阵w设置成n行1列的全零矩阵,赋值给w

        ③以给定最大迭代次数来循环

                计算当前w矩阵乘以feature矩阵的结果的sigmoid值矩阵,赋值给h

                计算当前label与h的差,此为误差值矩阵,赋值给err

                每循环100次打印一次当前状态

                修正权重矩阵,修改公式为:新权重矩阵 = 原权重矩阵 + 学习率 * 特征矩阵的转置 * 误差矩阵

        ④返回最终权重矩阵

 

3.主训练函数中出现的求sigmoid值矩阵的自定义函数

def sigmoid(x):
    '''
    sigmoid函数
    :param x: 特征矩阵*权重矩阵  产生的结果矩阵(mat)
    :return: sigmoid值矩阵(mat)
    '''
    return 1 / (1 + np.exp(-x))

        数学上的sigmoid函数公式为f(x)=\frac{1}{1+e^{(-x)}},因此有此此定义函数中出现的公式。

 

4.主训练函数中出现的计算误差率的自定义函数error_rate

def error_rate(h, label):
    '''
    损失函数,计算当前的损失函数值
    :param h: 预测值矩阵(mat)
    :param label: 实际值矩阵(mat)
    :return: err/m:错误率(float)
    '''
    m = np.shape(h)[0]  # 获得预测值个数
    sum_err = 0.0  # 初始化误差和
    for i in range(m):  # 循环m次
        if h[i, 0] > 0 and (1 - h[i, 0]) > 0:  # 若h[i,0]大于0并且1-h[i,0]也大于0,其实也就是0 < h[i,0] < 1就符合要求。
            # 新的误差和等于原自身减去下面这个公式
            sum_err -= (label[i, 0] * np.log(h[i, 0]) + (1 - label[i, 0]) * np.log(1 - h[i, 0]))
        else:
            # 否则新的误差和无变化,此步其实可以省略
            sum_err -= 0
    '''
    所以上面的for循环其实可以简化成下面这样:
    for i in range(m):
        if h[i, 0] > 0 and h[i, 0] < 1:
            sum_err -= label[i, 0] * np.log(h[i, 0]) + (1 - label[i, 0]) * np.log(1 - h[i, 0])
    '''
    return sum_err / m

        ①获得预测值个数,也就是输入参数h的列数(h为m行1列的矩阵),赋值给m

        ②初始化误差和(零化)

        ③以预测值个数m作为循环次数

                如果当前循环次数i下的h[i,0]这个值大于0小于1

                        则误差和减去以下公式的值  (label[i, 0] * np.log(h[i, 0]) + (1 - label[i, 0]) * np.log(1 - h[i, 0]))

        ④返回误差和除以预测值个数m的值

 


主要难点:

求误差率函数中,在符合条件下的误差和减去的那条公式比较难懂,不过既然是给定的,可以硬记下来没关系。

另外,主训练函数中的输入参数:最大迭代次数以及学习率的确定也是很值得深思的一个问题。在此例题中,本书作者最终调用时,输入的最大迭代次数为1000次,学习率为0.01


5.导入数据函数

def load_data(filename):
    '''
    :param filename:文件名(string) 
    :return: feature:特征矩阵(mat)
              label:标签矩阵(mat)
    '''
    feature = []
    label = []
    with open(filename, "r", encoding="utf-8") as f:
        for line in f.readlines():
            feature_tmp = []
            label_tmp = []
            lines = line.strip("\t")
            feature_tmp.append(1)  # 偏置项
            for i in range(len(lines) - 1):
                feature_tmp.append(float(lines[i]))
            label_tmp.append(float(lines[-1]))
            feature.append(feature_tmp)
            label.append(label_tmp)
    return np.mat(feature), np.mat(label)

        其实说白了就是将txt文档里面的数据转换成对应的矩阵而已,输出的是特征矩阵和标签矩阵,唯一难懂点的地方就是每一行都会有一个偏置项1。

        如果是我写这个读取数据的自定义函数,我可能会这么写

import os
import pickle
def load_data(dataname, filename):
    '''
    :param dataname:已处理数据文件名(string)
    :param filename:未处理数据文件名(string)
    :return: feature:特征矩阵(mat)
              label:标签矩阵(mat)
    '''
    if os.path.exists(dataname):
        with open(dataname, "rb") as f:
            data = pickle.load(f)
        return data[:, :-1], data[:, -1]
    feature_data = []
    label_data = []
    with open(filename, "r", encoding="utf-8") as f:
        for line in f.readlines():
            feature_tmp = []
            label_tmp = []
            lines = line.strip("\t")
            feature_tmp.append(1)  # 偏置项
            for i in range(len(lines) - 1):
                feature_tmp.append(float(lines[i]))
            label_tmp.append(float(lines[-1]))
            feature_data.append(feature_tmp)
            label_data.append(label_tmp)
    feature = np.mat(feature_data)
    label = np.mat(label_data)
    data = np.hstack((feature, label))
    with open(dataname, "wb") as f:
        pickle.dump(data, f)
    return feature, label

        传入的参数有两个,一个是已处理数据的文件名,一个是未处理数据的文件名

        先是判断如果存在已处理过的数据的文件,直接读取该文件即可,就没必要每次都重复去处理这些数据了。

        如果没有,就读取未处理数据文件,进行处理后,保存成已处理数据文件,顺带输出。

        pickle模块的好处是可以实现任意数据类型的数据的本地读写,什么格式保存的,读进来就已经是原先的格式,不需要再做任何转换处理,唯一不好的地方就是,该模块用的是二进制模式存取数据,所以不支持人眼阅读。因为在我们看来,这些数据就成了乱码。

6.保存数据函数(将权重矩阵保存到本地)

def save_model(filename, w):
    '''
    :param filename:需要保存成的文件名(string)
    :param w: 需要保存的LR模型的权重矩阵(mat)
    :return:
    '''
    with open(filename, "wb") as f:
        pickle.dump(w, f)

7.主代码段

if __name__ == '__main__':
    # 1.加载训练数据
    feature, label = load_data("data.mat", "data.txt")
    # 2.训练LR模型
    w = lr_train_bgd(feature, label, 1000, 0.01)
    # 3.保存最终模型
    save_model("model.mat", w)

以上,便是基于梯度下降法训练的LR模型,下一篇文章继续编辑如何利用训练出来的模型进行数据预测。

展开阅读全文

没有更多推荐了,返回首页