基于回归任务的气温预测

回归任务:通过输入一些样本数据或者特征,经过多层神经网络,最后能得到一个预测值。

1.气温数据处理

1.数据加载

1
2
3
4
5
6
7
def load_csv():  
path = Path("data")
filename = "temps.csv"
return pd.read_csv(path / filename)

print(f"shape:{features.shape}\n columns:{features.columns}")
print(features[:5]) # 打印前5条数据
1
2
3
4
5
6
7
8
9
10
shape:(348, 9)

columns:Index(['year', 'month', 'day', 'week', 'temp_2', 'temp_1', 'average', 'actual', 'friend'], dtype='object')

year month day week temp_2 temp_1 average actual friend
0 2016 1 1 Fri 45 45 45.6 45 29
1 2016 1 2 Sat 44 45 45.7 44 61
2 2016 1 3 Sun 45 44 45.8 41 56
3 2016 1 4 Mon 44 41 45.9 40 53
4 2016 1 5 Tues 41 40 46.0 44 41

temps.csv是个348 * 9的数据,temp_2是那一天对应的前天的最高温度,temp_1是那一天对应的昨天的最高温度,average历史上相同monthday的平均最高温度,actual当天实际问题,也是真实标签值,friend朋友预测的最高温度。

简而言之,用year month day week temp_2 temp_1 average friend对应的数据预测actual对应的。

2.独热编码

1
2
3
features = load_csv()
features = pd.get_dummies(features)
print(features[:5])

独热编码(One-Hot Encoding)将每个类别转换为一个独立的二进制向量,有以下几个特点:

  • 无序特征表示:用于无顺序关系的类别特征。例如,颜色,形状
  • 维度增加:每个类别会增加一列,维度的增加,可能会增加计算的复杂性
  • 稀疏矩阵:生成的矩阵通常是稀疏的,即大多数值都是 0。因此,在某些情况下,可以使用稀疏矩阵来节省内存。
  • 没有信息损失:每个类别都被完整地表示为一个向量,没有丢失任何关于类别的信息
  • 不引入大小关系:不会误导模型认为类别之间存在某种顺序或大小关系。例如,Red、Green 和 Blue 都被等价地表示为三个不同的向量,而不是 Red > Green > Blue 之类的关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
编码前
week
0 Fri
1 Sat
2 Sun
3 Mon
4 Tues
编码后
week_Fri week_Mon week_Sat week_Sun week_Thurs week_Tues week_Wed
0 True False False False False False False
1 False False True False False False False
2 False False False True False False False
3 False True False False False False False
4 False False False False False True False

3.日期转化与数据绘制

1
2
3
4
5
6
# 转换日期  -> datetime对象
def handle_datetime(features):
years = features['year']
months = features['month']
days = features['day']
return [datetime.datetime(year, month, day) for year, month, day in zip(years, months, days)]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def draw_csv(dates, features):  
# 指定默认风格
plt.style.use('fivethirtyeight')

# 设置布局
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2, figsize=(10, 10))
fig.autofmt_xdate(rotation=45)

# 当天最高温度(标签值)
ax1.plot(dates, features['actual'])
ax1.set_xlabel('')
ax1.set_ylabel('Temperature')
ax1.set_title('Max Temp')

# 昨天最高温度
ax2.plot(dates, features['temp_1'])
ax2.set_xlabel('')
ax2.set_ylabel('Temperature')
ax2.set_title('Previous Max Temp')

# 前天最高温度
ax3.plot(dates, features['temp_2'])
ax3.set_xlabel('Date')
ax3.set_ylabel('Temperature')
ax3.set_title('Two Days Prior Max Temp')

# 朋友预测最高温度
ax4.plot(dates, features['friend'])
ax4.set_xlabel('Date')
ax4.set_ylabel('Temperature')
ax4.set_title('Friend Estimate')

plt.tight_layout(pad=2)
plt.show()

4.数据处理

1
2
3
4
5
6
from sklearn import preprocessing

def get_train_data(features):
data = features.drop('actual', axis=1) # 移除真实标签值,留下特征值
data = np.array(data) # 转换成用于数值计算的高效数据结构的NumPy数组
return preprocessing.StandardScaler().fit_transform(data) # 数据变化,适应训练

preprocessing.StandardScaler(): 对特征数据进行标准化,将数据的均值调整为 0,标准差调整为 1。对于特征的取值范围差异较大时,模型可能会对大范围的特征更敏感,标准化可以使得每个特征具有相似的尺度,从而提高模型训练的效果。另外在数据标准化后一些优化算法(如梯度下降法)的收敛速度会加快。

2.模型定义

1.构造参数

input_size: 输入特征数量
hidden_size: 隐藏层的神经元数量
output_size:输出的大小(在回归任务中通常是 1,表示预测值)

2.全连接层

nn.Linear(input_size, hidden_size) 创建了一个 全连接层,将输入数据从 input_size 维度映射到 hidden_size 维度。即:
• 输入层的特征数为 input_size,每个样本有 input_size个特征
• 隐藏层有hidden_size个神经元

nn.Linear(hidden_size, output_size) 另一个全连接层,它将隐藏层的输出从 hidden_size 维度映射到 output_size 维度。对于回归任务,output_size 通常是 1,表示预测一个连续值。

3.激活函数

nn.Sigmoid()Sigmoid激活函数,对隐藏层的输出进行非线性变换。Sigmoid 函数的输出范围是 (0, 1),它通常用于二分类任务中的输出层,或者用作隐藏层的激活函数来引入非线性。

4.前向传播

forward() 定义了数据在网络中的流动方式。每当数据通过模型时,都会调用这个方法。

  • 输入数据 x 通过第一层 fc1(即全连接层)进行计算。输出是大小为 hidden_size 的向量
  • 经过全连接层计算后,输出将通过 Sigmoid 激活函数,引入非线性变换
  • 隐藏层的输出 x 再通过第二层 fc2(即另一个全连接层)进行计算,得到最终的输出。此时,输出是大小为 output_size 的向量
1
2
3
4
5
6
7
8
9
10
11
class Weather_forecast_NN(nn.Module):  
def __init__(self, input_size, hidden_size, output_size):
super(Weather_forecast_NN, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size) # 第一层:输入层 -> 隐藏层
self.sigmoid = nn.Sigmoid() # Sigmoid 激活函数
self.fc2 = nn.Linear(hidden_size, output_size) # 第二层:隐藏层 -> 输出层
def forward(self, x):
x = self.fc1(x) # 第一层
x = self.sigmoid(x) # 激活函数
x = self.fc2(x) # 第二层
return x

3.模型训练、验证

1.损失函数

torch.nn.MSELoss(reduction='mean') 均方误差(MSE)损失函数,表示计算损失时将所有样本的损失求平均值,常用于回归任务,衡量预测值与真实值之间的差距。

2.优化器

 Adam 优化器用于一种自适应优化算法,能够在训练过程中调整学习率,用于优化模型参数,学习率设置为0.001,为何设置0.001?因为0.001是一个比较保守的学习率值,它既不会太大导致不稳定,也不会太小导致收敛过慢,因此通常能够平衡训练速度与稳定性。 

3.Mini-batch

 将训练数据分成多个小批次(mini-batches),并在每个小批次上执行一次梯度更新。这样做的好处是避免了批量梯度下降的高计算开销,同时还能减少随机梯度下降的噪声,具有较好的稳定性和计算效率。
优点:

  1. 内存高效:每次仅使用小批次的数据进行计算,适合大规模数据集
  2. 更稳定的训练过程:相比随机梯度下降,mini-batch 提供了更平滑的更新,使得训练过程更为稳定
  3. 利用并行计算:小批量训练可以很好地与 GPU 等硬件加速工具配合,提高训练效率
  4. 较快的收敛速度:由于每次更新的样本数较多,相比单样本更新(SGD),mini-batch 方法通常能更快收敛

缺点:

  1. 收敛不如批量梯度下降精确:每次更新基于一个小批量数据,梯度计算不如批量梯度下降精确
  2. 选择批次大小难度:需要根据任务、数据量和硬件等因素选择合适的 mini-batch 大小。如果选择不当,可能会导致训练不稳定或收敛过慢

4.tensor转换

提取当前批次的输入数据,转换为 PyTorch 张量requires_grad=True 表示我们需要计算该数据的梯度。不需要为目标 yy 设置 requires_grad,因为它不是模型的参数,yy中使用 .view(-1, 1) 将其形状调整为 (batch_size, 1),确保目标数据与模型输出的形状一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def train(train_data, valid_data):  
input_size = train_data.shape[1] # 每个样本特征数
hidden_size = 128 # 隐藏层的神经元数量
output_size = 1 # 输出层的神经元数量
batch_size = 16 # 每次更新模型时使用 16 个样本的数据
model = Weather_forecast_NN(input_size, hidden_size, output_size)
mse_loss = torch.nn.MSELoss(reduction='mean')
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

losses = []
for i in range(1000):
batch_loss = []
# MINI-Batch方法来进行训练
for start in range(0, len(train_data), batch_size):
end = start + batch_size if start + batch_size < len(train_data) else len(train_data)
xx = torch.tensor(train_data[start:end], dtype=torch.float32, requires_grad=True)
yy = torch.tensor(valid_data[start:end], dtype=torch.float32).view(-1, 1)
prediction = model(xx) # 获取模型预测值
loss = mse_loss(prediction, yy) # 计算模型预测值和真实标签之间的均方误差损失
optimizer.zero_grad() # 清空之前计算的梯度
loss.backward() # 反向传播:自动计算每个参数的梯度
optimizer.step() # 使用优化器更新模型的参数(权重和偏置)
batch_loss.append(loss.data.numpy()) # 保存损失值并转化为NumPy数组

# 打印损失
if i % 100 == 0: # 每 100 次迭代打印一次损失
losses.append(np.mean(batch_loss))
print(i, np.mean(batch_loss))

return model

4.模型测试

使用matplotlib绘制出真实天气与预测天气的对比图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 预测值和实际值对比图
def draw_valid_and_predictions_data(dates, model, train_data, valid_data):

x = torch.tensor(train_data, dtype=torch.float)
predict = model(x).data.numpy()
# 实际值
plt.plot(dates, valid_data, 'b-', label='actual')
# 预测值
plt.plot(dates, predict, 'ro', label='prediction')
plt.xticks(rotation=60)
plt.legend()
# 绘图配置
plt.xlabel('Date')
plt.ylabel('Maximum Temperature (F)')
plt.title('Actual and Predicted Values')
plt.show()

5.总结

本文主要讲的是一个基于神经网络的天气预测模型,通过读取天气数据、预处理、模型训练展示了如何使用神经网络来解决一个回归问题,涉及知识点:

  1. 数据标准化处理,对训练数据的特征进行归一化处理,使得每个特征具有均值为 0,标准差为 1,减少特征尺度差异对训练过程的影响
  2. 数据可视化,matplotlib绘制气温变化图和对比图
  3. 神经网络模型,一个简单的前馈神经网络(1 个隐藏层包含128个神经元)
  4. 损失函数与优化器
    • 损失函数:使用 均方误差(MSE)来衡量模型的预测结果与真实结果之间的差距
    • 优化器:使用 Adam 优化器,这是一种自适应优化算法,能够动态调整学习率,通常能带来更好的训练效果

7.备注

环境:

  • mac: 15.2
  • python: 3.12.4
  • pytorch: 2.5.1
  • matplotlib: 3.8.4
  • numpy: 1.26.4
  • panda: 2.2.2

数据集:
https://github.com/keychankc/dl_code_for_blog/tree/main/002_nn_weather_forecast/data

完整代码:
https://github.com/keychankc/dl_code_for_blog/tree/main/002_nn_weather_forecast