首先粗略的讲一下原理,如果这部分你看起来有难度,建议去看看西瓜书或者其他作者的博客。
我们知道线性回归的损失函数如下:
L o s s = 1 N ∑ i = 1 N ( y i − ( w x i + b ) ) 2 Loss=\frac{1}{N}\sum_{i=1}^N(y_i-(wx_i+b))^2 Loss=N1i=1∑N(yi−(wxi+b))2
我们所要做的是:
通过寻找最优的参数 w , b w,b w,b,使得上述损失函数最小。
你可以通过求偏导来实现,但是这种方法的适应范围在机器学习领域十分有限,所以我更推荐你使用更为通用的方法:梯度下降法。
所谓梯度下降法,是通过让参数通过梯度去更新自身。显然,损失函数对两个参数的梯度分别为:
∂ L o s s ∂ w = − 2 N ∑ i = 1 N x i ( y i − ( w x i + b ) ) \frac{\partial Loss}{\partial w}=-\frac{2}{N}\sum_{i=1}^Nx_i(y_i-(wx_i+b)) ∂w∂Loss=−N2i=1∑Nxi(yi−(wxi+b))
∂ L o s s ∂ b = − 2 N ∑ i = 1 N ( y i − ( w x i + b ) ) \frac{\partial Loss}{\partial b}=-\frac{2}{N}\sum_{i=1}^N(y_i-(wx_i+b)) ∂b∂Loss=−N2i=1∑N(yi−(wxi+b))
以参数w为例,其更新的策略为:
w = w − ∂ L o s s ∂ w w=w-\frac{\partial Loss}{\partial w} w=w−∂w∂Loss
其余参数类似。
首先,作为一个类,该模型需要传入一些必要的参数,比如特征与目标:
class LinearRegression:
def __init__(self, data, target, ):
self.data = data
self.target = target
n_feature = data.shape[1] # 特征个数
self.w = np.zeros(n_feature)
self.b = np.zeros(1)
作为一个机器学习模型,预测模块是必不可少的,也是最基础的模块,这在神经网络中被称为前向传播模块:
def predict(self):
out = self.data.dot(self.w) + self.b
return out
在这个模块中,我们计算当前损失函数对当前参数的梯度,为此,我们需要使用第一节中的两个梯度公式:
∂ L o s s ∂ w = − 2 N ∑ i = 1 N x i ( y i − ( w x i + b ) ) \frac{\partial Loss}{\partial w}=-\frac{2}{N}\sum_{i=1}^Nx_i(y_i-(wx_i+b)) ∂w∂Loss=−N2i=1∑Nxi(yi−(wxi+b))
∂ L o s s ∂ b = − 2 N ∑ i = 1 N ( y i − ( w x i + b ) ) \frac{\partial Loss}{\partial b}=-\frac{2}{N}\sum_{i=1}^N(y_i-(wx_i+b)) ∂b∂Loss=−N2i=1∑N(yi−(wxi+b))
代码:
def gradient(self):
"""计算损失函数对w,b的梯度"""
sample_num = self.data.shape[0] # 发样本个数
dw = (-2 / sample_num) * np.sum(self.data.T.dot(self.target - self.predict()))
db = (-2 / sample_num) * np.sum(self.target - self.predict())
return dw, db
在这个模块中,通过梯度下降法去更新参数,实际上这也是训练(学习)的过程,既然是训练(学习),则必须要有训练的次数,即max_iter和学习率alpha:
def train(self, alpha=0.01, max_iter=200):
"""训练"""
loss_history = []
for i in range(max_iter):
dw, db = self.gradient()
self.w -= alpha * dw
self.b -= alpha * db # 更新权重
now_loss = self.loss()
loss_history.append(now_loss)
return loss_history
如果你足够细心,你可能已经发现,在train模块中,有一个self.loss()方法,这个方法是用来计算当前预测值与真实值之间的误差(损失)的:
def loss(self):
"""误差,损失"""
sample_num = self.data.shape[0]
pre = self.predict()
loss = (1 / sample_num) * np.sum((self.target - pre) ** 2)
return loss
通过这个方法,将每次迭代得到的误差记录下来,便于我们观察训练情况,不仅如此,如果你对神经网络有所了解,你会发现这是一种非常常见的好方法。
选取你的训练集,将其传入实例:
my_lr = LinearRegression(data=x_train_s, target=y_train)
loss_list = my_lr.train()
plt.plot(np.arange(len(loss_list)),loss_list,'r--')
plt.xlabel('iter num')
plt.ylabel('$Loss$')
plt.show()
也许你的程序会抛出这样的警告:
RuntimeWarning: overflow encountered in multiply
return 2 * self.X.T.dot(self.X.dot(self.w_hat) - self.y)
那是因为你可能忘记将你的数据进行标准化处理了,因为线性回归是要求样本近似服从正态分布的。
除此之外,还有其他归一化的好处:(摘自https://blog.csdn.net/weixin_43772533/article/details/100826616)
理论层面上,神经网络是以样本在事件中的统计分布概率为基础进行训练和预测的,所以它对样本数据的要求比较苛刻。具体说明如下:
归一化可以避免一些不必要的数值问题。因为激活函数sigmoid/tanh的非线性区间大约在[-1.7,1.7]。意味着要使神经元有效,线性计算输出的值的数量级应该在1(1.7所在的数量级)左右。这时如果输入较大,就意味着权值必须较小,一个较大,一个较小,两者相乘,就引起数值问题了。
若果输出层的数量级很大,会引起损失函数的数量级很大,这样做反向传播时的梯度也就很大,这时会给梯度的更新带来数值问题。
知道梯度非常大,学习率就必须非常小,因此,学习率(学习率初始值)的选择需要参考输入的范围,不如直接将数据归一化,这样学习率就不必再根据数据范围作调整。 对w1适合的学习率,可能相对于w2来说会太小,若果使用适合w1的学习率,会导致在w2方向上步进非常慢,会消耗非常多的时间,而使用适合w2的学习率,对w1来说又太大,搜索不到适合w1的解。
by——神采的二舅