线性回归(Liner Regression) —— 蕴含机器学习基本思想的入门级模型

浏览: 589

作者:张磊 机器学习爱好者  

知乎:https://zhuanlan.zhihu.com/c_184412713

个人网站:novasky.top  

GitHub:https://github.com/zlxy9892 



1 原理

1.1 引入

线性回归是最为常用的一种数据分析手段,通常我们拿到一组数据后,都会先看一看数据中各特征之间是否存在明显的线性关系。例如,现在我们拿到了一组学校中所有学生基本资料的数据,该数据以二维表格的形式呈现,如下表所示。

每行代表一个学生,每列代表该学生的一个属性(或称为特征),那么如果我们对特征进行仔细观察,不难发现身高和年龄总是呈现正相关关系,数学成绩与物理成绩也基本呈现正相关关系。那么我们是否可以给这样的两个特征之间拟合出一条近似的直线来表达他们之间的线性函数关系呢?这里我们的想法其实就是机器学习的世界观:数据驱动构建模型。

1.2 模型

只不过这里的模型非常简单,只是线性模型,也就是一条直线方程,通长我们可以表达成如下公式:

这里,  是代表了预测值,数据中我们将某一特征列作为自变量 x (例如身高),因变量 y (如体重)也就是我们想要预测的值, x 和 y 都已知,现在的任务就是:加入新增了一个 x ,而其对应的 y 未知,那么我们该如何预测出一个 ?显然,我们需要构建 y 与 x 之间的函数关系:

对于身高体重这样的简单问题而言,就可以直接使用上述的线性方程作为我们想要拟合的模型。

接下来的问题就是,如何拟合这个模型,也就是说,如何求得线性模型中的两个参数  w 和 b ?

1.3 损失函数

要求解最佳的参数,首先我们需要让计算机知道一个目标,毕竟解决任何问题都需要确立一个明确的目标才行,对于计算机这样的数字世界,我们就需要给它确定一个定量化的目标函数式,在优化问题中,我们通常称之为目标函数,或者损失函数(Loss function)。无论我们选择什么样的模型,最终都是可以得到一组预测值  ,对比已有的真实值 y ,数据行数为 n ,我们很自然地可以将损失函数定义如下:

即预测值与真实值之间的平均的平方距离,统计中我们一般称其为MAE(mean square error)均方误差。把之前我们确定的

 带入损失函数:

注意,对于损失函数 L 而言,其自变量不再是我们习惯中的  x (其实 x  和 y 都是在训练数据中的已知值),损失函数 L 的自变量应该是我们要求解的参数 w 和 b,因此我们可以把损失函数重新记为:

替换高清大图

现在,我们的任务就是希望把这个损失函数交给计算机,然后跟计算机说,帮我把这个函数最小化,然后告诉我 L 最小时的一组 w 和 b 是多少就行了。但是显然计算机还没那么聪明,它并不知道怎么算,我们还是要靠自己解决。

核心的优化目标式:

这里有两种方式:

  • 一种是“最小二乘法”(least square method),可直接求解;
  • 另一种是梯度下降(gradient descent),有关梯度下降的方法原理可参考我之前这篇文章 -> [link].

1.4 最小二乘法

求解  和  是使损失函数最小化的过程,在统计中,称为线性回归模型的最小二乘“参数估计”(parameter estimation)。我们可以将 L 分别对 w 和 b 求导,得到:

令上述两式为0,可得到 w 和 b 最优解的闭式(closed-form)解:

1.5 梯度下降法求解

2 代码实现 (使用梯度下降法)

完整代码可参考:zlxy9892/ml_code

首先建立 liner_regression.py 文件,用于实现线性回归的类文件,包含了线性回归内部的核心函数:

# -*- coding: utf-8 -*-
import numpy as np
class LinerRegression(object):
   def __init__(self, learning_rate=0.01, max_iter=100, seed=None):
           np.random.seed(seed)        
           self.lr = learning_rate        
           self.max_iter = max_iter        
           self.w = np.random.normal(1, 0.1)      
           self.b = np.random.normal(1, 0.1)        
           self.loss_arr = []    
   def fit(self, x, y):
           self.x = x        
           self.y = y        
           for i in range(self.max_iter):
                       self._train_step()            
                       self.loss_arr.append(self.loss())            
                       # print('loss: {:.3}'.format(self.loss()))            
                       # print('w: {:.3}'.format(self.w))            
                       # print('b: {:.3}'.format(self.b))    
   def _f(self, x, w, b):
          return x * w + b    
    
   def predict(self, x=None):
           if x is None:
                       x = self.x        
            y_pred = self._f(x, self.w, self.b)              
            return y_pred
   
   def loss(self, y_true=None, y_pred=None):
           if y_true is None or y_pred is None:
                       y_true = self.y            
                       y_pred = self.predict(self.x)                    
           return np.mean((y_true - y_pred)**2)
   
    def _calc_gradient(self):
            d_w = np.mean((self.x * self.w + self.b - self.y) * self.x)        
            d_b = np.mean(self.x * self.w + self.b - self.y)        
            return d_w, d_b
    
    def _train_step(self):
            d_w, d_b = self._calc_gradient()        
            self.w = self.w - self.lr * d_w        
            self.b = self.b - self.lr * d_b        
            return self.w, self.b

建立 train.py 文件,用于生成模拟数据,并调用 liner_regression.py 中的类,完成线性回归任务:

# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
from liner_regression import *
def show_data(x, y, w=None, b=None):
   plt.scatter(x, y, marker='.')    
   if w is not None and b is not None:
           plt.plot(x, w*x+b, c='red')    
   plt.show()
# data generation
np.random.seed(272)
data_size = 100
x = np.random.uniform(low=1.0, high=10.0, size=data_size)
y = x * 20 + 10 + np.random.normal(loc=0.0, scale=10.0, size=data_size)
# plt.scatter(x, y, marker='.')
# plt.show()
# train / test split
shuffled_index = np.random.permutation(data_size)
x = x[shuffled_index]
y = y[shuffled_index]
split_index = int(data_size * 0.7)
x_train = x[:split_index]
y_train = y[:split_index]
x_test = x[split_index:]
y_test = y[split_index:]
# visualize data
# plt.scatter(x_train, y_train, marker='.')
# plt.show()
# plt.scatter(x_test, y_test, marker='.')
# plt.show()
# train the liner regression model
regr = LinerRegression(learning_rate=0.01, max_iter=10, seed=314)
regr.fit(x_train, y_train)
print('cost: {:.3}'.format(regr.loss()))
print('w: {:.3}'.format(regr.w))
print('b: {:.3}'.format(regr.b))
show_data(x, y, regr.w, regr.b)
# plot the evolution of cost
plt.scatter(np.arange(len(regr.loss_arr)), regr.loss_arr, marker='o', c='green')
plt.show()

显示结果:

原始数据x, y和拟合的直线方程

推荐 0
本文由 人工智能爱好者社区 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

0 个评论

要回复文章请先登录注册