机器学习——线性回归
介绍
线性回归,是接触机器学习时候,最开始的内容。线性回归可以做拟合问题,也可以做分类问题。在线性回归问题(或者说任何问题)只需要关注三个问题:模型公式、评价模型、更新模型。
线性回归——模型公式
什么是线性回归?
回归分析中,只包括一个自变量和一个因变量,且二者的关系可用一条直线近似表示,这种回归分析称为一元线性回归分析。如果回归分析中包括两个或两个以上的自变量,且因变量和自变量之间是线性关系,则称为多元线性回归分析。
假设有这样一个问题,有10张20*20的手写体数字0~9的图片,经过人为提取获得每张图片的特征向量(假设每张图片可以获得6个特征):
V0:[V01,V02,V03,V04,V05,V06,1]
V1:[V11,V12,V13,V14,V15,V16,1]
…
V9:[V91,V92,V93,V94,V95,V96,1]
依据线性方程的思想,如果能构造一个函数f(x),当x输入为一种特征向量,f(x)就能输出对应类别,于是我们可以假设模型方程是
Yi = w1vi1+w2vi2+w3vi3+w4vi4+w5vi5+w6vi6+b
W=[w1,w2,w3,w4,w5,w6,b]
因为一般线性方程的模式力很差,所以可以为它在加上一个常量系数b,就形成了上述线性方程。
写成矩阵形式
Yi = WViT
线性回归——评价模型
评价模型的方法就是构造一个损失函数(也叫目标函数)。损失函数就是评价预测结果和真实情况的差别。如果差别较小,则损失小,模型较好。常用损失函数有以下几种:
- MSE(Mean Square Error)均方误差——统计预测数据和原始数据差值的平方和的均值
M S E = 1 n ∑ i = 0 n ( y i − y ^ i ) 2 MSE={1\over n} \sum_{i=0}^n (y_i- \hat y_i)^2 MSE=n1i=0∑n(yi−y^i)2 - RMSE(Root Mean Square Error)标准差——预测数据和原始数据差值平方和的均值的开平方,用来衡量预测数据同真实数据的偏差
R M S E = M S E = 1 n ∑ i = 0 n ( y i − y ^ i ) 2 RMSE = \sqrt {MSE} = \sqrt {{1\over n} \sum_{i=0}^n (y_i- \hat y_i)^2} RMSE=MSE=n1i=0∑n(yi−y^i)2 - MAE(Mean Absolute Error)——预测数据和原始数据之间绝对误差的平均值
M A E = 1 n ∑ i = 0 n ∣ y i − y ^ i ∣ MAE = {1\over n} \sum_{i=0}^n |y_i-\hat y_i| MAE=n1i=0∑n∣yi−y^i∣
有人将上述评价模型的三种求差方式称之为点对点误差,经常使用的是第一种MSE作为模型的损失函数。
对于刚接触机器学习的我们,肯定会有这样的疑问:为什么我们知道了Yi = WViT 模型后,还要设定一个MSE的损失函数呢?
我是这样理解的,因为我们假设数据的特征是有规律的,而我们有假定这种规律是服从线性回归的,于是我们有了Yi = WViT这个公式(Vi是特征,W是线性参数,Yi是预测结果)。但我们需要知道这个公式是不是对的,所以我们一般能够想到:用预测出来的结果和真实值做比较。如果直接使用Yi-Y(Y为真实值)作为判断依据,每次的判断结果有可能是正的,也有可能是负的,就不能很好的让损失函数向0值收敛(因为当损失函数为0的时候,预测值=真实值,这是最想要的结果),所以我们要使用均方差(MSE)公式作为损失函数,因为它是一个二次函数,一定有一个最值,这个最值就我们要找的点,同时保证误差大于等于0,使得误差下降方向只有一个方向。
线性回归——更新模型
确定了损失函数
l
o
s
s
=
1
n
∑
i
=
0
n
(
y
i
−
y
^
i
)
2
=
1
n
∑
i
=
0
n
(
W
V
i
T
−
y
^
i
)
2
loss = {1\over n} \sum_{i=0}^n (y_i- \hat y_i)^2 = {1\over n} \sum_{i=0}^n (WV_i^T- \hat y_i)^2
loss=n1i=0∑n(yi−y^i)2=n1i=0∑n(WViT−y^i)2我们如何找到那个让函数值最小的W呢?找那个最合适W的过程,就是模型更新的过程。
如果将该损失函数展开,它就是一个多元的二次函数,对于一元二次函数我们总能找到一个最小值。观察可以发现,当损失函数的值最小时,函数的梯度也是最小的。于是,自然而然的想到使用梯度下降的方法找到梯度最小的点。
该损失函数的梯度怎么计算呢?
d
l
o
s
s
d
W
=
d
1
n
∑
i
=
0
n
(
W
V
i
T
−
y
^
i
)
2
d
W
{dloss\over dW} = {d{1\over n} \sum_{i=0}^n (WV_i^T- \hat y_i)^2\over dW}
dWdloss=dWdn1∑i=0n(WViT−y^i)2
因为W参数向量会有多个值,一般你只要掌握一个参数的推导过程就好,理解就好。实际应用的过程,有很多计算工具能够帮我们轻松实现梯度计算,比如pytorch中的autograd。这里想介绍的一种模型迭代的方法,就是使用矩阵运算。这种方法能减少算法的时间复杂度
- 假设有100个样本(或者更多的样本,但是可以将这些样本以100个为单位分组,每次只取100个,这就是block)。100个样的label矩阵是Y,维度是1*100;我们把100个样本的特征都提取出来(假设每个样本的特征是1*7),组成一个大的特征矩阵X,维度是100*7。然后进行矩阵计算
l o s s = S U M ( ( W X T − Y ) 2 ) / n loss = SUM((WX^T-Y)^2)/n loss=SUM((WXT−Y)2)/n
WX^T-Y的维度是1*100,然后使用一个矩阵求和公式,比如np.sum(),就很容易获得100个样本的损失值之和。 - 梯度计算,推导过程
如上图所示,使用公式
W = W − ( W X − Y ) X ∗ l e a r n i n g _ r a t e W = W-(WX-Y)X*learning\_rate W=W−(WX−Y)X∗learning_rate
一次性就将线性参数向量中的所有元素进行了更新。 - 接着将更新的参数向量在带入步骤1中,不断重复迭代。
线性回归——Demo
import cv2
import torch
import numpy as np
#label
labels = torch.tensor([0,1,2,3,4,5,6,7,8,9])
#初始化线性参数W
W = torch.rand(1,40,requires_grad=True)
#获得单个样本的特征
def get_feature(img_data):
'''
:param img_data: 图像数据
:return: 返回样本特征向量
'''
img_gray = cv2.cvtColor(img_data,cv2.COLOR_BGR2GRAY)
#二值化
img_er = img_gray/255.0
#特征是rows,cols中1的个数
rows = torch.sum(torch.from_numpy(img_er),0)
cols = torch.sum(torch.from_numpy(img_er),1)
#将行投影和列投影的结果,组合起来得到1*40维的特征向量
feature = torch.hstack((rows,cols))
feature = feature/torch.sum(feature)
#将feature转置,并将数据类型转为float
return torch.reshape(feature,shape=(feature.size(0),1)).float()
#获得所有样本的特征矩阵
def get_all_feature(dir_path):
'''
:param dir_path: 样本所在目录的路径
:return: 返回样本特征矩阵
'''
features = torch.empty(40,1).float()
for i in range(10):
img_path = dir_path + str(i) +'.png'
img = cv2.imread(img_path)
feature = get_feature(img)
features = torch.hstack((features,feature))
return features[:,1:]
#识别模型
def model(feature,W):
'''
:param feature: 图像特征
:param W: 模型参数
:return: 预测结果
'''
y = W.mm(feature) #先将feature从1*40转为40*1,然后feature与W求矩阵的乘
return y
#训练模型
def train_model(label,W,lr=0.05):
'''
:param label: 样本的标签集
:param W: 模型参数矩阵
:param lr: 学习率
:return: 返回训练完的模型参数W
'''
features = get_all_feature('./data/')
for epoch in range(3000):
#预测值
y = model(feature,W)
#loss function
loss = torch.sum(0.5*(y - label)*(y - label))
# 使用pytorch中的autograd可以跟踪函数解析过程,很容易求得导数
loss.backward()
# 更新W——使用梯度下降法
# W = W - d(loss)/d(W) * learning_rate
W.data = W.data - W.grad.data*lr
#必须用完梯度,将梯度清零
W.grad.data.zero_()
if epoch%100==0:
print('epoch=%s,loss=%s,W.is_leaf:%s'%(epoch,loss,W.is_leaf))
return W
if __name__ == '__main__':
model_w = train_model(labels,W,0.09)
print('_______________________')
img_test = cv2.imread('./test/0.png')
Y = model(get_feature(img_test),model_w)
print('实际值=%s,预测值=%s,Y.is_leaf:%s'%(0,Y,Y.is_leaf))
img_test = cv2.imread('./test/5.png')
Y = model(get_feature(img_test), model_w)
print('实际值=%s,预测值=%s,Y.is_leaf:%s'%(5,Y,Y.is_leaf))
img_test = cv2.imread('./test/3.png')
Y = model(get_feature(img_test), model_w)
print('实际值=%s,预测值=%s,Y.is_leaf:%s'%(3,Y,Y.is_leaf))
使用单个线性回归模型做多分类,结果肯定好不到哪里去。但这是基础,在机器学习的过程就是由简单到复杂的进化过程。
本文的demo上传到https://download.csdn.net/download/weilixin88/13183896,想要验证的同学自行下载(免积分的)。