zoukankan      html  css  js  c++  java
  • 机器学习

    之前对线性回归和逻辑回归的理论部分做了较为详细的论述,下面通过一些例子再来巩固一下之前所学的内容。
    需要说明的是,虽然我们在线性回归中都是直接通过公式推导求出w和b的精确值,但在实际运用中基本上都会采用梯度下降法作为首选,因为用代码表示公式会比较繁琐,而梯度下降法只需要不断对参数更新公式进行迭代即可,用代码表示非常简单,且迭代次数基本上不会对性能有太大的拖累,所以下面我们也将采用梯度下降的方法。

    使用线性回归模拟数据分布

    了解数据初始分布情况

    现有一个数据集(提取码:yim9),描述了一系列点的分布情况。我们先将初始的分布情况绘制出来,便于对数据有个整体的了解:

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import LinearRegression
    
    data = np.loadtxt('./data/linear_regression_data.txt', delimiter=',')
    print(data[:10])  # 查看部分样本数据
    data.shape        # 查看样本规模
    
    # 将数据集转换成 y = w*x 的向量形式
    x = np.c_[np.ones(data.shape[0]), data[:, 0]]  # 使用np.c_转换为列向量
    y = np.c_[data[:, 1]]  # 1是给b留的位置
    
    # 展示样本分布情况
    plt.scatter(x[:, 1], y, color='green', marker='x', linewidth=1)
    plt.xlabel('X')
    plt.ylabel('Y')
    


    可以看到,点在X轴左侧分布得比较密集,但是越往右,越呈现出按照直线分布的趋势,所以可以尝试使用线性回归进行模拟。

    使用最小二乘法构造目标函数

    我们需要求出目标函数,以便求出梯度,并记录目标函数在每轮迭代中的值

    # 目标函数
    def target_func(x, y, w=[[0],[0]]):
        # 这里我们设置w和b的初始值为0
        h = x.dot(w)                                   # h表示预测值
        m = y.size                                     # 通常会使用样本大小m平均整体误差的值
        target = 1.0/(2*m) * (np.sum(np.square(h-y)))  # 使用最小二乘法得到目标函数
        return target
    

    计算梯度及参数更新公式

    # 计算梯度
    def grad(x, y, w):
        h = x.dot(w)
        m = y.size
        grad = (1.0/m)*(x.T.dot(h-y))  # 对目标函数求导即是梯度
        return grad
    
    # 使用梯度循环更新参数
    def grad_descent(x, y, nums_iter=1000, n=0.01, w=[[0],[0]]):
        # 默认迭代1000次,学习率为0.01
        target_his = np.zeros(nums_iter)    # 存放目标函数每轮迭代后的值
        for i in np.arange(nums_iter):
            # 循环更新w,并记录目标函数的值
            w = w - n*grad(x, y, w)
            target_his[i] = target_func(x, y, w)
        return w, target_his
    
    w, target_his = grad_descent(x, y)
    
    # 展示迭代效果
    plt.plot(target_his)
    plt.xlabel('Iter Times')
    plt.ylabel('Loss Function')
    


    可以看出,迭代1000次,性能逐渐趋于平稳。
    最后与sklearn的结果相对比:

    # 样本
    plt.scatter(x[:, 1], y, color='red', marker='x', linewidth=1)
    # 自主
    xx = np.arange(5, 23)
    yy = w[0] + xx*w[1]
    plt.plot(xx, yy, label='LD(GR)')
    
    # sklearn
    regr = LinearRegression()
    # reshape转换成列向量
    regr.fit(x[:, 1].reshape(-1, 1), y.ravel())
    yy2 = regr.intercept_ + regr.coef_ * xx
    plt.plot(xx, yy2, label='LD(sklearn)')
    
    plt.xlim(4, 24)
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.legend(loc=4)
    


    可以看出效果基本一致。

    使用逻辑回归分类

    现有一个考试成绩数据集(提取码:7n04),该数据集为某班级英语和数学两个科目的考试成绩,前两列分别表示英语和数学成绩,最后一列表示最终是否通过。

    加载数据,查看数据样本及可视化

    import pandas as pd
    import numpy as np
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    from scipy.optimize import minimize
    
    # Step 1. 加载数据
    def loaddata(data):
         return np.loadtxt(data, delimiter=',')
    
    data = loaddata('./data/sample_1.txt')
    # 查看数据
    print(data.shape)
    print(data[:5, :])
    
    # 运行结果:
    (100, 3)
    [[34.62365962 78.02469282  0.        ]
     [30.28671077 43.89499752  0.        ]
     [35.84740877 72.90219803  0.        ]
     [60.18259939 86.3085521   1.        ]
     [79.03273605 75.34437644  1.        ]]
    
    # Step 2. 整理训练样本
    # np.c_是按行连接两个矩阵,就是把两矩阵左右相加,要求行数相等
    X = np.c_[np.ones((data.shape[0],1)), data[:,0:2]]
    y = np.c_[data[:,2]]
    print(X[:5])
    print(X.shape)
    print(y[:5])
    
    # 运行结果:
    [[ 1.         34.62365962 78.02469282]
     [ 1.         30.28671077 43.89499752]
     [ 1.         35.84740877 72.90219803]
     [ 1.         60.18259939 86.3085521 ]
     [ 1.         79.03273605 75.34437644]]
    (100, 3)
    [[0.]
     [0.]
     [0.]
     [1.]
     [1.]]
    

    X的第一列是给偏置b预留的,其余两列为特征值。

    # Step 3. 可视化
    def plotData(data, label_x, label_y, label_pos, label_neg, axes=None):
        # 获得正负样本的下标(正样本表示考试通过的,负样本表示考试未通过)
        neg = data[:,2] == 0
        pos = data[:,2] == 1
        if axes == None:
            axes = plt.gca() # 获取图的坐标信息
    
        # 依次绘制正负样本,使用不同颜色区分
        axes.scatter(data[pos][:,0], data[pos][:,1], marker='*', c='r', s=30, linewidth=2, label=label_pos)
        axes.scatter(data[neg][:,0], data[neg][:,1], c='c', s=30, label=label_neg)
        axes.set_xlabel(label_x)
        axes.set_ylabel(label_y)
        axes.legend(frameon= True, fancybox = True);
        
    plotData(data, 'English', 'Math', 'Pass', 'Fail')
    


    从样本分布可以看出,正负样本分布的还算整齐,可以使用决策边界进行划分。

    定义Sigmoid函数、损失函数(即目标函数)、梯度函数

    # Step 4. 训练前准备
    # Sigmoid函数
    def sigmoid(z):
        return(1 / (1 + np.exp(-z)))
    
    # 损失函数
    def lossFunction(w, X, y):
        m = y.size
        h = sigmoid(X.dot(w.reshape(-1, 1)))
        # 套公式得到损失函数
        J = -1.0*(1.0/m)*(np.log(h).T.dot(y)+np.log(1-h).T.dot(1-y))
        if np.isnan(J[0]):
            return(np.inf)
        return J[0]
    
    # 梯度计算函数
    def gradient(X, y, w):
        grad = np.zeros(w.shape)
        error = (sigmoid(X.dot(w.reshape(-1, 1))).reshape(-1, 1) - y).ravel()
        for j in range(len(w.ravel())):
            # 循环更新w,得到梯度
            term = np.multiply(error, X[:,j])
            grad[j] = np.sum(term) / len(X)
        return grad
    
    # 初始化w
    initial_w = np.zeros(X.shape[1])
    loss = lossFunction(initial_w, X, y)
    grad = gradient(X, y, initial_w)
    print('Loss: 
    ', loss)
    print('Grad: 
    ', grad)
    
    # 输出结果:
    Loss: 
     [0.69314718]
    Grad: 
     [ -0.1        -12.00921659 -11.26284221]
    

    关于损失函数,代码里使用的是下面这个公式,而不是之前推导到最后的公式,因为用代码更好实现:

    使用梯度下降法训练

    # step 5. 使用梯度下降法训练
    # 梯度下降函数
    def gradientDescent(X, y, w, alpha=0.01, num_iters=1000):
        m = y.size
        J_history = np.zeros(num_iters)
        for iter in np.arange(num_iters):
            h = X.dot(w.reshape(-1, 1))
            grad = gradient(X, y, w)
            w = w - alpha*(1.0/m)*grad
            J_history[iter] = lossFunction(w, X, y)
        return(w, J_history)
    
    # 画出每一次迭代和损失函数变化
    w , Cost_J = gradientDescent(X, y, initial_w, alpha=0.15, num_iters=100000)
    
    plt.plot(Cost_J)
    plt.ylabel('Loss J')
    plt.xlabel('Iter')
    


    通过将学习率设置为0.15,并经过10W次的迭代后,损失函数计算出来的误差控制在了0.32左右。
    下面对迭代出来的w进行可视化。

    可视化决策边界

    # Step 6. 可视化
    plotData(data, 'English', 'Math', 'Pass', 'Fail')
    # np.linspace(x1_min, x1_max) 生成的是x轴英语成绩的列向量
    # np.linspace(x2_min, x2_max) 生成的是y轴数学成绩的列向量
    # xx1, xx2为meshgrid生成的坐标矩阵
    x1_min, x1_max = X[:,1].min(), X[:,1].max(),
    x2_min, x2_max = X[:,2].min(), X[:,2].max(),
    xx1, xx2 = np.meshgrid(np.linspace(x1_min, x1_max), np.linspace(x2_min, x2_max))
    # 将预测值作为等高线绘制出来,即决策边界
    # 将xx1和xx2作为x的特征值,得到预测值h
    h = sigmoid(np.c_[np.ones((xx1.ravel().shape[0],1)), xx1.ravel(), xx2.ravel()].dot(w.T))
    h = h.reshape(xx1.shape)
    plt.contour(xx1, xx2, h, 1, linewidths=1, colors='b');
    

    使用scipy.optimize.minimize实现

    scipy.optimize.minimize可以根据现有的损失函数和梯度,优化整个梯度下降的过程,可以自动选择学习率,从而免去了不断手动调试的麻烦。

    from scipy.optimize import minimize
    
    res = minimize(lossFunction, initial_w, args=(X,y), jac=gradient, options={'maxiter':400})
    print(res)
    


    传入相应的参数,并设置迭代轮数(这里仅设置了400次),就可得到优化后的w。从输出可以看到,损失函数的误差已经减小到了0.2,比之前手动实现的要小很多,而且更加省时。

    # Step 5. 可视化
    plotData(data, 'English', 'Math', 'Pass', 'Fail')
    x1_min, x1_max = X[:,1].min(), X[:,1].max(),
    x2_min, x2_max = X[:,2].min(), X[:,2].max(),
    xx1, xx2 = np.meshgrid(np.linspace(x1_min, x1_max), np.linspace(x2_min, x2_max))
    
    h = sigmoid(np.c_[np.ones((xx1.ravel().shape[0],1)), xx1.ravel(), xx2.ravel()].dot(res.x.T))
    h = h.reshape(xx1.shape)
    plt.contour(xx1, xx2, h, 1, linewidths=1, colors='b');
    


    从训练结果来看,手动和调包实现的效果好像差不多,但是明显后者更加省时省力。

  • 相关阅读:
    Maximum Depth of Binary Tree
    Single Number
    Merge Two Sorted Lists
    Remove Nth Node From End of List
    Remove Element
    Remove Duplicates from Sorted List
    Add Two Numbers
    编译视频直播点播平台EasyDSS数据排序使用Go 语言 slice 类型排序的实现介绍
    RTMP协议视频直播点播平台EasyDSS在Linux系统中以服务启动报错can’t evaluate field RootPath in type*struct排查
    【解决方案】5G时代RTMP推流服务器/互联网直播点播平台EasyDSS实现360°全景摄像机VR直播
  • 原文地址:https://www.cnblogs.com/dev-liu/p/15090590.html
Copyright © 2011-2022 走看看