#!/usr/bin/env python
# coding: utf-8

# # 线性回归示例

# In[4]:


import numpy as np
import matplotlib.pyplot as plt


# In[5]:


#读取数据源: 面积、房价
filename = 'ex1data2.txt'
content = np.loadtxt(filename, delimiter=',', usecols=(0, 2))


# In[6]:


#数据归一化
def nomalization(arr):
    range = np.max(arr) - np.min(arr)
    return (arr - np.min(arr)) / range


# In[7]:


#选择第一列数据作为x轴
x = content[:, :1]
#选择第二列数据作为y轴
y = content[:, 1:]


# In[8]:


x = nomalization(x)
y = nomalization(y)


# In[9]:


plt.scatter(x, y)


# In[10]:


#线性函数公式
def formula(a, b, x):
    return a*x + b


# In[11]:


#优化函数
def optimifunc(a, b, x, y, times):
    rate = 0.01
    n = x.size
    yhat = y
    for i in range(times):
        yhat = formula(a, b, x)
        da = np.sum((yhat - y) * x) / n
        db = np.sum(yhat - y) /n
        a = a - rate * da
        b = b - rate * db
    plt.scatter(x,y)
    plt.plot(x, yhat)
    return a, b


# In[12]:


#初始化参数
a, b = 0, 0


# In[13]:


#学习、训练参数
a, b = optimifunc(a, b, x, y, 1)


# In[14]:


a, b = optimifunc(a, b, x, y, 50)


# In[15]:


a, b = optimifunc(a, b, x, y, 100)


# In[16]:


a, b = optimifunc(a, b, x, y, 500)


# In[17]:


a, b = optimifunc(a, b, x, y, 10000)


# # 理论知识

# 先看下房子面积与价格的趋势图:

# In[20]:


plt.scatter(content[:, :1], content[:, 1:])


# 根据上图可以看出,房子的面积与房价之间的关系是线性的,也就是房子面积越大价格越高。现在要根据已有数据,根据新给的房子面积,预测出最合理的价格。

# ### 数据模型

# 线性关系,可以用线性函数来表示:y = ax + b

# 现在x(房子的面积)和y(房子的价格)已经有了,因此,主要是根据历史数据得出a、b的解。例如`2014 * a + b = 399900`,可以看到因为有两个参数,所以无法直接得出a和b的解。

# 这就是机器学习的特殊的地方,一个是历史数据多,而且数据不太规则;另一个就是无法直接套用公式得出解。那怎么办呢?采用逼近法。就是先假设a和b的值,然后套用公式得出y的值和历史的y值进行比较,
# 根据y值的接近程度来调整a和b,直到a、b带入到整个历史数据中得出y的值和历史的y值最接近,就称为公式的最优解。

# 假设a、b的初始值为0,带入公式:`2014 * 0 + 0 = 0`,结果为0(我们将计算出来的y值称为yhat,用于真实的y值做区分),而真实y的值为`399900`,因此0不是a、b的最优解。如何评判是不是最优解呢?
# 一般通过yhat与y值进行比较。yhat与y进行比较的函数我们叫做损失函数(Loss Function)或代价函数(Cast Function)。

# ### 代价函数

# 损失函数是单个样本上的误差,如上例中的yhat计算结果为0,而实际y值为399900,两者的误差为y-yhat=399900。y-yhat就是损失函数。而代价函数指的是整个训练集上所有样本的误差。

# 代价函数的作用就是来评价参数a、b与预期结果的拟合程度。

# 上面的示例中,我们采用的代价函数是$$\frac{1}{2m}\sum_{i=1}^{m}(h(x^i)-y^i)^2$$

# yhat-y就是误差值,通过这个意义就可以看出代价函数是用来计算误差的,误差越小说明越接近目标。对误差值平方再求和,一看就知道是方差公式(方差定义参见另一篇文章)。也就是说这里用方差作为代价函数。

# 只要我们不停变换参数a、b的值带入训练集,然后通过代价函数来评判哪个更优,将a、b所有的可能性遍历完后,就可以得出最优a和b。

# ### 梯度下降

# 通过代价函数可以计算出来不同的参数的优劣,然而我们不可能真的将所有a、b遍历计算。因此需要找一种好的方法,用最快的速度找出a和b,也就是最短路径。

# 梯度下降就是找出最短路径的一种方法。其原理类似你站在一个山坡上,360度观察四周,朝哪个方向走可以快速下山。转换成数学方式,就是求导。

# 因为我们的代价函数是二阶函数,二阶导数的意义是凹凸性,通过凹凸性来判断是上坡还是下坡,让我们朝坡下快速行进。需要注意的是,因为函数可能存在多个凹点,因此最终得出的a、b只是局部最优解,而不是全局最优解。

# 最后一点是rate,就是下山的方向每次迈出的步子有多大,不能太大也不能太小。对代价函数求导后的函数,我们一般称之为优化函数。

# 对a求导:$$\frac{\partial J}{\partial a}= \frac{1}{m}\sum_{i=1}^{m}x(h(x^i)-y^i)$$

# 对b求导:$$\frac{\partial J}{\partial b}= \frac{1}{m}\sum_{i=1}^{m}(h(x^i)-y^i)$$

# 更新a:       $a = a - \alpha \frac{\partial J}{\partial a}$

# 更新b:       $b = b - \alpha \frac{\partial J}{\partial b}$

# 其中$\alpha$就是rate。

# ### 欠拟合、过拟合

# 首先确定一点:过拟合和欠拟合都是不好的,预测的准确度低。

# 在做机器学习时,会将历史数据分为两部分:一部分是训练集,另一部分是测试集。用训练集得出最优函数,再用测试集去验证这个函数是否符合实际需求。

# 欠拟合就是训练集上表现差,测试集上预测准确度低。过拟合是在训练集上表现过于良好,但在测试集上表现差。

# ### 总结

# 机器学习涉及到很多方面的知识,跟以往我们解决问题的方式有很大区别,因此要先从思维上做些转换,再根据自身的目的,对涉及面做些取舍,毕竟我们是为了解决问题,而不是要成为这方面的专家。