摘要
本周对上周的学习内容中李宏毅老师举出来的例子用代码进行实现,学习了机器学习中模型差异与最优化问题的区别、解决办法以及batch和梯度下降相关的知识。
Abstract
This week, the examples given by Mr. Li Hongyi in last week’s learning content are implemented in code, and I learned the difference between model differences and optimization problems in machine learning, solutions, and knowledge related to batch and gradient descent.
1.机器学习
1.1 model bias和最优化
train数据集上的loss小,test数据集上的loss大,这就有可能是overfitting,在训练集数据不够时,会出现overfitting
解决办法:
①增加训练集
②数据增强
③早停法
- model bias
函数集合太小了,集合中没有包含一个函数,可以使得损失降低
解决办法:重新设计一个更灵活、有弹性、有更多未知参数的函数
- 最优化失败
训练时loss逐渐平稳或者训练过程中loss一直下不去,梯度下降时卡在了局部最小值/鞍点,找不到真正让损失很低的参数。
在settle point附近的loss方程为
L ( θ ) = L ( θ ′ ) + ( θ − θ ′ ) T g + 1 2 ( θ − θ ′ ) T H ( θ − θ ′ ) , g = ∇ L ( θ ′ ) , g i = δ L ( θ ′ ) δ θ i , H i j = δ 2 L ( θ ′ ) δ θ i δ θ j L(\theta)=L(\theta^{'})+(\theta-\theta^{'})^Tg+\frac{1}{2}(\theta-\theta^{'})^{T}H(\theta-\theta^{'}),g=\nabla L(\theta^{'}),g_i=\frac{\delta L(\theta^{'})}{\delta \theta_i},H_{ij}=\frac{\delta^{2}L(\theta^{'})}{\delta \theta_i \delta \theta_j} L(θ)=L(θ′)+(θ−θ′)Tg+21(θ−θ′)TH(θ−θ′),g=∇L(θ′),gi=δθiδL(θ′),Hij=δθiδθjδ2L(θ′)
第一项中, L ( θ ′ ) L(\theta') L(θ′),当 θ \theta θ跟 θ ′ \theta' θ′很近的时候, L L L很靠近
第二项中, g g g代表梯度(一阶导数),可以弥补 L ( θ ′ ) L(\theta') L(θ′)与 L ( θ ) L(\theta) L(θ)之间的差距; g g g的第i个component,就是θ的第i个component对L的微分
第三项中,
H
H
H表示海塞矩阵,是
L
L
L的二阶导数
在Critical point附近时:第二项为0,根据第三项来判断→只需考察H的特征值
其中 1 2 ( θ − θ ′ ) T H ( θ − θ ′ ) \frac{1}{2}(\theta-\theta^{'})^{T}H(\theta-\theta^{'}) 21(θ−θ′)TH(θ−θ′)可以写成二次型 v T H v v^{T}Hv vTHv
在一个critical point时,当
- 二次型 v T H v > 0 v^THv>0 vTHv>0 H正定时,局部最小值
- 二次型 v T H v < 0 v^THv<0 vTHv<0 H负定时,局部最大值
- 二次型 v T H v v^THv vTHv,H不正定也不负定,有时大于0,有时小于0,这个点就是一个鞍点
例如:模型 y = w 1 w 2 x y=w_1w_2x y=w1w2x
L = ( y ^ − w 1 w 2 x ) 2 = ( 1 − w 1 w 2 ) 2 , δ L δ w 1 = 2 ( 1 − w 1 w 2 ) ( − w 2 ) , δ L δ w 2 = 2 ( 1 − w 1 w 2 ) ( − w 1 ) L=(\hat{y}-w_1w_2x)^2=(1-w_1w_2)^2,\frac{\delta L}{\delta w_1}=2(1-w_1w_2)(-w_2),\frac{\delta L}{\delta w_2}=2(1-w_1w_2)(-w_1) L=(y^−w1w2x)2=(1−w1w2)2,δw1δL=2(1−w1w2)(−w2),δw2δL=2(1−w1w2)(−w1)
w 1 = 0 , w 2 = 0 w_1=0, w_2=0 w1=0,w2=0时就是Critical Point
H = [ δ 2 L δ w 1 2 = 2 ( − w 2 ) ( − w 2 ) δ 2 L δ w 1 δ w 2 = − 2 + 4 w 1 w 2 δ 2 L δ w 2 δ w 1 = − 2 + 4 w 1 w 2 δ 2 L δ w 2 2 = 2 ( − w 1 ) ( − w 1 ) ] H=\begin{bmatrix} \frac{\delta^2 L}{\delta w_1^{2}}=2(-w_2)(-w_2) & \frac{\delta^2 L}{\delta w_1\delta w_2}=-2+4w_1w_2 \\ \frac{\delta^2 L}{\delta w_2\delta w_1}=-2+4w_1w_2 & \frac{\delta^2 L}{\delta w_2^{2}}=2(-w_1)(-w_1) \end{bmatrix} H=[δw12δ2L=2(−w2)(−w2)δw2δw1δ2L=−2+4w1w2δw1δw2δ2L=−2+4w1w2δw22δ2L=2(−w1)(−w1)]
特征值为 λ 1 = 2 , λ = − 2 \lambda_1=2,\lambda=-2 λ1=2,λ=−2,有正有负,根据H矩阵判断这个点就是鞍点
H矩阵可以告诉我们参数更新的方向
u u u是H的一个特征向量, λ \lambda λ是 u u u的特征值,那么 u T H u = u T ( λ u ) = λ ∣ ∣ u 2 ∣ ∣ u^THu=u^T(\lambda u)=\lambda||u^2|| uTHu=uT(λu)=λ∣∣u2∣∣
在梯度为0,但是处于鞍点时,当 λ < 0 \lambda<0 λ<0,根据特征向量的方向去更新参数可以降低损失
区分两种情况:
①对于没见过的问题,那么可以先使用简单的网络进行训练或者支持向量机和线性模型初步判断是不是最优化问题,这些模型比较容易做最优化。
②在出现多层的网络模型loss高于低层的网络模型,那就是最优化问题。
1.2 Batch
在 Update 参数的时候,我们是拿B项样本数据出来,算Loss,算Gradient
所有的 Batch 看过一遍,叫做一个 Epoch
1.1 batch图
特别地,每个epoch开始前往往会重新分一次batch,每一个 Epoch 的 Batch 都不一样
1.2 样本图
- 左边的 Case,必须要把所有20笔 Example s 都看完以后,我们的参数才能够 Update 一次
- 左边是蓄力时间长,但是威力比较大,
- 右边的 Case,只需要一个样本数据就update一次参数。用一笔资料算出来的 Loss,显然是比较Noisy 的,所以我们今天 Update 的方向是“曲曲折折”的
- 右边技能冷却时间短,但是它是比较“不准”的
- ⇒Noisy 的 Gradient,反而可以帮助 Training
时间性能:“并行计算”左边(大的BatchSize)并不一定时间比较长
图1.3 并行计算
现象:
- Batch Size 是从1到1000,所需要的时间几乎是一样的,
- 增加到 10000,乃至增加到60000的时候,一个 Batch所要耗费的时间,确实有随着 Batch Size 的增加而逐渐增长
原因:
- 有 GPU,可以做并行运算,所以1000笔资料所花的时间,并不是一笔资料的1000倍。
- GPU 平行运算的能力还是有它的极限,当你的 Batch Size 真的非常非常巨大的时候,GPU 在跑完一个 Batch,计算出 Gradient 所花费的时间,还是会随著 Batch Size 的增加,而逐渐增长
对总时间的影响:
图1.4 时间对比
因为有并行运算的能力,因此实际上,当你的Batch Size 小的时候,你要跑完一个 Epoch,花的时间比大的 Batch Size 还要多的;反之,大的 Batch Size下,跑完一个Epoch花的时间反而是比较少的
Batch Size小时,Update的次数大大增加
结论1:使用较小的BatchSize,在更新参数时会有Noisy,有利于训练
结论2:使用较小的BatchSize,可以避免Overfitting,有利于测试(Testing)
在一个峡谷裡面的Local Minima 是坏的 Minima;然后它在一个平原上的Local Minima是好的 Minima
把 Training 的 Loss 往右平移,对于在一个盆地裡面的 Minima来说,它的在 Training 跟 Testing 上面的结果,不会差太多;对于在峡谷裡面的 Minima 来说,一差就可以天差地远,大的 Batch Size,会让我们倾向於走到峡谷裡面,而小的 Batch Size,倾向於让我们走到盆地裡面
- 小的 Batch有很多的 Loss,每次 Update 的方向都不太一样,如果这个峡谷非常地窄,它可能一个不小心就跳出去了,
- 大的 Batch Size,反正它就是顺著规定 Update,然后它就很有可能,走到一个比较小的峡谷裡面
BatchSize是一个需要调整的参数,它会影响训练速度与优化效果。
1.3 Momentum(动量)
图1.5 动量
Vanilla Gradient Descent(一般的梯度下降),只考虑梯度的方向,向反方向移动
图1.6 方向
Gradient Descent + Momentum(考虑动量),综合梯度+前一步的方向
所谓的 Momentum, Update 的方向不是只考虑现在的 Gradient,而是考虑过去所有 Gradient 的总合.
图1.7 动量更新
2.温度预测
李宏毅老师的例子是根据自己在youtube发布的机器学习频道的历史观看人数来预测未来的观看人数,数据集是单变量的,仅根据前一段时间的观看人数来预测。由于数据集没办法获取到同样的,于是我找到一个温度预测的单变量数据集,对这个例子进行了新的实现。
2.1线性模型
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
#训练集
df=pd.read_csv("train_temp.csv")
#测试集
test=pd.read_csv('test.csv')
#验证集
vaild=pd.read_csv('verify.csv')
def linear_model(x,w,b):
"""线性模型: y = w*x + b"""
return w * x + b
def mse_loss(y_true, y_pred):
"""均方误差损失函数"""
return np.mean((y_true - y_pred) ** 2)
def train(X, y, learning_rate=0.01, epochs=100):
# 初始化参数
w = 0.0
b = 0.0
n = len(X)
# 梯度下降
for epoch in range(epochs):
# 前向传播
y_pred = linear_model(X, w, b)
# 计算损失
loss = mse_loss(y, y_pred)
# 计算梯度
dw = (-2/n) * np.sum(X * (y - y_pred))
db = (-2/n) * np.sum(y - y_pred)
# 更新参数
w -= learning_rate * dw
b -= learning_rate * db
if epoch % 10 == 0:
print(f'Epoch {epoch}: loss={loss:.4f}, w={w:.4f}, b={b:.4f}')
return w, b
X_train=df['Prev_Temp'].values
y_train=df['Temp'].values
w, b = train(X_train, y_train, learning_rate=0.001, epochs=100)
print(f'\n训练完成: w={w:.4f}, b={b:.4f}')
last_temp = df['Temp'].iloc[-1]
# 5. 递归预测测试集
predictions = []
prev_temp = last_temp # 使用训练集最后一个温度作为起点
for date in test['Date']:
# 预测当前日期的温度
current_pred = linear_model(prev_temp, w, b)
# 保存预测结果
predictions.append(current_pred)
# 将当前预测值用作下一天的前一天温度
prev_temp = current_pred
test['Pred_Temp'] = predictions
test.to_csv('test_predictions.csv', index=False)
print("\n测试集预测结果:")
print(test.head(10))
vaild['Pred_Temp']=predictions
y_true = vaild['Temp'].values
y_pred = vaild['Pred_Temp'].values
valid_loss = mse_loss(y_true, y_pred)
print(f"\n验证集均方误差(MSE): {valid_loss:.4f}")
print(f"验证集均方根误差(RMSE): {np.sqrt(valid_loss):.4f}")
根据这个简单的线性模型训练出来的效果很差,MSE和RMSE的数值很大
根据前一天的值来预测的结果
验证集均方误差(MSE): 62.7683
验证集均方根误差(RMSE): 7.9226
采用前三十天的数据来预测
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# df=pd.read_csv('train.csv')
# df["Prev_Temp"]=df['Temp'].shift(1)
# df=df.dropna(subset=["Prev_Temp"])
# df.to_csv('train_temp.csv',index=False)
df=pd.read_csv("train_temp.csv")
test=pd.read_csv('test.csv')
vaild=pd.read_csv('verify.csv')
# test=test['Date']
# test.to_csv('test.csv',index=False)
def copy_features(df, num_lags=30):
"""创建滞后特征,使用前30天的温度作为特征"""
df_copy = df.copy()
for i in range(1, num_lags+1):
df_copy[f"lag_{i}"] = df_copy['Temp'].shift(i)
# 删除包含缺失值的行
df_copy = df_copy.dropna()
return df_copy
df=copy_features(df,num_lags=30)
print(df.head(5))
def mse_loss(y_true, y_pred):
"""均方误差损失函数"""
return np.mean((y_true - y_pred) ** 2)
# 3. 多元线性回归模型(30维特征)
class linear_model:
def __init__(self, input_size):
self.w = np.zeros(input_size) # 权重向量
self.b = 0.0 # 偏置项
def predict(self, X):
"""预测函数: y =\Sigma_i^{30}x+ b"""
return np.dot(X, self.w) + self.b
def train(self, X, y, learning_rate=0.001, epochs=100):
"""训练函数: 使用梯度下降"""
n = len(X)
history = []
for epoch in range(epochs):
# 前向传播
y_pred = self.predict(X)
# 计算损失
loss = mse_loss(y,y_pred)
history.append(loss)
# 计算梯度 (使用向量化操作)
dw = (1/n) * np.dot(X.T, (y_pred - y))
db = (1/n) * np.sum(y_pred - y)
# 更新参数
self.w -= learning_rate * dw
self.b -= learning_rate * db
if epoch % 20 == 0:
print(f'Epoch {epoch}: loss={loss:.4f}')
return history
lag_cols = [f'lag_{i}' for i in range(1, 31)]
X_train = df[lag_cols].values
y_train = df['Temp'].values
# 5. 训练模型
model = linear_model(input_size=30)
history = model.train(X_train, y_train, learning_rate=0.0001, epochs=100)
initial_window = df['Temp'].values[-30:].reshape(1, -1)
# 8. 递归预测测试集
predictions = []
current_window = initial_window.copy()
for date in test['Date']:
# 使用当前窗口预测下一天的温度
current_pred = model.predict(current_window)
predictions.append(current_pred[0])
# 更新窗口:移除最旧的数据,添加最新的预测值
current_window = np.roll(current_window, -1)
current_window[0, -1] = current_pred
# 9. 保存结果
test['Pred_Temp'] = predictions
test.to_csv('test_predictions.csv', index=False)
# 10. 评估验证集
vaild['Pred_Temp'] = predictions
y_true = vaild['Temp'].values
y_pred = vaild['Pred_Temp'].values
valid_loss = np.mean((y_true - y_pred) ** 2)
print(f"\n验证集均方误差(MSE): {valid_loss:.4f}")
print(f"验证集均方根误差(RMSE): {np.sqrt(valid_loss):.4f}")
根据前三十天的值来预测的结果
验证集均方误差(MSE): 34.6879
验证集均方根误差(RMSE): 5.8896
对比之下均方误差小了很多,但是与真实值的距离还是太远,简单模型的预测能力十分有限
2.2多参数模型
使用多个sigmoid函数来拟合温度预测
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
def sigmoid(x):
"""Sigmoid激活函数"""
return 1 / (1 + np.exp(-x))
def mse_loss(y_true, y_pred):
"""均方误差损失函数"""
return np.mean((y_true - y_pred) ** 2)
class linear_model:
def __init__(self, n_components=3, input_size=1):
"""
初始化模型
:param n_components: 使用的Sigmoid组件数量
:param input_size: 输入特征维度
"""
self.n_components = n_components
self.input_size = input_size
# 初始化参数
# 每个组件有: b_i, w_i, c_i
self.b_i = np.random.randn(n_components) * 0.1 # Sigmoid的偏置
self.w_i = np.random.randn(n_components, input_size) * 0.1 # Sigmoid的权重
self.c_i = np.random.randn(n_components) * 0.1 # Sigmoid的输出系数
self.b = 0.0 # 全局偏置
def forward(self, X):
"""前向传播计算预测值"""
# 计算每个组件的Sigmoid输出
sigmoid_outputs = np.zeros((X.shape[0], self.n_components))
for i in range(self.n_components):
# 计算线性部分: b_i + w_i * X
linear_part = self.b_i[i] + np.dot(X, self.w_i[i])
# 应用Sigmoid函数
sigmoid_outputs[:, i] = sigmoid(linear_part)
# 组合所有Sigmoid输出: Σ_i(c_i * sigmoid_output)
combined = np.dot(sigmoid_outputs, self.c_i) + self.b
return combined, sigmoid_outputs
def predict(self, X):
"""预测函数"""
y_pred, _ = self.forward(X)
return y_pred
def train(self, X, y, learning_rate=0.01, epochs=1000, verbose=True):
"""训练函数: 使用梯度下降"""
n = len(X)
history = []
for epoch in range(epochs):
# 前向传播
y_pred, sigmoid_outputs = self.forward(X)
# 计算损失
loss = mse_loss(y, y_pred)
history.append(loss)
# 计算梯度 (反向传播)
# 公共误差项
error = (y_pred - y) / n * 2 # dL/dy_pred
# 计算全局偏置的梯度
db = np.sum(error)
# 计算c_i的梯度
dc_i = np.dot(error, sigmoid_outputs)
# 计算b_i和w_i的梯度
db_i = np.zeros(self.n_components)
dw_i = np.zeros((self.n_components, self.input_size))
for i in range(self.n_components):
# Sigmoid的导数: sigmoid'(x) = sigmoid(x) * (1 - sigmoid(x))
sigmoid_deriv = sigmoid_outputs[:, i] * (1 - sigmoid_outputs[:, i])
# 计算b_i的梯度
db_i[i] = np.dot(error * self.c_i[i], sigmoid_deriv)
# 计算w_i的梯度
for j in range(self.input_size):
dw_i[i, j] = np.dot(error * self.c_i[i] * sigmoid_deriv, X[:, j])
# 更新参数
self.b -= learning_rate * db
self.c_i -= learning_rate * dc_i
self.b_i -= learning_rate * db_i
self.w_i -= learning_rate * dw_i
if verbose and epoch % 10 == 0:
print(f'Epoch {epoch}: loss={loss:.4f}')
return history
df = pd.read_csv("train_temp.csv")
test = pd.read_csv('test.csv')
valid = pd.read_csv('verify.csv')
X_train = df[['Prev_Temp']].values # 输入特征
y_train = df['Temp'].values # 目标温度
model = linear_model(n_components=3, input_size=1)
history = model.train(X_train, y_train,
learning_rate=0.001,
epochs=2000)
# 在训练集上评估
train_pred = model.predict(X_train)
train_mse = mse_loss(y_train, train_pred)
print(f"\n训练集评估:")
print(f"训练集MSE: {train_mse:.4f}")
print(f"训练集RMSE: {np.sqrt(train_mse):.4f}")
# 递归预测函数
def recursive_forecast(model, initial_temp, dates):
predictions = []
prev_temp = initial_temp
for _ in range(len(dates)):
current_pred = model.predict(np.array([[prev_temp]]))[0]
predictions.append(current_pred)
prev_temp = current_pred # 使用预测值作为下一次的输入
return np.array(predictions)
# 使用训练集最后一个温度作为初始值
last_temp = df['Temp'].iloc[-1]
# 验证集预测
valid_dates = valid['Date']
valid_predictions = recursive_forecast(model, last_temp, valid_dates)
valid['Pred_Temp'] = valid_predictions
# 验证集评估
y_true_valid = valid['Temp'].values
y_pred_valid = valid['Pred_Temp'].values
valid_mse = mse_loss(y_true_valid, y_pred_valid)
print(f"\n验证集评估:")
print(f"验证集MSE: {valid_mse:.4f}")
print(f"验证集RMSE: {np.sqrt(valid_mse):.4f}")
最终结果为
验证集MSE: 15.6092
验证集RMSE: 3.9508
采用多个激活函数的模型的loss小了很多,但是预测值后段不发生变化,可能是模型不适合这个问题,或者我没有理解这个问题。
总结
这周对机器学习基础进行实战,把公式用python代码实现一次,同时学习了梯度下降可能出现的问题、批次大小的影响和动量的作用。下周会对自适应学习率进行学习。