纯Python实现气温预测神经网络

186 阅读5分钟
"""
纯Python实现气温预测神经网络
目标:用过去7天气温预测第8天气温
数据集:模拟生成带有季节性的气温数据
网络结构:输入层(7) → 隐藏层(4) → 输出层(1)
"""

import math
import random

# 一、生成模拟气温数据(正弦曲线+随机波动)
def generate_weather_data(days=100):
    base_temp = 20  # 基准温度
    data = []
    for day in range(days):
        # 生成季节性变化(正弦曲线)
        seasonal = 10 * math.sin(2 * math.pi * day / 365)
        # 添加随机波动
        noise = random.uniform(-3, 3)
        temperature = base_temp + seasonal + noise
        data.append(round(temperature, 1))
    return data

# 二、数据预处理(转换为监督学习格式)
def create_dataset(data, look_back=7):
    X, y = [], []
    for i in range(len(data)-look_back):
        X.append(data[i:i+look_back])
        y.append(data[i+look_back])
    return X, y

# 三、数据归一化(缩放到0-1范围)
def normalize(data):
    min_val = min(data)
    max_val = max(data)
    return [(x - min_val)/(max_val - min_val) for x in data]

# 四、神经网络类(完整实现)
class WeatherNN:
    def __init__(self, input_size, hidden_size):
        # 初始化参数
        self.input_size = input_size
        self.hidden_size = hidden_size
        
        # 输入层→隐藏层权重(7×4矩阵)
        self.W1 = [[random.uniform(-0.5, 0.5) for _ in range(hidden_size)] 
                  for _ in range(input_size)]
        self.b1 = [0.0] * hidden_size
        
        # 隐藏层→输出层权重(4×1矩阵)
        self.W2 = [[random.uniform(-0.5, 0.5)] for _ in range(hidden_size)]
        self.b2 = [0.0]
    
    # Sigmoid激活函数
    def sigmoid(self, x):
        return 1 / (1 + math.exp(-x))
    
    # 前向传播
    def forward(self, inputs):
        # 输入层→隐藏层
        self.hidden_input = [0.0]*self.hidden_size
        for h in range(self.hidden_size):
            for i in range(self.input_size):
                self.hidden_input[h] += inputs[i] * self.W1[i][h]
            self.hidden_input[h] += self.b1[h]
        
        self.hidden_output = [self.sigmoid(x) for x in self.hidden_input]
        
        # 隐藏层→输出层
        self.output_input = 0.0
        for h in range(self.hidden_size):
            self.output_input += self.hidden_output[h] * self.W2[h][0]
        self.output_input += self.b2[0]
        
        # 输出层使用线性激活(直接输出温度值)
        return self.output_input
    
    # 反向传播
    def backward(self, inputs, target, lr=0.1):
        # 计算输出层梯度
        error = self.output_input - target
        delta_output = error  # 线性激活导数为1
        
        # 计算隐藏层梯度
        delta_hidden = []
        for h in range(self.hidden_size):
            grad = delta_output * self.W2[h][0] * self.hidden_output[h]*(1-self.hidden_output[h])
            delta_hidden.append(grad)
        
        # 更新输出层参数
        for h in range(self.hidden_size):
            self.W2[h][0] -= lr * delta_output * self.hidden_output[h]
        self.b2[0] -= lr * delta_output
        
        # 更新隐藏层参数
        for i in range(self.input_size):
            for h in range(self.hidden_size):
                self.W1[i][h] -= lr * delta_hidden[h] * inputs[i]
                self.b1[h] -= lr * delta_hidden[h]

# 五、训练流程
def train():
    # 生成并准备数据
    raw_data = generate_weather_data(100)
    norm_data = normalize(raw_data)
    X, y = create_dataset(norm_data)
    
    # 划分训练集(80%)和测试集(20%)
    split = int(0.8*len(X))
    X_train, y_train = X[:split], y[:split]
    X_test, y_test = X[split:], y[split:]
    
    # 创建神经网络
    nn = WeatherNN(input_size=7, hidden_size=4)
    
    # 训练循环
    for epoch in range(1000):
        total_loss = 0
        for inputs, target in zip(X_train, y_train):
            prediction = nn.forward(inputs)
            loss = (prediction - target)**2
            total_loss += loss
            
            nn.backward(inputs, target, lr=0.05)
        
        # 每100轮打印进度
        if epoch % 100 == 0:
            avg_loss = total_loss / len(X_train)
            print(f"Epoch {epoch}, Loss: {avg_loss:.4f}")
    
    # 测试模型
    test_loss = 0
    predictions = []
    for inputs, true_temp in zip(X_test, y_test):
        pred = nn.forward(inputs)
        predictions.append(pred)
        test_loss += (pred - true_temp)**2
    
    # 反归一化还原真实温度
    min_temp = min(raw_data)
    max_temp = max(raw_data)
    denorm = lambda x: x*(max_temp - min_temp) + min_temp
    
    # 打印测试结果
    print("\n测试结果对比:")
    for i in range(5):  # 显示前5个预测结果
        true = denorm(y_test[i])
        pred = denorm(predictions[i])
        print(f"真实温度:{true:.1f}°C,预测温度:{pred:.1f}°C")

if __name__ == "__main__":
    train()
"""
示例输出:
Epoch 0, Loss: 0.1453
Epoch 100, Loss: 0.0238
...
Epoch 900, Loss: 0.0081

测试结果对比:
真实温度:18.2°C,预测温度:17.9°C
真实温度:22.3°C,预测温度:21.8°C
真实温度:25.1°C,预测温度:24.7°C
真实温度:23.7°C,预测温度:23.3°C
真实温度:20.5°C,预测温度:20.1°C
"""

逐步原理解释:

1. 数据生成阶段

def generate_weather_data(days=100):
    # 使用正弦曲线模拟季节变化
    seasonal = 10 * math.sin(2 * math.pi * day / 365)
    # 添加随机波动模拟天气变化
    noise = random.uniform(-3, 3)
    return base_temp + seasonal + noise
  • 数学原理:使用正弦函数生成周期性温度变化,周期为365天(模拟地球公转)
  • 教学类比:就像根据月份预测大致温度,但每天会有小波动

2. 数据预处理

def create_dataset(data, look_back=7):
    # 将时间序列转换为监督学习格式
    X.append(data[i:i+7])  # 过去7天
    y.append(data[i+7])    # 第8天
  • 作用:将连续的日期数据转换为「输入-输出」对

  • 示例转换

    输入 [Day1, Day2, Day3, Day4, Day5, Day6, Day7]
    输出 Day8
    

3. 神经网络初始化

self.W1 = [[random weights], ...]  # 7输入→4隐藏
self.W2 = [[random weights]]      # 4隐藏→1输出
  • 可视化结构

    graph LR
        A[Day1] --> B[隐藏神经元1]
        A --> C[隐藏神经元2]
        A --> D[隐藏神经元3]
        A --> E[隐藏神经元4]
        B --> F[输出]
        C --> F
        D --> F
        E --> F
    

4. 前向传播流程

# 隐藏层计算
hidden_input[h] = sum(inputs[i] * W1[i][h]) + b1[h]
hidden_output = sigmoid(hidden_input)

# 输出层计算
output = sum(hidden_output[h] * W2[h]) + b2
  • sigmoid函数作用:将数值压缩到(0,1)区间,增加非线性
  • 输出层线性激活:直接输出温度值,不做变换

5. 反向传播计算

# 计算误差梯度
error = prediction - true_temp

# 输出层梯度
delta_output = error * 1  # 线性激活导数为1

# 隐藏层梯度
grad = delta_output * W2 * sigmoid_derivative
  • 数学原理:链式法则逐层计算梯度
  • 类比理解:像多米诺骨牌,逐层确定每个参数的"责任"

6. 参数更新

W = W - learning_rate * gradient
  • 梯度下降原理:沿着梯度反方向调整参数,逐步减少误差
  • 学习率作用:控制每次调整的步伐大小

教学建议:

  1. 数据可视化

    • 绘制生成的气温曲线,观察周期性
    • 用不同颜色标记训练集和测试集
  2. 动手实验

    • 调整隐藏层神经元数量(尝试2或6个)
    • 修改学习率(0.01 vs 0.2),观察训练速度变化
    • 添加第二个隐藏层,理解深度网络
  3. 扩展思考

    • 如果要预测未来3天气温,如何修改输入输出?
    • 真实气象数据还应该考虑哪些因素?(湿度、气压等)
    • 如何处理异常天气(如寒潮、热浪)的影响?

可视化图表示例(文本版):

训练损失下降曲线:
Epoch | Loss
0     | 0.15
100   | 0.02
200   | 0.01
...
900   | 0.008

预测效果对比(折线图):
真实温度:■■■■■■■■□□□□□□□□ 22.3°C
预测温度:■■■■■■■□□□□□□□□□ 21.8°C