一、回归(Regression)
回归分析是一种预测性的建模技术,它研究的是因变量(目标)和自变量(预测器)之间的关系。这种技术通常用于预测分析,时间序列模型以及发现变量之间的因果关系。
比如说,在当前的经济条件下,你要估计一家公司的销售额增长情况。现在,你有公司最新的数据,这些数据显示出销售额增长大约是经济增长的2.5倍。那么使用回归分析,我们就可以根据当前和过去的信息来预测未来公司的销售情况。
一般来说有线性回归、逻辑回归、多项式回归、逐步回归、岭回归、套索回归、ElasticNet回归等七种最常用的回归技术。
用数学公式一般的来表达:二、简介线性回归
起源
“回归”是由英国著名生物学家兼统计学家高尔顿(Francis Galton,1822~1911. 生物学家达尔文的表弟)在研究人类遗传问题时提出来的。为了研究父代与子代身高的关系,高尔顿搜集了1078对父亲及其儿子的身高数据。他发现这些数据的散点图大致呈直线状态,也就是说,总的趋势是父亲的身高增加时,儿子的身高也倾向于增加。但是,高尔顿对试验数据进行了深入的分析,发现了一个很有趣的现象—回归效应。因为当父亲高于平均身高时,他们的儿子身高比他更高的概率要小于比他更矮的概率;父亲矮于平均身高时,他们的儿子身高比他更矮的概率要小于比他更高的概率。它反映了一个规律,即这两种身高父亲的儿子的身高,有向他们父辈的平均身高回归的趋势。对于这个一般结论的解释是:大自然具有一种约束力,使人类身高的分布相对稳定而不产生两极分化,这就是所谓的回归效应。
1855年, 高尔顿发表《遗传的身高向平均数方向的回归》一文,他和他的学生卡尔•皮尔逊Karl·Pearson通过观察1078对夫妇的身高数据,以每对夫妇的平均身高作为自变量,取他们的一个成年儿子的身高作为因变量,分析儿子身高与父母身高之间的关系,发现父母的身高可以预测子女的身高,两者近乎一条直线。当父母越高或越矮时,子女的身高会比一般儿童高或矮,他将儿子与父母身高的这种现象拟合出一种线形关系,分析出儿子的身高y与父亲的身高x大致可归结为一下关系:
y=33.73+0.516*x (单位为英寸)
根据换算公式1英寸=0.0254米, 1米=39.37英寸。单位换算成米后:
Y= 0.8567+0.516*X (单位为米)
假如父母辈的平均身高为1.75米,则预测子女的身高为1.7597米。这种趋势及回归方程表明父母身高每增加一个单位时,其成年儿子的身高平均增加0.516个单位。这就是回归一词最初在遗传学上的含义。
有趣的是,通过观察,高尔顿还注意到,尽管这是一种拟合较好的线形关系,但仍然存在例外现象:矮个父母所生的儿子比其父要高,身材较高的父母所生子女的身高却回降到多数人的平均身高。换句话说,当父母身高走向极端,子女的身高不会象父母身高那样极端化,其身高要比父母们的身高更接近平均身高,即有“回归”到平均数去的趋势,这就是统计学上最初出现“回归”时的涵义,高尔顿把这一现象叫做“向平均数方向的回归” (regression toward mediocrity),或者翻译为“向平均数的衰退”。虽然这是一种特殊情况,与线形关系拟合的一般规则无关,但“线形回归”的术语却因此沿用下来,作为根据一种变量(父母身高)预测另一种变量(子女身高)或多种变量关系的描述方法。
线性回归
线性回归(Linear regression)是一种以线性模型来建模自变量与因变量关系的方法。通常来说,当自变量只有一个的情况被称为简单线性回归(或者一元线性回归),自变量大于一个的情况被称为多重线性回归(或者多元线性回归)。
在线性回归模型中, 模型的未知参数由数据中估计得到。最常用的拟合方法是最小二乘法,但是也有许多其他的拟合方法。因此需要甄别的是,使用最小二乘法求解并不是构成线性回归模型的必要条件。
应用范围
线性回归是应用最广泛的回归分析之一,主要可以用于以下两类:
- 预测:线性回归可以在拟合到已知数据集后用于预测自变量所对应的因变量
- 解释:线性回归可以用于量化因变量与自变量之间关系的强度
三、线性回归的理论简介
一元线性回归
在线性回归中,最小二乘法就是试图找到一条直线,使所有样本到直线上的欧式距离之和最小。现在我们假设数据样本为:(x1,y1), (x2,y2), (x3,y3), ... (xn,yn),设一元线性回归方程为:
这里我们想要最后拟合的结果函数能尽可能的使得每个样本点都落在一元线性回归方程上,因为实际上,基本不存在这样一条直线(超平面)能满足所有点都在回归方程上。于是拟合的过程就会有误差,回归直线应满足的条件是:全部观测值与对应的回归估计值的误差(或者是距离)最小。
令分别求导的结果为0,得:
其中 为自变量x的数据样本平均值。
一元的线性回归便于理解,方便作图,使后面的多元线性回归和对数几率回归更好理解。最小二乘法的过程就像下图:
图左边的红点就相当于是在对公式中的 w 和 b 求导,现象就是红点在两个维度上不停的下降,直到得到两个参数求导后的最优闭式(closed-form)解。右边拟合的结果函数随着求导的过程,不断拟合逼近数据的回归,最后就可以用函数来预测和分析数据。这里说明,我理解的回归应该是数据的本质,而拟合只是达到了数据的表象。
多元线性回归
一元线性回归便于理解,也很简单的使用,但是现实中,即使再怎么控制变量,实际影响结果的变量总是会是多个,那么多元线性回归就出现了。数学公式上,原来的w和b变成了矩阵w和b,原来的公式:
化为矩阵的形式:
其中:
多元线性回归的损失函数和最小二乘法的使用推导见《机器学习》。
对数线性回归与广义线性模型
不管一元线性回归还是多元线性回归,始终都是在线性的尺度上拟合,而现实中有更多的问题是在非线性尺度上的拟合。于是我们把上述的公式改写,得到一个对数线性回归(log-linear regression):他的意思是:
在不断的逼近于y。这样就做到了输入空间到输出空间的非线性映射。
然后更一般的我们推导出广义线性模型:
其中,函数 g(·) 称为“联系函数”,其单调可微。
很显然,对数线性回归是广义线性模型在 g(·)=ln(·) 时的特例。
可以看出联系函数是线性的函数还是非线性的函数就决定着广义线性模型的线性与非线性。
对数几率回归
对数几率回归(Logistic Regression )又称为逻辑回归,完全是因为音译的翻译错误,根本与中文的逻辑没有半毛钱关系。Logistic Regression 虽然被称为回归,但其实际上是分类模型,并常用于二分类。Logistic Regression 因其简单、可并行化、可解释强深受工业界喜爱。
Logistic 回归的本质是:假设数据服从这个分布,然后使用极大似然估计做参数的估计。
许多的时候认为SVM(支持向量机)与对数几率回归傻傻分不清,因为他们都在尝试对已有的分布数据找到一个可以切割开数据的超平面,只是使用的方法不同,关注的方面不一样。然后自然SVM和LR队数据分类的结果也会有一些差异,下面我们看对数几率回归。
- sigmoid 函数
LR来做分类,有一个好处是,可以明确的计算出分类的概率。我们知道,概率是属于[0,1]区间。但是像:
函数图像为:
-
对数几率函数
对数几率函数:
我们结合sigmoid函数,线性回归函数,把线性回归模型的输出作为sigmoid函数的输入。于是最后就变成了逻辑回归模型。
于是有:
也就是说,输出 Y=1 的对数几率是由输入 x 的线性函数表示的模型,这就是对数几率回归模型。
通过上述推导我们可以看到 Logistic 回归实际上是使用线性回归模型的预测值逼近分类任务真实标记的对数几率,其优点有:
- 直接对分类的概率建模,无需实现假设数据分布,从而避免了假设分布不准确带来的问题;
- 不仅可预测出类别,还能得到该预测的概率,这对一些利用概率辅助决策的任务很有用;
- 对数几率函数是任意阶可导的凸函数,有许多数值优化算法都可以求出最优解。
求解参数
如线性回归中使用最小二乘法来求解参数。在逻辑回归模型的数学形式确定后,统计学中,常常使用极大似然估计法来求解,即找到一组参数,使得在这组参数下,我们的数据的似然度(概率)最大。
求解过程可以使用随机梯度下降和牛顿法。除此之外再机器学习中,为了降低过拟合,需要加入一些正则项。推导见《机器学习》。
与线性回归的区别
- 逻辑回归解决的是分类问题,输出的是离散值,线性回归解决的是回归问题,输出的连续值
- 线性回归是在实数域范围内进行预测,而分类范围则需要在 [0,1],逻辑回归减少了预测范围
- 线性回归在实数域上敏感度一致,而逻辑回归在 0 附近敏感,在远离 0 点位置不敏感,这个的好处就是模型更加关注分类边界,可以增加模型的鲁棒性
四、Linear Regression and Logistic Regression
一元线性回归
使用教材上的数据:matlab来拟合代码如下:
x = [10, 20, 30, 40, 50, 60, 70, 80];
y = [0.18, 0.50, 0.61, 0.77, 1.05, 1.10, 1.36, 1.58];
x = x';
y = y';
plot(x, y, '*');
xlabel('总量');
ylabel('吸附量');
title('Linear Regression')
hold on
[B, BINT, R, RINT, STATS] = regress(y, x);
disp(B) % 回归系数向量
disp(BINT) % 回归系数的估计区间
disp(R) % 残差,就是最小二乘法计算后的结果,也是误差,欧式距离
disp(RINT) % 置信区间
disp(STATS) % 检验回归模型的统计量。有4个数值:判定系数r2,F统计量观测值,检验的p的值,误差方差的估计
yf = B(1) * x;
plot(x, yf);
legend('原始数据', '1阶拟合');
hold off
上面的代码运行的绘图结果为:输出为:
0.0197
0.0187 0.0206
-0.0166
0.1069
0.0203
-0.0163
0.0672
-0.0794
-0.0160
0.0075
-0.1684 0.1353
-0.0008 0.2145
-0.1280 0.1686
-0.1624 0.1298
-0.0596 0.1939
-0.1935 0.0347
-0.1485 0.1165
-0.1192 0.1341
0.9845 NaN NaN 0.0034
由图可知,拟合的函数基本可以代表所有的数据点。在matlab中regress
函数是数学分析的函数,线性模型使用fitlm
,是机器学习中的模型。两种方法所有的结果都可以求出来,按照自己的需求来选择怎么使用,方便就好。
多元线性回归
我这里采集了一些成都市天府新区2970套房产的信息,然后去除掉一些信息不全的房产,剩下2617套房产,把所有数据都量化后,得到一个2617 * 7
的房产信息矩阵。
然后就得到了多元线性回归的数据,2617 * 7
的矩阵到2617 * 1
的矩阵的映射。现在要找到他们的映射关系。
这里我使用经典的python机器学习库scikit-learn,来把多元线性回归在计算机模拟,如下:
import numpy as np
from sklearn.linear_model import LinearRegression
from data import *
X, y = load_linear_regression_data()
reg = LinearRegression().fit(X, y)
score = reg.score(X, y)
coef = reg.coef_
intercept = reg.intercept_
py = reg.predict(np.array([[2010, 9, 92.19, 6, 6, 2, 14993]]))
print("---------")
print(py)
输出的结果为:[138.71334569]
就是房价预测为138.7万,我修改的原数据为:2005 9 92.19 6 6 2 13993 129
,原数据中最后一列房价为129万,但是预测时改动了一点数据,基本上差距不大。可以尝试一些波动更大的数据进去测试,看看结果偏离是否大。
对数几率回归
在多元线性回归里面,我们做的是房价的预测,看房价是不是太偏离预测的回归函数。我们就可以得知房价是高了些还是低了些,高了则不要,低了就可以考虑入手。
但是房价略低于回归函数的房子可能由很多,我们不太可能一个一个去看,很花时间。我们就可以使用对数几率回归,做一个简单的分类,分出自己心仪类型的房,然后结合房价的回归函数,筛选出更少的房,再去做人工的查看。
这里我同样使用成都市二手房的数据,然后自己做了数据标注。2617 * 8
的矩阵到2617 * 1
的矩阵的映射,只不过这里2617 * 1
的矩阵值都是0和1,0表示不要,1表示要。负例合计1718个,正例为899个。
这里使用比较新的AI算法库pytorch,来实现对数几率函数的训练学习,鉴于数据量本身也不多,这里就不切分数据做验证,想看验证的代码,请移步他处。数据量本身也不大,不实用GPU计算,使用CPU计算就够了,代码如下:
import numpy as np
import torch
from data import *
import torch.nn as nn
train_dataset = LRDataSet()
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=100,
shuffle=True)
# Logistic regression model, Loss and optimizer
model = nn.Linear(8, 2)
criterion = nn.CrossEntropyLoss()
# optimizer = torch.optim.SGD(model.parameters(), lr=0.0001)
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)
# Train the model
epochs = 2000
total_step = len(train_loader)
for epoch in range(epochs):
model.train()
for i, (sources, labels) in enumerate(train_loader):
model.train()
# Forward pass
outputs = model(sources)
loss = criterion(outputs, labels)
# Backward and optimize
optimizer.zero_grad()
loss.backward()
optimizer.step()
if ((epoch + 1) % 100 == 0 or epoch == 0) and (i + 1) % 20 == 0:
# Test the model
model.eval()
with torch.no_grad():
correct = 0
total = 0
for sources, labels in train_loader:
outputs = model(sources)
_, predicted = outputs.max(1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = 100 * correct / total
print(
'Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}, Accuracy: {:.4f}'
.format(epoch + 1, epochs, i + 1, total_step, loss.item(),
accuracy))
# Save the model checkpoint
#torch.save(model.state_dict(), 'model.ckpt')
运行结果如下:
Epoch [1/2000], Step [5/9], Loss: 642.1466, Total: 2617, Correct: 899, Accuracy: 34.35%
Epoch [100/2000], Step [5/9], Loss: 9.7680, Total: 2617, Correct: 1571, Accuracy: 60.03%
Epoch [200/2000], Step [5/9], Loss: 1.7235, Total: 2617, Correct: 1793, Accuracy: 68.51%
Epoch [300/2000], Step [5/9], Loss: 0.9440, Total: 2617, Correct: 1803, Accuracy: 68.90%
Epoch [400/2000], Step [5/9], Loss: 1.2405, Total: 2617, Correct: 1809, Accuracy: 69.12%
Epoch [500/2000], Step [5/9], Loss: 0.9300, Total: 2617, Correct: 1817, Accuracy: 69.43%
Epoch [600/2000], Step [5/9], Loss: 0.8301, Total: 2617, Correct: 1832, Accuracy: 70.00%
Epoch [700/2000], Step [5/9], Loss: 0.7874, Total: 2617, Correct: 1857, Accuracy: 70.96%
Epoch [800/2000], Step [5/9], Loss: 0.6431, Total: 2617, Correct: 1855, Accuracy: 70.88%
Epoch [900/2000], Step [5/9], Loss: 0.6474, Total: 2617, Correct: 1843, Accuracy: 70.42%
Epoch [1000/2000], Step [5/9], Loss: 0.6203, Total: 2617, Correct: 1951, Accuracy: 74.55%
Epoch [1100/2000], Step [5/9], Loss: 0.5937, Total: 2617, Correct: 1986, Accuracy: 75.89%
Epoch [1200/2000], Step [5/9], Loss: 0.4514, Total: 2617, Correct: 2014, Accuracy: 76.96%
Epoch [1300/2000], Step [5/9], Loss: 0.4304, Total: 2617, Correct: 2013, Accuracy: 76.92%
Epoch [1400/2000], Step [5/9], Loss: 0.5772, Total: 2617, Correct: 1990, Accuracy: 76.04%
Epoch [1500/2000], Step [5/9], Loss: 0.4965, Total: 2617, Correct: 2025, Accuracy: 77.38%
Epoch [1600/2000], Step [5/9], Loss: 0.4640, Total: 2617, Correct: 2021, Accuracy: 77.23%
Epoch [1700/2000], Step [5/9], Loss: 0.5060, Total: 2617, Correct: 2037, Accuracy: 77.84%
Epoch [1800/2000], Step [5/9], Loss: 0.4431, Total: 2617, Correct: 2039, Accuracy: 77.91%
Epoch [1900/2000], Step [5/9], Loss: 0.4461, Total: 2617, Correct: 2030, Accuracy: 77.57%
Epoch [2000/2000], Step [5/9], Loss: 0.4659, Total: 2617, Correct: 2039, Accuracy: 77.91%
最后的结果在训练集上的准确率为77%,这当然是不够的。需要再通过学习率的更新,优化器的选择等方法去优化。极端的可能,是数据在对数几率回归上无法分离开,但是这种情况几乎不会遇到。
总结
我在上面的处理中,总共存在几个问题,留给大家自己思考和改正:
- 没有把数据分为训练和验证集,没有验证拟合的结果是不是够好
- 数据没有做归一化的处理,导致某一些数值较大的数据会极大的影响结果,需要读者自己再做归一化处理
- 在使用过程中,房价有关的因素显然不止7个维度,数据维度方面需要再扩充才可以达到实用的地步