PM2.5预测是李宏毅老师机器学习课程第一个作业,要求实现linear regression预测PM2.5的数值。在第一个线性回归的作业中遇到了很多难点,第一是虽然给出了数据集,但是要对数据集做预处理,整理出需要的数据,并保存到矩阵中和对矩阵的操作,将原始数据依照每个月份重组成 12 个 18 (特征) * 480 (小时) 的资料。第二是训练模型,需要用adagrad算法来更新学习率,用梯度下降的方法来更新参数并计算loss。第三是对numpy库中函数的了解和运用。因为刚开始接触,主要是去理解分析老师给出的代码学习思路然后仿写,不懂的地方看课程里的代码讲解和一些相关的博客或者问同学朋友。
训练集是:12个月–>每月的前20天–>每天的24小时–>每小时的18种不同属性的数值
测试集:240天–>连续的9小时–>18种不同的属性
要求的输出:根据某天连续的9小时–>18种不同的属性,预测第10小时的PM2.5的值。也就是通过一个18*9的矩阵预测出来一个值
可以分为八个部分:
1.预处理
2.提取特征
3.归一化
4.将训练数据分为训练集和验证集
5.训练
6.测试
7.预测
8.保存预测到CSV文件
数据的第一行是说明,分别是时间,地点,各空气中物质的名称,以及0-23时他们的浓度。
RAINFALL的值是NR,表示没有下雨,所以把它的值改为0方便处理。第一步是数据预处理,因为这里数据是通过矩阵来保存的,第一步就是删减掉不需要的行与列,然后将其保存到矩阵中。
import pandas as pd
import numpy as np
data = pd.read_csv('./data/train.csv', encoding='big5') # 读取数据保存到data中
data = data.iloc[:, 3:] # 行保留所有,列保留从第三列开始往后,去除了数据中的时间、地点、参数等信息
data[data == 'NR'] = 0 # NR表示没有雨,全部置为0方便处理
raw_data = data.to_numpy() # 将data的所有数据转换为二维数据并用raw_data来保存
print(raw_data)
raw_data
用滑动窗口的思想,将一个月的第一天到第二十天每天二十小时横向排序,取大小为9的窗口,从第一天的第0时一直可以划到第20天的第14时,可以用到更多的数据,这样积累的话使整个数据集得到明显的提升。
month_data = {}
for month in range(12):
sample = np.empty([18, 480]) # 返回一个18行480列的数组,用来保存一个月的数据(一个月只有20天,一天24个小时)
for day in range(20):
sample[:, day * 24 : (day + 1) * 24] = raw_data[18 * (20 * month + day) : 18 * (20 * month + day + 1), :]
# raw的行每次取18行,列取全部列。送到sample中(sample是18行480列)行给全部行,列给24列,然后列往后增加
month_data[month] = sample
x = np.empty([12 * 471, 18 * 9], dtype = float)
# 一共480个小时,每9个小时一个数据(480列最后一列不可以计入,因为如果取到最后一行那么最后一个数据便没有了结果{需要9个小时的输入和第10个小时的第10行作为结果}),
# 480-1-9+1=471。471*12个数据集按行排列,每一行一个数据;数据是一个小时有18个特征,而每个数据9个小时,一共18*9列
y = np.empty([12 * 471, 1], dtype = float) # 结果是471*12个数据,每个数据对应一个结果,即第10小时的PM2.5浓度
for month in range(12):
for day in range(20):
for hour in range(24):
if day == 19 and hour > 14: # 取到raw_data中的最后一块行为18,列为9的块之后,就不可以再取了,再取就会超过界限了
continue
x[month * 471 + day * 24 + hour, :] = month_data[month][:,day * 24 + hour : day * 24 + hour + 9].reshape(1, -1)
# 取对应month:行都要取,列取9个,依次进行,最后将整个数据reshape成一行数据(列数无所谓)。然后赋给x,x内的坐标只是为了保证其从0-471*12
# 列18*9 (9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9)
y[month * 471 + day * 24 + hour, 0] = month_data[month][9, day * 24 + hour + 9] # 结果对应的一直是第9(即第10行PM2.5)然后列数随着取得数据依次往后进行
#print(x)
#print(y)
x
y
很多特征的分布的范围很不一样,把他们的范围缩放,使得不同输入的范围是一样的。对数据进行特征缩放。
#特征缩放
mean_x = np.mean(x, axis = 0) # 18 * 9 求均值,axis = 0表示对各列求均值,返回 1* 列数 的矩阵
std_x = np.std(x, axis = 0) # 18 * 9 求标准差,axis = 0表示对各列求均值,返回 1* 列数 的矩阵
for i in range(len(x)): #12 * 471
for j in range(len(x[0])): #18 * 9
if std_x[j] != 0:
x[i][j] = (x[i][j] - mean_x[j]) / std_x[j]
交叉验证就是将训练集再分为两部分,一部分作为训练集,一部分作为验证集。用训练集训练模型,然后在验证集上比较,得出最好的模型之后,再用全部的训练集训练,然后再用测试集进行测试。这样的好处是因为最终只给我们test data的输入而没有给我们输出,所以我们无法定量我们模型的好坏,而使用验证数据可以简单验证我们模型的好坏,让我们自己心里有数。
import math
x_train_set = x[: math.floor(len(x) * 0.8), :]
y_train_set = y[: math.floor(len(y) * 0.8), :]
x_validation = x[math.floor(len(x) * 0.8): , :]
y_validation = y[math.floor(len(y) * 0.8): , :]
#print(x_train_set)
#print(y_train_set)
#print(x_validation)
#print(y_validation)
#print(len(x_train_set))
#print(len(y_train_set))
#print(len(x_validation))
#print(len(y_validation))
决定模型好坏的就是训练的成果,这里使用Loss function和梯度下降算法。
训练中使用Adagrad算法来更新学习率,每个参数的学习率都把它除上之前微分的均方根。
loss function 用的是均方根误差。
dim = 18 * 9 + 1 # 用来做参数vector的维数,加1是为了对bias好处理。最后的h(x)=w1x1+w2x2+'''+WnXn+b
w = np.zeros([dim, 1]) # 生成一个dim行1列的数组用来保存参数值
x = np.concatenate((np.ones([12 * 471, 1]), x), axis = 1).astype(float)
# np.ones来生成12*471行1列的全1数组,np.concatenate,axis=1表示按列将两个数组拼接起来,即在x最前面新加一列内容,
# 之前x是12*471行18*9列的数组,新加一列之后变为12*471行18*9+1列的数组
learning_rate = 100#学习率
iter_time = 1000#迭代次数
adagrad = np.zeros([dim, 1]) #生成dim行即163行1列的数组,adagrad算法更新学习率
eps = 0.0000000001 # 所以加上一个极小数,使分母adagrad不为0
for t in range(iter_time):
loss = np.sqrt(np.sum(np.power(np.dot(x, w) - y, 2))/471/12)#均方根误差,观测值与真值偏差的平方和与观测次数m比值的平方根。
if t%100==0:
print(str(t) + ":" + str(loss)) # 每一百次迭代就输出其损失
gradient = 2 * np.dot(x.transpose(), np.dot(x, w) - y)
# dim*1 x.transpose即x的转置,后面是X*W-Y,即2*(x的转置*(X*W-Y))是梯度,具体可由h(x)求偏微分获得.最后生成1行18*9+1列的数组。
# 转置后的X,其每一行是一个参数,与h(x)-y的值相乘之后是参数W0的修正值,同理可得W0-Wn的修正值保存到1行18*9+1列的数组中,即gradient
adagrad += gradient ** 2 # adagrad用于保存前面使用到的所有gradient的平方,进而在更新时用于调整学习率
w = w - learning_rate * gradient / np.sqrt(adagrad + eps) # 更新w
np.save('weight.npy', w) # 将参数保存下来
print(w)
每一百次迭代就输出损失
打印w,共18*9+1行,包含bias
...
载入验证集验证
w = np.load('weight.npy')
# 使用x_validation和y_validation来计算模型的准确率,因为X已经normalize了,所以不需要再来一遍,只需在x_validation上添加新的一列作为bias的乘数即可
x_validation = np.concatenate((np.ones([1131, 1]), x_validation), axis=1).astype(float)
ans_y = np.dot(x_validation, w)
loss = np.sqrt(np.sum(np.power(ans_y - y_validation, 2)) / 1131)
print(loss)
loss
载入测试数据,并且和训练集一样对测试集做预处理,使测试数据形成 240 个维度为 18 * 9 + 1 的资料。载入训练得到权重w开始预测。
test_data = pd.read_csv('./data/test.csv', header = None, encoding = 'big5')
test_data = test_data.iloc[:, 2:] # 取csv文件中的全部5行和第3列到结束的列数所包含的数据
test_data[test_data == 'NR'] = 0 # 将其转换为数组
test_data = test_data.to_numpy()
test_x = np.empty([240, 18*9], dtype = float)# 创建一个240行18*9列的空数列用于保存text data的输入
for i in range(240): # 共240个测试输入数据
test_x[i, :] = test_data[18 * i: 18* (i + 1), :].reshape(1, -1)
for i in range(len(test_x)):
for j in range(len(test_x[0])):
if std_x[j] != 0:
test_x[i][j] = (test_x[i][j] - mean_x[j]) / std_x[j]
test_x = np.concatenate((np.ones([240, 1]), test_x), axis = 1).astype(float) # 在test_x前面拼接一列全1数组,构成240行,163列数据
test_x
# 进行预测
w = np.load('weight.npy')
ans_y = np.dot(test_x, w) # test data的预测值ans_y=test_x与W的积
# 将预测结果填入文件当中
import 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)
以上就完成了第一个作业