李宏毅2020机器学习作业1——Linear Regression
这篇文章详细描述了如何利用Python和Jupyter Notebook完成空气质量预测任务。以下是总结:
环境与数据获取
使用Google Colab作为运行环境,数据集从“李宏毅机器学习作业说明”资源中获取,路径为“E:/jupyter/data/hw1/train.csv”和“E:/jupyter/data/hw1/test.csv”。需要下载网盘链接获取数据。
数据预处理
- 数据读取与转换:使用pandas读取数据,将PM2.5列转换为数值类型,处理缺失值(用“NR”表示,转换为0)。
- 归一化处理:对数据进行标准化,确保特征之间的尺度相近,以提高模型训练效果。
- 数据重组:将一个月的数据分成12个样本,每个样本包含18个变量,每个变量有9个时间点的数据。一个月的数据被分解为12个样本,每个样本有162个数据点(18变量×9时间点)。
- 拆分数据集:将数据拆分成训练集和验证集,比例为80:20。
模型构建与训练- 线性回归模型:使用最小二乘法拟合数据,损失函数为均方根误差。
- 优化算法:使用RMSprop优化算法,但在代码中未正确应用,而是直接使用梯度平方进行优化,可能导致梯度消失问题。
- 超参数设置:学习率、迭代次数等参数被设置,训练30000次,损失值逐步收敛。
课程背景
数据准备
实验环境
任务要求
代码实现
- sys:提供对解释器的使用和维护所需的变量访问权限,同时支持与解释器的深度交互。
- pandas:提供了一套高效处理和分析结构化数据的强大工具集。
- numpy:作为Python的一个扩展库,支持高效处理多维数组和矩阵运算。
- math:专门用于执行数学运算的基础库。
无需库的用户请自行安装(Jupyter Notebook的安装方法:进入自己的环境后,使用conda命令安装名为 命名词 的库即可完成安装。)
import sys
import pandas as pd
import numpy as np
import math
现在导入train数据
#导入数据(前面‘’为数据存放路径,后面big5对字符串进行编码转换)
data = pd.read_csv('E:/jupyter/data/hw1/train.csv',encoding='big5')
我们的数据是csv格式用excel打开会出现乱码,可以用Notepad++打开

对数据进行处理,取第4列开始的数据
#分割出前3列,从第4列开始将数据存到data
data = data.iloc[:, 3:]
data[data == 'NR'] = 0
raw_data = data.to_numpy()
print(raw_data)
运行之后结果,对照train数据可以看出,前3列数据已经被删掉了
[['14' '14' '14' ... '15' '15' '15']
['1.8' '1.8' '1.8' ... '1.8' '1.8' '1.8']
['0.51' '0.41' '0.39' ... '0.35' '0.36' '0.32']
...
['36' '55' '72' ... '118' '100' '105']
['1.9' '2.4' '1.9' ... '1.5' '2' '2']
['0.7' '0.8' '1.8' ... '1.6' '1.8' '2']]
我们再对数据进行重组,将原始的4320×24的数据按照每月重组成12个18×480的数据


#对data进行调整,将4320*24重组为12*18*480
month_data = {}
for month in range(12):
sample = np.empty([18,480])
for day in range(20):
sample[:,day * 24 : ( day + 1 ) * 24] = raw_data [ 18 * ( 20 * month + day ) : 18 * ( 20 * month + day + 1 ),: ]
month_data[month] = sample
按照作业要求,遵循每9个小时的数据集来构建预测模型,用于预测第10个小时的PM2.5浓度。每日24小时中,每9小时构成一个数据样本,第10小时作为目标变量(Label)。这样,每日可生成24-9=15个数据样本和目标变量。每个自然月可提供300个数据样本,因此一年将拥有12×20×15=3600个数据样本。由于一个月的20天是连续的,可以将20天的480个小时视为连续时间,因此一个月将产生480-9=471个数据样本,全年则有471×12=5652个数据样本。同时,每个数据样本对应一个Label,因此全年也将有5652个目标变量(第10个小时的PM2.5浓度)。通过这种方法,可以有效构建充足的训练数据集。每个数据样本中包含9×18个测量数据。

使用如下代码实现:
x = np.empty([12*471,18*9],dtype = float)
y = np.empty([12*471,1],dtype = float)
for month in range(12):
for day in range(20):
for hour in range(24):
if day == 19 and hour>14:
continue
x[month * 471 + day * 24 + hour, :] = month_data[month][:,day * 24 + hour : day * 24 + hour + 9].reshape(1,-1)
y[month * 471 + day * 24 + hour, 0] = month_data[month][9,day * 24 + hour + 9]
print(x)
print(y)
我们可以看一下运行结果,
[[14. 14. 14. ... 2. 2. 0.5]
[14. 14. 13. ... 2. 0.5 0.3]
[14. 13. 12. ... 0.5 0.3 0.8]
...
[17. 18. 19. ... 1.1 1.4 1.3]
[18. 19. 18. ... 1.4 1.3 1.6]
[19. 18. 17. ... 1.3 1.6 1.8]]
[[30.]
[41.]
[44.]
...
[17.]
[24.]
[29.]]
如下图所示,对比train.cxv数据可以看出data和label已经被构建出来

对数据实施归一化处理。从训练集中划分一部分作为验证集,其目的是为了验证模型的性能。
#归一化
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]
#将训练集分成训练-验证集,用来最后检验我们的模型
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))
运行结果
[[-1.35825331 -1.35883937 -1.359222 ... 0.26650729 0.2656797
-1.14082131]
[-1.35825331 -1.35883937 -1.51819928 ... 0.26650729 -1.13963133
-1.32832904]
[-1.35825331 -1.51789368 -1.67717656 ... -1.13923451 -1.32700613
-0.85955971]
...
[ 0.86929969 0.70886668 0.38952809 ... 1.39110073 0.2656797
-0.39079039]
[ 0.71018876 0.39075806 0.07157353 ... 0.26650729 -0.39013211
-0.39079039]
[ 0.3919669 0.07264944 0.07157353 ... -0.38950555 -0.39013211
-0.85955971]]
[[30.]
[41.]
[44.]
...
[ 7.]
[ 5.]
[14.]]
[[ 0.07374504 0.07264944 0.07157353 ... -0.38950555 -0.85856912
-0.57829812]
[ 0.07374504 0.07264944 0.23055081 ... -0.85808615 -0.57750692
0.54674825]
[ 0.07374504 0.23170375 0.23055081 ... -0.57693779 0.54674191
-0.1095288 ]
...
[-0.88092053 -0.72262212 -0.56433559 ... -0.57693779 -0.29644471
-0.39079039]
[-0.7218096 -0.56356781 -0.72331287 ... -0.29578943 -0.39013211
-0.1095288 ]
[-0.56269867 -0.72262212 -0.88229015 ... -0.38950555 -0.10906991
0.07797893]]
[[13.]
[24.]
[22.]
...
[17.]
[24.]
[29.]]
4521
4521
1131
1131
Training:
- 设置超参数:学习率,迭代次数等
- 计算损失L
- 计算梯度gradient
- 梯度下降
该损失函数遵循均方根误差公式,即root mean square error(RMS)损失函数。

对参数W计算梯度值

梯度下降,其中RMSprop(指数加权移动平均数)是一种优化算法,对于下面公式存在疑问的同学,建议先自行查阅相关资料以加深理解。

在原文中,似乎未考虑到上一次迭代计算的梯度值(可能遇到梯度消失现象,原因不明),仅基于本次迭代的梯度值平方进行优化。这正是Adagrad方法的特性,原理图位于文末。

代码实现如下:
#因为存在偏差bias,所以dim+1
dim = 18 * 9 + 1
# w维度为163*1
w = np.zeros([dim,1])
# x_train_set维度为 4521*163
x_train_set= np.concatenate((np.ones([len(x_train_set),1]),x_train_set),axis = 1).astype(float)
#设置学习率
learning_rate = 10
#设置迭代数
iter_time = 30000
#RMSprop参数初始化
adagrad = np.zeros([dim,1])
eps = 0.0000000001
#beta = 0.9
#迭代
for t in range(iter_time):
loss = np.sqrt(np.sum(np.power(np.dot(x_train_set,w)-y_train_set,2))/len(x_train_set))
if(t%100 == 0):
print("迭代的次数:%i , 损失值:%f"%(t,loss))
#gradient = 2*np.dot(x.transpose(),np.dot(x,w)-y)
#计算梯度值
gradient = (np.dot(x_train_set.transpose(),np.dot(x_train_set,w)-y_train_set))/(loss*len(x_train_set))
adagrad += (gradient ** 2)
#更新参数w
w = w - learning_rate * gradient / np.sqrt(adagrad + eps)
#保存参数w
np.save('weight.npy',w)
观察运行结果时,我发现打印值仅部分展示,经过30000次迭代,损失值收敛至19。学习率设置为自定义参数可能有助于优化训练体验。观察到损失值出现波动,但好消息是,经过一段时间训练后,损失值趋于稳定。使用RMSprop优化方法时,可能会出现类似情况。如果仅使用第一个梯度进行优化,可能会出现波动现象。建议尝试采用更全面的梯度信息以改善训练效果。
迭代的次数:0 , 损失值:27.239592
迭代的次数:100 , 损失值:598.991742
迭代的次数:200 , 损失值:96.973083
迭代的次数:300 , 损失值:240.807182
迭代的次数:400 , 损失值:71.607934
迭代的次数:500 , 损失值:212.116933
迭代的次数:600 , 损失值:117.461546
迭代的次数:700 , 损失值:189.660439
迭代的次数:800 , 损失值:87.943008
迭代的次数:900 , 损失值:158.851111
迭代的次数:1000 , 损失值:74.318934
迭代的次数:1100 , 损失值:138.784655
迭代的次数:1200 , 损失值:67.418347
迭代的次数:1300 , 损失值:124.302389
迭代的次数:1400 , 损失值:63.235512
迭代的次数:1500 , 损失值:113.160475
迭代的次数:1600 , 损失值:60.299076
至此为止,模型已经完成训练。在验证集上进行模型验证,并完成对test.cxv数据集的预测任务。
先对测试集test.csv进行预处理
testdata = pd.read_csv('E:/jupyter/data/hw1/test.csv',header = None ,encoding = 'big5')
test_data = testdata.iloc[:,2:]
test_data[test_data == 'NR'] = 0
test_data = test_data.to_numpy()
test_x = np.empty([240,18*9],dtype = float)
for i in range(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)
print(test_x)
看看运行结果
array([[ 1. , -0.24447681, -0.24545919, ..., -0.67065391,
-1.04594393, 0.07797893],
[ 1. , -1.35825331, -1.51789368, ..., 0.17279117,
-0.10906991, -0.48454426],
[ 1. , 1.5057434 , 1.34508393, ..., -1.32666675,
-1.04594393, -0.57829812],
...,
[ 1. , 0.3919669 , 0.54981237, ..., 0.26650729,
-0.20275731, 1.20302531],
[ 1. , -1.8355861 , -1.8360023 , ..., -1.04551839,
-1.13963133, -1.14082131],
[ 1. , -1.35825331, -1.35883937, ..., 2.98427476,
3.26367657, 1.76554849]])
验证模型并预测
#在验证集上进行验证
w = np.load('weight.npy')
x_validation= np.concatenate((np.ones([len(x_validation),1]),x_validation),axis = 1).astype(float)
for m in range(len(x_validation)):
Loss = np.sqrt(np.sum(np.power(np.dot(x_validation,w)-y_validation,2))/len(x_validation))
print ("the Loss on val data is %f" % (Loss))
#预测
ans_y = np.dot(test_x, w)
print('预测PM2.5值')
print(ans_y)
运行结果
the Loss on val data is 18.427185
预测PM2.5值
[[-15.78367116]
[ -2.32261409]
[ 59.74234153]
[ -2.69635112]
[ 39.23820506]
[ 13.8801302 ]
[ 22.58641017]
[ 31.11258594]
[ 41.92474119]
[ 68.36693984]
[ 17.54723298]
[ 42.69150518]
[ 85.92726242]
[ 64.53572169]
[ 26.60792925]
[ -7.59077676]
从实验结果来看,在我们设置的验证集上,误差值为18.4,这一数值表明模型在优化过程中仍存在明显的问题。值得注意的是,测试集上的预测结果出现负值,这进一步验证了模型的局限性,需要采取相应的优化工作以提升预测精度。
注:在程序运行过程中遇到维度不匹配的情况时,重新运行程序即可。发现错误的地方,欢迎指正交流,谢谢!
梯度下降算法存在误解:最近观看了Gradient Descent1的视频,adagrad方法如下图所示


