精通数据科学(6)第4章 线性回归:模型之母(2)

上手实践:模型实现

用pandas读取数据示例:

import pandas as pd
# 确保path等于数据路径,读取数据
data = pd.read_csv(path, delimiter=",", header=0)
# 选择列
data["x"] # 从“data”中取出列名为“x”的数据列
data[["x", "y"]] # 同时选取多个列
data.x # 从“data”中取出列名为“x”的数据列
# 选择行
# 对于行的选取,完全跟Python的列表一致
data[:10]
data[10:]
data[2:3]
  • delimiter:参数类型为字符型。在csv文件中,一行数据用一个字符串来储存。列数据与列数据之间用分隔符(delimiter)隔开。read_csv函数通过这个字符,将原始的字符串转化了一列一列的数据。
  • header:参数类型为整形或者整形列表。该参数表明,在“csv”文件里,第几行数据将被作为数据的列名。比如在第4行代码中,“header=0”表示文件里的第1行是数据的列名,而非数据本身。如果文件中没有列名,则传入“None”值,即“header=None”,这时候程序会自动根据位置,生成数字列名:0,1,2,…
  • read_csv函数将数据封装成一个DataFrame。就像使用列表(list)一样,你可以很方便地选择DataFrame中任意几列和任意几行。

本书原始代码:https://github.com/GenTang/intro_ds/tree/master/ch04-linear/simple_example

1.机器学习代码实现

模型效果

  • 小陈估计模型是 $\hat{y_i=1.012-0.628}$。也就是说他估计,玩偶的平均成本为1.012,生产的固定成本为 -0.628。估计的平均成本与真实的生产成本1相差并不大。但是估计的固定成本是一个负数,这有些违背常识。
  • 模型的均方差被估算为0.726。它表示,如果用这个模型去估计生产总成本,平均的误差为0.85($\sqrt[2]{0.726}\approx 0.85$ )。
  • 模型的决定系数等于0.828。这表示大约83%的成本变化可以由模型解释,即83%的成本是由玩偶制作过程产生的。这部分成本是可控的,与生产个数是严格的线性关系。而剩下的17%是暂时未知的随机成本。

模型实现

从整体结构来看,解决方案分为4步,如程序清单4-2所示。

(1)将数据划分为训练数据集和测试数据集。这一步是为了解决过度拟合的问题,这部分内容在这里先不做介绍,将在后续再做详细讨论。

(2)利用训练数据集训练模型,估计模型参数。

(3)利用测试数据集评价模型,计算对应的均方差和决定系数。

(4)用图形化的方式,展示模型效果。

# 程序清单4-2 线性回归
def linearModel(data):
    """
    线性回归模型建模步骤展示
    参数
    ----
    data : DataFrame,建模数据
    """
    features = ["x"]
    labels = ["y"]
    # 划分训练集和测试集,这里平分
    trainData = data[:15]
    testData = data[15:]
    # 产生并训练模型
    model = trainModel(trainData, features, labels)
    # 评价模型效果
    error, score = evaluateModel(model, testData, features, labels)
    # 图形化模型结果
    visualizeModel(model, data, features, labels, error, score)

使用第三方开源算法库scikit-learn来搭建和训练他的线性回归模型。就像程序清单4-3所展示的那样,整个过程十分简洁。除去说明文档外,其实就两行代码。

(1)首先创建所需的线性回归模型“linear_model.LinearRegression” 。

可以通过“LinearRegression”的参数“fit_intercept”(默认为True)来决定是否在模型中加入截距,即公式$\hat{y_i}=ax_i+b$中的$b$ 。比如代码“linear_model.LinearRegression(fit_intercept=False)”表示创建一个没有截距的线性回归模型。

(2)再使用训练数据估计模型参数 。model.fit函数一旦被调用,它将根据传入的数据,估计模型参数并修改对应的对象“model”。所以在示例代码里,虽然没有任何赋值,但“model”在这行代码里已经被修改了。

读者可以通过修改训练数据来验证截距项的作用。具体来讲,先分别创建有截距和没有截距的线性回归模型,并在模型的基础上进行数据平移以及数据单位转换,观察模型参数的变化。其中:

  • 数据平移对应的代码是model.fit(trainData[features], trainData[labels] + 1)
  • 数据单位转换对应的代码是model.fit(trainData[features], trainData[labels] / 100.0)
# 程序清单4-3 训练模型——scikit-learn
from sklearn import linear_model

def trainModel(trainData, features, labels):
    """
    利用训练数据,估计模型参数
    参数
    ----
    trainData : DataFrame,训练数据集,包含特征和标签
    features : 特征名列表
    labels : 标签名列表
    返回
    ----
    model : LinearRegression, 训练好的线性模型
    """
    # 创建一个线性回归模型
    model = linear_model.LinearRegression()
    # 训练模型,估计模型参数
    # model.fit函数一旦被调用,它将根据传入的数据,估计模型参数并修改对应的对象“model”。
    # 该行代码里,虽然没有任何赋值,但“model”在这行代码里已经被修改了。返回的 “model”就是用数据训练好的模型。
    model.fit(trainData[features], trainData[labels])
    return model

评价模型的代码也十分简单,如程序清单4-4所示。

(1)对象“model”有一个predict函数。它对传入的特征数据使用已经训练好的模型,以得到相应的预测。如代码中的model.predict(testData[features])

(2)对象“model”还有一个score函数。它将计算模型在传入数据集上的决定系数。如代码中的model.score(testData[features], testData[labels])

# 程序清单4-4 评价模型
def evaluateModel(model, testData, features, labels):
    """
    计算线性模型的均方差和决定系数
    参数
    ----
    model : LinearRegression, 训练完成的线性模型
    testData : DataFrame,测试数据
    features : list[str],特征名列表
    labels : list[str],标签名列表
    返回
    ----
    error : np.float64,均方差
    score : np.float64,决定系数
    """
    # 均方差(The mean squared error),均方差越小越好
    error = np.mean(
        (model.predict(testData[features]) - testData[labels]) ** 2)
    # 决定系数(Coefficient of determination),决定系数越接近1越好
    score = model.score(testData[features], testData[labels])
    return error, score

最后,使用第三方库Matplotlib将模型结果表现在图上,方便直观理解。

虽然可视化对数据科学很重要,特别是与非技术人员交流时,一图胜千言。但这部分的实现比较繁琐和机械化

完整代码(data直接写在代码中了):

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
此脚本用于展示使用sklearn搭建线性回归模型
"""


# import os
import sys

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from sklearn import linear_model


def evaluateModel(model, testData, features, labels):
    """
    计算线性模型的均方差和决定系数

    参数
    ----
    model : LinearRegression, 训练完成的线性模型

    testData : DataFrame,测试数据

    features : list[str],特征名列表

    labels : list[str],标签名列表

    返回
    ----
    error : np.float64,均方差

    score : np.float64,决定系数
    """
    # 均方差(The mean squared error),均方差越小越好
    error = np.mean(
        (model.predict(testData[features]) - testData[labels]) ** 2)
    # 决定系数(Coefficient of determination),决定系数越接近1越好
    score = model.score(testData[features], testData[labels])
    return error, score


def visualizeModel(model, data, features, labels, error, score):
    """
    模型可视化
    """
    # 为在Matplotlib中显示中文,设置特殊字体
    plt.rcParams['font.sans-serif']=['SimHei']
    plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
    # 创建一个图形框
    fig = plt.figure(figsize=(6, 6), dpi=80)
    # 在图形框里只画一幅图
    ax = fig.add_subplot(111)
    # 在Matplotlib中显示中文,需要使用unicode
    # 在Python3中,str不需要decode
    if sys.version_info[0] == 3:
        ax.set_title(u'%s' % "线性回归示例")
    else:
        ax.set_title(u'%s' % "线性回归示例".decode("utf-8"))
    ax.set_xlabel('$x$')
    ax.set_ylabel('$y$')
    # 画点图,用蓝色圆点表示原始数据
    # 在Python3中,str不需要decode
    if sys.version_info[0] == 3:
        ax.scatter(data[features], data[labels], color='b',
            label=u'%s: $y = x + \epsilon$' % "真实值")
    else:
        ax.scatter(data[features], data[labels], color='b',
            label=u'%s: $y = x + \epsilon$' % "真实值".decode("utf-8"))
    # 根据截距的正负,打印不同的标签
    if model.intercept_ > 0:
        # 画线图,用红色线条表示模型结果
        # 在Python3中,str不需要decode
        if sys.version_info[0] == 3:
            ax.plot(data[features], model.predict(data[features]), color='r',
                label=u'%s: $y = %.3fx$ + %.3f'\
                % ("预测值", model.coef_, model.intercept_))
        else:
            ax.plot(data[features], model.predict(data[features]), color='r',
                label=u'%s: $y = %.3fx$ + %.3f'\
                % ("预测值".decode("utf-8"), model.coef_, model.intercept_))
    else:
        # 在Python3中,str不需要decode
        if sys.version_info[0] == 3:
            ax.plot(data[features], model.predict(data[features]), color='r',
                label=u'%s: $y = %.3fx$ - %.3f'\
                % ("预测值", model.coef_, abs(model.intercept_)))
        else:
            ax.plot(data[features], model.predict(data[features]), color='r',
                label=u'%s: $y = %.3fx$ - %.3f'\
                % ("预测值".decode("utf-8"), model.coef_, abs(model.intercept_)))
    legend = plt.legend(shadow=True)
    legend.get_frame().set_facecolor('#6F93AE')
    # 显示均方差和决定系数
    # 在Python3中,str不需要decode
    if sys.version_info[0] == 3:
        ax.text(0.99, 0.01, 
            u'%s%.3f\n%s%.3f'\
            % ("均方差:", error, "决定系数:", score),
            style='italic', verticalalignment='bottom', horizontalalignment='right',
            transform=ax.transAxes, color='m', fontsize=13)
    else:
         ax.text(0.99, 0.01, 
            u'%s%.3f\n%s%.3f'\
            % ("均方差:".decode("utf-8"), error, "决定系数:".decode("utf-8"), score),
            style='italic', verticalalignment='bottom', horizontalalignment='right',
            transform=ax.transAxes, color='m', fontsize=13)
    # 展示上面所画的图片。图片将阻断程序的运行,直至所有的图片被关闭
    # 在Python shell里面,可以设置参数"block=False",使阻断失效。
    plt.show()


def trainModel(trainData, features, labels):
    """
    利用训练数据,估计模型参数

    参数
    ----
    trainData : DataFrame,训练数据集,包含特征和标签

    features : 特征名列表

    labels : 标签名列表

    返回
    ----
    model : LinearRegression, 训练好的线性模型
    """
    # 创建一个线性回归模型
    model = linear_model.LinearRegression()
    # 训练模型,估计模型参数
    model.fit(trainData[features], trainData[labels])
    return model


def linearModel(data):
    """
    线性回归模型建模步骤展示

    参数
    ----
    data : DataFrame,建模数据
    """
    features = ["x"]
    labels = ["y"]
    # 划分训练集和测试集
    trainData = data[:15]
    testData = data[15:]
    # 产生并训练模型
    model = trainModel(trainData, features, labels)
    # 评价模型效果
    error, score = evaluateModel(model, testData, features, labels)
    # 图形化模型结果
    visualizeModel(model, data, features, labels, error, score)


def readData(path):
    """
    使用pandas读取数据
    """
    data = pd.read_csv(path)
    return data


if __name__ == "__main__":
    # homePath = os.path.dirname(os.path.abspath(__file__))
    # # Windows下的存储路径与Linux并不相同
    # if os.name == "nt":
    #     dataPath = "%s\\data\\simple_example.csv" % homePath
    # else:
    #     dataPath = "%s/data/simple_example.csv" % homePath
    # data = readData(dataPath)
    dataStr='''10,7.7
10,9.87
11,11.18
12,10.43
13,12.36
14,14.15
15,15.73
16,16.4
17,18.86
18,16.13
19,18.21
20,18.37
21,22.61
22,19.83
23,22.67
24,22.7
25,25.16
26,25.55
27,28.21
28,28.12
'''
    x = [int(d.split(',')[0]) for d in dataStr.splitlines()]
    y = [float(d.split(',')[1]) for d in dataStr.splitlines()]
    data=pd.DataFrame({'x':x,'y':y})
    linearModel(data)

2.统计方法代码实现

不同于上一节,这里将着重展示分析数据,修改模型的过程。

© Licensed under CC BY-NC-SA 4.0

要节约用水,尽量和女友一起洗澡——加菲猫

发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!