预测广告效果的线性回归
线性回归是利用数理统计中回归分析,来确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法。说得很抽象,举个例子就知道了,一个公司投入广告费,收获销售额,想做一个广告投入与销售额之间的预测。这里就用到线性回归。
为了预测,事先我们把以前的数据拿到,在坐标系上画出这些数据点,然后根据这些点试图划出一条线,比较好地拟合这些点。
广告投入:40000,30000,25000,20000,15000,…
销售额:200000,180000,150000,120000,100000,…
图示如下:
红线表示拟合线,蓝色是偏差值。如果偏差值小,我们就认为拟合得比较好。学过中学数学的人都知道这条线可以用y=ax+b来表示。
但是这些值是离散的点,怎么衡量偏差值?这里可以引入一个Cost Function(损失函数)来表示偏差,比较简单的情况,我们用以前讲过的方差值当成损失函数。
这样问题就变成了,怎样确定a和b,使得损失函数值最小。机器不是神仙,不可能一下子知道这个合适的a和b。只能根据经验先给一个初值,用这个初值来拟合再用损失函数计算损失。接下来就到了关键的一步,怎么评估这个损失呢?只有进行比较才知道。我们这里一定要有一个优化函数,来评估两次不同的参数变化带来的损失的变化,如果损失变大了,说明参数的变化方向反了,要调回来。这样一步步修正,直到某一个参数,往大变后损失变大,往小变后损失也变大,说明这个参数是最小值(当然也有可能是局部最小值)。这就是机器学习的过程。
我们现在可以着手编程序了。有一些基础工作要做,我们看到了这里处理的数据是序列数据,如广告费:40000,30000,25000,20000,15000,…,如销售额:200000,180000,150000,120000,100000,…。两个序列之间可能需要做整体操作如比对加减,自然我们可以在数组里面循环,不过我们有更加好的方式进行整体操作。Python在处理数组方面给我们提供了很好的基础,我们看一下:
a=[1,2,3,4],n=sum(a)会把数组a中的所有值相加,返回10;n=max(a)会把数组中的最大值4返回来;b=[x*2 for x in a]会新生成一个数组b,里面的元素是a数组的元素的平方;sqrt(sum(b))计算数组a的元素的平方和之后取平方根,就相当于求向量a的长度了;c=zip(a,b),将a和b元素两两配对打包,list(c)返回[(1, 1), (2, 4), (3, 9), (4, 16)];c=[x + y for x, y in zip(a, b)]就是把a和b相加得出新数组。接下来这就是我们要准备的知识之一:向量。向量有两种理解,一种是空间中的矢量,如二维平面,可以用[x,y]两个坐标表示这个矢量,三维空间中用[x,y,z]三个坐标表示这个是矢量;还有一种理解就是一组数。向量之间有整体运算,如相加、相减、均差、方差、与数的乘积、点乘、夹角大小、水平投影、垂直投影、向量积等等。简介如下:向量相加。A=[1,2,3],B=[2,0,5],这两个向量相加为C=A+B=[1+2,2+0,3+5]=[3,2,8] 向量点积。A=[1,2,3],B=[2,0,5],这两个向量相加为C=sum([12,20,35])=17
协方差。两个向量A和B,分别求出均差向量A’和B',再求点积。
方差。协方差中A和B是同一个向量,则得到的是方差。
class vector(object):
def __init__(self, values):
self.list = values
def __str__(self):
return 'Vector: {}'.format(self.list)
def __eq__(self, v):
return self.list == v.list
def plus(self, v):
new_list = [x + y for x, y in zip(self.list, v.list)]
return vector(new_list)
def minus(self, v):
new_list = [x - y for x, y in zip(self.list, v.list)]
return vector(new_list)
def times_scalar(self, c):
new_list = [c * x for x in self.list]
return vector(new_list)
def magnitude(self):
list_squared = [x ** 2 for x in self.list]
return sqrt(sum(list_squared))
def mean(self):
return sum(self.list)/len(self.list)
def dot(self, v):
return sum([x * y for x, y in zip(self.list, v.list)])
def de_mean(self):
avg = self.mean()
return [x - avg for x in self.list]
def covariance(self,v):
return (self.dot(self.de_mean(),v.de_mean()))/(len(self.list)-1)
为了保持一个类的定义的完整性,用到了str__和__eq,方便打印输出和判断是否相等。这个类中定义了向量的基本运算,特别是有协方差和点积运算。
这些也是比较常见的运算啊,Python没有提供吗?你心里嘀咕了。真没有提供,不过呢,有人提供了,大家常用,这就是numpy包。后面我们直接用numpy包里面的工具。
好,到此,我们可以写线性回归程序了。
我们先看损失函数,我们简单地使用方差,对广告费v1和销售额v2,对v1里面的每一个元素求值ax+b,然后减去v2里面的值,最后平方和,除元素个数(此时得到的值叫MSE),再取一半,用数学表达是:
用程序表达是如下语句:
1/(2*n) * sum([(a*x+b-y)**2 for x,y in zip(v1,v2)])
再看优化函数,我们先得知道这个a和b的变化值是变大了还是变小了,所谓变化值,从数学上,就是损失函数对a和b的偏微分,我们计算得出如下公式:
用程序表达就是如下语句:
da = (1.0/n) * sum([(a*x+b-y)*x for x,y in zip(v1,v2)])
db = (1.0/n) * sum([a*x+b-y for x,y in zip(v1,v2)])
知道变化值,就可以得到下一组a和b了。用这个语句(其中alpha为控制参数,一般去一个很小的值,保证a,b是逐步变化的):
a = a - alpha*da
b = b - alpha*db
有了这些,可以得到下面的程序:
def costeval(a, b, v1, v2):
n = len(v1)
return 0.5/n * sum([(a*x+b-y)**2 for x,y in zip(v1,v2)])
def optimize(a,b,v1,v2):
n = len(v1)
alpha = 0.01
da = (1.0/n) * sum([(a*x+b-y)*x for x,y in zip(v1,v2)])
db = (1.0/n) * sum([a*x+b-y for x,y in zip(v1,v2)])
a = a - alpha*da
b = b - alpha*db
return a, b
a=10
b=0
v1=[1,2,3,4,5]
v2=[10,22,28,40,48]
for i in range(0,10):
cost0=costeval(a,b,v1,v2)
print(a,b,cost0)
a,b=optimize(a,b,v1,v2)
我们上面的程序迭代十次,可以看结果:
10 0 1.2
9.98 -0.0 1.14
9.95 -0.01 1.1
9.94 -0.01 1.07
9.92 -0.01 1.04
9.9 -0.01 1.02
9.89 -0.01 1.0
9.88 -0.01 0.99
9.87 -0.02 0.98
9.86 -0.02 0.97
可以看到a,b参数值在逐步调整,损失值也在逐步缩小。这种逐步逼近的办法在机器学习中是通用的,后面的神经网络也会用到。我相信生命和智慧的进化,逼近也是一个手段。
除了机器学习的办法逐步阶梯下降找拟合,数学家们研究了怎么确定a和b,提出的方法有一种比较简单又用得比较多的是最小二乘法(Least Square)。介绍如下。
其实我们上面已经分析到了,找最小的残差平方和,再化为找偏微分函数的零点。最后求得:
a值为:,b值为:
。整个推导过程需要高等数学,但是结论还是在初等数学的范围。我这里不进行推导了,总之要知道是数学家帮我们论证好了的结论,我们的任务就是学习并用代码加以实现。
程序我们提供几个函数就可以了。
#平均值
def mean(v):
sum(v)/len(v)
#均差序列
def de_mean(v):
avg= mean(v)
return [x - avg for x in v]
#点积
def dot(v1,v2):
return sum([x * y for x, y in zip(v1, v2)])
#回归系数,最小二乘法
def line_coef(v1,v2):
s1 = dot(v2,de_mean(v1))
s2 = dot(de_mean(v1),de_mean(v1))
a = s1/s2
b = mean(v2)-a*mean(v1)
return a,b
还是用以前的数据测试一下:
v1=[1,2,3,4,5]
v2=[10,22,28,40,48]
print(line_coef(v1,v2))
结果为:
(9.4, 1.40)
最小二乘法是利用数学公式进行求解,跟前面介绍的逼近方法不同,逼近方法更加体现了机器学习的过程,并且是通用的方法。当然我们实际工作中不太可能自己去写回归模型,对机器学习数据挖掘和分析,简单高效最常用的第三方工具是scikit-learn。