Machine Learining(李宏毅2020) —— hw01

本文详细介绍了如何处理机器学习作业数据,包括数据预处理、缺失值处理、特征提取、数据标准化、训练集划分以及模型训练。使用了标准差标准化方法进行数据规范化,并通过Adagrad算法进行梯度下降训练,最终实现对PM2.5含量的预测。通过训练集和验证集的损失函数比较,评估模型性能。

Machine Learining —— hw01:Regression

1、hw01作业理解

在这里插入图片描述
在这里插入图片描述
作业数据给:数据集和测试集
大概内容是数据集中记录了12个月的前20天的24小时的18个数据,测试集是从剩余的数据中再取的

  • CSV文件,包括台湾丰原地区240天(12x20)的气象观测资料(取每个月前20天的数据做训练集,每个月后10天数据用作测试,学生不可见)
  • 每天的监测时间点为0时、1时、2时、…、23时,共24个小时
  • 每天检测的指标包括CO、NO、PM2.5、PM10等气体信息,以及是否降雨、刮风等气象信息,共计18项
  • 模型的输入是前9个小时的所有观测数据,即 9 ∗ 18 9*18 918的参数值
  • 模型的输出时一个值表示预测的第10个小时的PM2.5含量
    使用Regression model

2、数据预处理

训练集中数据排列形式符合人类观看数据的习惯,但并不能直接拿来“喂”给模型进行训练,因此需要对数据进行预处理。
在这里插入图片描述
浏览数据可知,在train中存在一定量的空数据NR,且多存在与RAINFALL(降雨)一项。
对于空数据的处理方法:删除和补全。RAINFALL表示对应时间点是否有降雨,有降雨值为1,无降雨值为NR,因此可以采用补全法处理空数据,即将空数据NR全部补为0。
根据作业要求,需要用到连续9个时间点的气象观测数据,来预测第10个时间点的PM2.5含量。
对每一天来说,其包含的信息维度为(18,24)(18项指标,24个时间点)。

参考思路

link

  • 将0-8时的数据截取出来,形成一个维度为(18,9)的数据帧,作为训练数据,将9时的PM2.5含量取出来,作为该训练数据对应的label
  • 将1-9时的数据作为训练数据,10时的PM2.5含量作为label
  • 以此类推,可将每天的信息分割为15个shape为(18,9)的数据帧和对应的15个label(9-23时)

在这里插入图片描述
在这里插入图片描述
训练集中共包含240天的数据,因此共可获得240x15=3600个数据帧和与之对应的3600个label
python中的数据是通过矩阵来保存的,所以第一步就是删掉不需要的行和列,然后将其保存在矩阵中

#导入必要的包
import pandas as pd
import numpy as np

data = pd.read_csv('./hw1/train.csv', encoding = 'big5')  #读取数据保存到data中

data = data.iloc[:, 3:]  #保留所以行,从第三列开始往后才保存,去除掉文件中的时间、地点、参数等信息
#print(data)
data[data == 'NR'] = 0  #将所有的NR全部置为0
#print(data)
raw_data = data.to_numpy()  #将data的所有数据转换为二维数据(数组)并用raw_data保存

以上步骤完后,已经将csv文件中的数据保存到矩阵中了
将一个月的第一天到第二十天横向排序,取大小为9的窗口,从第一天的0时一直划到第20天的第14时,这样每个月的数据量就会多9个,使整个数据集的利用率得到明显的提升。(参考)link

3、提取特征值

于是我们可以使用以下步骤将每一个月的数据放到一个大行中,如图所示
在这里插入图片描述
在这里插入图片描述

month_data = {}#先创建一个空字典month_data来存储数据
for month in range(12): #从0-11共12个月,效果是:{0:“数据”,1:“数据”,...,12:“数据”}
    sample = np.empty([18, 480]) 
    #在内存循环开始前先使用empty()创建一个空的数组sample,用来保存一个月的数据(一个月20天24小时,18项参数)
    for day in range(20): #day从0-19共20天
        sample[:, day * 24: (day + 1)*24] = raw_data[18 * (20 * month + day): 18 * (20 * month + day +1), :]
        #sample[]中“,”前表示行的内容全都要;“,”后的是列的内容,按照0-24-48-...这样将24个小时的数据提取出来保存
        #raw的行每次取18行(0-18-36-...),列全取。送到sample中(18x480),行全给行,列只给24列,然后列往后增加
    month_data[month] = sample
    #一个月20天的数据全取完后保存到month_data字典中

将数据放出month_data,代码步骤图:
在这里插入图片描述
继续提取数据
在这里插入图片描述
每个月有480小时,每9个小时为一个data,每个月就有471个data,12个月总共12*471笔数据
每小时18个参数

x = np.empty([12 * 471, 18 * 9], dtype = float)
    #一共480小时,每9个小时一个数据
    #需要9个小时的输入和第10个小时的PM2.5值作为结果
    #480-9=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):           # month :0-11
    for day in range(20):         #   day: 0-19
        for hour in range(24):    #  hour:0-23
            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
            y[month * 471 + day * 24 + hour, 0] = month_data[month][9, day * 24 + hour + 9]
                #结果对应的行数一直是第9行(即第10行的PM2.5值),列数随着取得的数据依次往后进行

整个循环其实是遍历month_data字典将数据分割为9个小时一组的训练数据
这部分工作是为获取x和y这两个数据,相当于准备训练的数据和目标的结果

  • 输入:x的维数是[12471,189],行数代表按照9个小时划分出的数据,列是每个小时18个参数
  • 输出:y的维数是[12*471, 1],代表和x对应的PM2.5值
  • reshape(1,-1)将month_data[month][…]取出的数据变换为1行数据,列的参数-1表示列数让程序自己计算
  • y取0-9这10个小时中的最后一个小时的PM2.5值
    从month_data中取数据
    在这里插入图片描述
    创建x,y的矩阵如下图
    在这里插入图片描述

4、Normalize与训练集分类

Normalize选最常用的标准差标准化方法

  • 数据标准化是将数据按比例缩放,使之落入一个小的特定区间,无量纲化
  • Z标准化(z-score标准化)即标准差标准化方法,利用原始数据的均值 μ \mu μ(mean)和标准差 σ \sigma σ(standard deviation)进行数据的标准化
  • 经过处理后的数据符合标准正态分布,均值为0,方差为1
  • 转化函数: x ∗ = x − μ σ x^*=\frac{x- \mu}{\sigma} x=σxμ
mean_x = np.mean(x, axis = 0) #18*9 求均值, axis = 0表示对各列求均值,返回 (1*列数)的矩阵
                                           #axis = 1表示对各行求均值,返回 (行数*1)的矩阵
std_x = np.std(x, axis = 0)   #18*9 求方差
for i in range(len(x)):       # 12*471   len(x)   矩阵的行数
   for j in range(lenx[0]):  # 18*9     lenx[0]  矩阵的列数
       if std_x[j] != 0:
           x[i][j] = (x[i][j] - mean_x[j]) / std_x[j]

接下来将训练数据按8:2拆成训练数据和验证数据,最终只给了我们test data的输入而没有给输出,无法定量模型的好坏,将训练数据拆分后可以验证模型的好坏

#将训练数据拆分为 训练:验证 = 8:2
import math

x_train_set = x[: math.floor(len(x) * 0.8), :]  #math.floor(x) 返回小于参数x的最大整数
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)) 

5、训练模型

训练使用的Loss function和梯度下降算法

  • Loss function选择的是Root Mean Square Error:
    R M S E = 1 n ∑ i = 1 n ( y i − y i ^ ) 2 = 1 471 × 12 ∑ i = 1 n [ ( w 1 x 1 + w 1 x 2 + . . . + w n x n ) − y i ^ ] 2 RMSE = \sqrt{\frac{1}{n}\sum_{i=1}^n(y_i-\hat{y_i})^2}=\sqrt{\frac{1}{471\times12}\sum_{i=1}^n[(w_1x_1+w_1x_2+...+w_nx_n)-\hat{y_i}]^2} RMSE=n1i=1n(yiyi^)2 =471×121i=1n[(w1x1+w1x2+...+wnxn)yi^]2
  • 学习率
    学习率最简单的办法是设置固定的,即每次迭代的学习率都相同,但这样效果比较差,良好的学习率应该随迭代次数减小,在此选择自适应学习率的Adagrad算法,即每次学习率都等于其除以之前所有的梯度平方和再开根号: 1 ∑ r = 2 t ( g r ) 2 + ϵ \frac{1}{\sqrt{\sum_{r=2}^t}(g_r)^2+\epsilon} r=2t (gr)2+ϵ1
  • 梯度
    对Loss function 的每一个参数求偏微分得到的矩阵
#训练模型
dim = 18 * 9 +1              #用作参数vector的维数,加1是为了对bias好处理,即最后的h(x)= W1X1+W2X2+...+WnXn+b
w = np.ones([dim, 1])        #生成一个dim行1列的数组用来保存参数值
x = np.concatenate((np.ones([12 * 471, 1]), x), axis = 1).astype(float)
    #np.ones用来生成12*471行1列的数组,np.concatenate,axis = 1表示按列(横向)将两个数组拼接起来
    #即在x前面加一列新内容,原x(12*471, 18*9),现x(12*471, 18*9+1)
learning_rate = 100           #学习率
iter_time = 10000            #迭代次数
adagrad = np.zeros([dim, 1]) #生成dim行即163行1列的数组,用来使用adagrad算法更新学习率
eps = 0.0000000001           #因为新学习率是 learning_rate/sqrt(sum_of_pre_grads**2),
                             #而adagrad=sum_of_grads**2,所以处在分母上迭代时adagrad可能为0,加上一个极小值,使其不除0
for t in range(iter_time):
    loss = np.sqrt(np.sum(np.power(np.dot(x, w) - y, 2)) / 471 / 12)
        #loss函数是从0-n的(X*W - Y)**2 之和除以(471*12)后再开根号
        #即使用均方根误差(root mean square error)
        #dot:矩阵乘法;power:幂运算;sum:输入参数所有元素之和
    if (t % 100 == 0):       #每迭代100次就输出其损失
        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^T *(X*W - Y))是梯度,具体可由h(x)的偏微分获得
        #转置后的X,每一行是一个参数,与h(x) - y 的值相乘之后是参数W0的修正值,同理可得W0到Wn的修正值保存到(18*9+1,1)的数组gradient中
    adagrad += gradient ** 2  
        #adagrad用于保存前面使用到的所有gradient的平方,进而在更新时用于调整学习率
    w = w - learning_rate * gradient / np.sqrt(adagrad + eps)  #更新权重

np.save('weight.npy', w)  #保存参数

6、载入验证集进行验证

#载入验证集进行验证
w = np.load('weight.npy')
#使用x_validation和y_validation来计算模型的准确性
#x已经标准化了,只需在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)

在第5部分进行训练使用的是全部的x

  • 使用全部的x进行训练,并通过x_validation和y_validation进行验证
    结果是训练集中最后一次迭代的loss为5.68,测试集中loss为5.42
  • 使用x_train_set进行训练,并通过x_validation和y_validation进行验证
    结果是训练集最后一次迭代的loss为5.72,测试集中的loss为5.66

另外值得注意的是

  • Loss function一定和训练的Loss function一致才可以通过比较它俩loss大小来评估模型好坏
  • 如果使用x_validation进行训练时,将训练步骤的X全部换成x_validation,并且在“/471/12”时除的是x_validation的大小即471120.8

7、预测testdata

  • 读取testdata
#预测testdata
testdata = pd.read_csv('./hw1/test.csv', header = None, encoding = 'big5')
test_data = testdata.iloc[:, 2: ]  #读取csv文件中的全行即第3列开始的所有数据
test_data[test_data == 'NR'] = 0   #将testdata中的NR替换为0
test_data = test_data.to_numpy()   #将数据转换为数组
test_x = np.empty([240, 18 * 9], dtype = float)  #创建一个240行18*9列的空数组保存testdata
for i in range(240):               #共240个测试输入数据
   test_x[i, :] = test_data[18 * i: 18 * (i + 1), :].reshape(1, -1)
  • Normalize
#使用与training data相同的方法对testdata进行Normalize
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列
  • 预测
#进行预测
w = np.load('weight.npy')
ans_y = np.dot(test_x, w)   #testdata的预测值ans_y 等于 test_x乘以w

  • 按要求保存数据
#将预测结果填入文件
import csv

with open('submit.csv', mode = 'w', newline = '') as submit_file:
    csv_writer = csv.writer(submit_file)
    #用csv.write()创建一个csv_writer对象
    header = ['id', 'value']
    print(header)
    csv_writer.writerow(header)
    #调用csv_writer的writerow()函数按行写入header
    for i in range(240):
        row = ['id_' + str(i), ans_y[i][0]]
        csv_writer.writerow(row)
        print(row)

8、总结

花了两天的时间对三篇参考博客的思路和代码理解和复现,总体收获很多,特别是分析代码,让自己明白代码的背后还是数学,再接再厉。

9、文章参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值