本文的任务,是通过训练一个简单的神经网络预测模型,实现了根据天气,季节,日期,星期等自变量,去预测对应日期的单车数量。主要包含的知识点为:数据预处理中的one-hot编码, 归一化操作,以及构建神经网络的流程 任务中使用到的数据结构如下。数据已共享在网盘中 其中,最后一列cnt为目标函数,即单车的数量。表中其他数据,我们选择season, hr, mnth,weekday, weathersit, temp, hum,holiday, yr等作为因变量,其他数据抛弃不用。每条数据以小时为单位,共约2w条数据,我们选取部分绘图看一下 整个任务实现流程可以分为以下三个步骤:
下面我们开始任务 1. 数据预处理数据预处理是整个建模步骤非常重要的环节,甚至会直接影响到训练效果。在这个环节我们的核心目标其实还是围绕一点:为了让训练效果更好。 我们观察数据会发现,原始数据不够好,直接拿来训练的话,很可能会影响到训练过程和最终的训练效果。 那么具体哪里不好,我们又该怎么处理呢? 1.1. 对类型变量的处理:one-hot编码 在我们这个案例中,季节,天气等几个变量都是我们为了日常生活方便而约定的规则,属于类型变量,他们的数字大小,并没有实际的意义,比如星期二并不代表它比星期一的值大,直接把大小输入模型,会造成神经网络的“误解”:即值越大,会更强烈地影响网络内部的变化。所以我们需要对这些类型变量做二进制处理。 还是拿星期举例,我们需要把星期的标识,全部转化为二进制编码,哪一位为1,就代表对应的类型(其实就相当于,会激活与之相关的神经元) 具体到代码实现,pandas有现成的方法,帮助我们快速生成one-hot编码 dummies = pd.get_dummies(rides['weekday'], prefix='weekday', drop_first=False 复制代码 下面展示真实代码。我们要操作的对象是'season', 'weathersit', 'mnth', 'hr', 'weekday'这几个属性。处理完成后,将生成的数据再合并进原来的数据中 。最后,去掉原数据集中不相关的特征 # 这是我们要处理的特征组 dummy_fields = ['season', 'weathersit', 'mnth', 'hr', 'weekday'] for each in dummy_fields: dummies = pd.get_dummies(rides[each], prefix=each, drop_first=False) # 合并进原来的数据 rides = pd.concat([rides, dummies], axis=1) # 把原有的类型变量对应的特征去掉,将一些不相关的特征去掉 fields_to_drop = ['instant', 'dteday', 'season', 'weathersit', 'weekday', 'atemp', 'mnth', 'workingday', 'hr'] data = rides.drop(fields_to_drop, axis=1) data.head() # 打印查看 复制代码 完成类型编码的转化之后,先别着急开始,我们还有一步要做 1.2. 数据值归一化 在很多时候,原是数据中不同单位的变量,他们的数值差异有可能会非常大。比如在我们的案例中,温度的范围可能在0-50之间浮动,而湿度则是在0-1之间浮动的小数。而有时的初始数据甚至还会出现几千几万的数量级。如果直接进行训练,当然也不是不可以,只不过神经网络的权重会学习的非常慢,而且最终的效果可能也不好。 所以,我们需要对不同单位的数值进行归一化处理,归一化的方式有很多,比如把数值按正态分布转化为[-1,1]区间。在这里使用常用的z-score标准化方法。 新数据 = (原数据 - 均值) / 标准差 # 标准化处理,这里将目标数量也做了归一化 quant_features = ['cnt', 'temp', 'hum', 'windspeed'] for each in quant_features: # 快速求均值和方差 mean, std = data[each].mean(), data[each].std() # 选取列名为each的特定列, 替换为归一化后的数据 data.loc[:, each] = (data[each] - mean) / std data.head() # 打印查看 复制代码 数据预处理环节就结束了,总结来看分为两点,这两点也是绝大部分神经网络训练之前都需要进行的步骤。
下面,我们就开始进入正式的训练过程 2. 构建模型,开始训练使用pytorch构建网络真是一件很爽的事情,你不需要关注梯度如何传播,线性映射和非线性映射具体如何运作,你只需要按照pytorch的方法用几行代码构建模型,然后给他输入,获取输出就可以了。 这部分开始,代码量就上升了,我会尽量讲的详细,注释也会尽可能的全面 2.1. 构建输入和目标函数 在这里,我们的输入为所有会影响当前单车数量的自变量,输出为对应的单车数量,当然,不要忘了分训练集和测试集,训练集用于训练,测试集用于测试最终模型的效果。 target_fields = ['cnt'] # 训练集,去掉最后21天的数据 train_data = data[:-21 * 24] # 测试集,最后21天的数据 test_data = data[-21 * 24:] # 定义输入,和预测目标 features, targets = train_data.drop(target_fields, axis=1), train_data[target_fields] test_features, test_target = test_data.drop(target_fields, axis=1), test_data[target_fields] 复制代码 上面代码定义的输入和预测目标,还是dataframe格式,我们需要把数据转化为array或者tensor,这里我们转成array格式。同时,为了方便计算,需要调整目标函数的维度。从0维(数据量)转化为1维(数据量 x 1) 2.2. 构建模型 我们的案例不需要使用到多复杂的模型,只需要确保在中间有一层非线性映射,用以拟合曲线即可,构建模型就是这么简单。 同时,pytorch也封装了不少损失函数方法,在这里使用简单MSE模型,直接调用即可。在线性回归拟合案例,透彻理解深度学习的反向传播一文中,有MSE的具体实现说明,很简单,一看就懂了。 input_size = np.array(features).shape[1] hidden_size = 10 output_size = 1 neu = torch.nn.Sequential( torch.nn.Linear(input_size, hidden_size), # 一层线性映射 torch.nn.Sigmoid(), # 一层sigmoid非线性映射 torch.nn.Linear(hidden_size, output_size) # 再一层线性映射 ) cost = torch.nn.MSELoss() optimizer = torch.optim.SGD(neu.parameters(), lr = 0.01) 复制代码 划重点:
到这里,模型已经构建好,下面进入训练过程(抽象出模型后,所有的训练过程都大同小异) 2.3 训练过程 首先定义一个batch_size。相当于定义了,把原始数据以128条为最小训练单位进行训练。 batch_size = 128 分batch是为了充分训练模型,而且增加数据的随机性,效果会更好。比如我们的20000条数据,不分批的话,那么跑完20000条数据,模型才被训练了一次,误差及计算了一次,反向传播了一次,权重调整了一次,这个效率就很低了;而如果把20000分成一小撮,每一撮是一个完整的训练过程,那么效率就大大提高了。而且,这样可以增加训练数据的随机性。 接着,我们就直接上代码了。看着代码量比较多,但其实很简单。 Losses = [] for i in range(10000): batch_loss = [] for start in range(0, len(X), batch_size): end = start + batch_size if start + batch_size < len(X) else len(X) # 获取本批次的输入和预测目标 xx = torch.tensor(X[start: end, :], dtype=torch.float, requires_grad = True) yy = torch.tensor(Y[start: end, :], dtype=torch.float, requires_grad = True) # 输入数据,得到预测值 predict = neu(xx) # 使用损失函数比较预测数据和真实数据,得到loss loss = cost(predict, yy) # 反向传播,优化权重 optimizer.zero_grad() loss.backward() optimizer.step() # 记录loss batch_loss.append(loss) Losses.append(batch_loss[1:]) 复制代码 步骤说明:
这里通过监听loss的变化来判断训练情况,如果loss的变化趋势是。一开始迅速下降,到一定值后趋于稳定不再下降,通常就说明模型表现良好,训练完成。 上图是我记录的,每个batch最低的loss,可以看到,大约在600轮训练后,模型就趋于稳定。 这个时候,把最后一步的predict拿出来,和真实值yy做对比,会发现惊人的一致。 当然这里的一致并不能说明什么问题,训练数据表现好不能证明模型效果好,这就好像你用A推演出B的结论,再用A去验证B正确一样荒谬。正确的做法是,我们用和A有一样特征的C,去尝试推演,看是否能得出B。 3. 验证模型验证的步骤,就是把上面训练过程最后一步的模型拿过来,放到测试集上跑一遍,观察预测值和真实值的差异 对比test_predict和test_yy的差异 上图观察可知,无论是工作日还是周末,也不管当天的天气如何,我们的预测基本能够贴近真实值。不过中间有几天的数据差异较大,最后分析数据发现,那几天是一个非常规的节假日,导致历史数据不具有参考意义。 到这里,整个训练过程就完成了,我们得到了一个最简单的模型,只要给定当天的气温,日期,星期等因素,它就可以预测出当天的单车使用数量。 结束 感谢您看到这里。 |
|