1. 相关知识
1.1 普通的梯度下降法
普通方法可能出现的问题
学习率太小(蓝色的线),下降慢,迭代次数多
学习率太大(绿色的线),下降快,但是太快容易在极优值附近振荡
学习率特别大(黄色的线),直接越过了极优值
改进的想法:随着迭代次数增加,让学习率变小。
初始迭代时,使用较大的学习率加速下降,迭代几次后,减小学习率防止振荡和越过。
1.2 Adagrad算法
每个参数的学习率都把它除上之前微分的均方根,σt是每个参数的所有偏微分的均方根
公式化简
求偏导细节参考第5节
# adagrad更新梯度
# 因为常数项的存在,所以dim需要多加一栏
# eps项是避免adagrad的分母为0而加的极小数值
# 每一个dim都会对应到各自的gradient, weight(w)
# 通过一次次的iteration学习
def adagrad(x, dim, num):
w = np.zeros([dim, 1])
x = np.concatenate((np.ones([num, 1]), x), axis = 1).astype(float)
learning_rate = 100
iter_time = 10000
adagrad = np.zeros([dim, 1])
eps = 0.0000000001
for t in range(iter_time):
loss = (np.sum(np.power(np.dot(x, w) - y, 2)) )/ num # mse
if(t%100==0):
print(str(t) + ":" + str(loss))
gradient = 2 * np.dot(x.transpose(), np.dot(x, w) - y) #大小为dim*1
adagrad += gradient ** 2
w = w - learning_rate * gradient / np.sqrt(adagrad + eps)
np.save('weight.npy', w)
2. 作业描述
根据某地一年内18项空气质量有关的检测指标,在24小时的变化数据,使用线性回归预测PM2.5(也是指标之一)的变化。
数据如图所示
训练集:每个月前20天的完整数据
测试集:从剩下的资料中(10 * 12天)中抽出240 * 10小时的数据作为测试
每条数据中,前9小时的观测数据作为feature,共9 * 18 = 162个参数,第10小时的PM2.5作为answer
3. 数据预处理
指标RF对应当天是否降雨,有降雨则为1,无降雨值为NR
考虑降雨对PM2.5有显著影响,将NR全部修改为0
去除无用数据,将pandas数据帧转换为numpy的二维数组
然后抽取训练数据
参考答案这里有些小错误
# 数据预处理
def dataProcess(data):
# 第一步,数据预处理
# 去除前3列的无用数据
data = data.iloc[1:, 3:]
# 将未降雨的值"NR"修改为0
data[data == 'NR'] = 0
raw_data = data.to_numpy()
return raw_data
4. 建立模型
线性模型
取损失函数为MSE
5. 损失函数求导
先将模型改成矩阵形式:
为了便于用numpy写代码,将常数项也看作系数矩阵的一部分。
这里参考了西瓜书的解答:
求偏导公式:
实际上基于均方误差最小化的最小二乘法,可以直接求其闭式最优解。
6. 完整代码
# -*- coding: utf-8 -*-
import sys
import pandas as pd
import numpy as np
import csv
# 数据预处理
def dataProcess(data):
# 第一步,数据预处理
# 去除前3列的无用数据
data = data.iloc[1:, 3:]
# 将未降雨的值"NR"修改为0
data[data == 'NR'] = 0
raw_data = data.to_numpy()
return raw_data
# 标准化
def normalize(x):
mean_x = np.mean(x, axis = 0)
std_x = np.std(x, axis = 0)
for i in range(len(x)):
for j in range(len(x[0])):
if std_x[j] != 0:
x[i][j] = (x[i][j] - mean_x[j]) / std_x[j]
return x
# adagrad更新梯度
# 因为常数项的存在,所以dim需要多加一栏
# eps项是避免adagrad的分母为0而加的极小数值
# 每一个dim都会对应到各自的gradient, weight(w)
# 通过一次次的iteration学习
def adagrad(x, dim, num):
w = np.zeros([dim, 1])
x = np.concatenate((np.ones([num, 1]), x), axis = 1).astype(float)
learning_rate = 100
iter_time = 10000
adagrad = np.zeros([dim, 1])
eps = 0.0000000001
for t in range(iter_time):
loss = (np.sum(np.power(np.dot(x, w) - y, 2)) )/ num # mse
if(t%100==0):
print(str(t) + ":" + str(loss))
gradient = 2 * np.dot(x.transpose(), np.dot(x, w) - y) #dim*1
adagrad += gradient ** 2
w = w - learning_rate * gradient / np.sqrt(adagrad + eps)
np.save('weight.npy', w)
return w # 只是方便在内存中查看结果
# 18个feature中的第10个为要预测的PM2.5
# 將原始 4320 * 18 的資料重組成 18 (features) * 4320 (hours) 的資料。
# 12个月,每个月20天 12 * 20 * 18 = 4320
if __name__ == '__main__':
data = pd.read_csv('train.csv', header = None, encoding = 'big5')
raw_data = dataProcess(data)
# 每九小时形成一条训练data
# 考虑PM2.5每日会有周期性的变化规律,比如工厂不会昼夜不停等
#(答案思路非常清奇,用0-8预测9,然后1-9预测10,会充分利用一个月数据中的480小时,得到471个训练数据)
# 选择用0 - 8点的数据来预测上午9点的PM2.5
# 权重项有18*9 = 162项
raw_data = raw_data[:,0:10]
x = np.empty([12 * 20, 18 * 9], dtype = float)
y = np.empty([12 * 20, 1], dtype = float)
for day in range(240):
y[day,0] = raw_data[18*day+9,9]
for day in range(240):
x[day,:] = raw_data[18*day:18*(day+1),0:9].reshape(1, -1)
x = normalize(x) # 训练数据标准化
dim = 18 * 9 + 1 # 加上截距项
num = 240 # 训练数据共12 * 20 = 240条
w = adagrad(x, dim, num)
# 用训练出来的w,计算y
data_test = pd.read_csv('test.csv', header = None, encoding = 'big5')
data_test = data_test.iloc[:, 2:]
data_test[data_test == 'NR'] = 0
data_test = data_test.to_numpy()
test_x = np.empty([240, 18*9], dtype = float)
for i in range(240):
test_x[i, :] = data_test[18 * i: 18* (i + 1), :].reshape(1, -1)
test_x = np.concatenate((np.ones([240, 1]), test_x), axis = 1).astype(float)
# 测试数据要和训练数据作相同的标准化
test_x = normalize(test_x)
ans_y = np.dot(test_x, w)
# 把预测结果存入csv
with open('submit.csv', mode='w', newline='') as submit_file:
csv_writer = csv.writer(submit_file)
header = ['id', 'value']
print(header)
csv_writer.writerow(header)
for i in range(240):
row = ['id_' + str(i), ans_y[i][0]]
csv_writer.writerow(row)
print(row)