暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

机器学习||利用python和numpy库实现入门经典案例波士顿房价预测

AI骇客 2021-07-27
420




也许是天气,也许是运气


也许是因为有人不放弃


-2021.07.27-


文章借鉴PaddlePaddle平台案例


波士顿房价预测任务

波士顿地区的房价受诸多因素影响。housing.data统计了13种可能影响房价的因素和该类型房屋的均价,期望构建一个基于13个因素进行房价预测的模型

因为房价是一个连续值,所以房价预测显然是一个回归任务。下面我们尝试用最简单的线性回归模型解决这个问题,并用神经网络来实现这个模型。


线性回归模型及其神经网络结构

假设房价和各影响因素之间能够用线性关系来描述:

模型的求解即是通过数据拟合出每个w和b。其中,w和b分别表示该线性模型的权重和偏置

线性回归模型使用均方误差作为损失函数(Loss),用以衡量预测房价和真实房价的差异,公式如下:

线性回归模型可以认为是神经网络模型的一种极简特例,是一个只有加权和、没有非线性变换的神经元(无需形成网络)

现在就可以开始撸代码啦!


数据处理部分

def load_data():   

 # 导入需要用到的包

    import numpy as np

    import matplotlib.pyplot as plt

# 读入训练数据

    datafile = 'housing.data'

    data = np.fromfile(datafile, sep=' ')

# 数据形状变换:将一维数据转换成二维

    column_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS',     'NOX', 'RM', 'AGE','DIS',  'RAD', 'TAX', 'PTRATIO',     'B', 'LSTAT', 'MEDV' ]

    column_length = len(column_names) 

    data=data.reshape(data.shape[0]//column_leng    th,  column_length)

# 数据集划分:将80%的数据用作训练集,20%用作  测试集

    ratio = 0.8

    offset = int(data.shape[0] * ratio)

    training_data = data[:offset]

 # 数据归一化处理:使得每个特征的取值缩放到0~1  之间     axis=0表示取列项计算

    maximums = training_data.max(axis=0)

    minimums = training_data.min(axis=0)

    avgs = training_data.sum(axis=0)

     / training_data.shape[0]

    for i in range(column_length):

        data[:, i] =  (data[:, i] - minimums[i])                 (maximums[i] - minimums[i])

    training_data = data[:offset]

    test_data = data[offset:]

    return training_data ,test_data


归一化后的training_data如图

模型设计

以“类和对象”的方式来描述计算预测输出的过程,类成员变量有参数w和b。并通过写一个forward函数(代表“前向计算”)完成从特征和参数到输出预测值的计算过程

z = w*x + b 


class Network(object):    

    def __init__(self, num_of_weights):   

    # 随机产生w的初始值        

    # 为了保持程序每次运行结果的一致,此处设置固定的随机 数种子       

        np.random.seed(0)        

        self.w = np.random.randn(num_of_weights, 1)               self.b = 0.           

    def forward(self, x):         

        z = np.dot(x, self.w) + self.b      

        return z


训练配置

模型设计完成后,需要通过训练配置寻找模型的最优值,即通过损失函数来衡量模型的好坏。

对于回归问题,最常采用的衡量方法是使用均方误差作为评价模型好坏的指标,具体定义如下:

Loss=[(y−z)^2]/N

def loss(self, z, y):     

        error = z - y        

        cost = error * error        

        cost = np.mean(cost)        

        return cost

训练过程

接下来介绍如何求解参数w和b的数值,这个过程也称为模型训练过程,其目标是让定义的损失函数Loss尽可能的小,也就是说找到一个参数解w和b,使得损失函数取得极小值。

即求Loss关于w和b的偏导数无限趋近与0时w和b的解,我们可以通过”梯度下降法“来求解

梯度下降法是用来计算函数最小值的,就像在山顶放了一个球会沿着山坡最陡峭的地方滚落到谷底,这里的坡度就是梯度。

微积分的基础知识告诉我们,沿着梯度的反方向,是函数值下降最快的方向,即最陡峭的山坡。简单理解,函数在某一个点的梯度方向是曲线斜率最大的方向,但梯度方向是向上的,所以下降最快的是梯度的反方向。


如何求梯度?(高数内容)


基于Numpy广播机制(对向量和矩阵计算如同对1个单一变量计算一样),可以更快速的实现梯度计算。

根据梯度的计算公式,总梯度是对每个样本对梯度贡献的平均值。

使用np.mean函数时会消除第0维。为了加减乘除等计算方便,gradient_w和w必须保持一致的形状,需要用到np.newaxis方法


综合上面的剖析,代码如下

    def gradient(self, x, y):        

        z = self.forward(x)        

        gradient_w = (z-y)*x        

        gradient_w = np.mean(gradient_w, axis=0)              gradient_w = gradient_w[:, np.newaxis]                  gradient_b = (z - y)        

        gradient_b = np.mean(gradient_b)                

    return gradient_w, gradient_b


下面开始研究更新梯度的方法,即确定损失函数更小的点,增加updata方法

eta:控制每次参数值沿着梯度反方向变动的大小,即每次移动的步长,又称为学习率


    def update(self, gradient_w, gradient_b, eta = 0.01):        

        self.w = self.w - eta * gradient_w        

        self.b = self.b - eta * gradient_b


调用上述方法封装成一个train方法实现训练目的

    def train(self, x, y, iterations=100, eta=0.01):              losses = []        

        for i in range(iterations):            

            z = self.forward(x)             

            L = self.loss(z, y)            

            gradient_w, gradient_b = self.gradient(x, y)             self.update(gradient_w, gradient_b, eta)                   losses.append(L)            

            if (i+1) % 10 == 0:               

                print('iter {}, loss {}'.format(i, L))        

        return losses


# 获取数据

train_data, test_data = load_data() 

x = train_data[:, :-1] 

y = train_data[:, -1:]

# 创建网络

net = Network(13) 

num_iterations=1000

# 启动训练

losses = net.train(x,y, iterations=num_iterations, eta=0.01)

# 画出损失函数的变化趋势

plot_x = np.arange(num_iterations) 

plot_y = np.array(losses) 

plt.plot(plot_x, plot_y) 

plt.show()

训练结果如图

随机梯度下降法

上述程序中,每次损失函数和梯度计算都是基于数据集中的全量数据。对于波士顿房价预测任务数据集而言,样本数比较少,只有404个。但在实际问题中,数据集往往非常大,如果每次都使用全量数据进行计算,效率非常低,通俗地说就是“杀鸡焉用牛刀”。由于参数每次只沿着梯度反方向更新一点点,因此方向并不需要那么精确。一个合理的解决方案是每次从总的数据集中随机抽取出小部分数据来代表整体,基于这部分数据计算梯度和损失来更新参数,这种方法被称作随机梯度下降法,核心概念如下:

mini-batch:每次迭代时抽取出来的一批数据被称为一个mini-batch。

batch_size:一个mini-batch所包含的样本数目称为batch_size。

epoch:当程序迭代的时候,按mini-batch逐渐抽取出样本,当把整个数据集都遍历到了的时候,则完成了一轮训练,也叫一个epoch。启动训练时,可以将训练的轮数num_epochs和batch_size作为参数传入。


随机打乱样本顺序,需要用到np.random.shuffle函数,该函数可以让列表元素在第0维被打乱,第一维顺序保持不变


更改部分的代码:

def train(self, training_data, num_epochs,batch_size=10, eta=0.01):        

    n = len(training_data)        

    losses = []        

    for epoch_id in range(num_epochs):            

# 在每轮迭代开始之前,将训练数据的顺序随机打乱   # 然后再按每次取batch_size条数据的方式取出                 np.random.shuffle(training_data)            

# 将训练数据进行拆分,每个mini_batch包含      batch_size条的数据  

    mini_batches =[training_data[k:k+batch_size]for     k in range(0, n, batch_size)]            

    for iter_id, mini_batch in      enumerate(mini_batches): 

        x = mini_batch[:, :-1]      

        y = mini_batch[:, -1:]                    

        a = self.forward(x)                

        loss = self.loss(a, y)                            

        gradient_w, gradient_b = self.gradient(x, y)          self.update(gradient_w, gradient_b, eta)                losses.append(loss)                

        print('Epoch {:3d} / iter {:3d}, loss = {:.4f}'.                format(epoch_id, iter_id, loss)) 

    return losses

# 获取数据

train_data, test_data = load_data()

# 创建网络

net = Network(13)

# 启动训练

losses = net.train(train_data, num_epochs=50, batch_size=100, eta=0.1)

# 画出损失函数的变化趋势

plot_x = np.arange(len(losses)) 

plot_y = np.array(losses) 

plt.plot(plot_x, plot_y) 

plt.show()


观察上述Loss的变化,随机梯度下降加快了训练过程,但由于每次仅基于少量样本更新参数和计算损失,所以损失下降曲线会出现震荡。

有代码出错请见谅


 • end • 

曲线图丨由本人绘制

文 | atropos-1671


/若你喜欢怪人

/其实我很可爱

文章转载自AI骇客,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论