
本文为365天深度学习训练营 中的学习记录博客 原作者K同学啊前言什么是循环神经网络RNN前面学过的CNN卷积神经网络擅长处理图像这种空间结构数据但它没法处理文本中这种先后的联系。比如看一句话我今天心情好CNN可以提取每个字的特征但它不知道今天在心情前面也不知道我是这句话的主语。而RNN的核心思想是网络在处理当前时刻的输入时会同时记住上一时刻的状态然后把两者结合起来做判断。代码实现设置 GPU 与导入依赖# 设置 GPU 与导入依赖importnumpyasnpimportpandasaspdimporttorchfromtorchimportnnimporttorch.nn.functionalasFimportseabornassns#设置GPU训练也可以使用CPUdevicetorch.device(cudaiftorch.cuda.is_available()elsecpu)device本地读取并加载数据数据分析思路本次实验的数据是结构化表格数据heart.csv包含 13 项特征年龄、性别、血压等生理指标和一个二分类标签。说明如下age年龄sex性别cp胸痛类型 (4 values)trestbps静息血压chol血清胆甾醇 (mg/dlfbs空腹血糖 120 mg/dlrestecg静息心电图结果 (值 0,1 ,2)thalach达到的最大心率exang运动诱发的心绞痛oldpeak相对于静止状态运动引起的ST段压低slope运动峰值 ST 段的斜率ca荧光透视着色的主要血管数量 (0-3)thal0 正常1 固定缺陷2 可逆转的缺陷target 0 心脏病发作的几率较小 1 心脏病发作的几率更大# 读取 CSV 数据文件dfpd.read_csv(heart.csv)print(f数据形状:{df.shape[0]}行 ×{df.shape[1]}列)print(f特征列名:{list(df.columns[:-1])})print(f标签列名:{df.columns[-1]})print(df.iloc[:,-1].value_counts().to_string())df.head()# 特征工程 数据集划分fromsklearn.preprocessingimportStandardScalerfromsklearn.model_selectionimporttrain_test_split Xdf.iloc[:,:-1]ydf.iloc[:,-1]# 将每一列特征标准化为标准正太分布scStandardScaler()Xsc.fit_transform(X)print(f标准化后的特征范围: [{X.min():.2f},{X.max():.2f}])Xtorch.tensor(np.array(X),dtypetorch.float32)ytorch.tensor(np.array(y),dtypetorch.int64)X_train,X_test,y_train,y_testtrain_test_split(X,y,test_size0.1,random_state70)print(f训练集样本数:{len(X_train)})print(f测试集样本数:{len(X_test)})# 维度扩增使其符合RNN模型可接受shapeX_trainX_train.unsqueeze(1)X_testX_test.unsqueeze(1)print(fX_train shape:{X_train.shape}→ (样本数, seq_len1, 特征数13))print(fX_test shape:{X_test.shape})print(fy_train shape:{y_train.shape})print(fy_test shape:{y_test.shape})# DataLoaderfromtorch.utils.dataimportTensorDataset,DataLoader train_dlDataLoader(TensorDataset(X_train,y_train),batch_size64,shuffleFalse)test_dlDataLoader(TensorDataset(X_test,y_test),batch_size64,shuffleFalse)# 取出一个 batch 看看实际形状sample_X,sample_ynext(iter(train_dl))print(f一个 batch 的数据形状:{sample_X.shape})print(f一个 batch 的标签形状:{sample_y.shape})print(f标签示例:{sample_y[:5].tolist()})构建循环神经网络RNN模型模型结构分析本次构建的是一个单层单向 RNN 两层全连接的二分类模型结构如下RNN 层nn.RNN(input_size13, hidden_size200, num_layers1, batch_firstTrue)。输入为 13 维特征向量隐藏状态维度为 200单层 RNN。batch_firstTrue让输入张量的形状为(batch, seq_len, input_size)。全连接层 1nn.Linear(200, 50)将 RNN 输出的 200 维隐藏状态压缩到 50 维进一步提取高层特征。全连接层 2输出层nn.Linear(50, 2)输出 2 个类别的 logits配合 CrossEntropyLoss 得到最终分类概率。classmodel_rnn(nn.Module):def__init__(self):super(model_rnn,self).__init__()self.rnn0nn.RNN(input_size13,hidden_size200,num_layers1,batch_firstTrue)self.fc0nn.Linear(200,50)self.fc1nn.Linear(50,2)defforward(self,x):out,_self.rnn0(x)outout[:,-1,:]# 只取最后一个时间步的输出outself.fc0(out)outself.fc1(out)returnout modelmodel_rnn().to(device)modelmodel(torch.rand(30,1,13).to(device)).shape训练函数# 训练循环deftrain(dataloader,model,loss_fn,optimizer):sizelen(dataloader.dataset)# 训练集的大小num_batcheslen(dataloader)# 批次数目, (size/batch_size向上取整)train_loss,train_acc0,0# 初始化训练损失和正确率forX,yindataloader:# 获取图片及其标签X,yX.to(device),y.to(device)# 计算预测误差predmodel(X)# 网络输出lossloss_fn(pred,y)# 计算网络输出和真实值之间的差距targets为真实值计算二者差值即为损失# 反向传播optimizer.zero_grad()# grad属性归零loss.backward()# 反向传播optimizer.step()# 每一步自动更新# 记录acc与losstrain_acc(pred.argmax(1)y).type(torch.float).sum().item()train_lossloss.item()train_acc/size train_loss/num_batchesreturntrain_acc,train_loss测试函数deftest(dataloader,model,loss_fn):sizelen(dataloader.dataset)# 测试集的大小num_batcheslen(dataloader)# 批次数目, (size/batch_size向上取整)test_loss,test_acc0,0# 当不进行训练时停止梯度更新节省计算内存消耗withtorch.no_grad():forimgs,targetindataloader:imgs,targetimgs.to(device),target.to(device)# 计算losstarget_predmodel(imgs)lossloss_fn(target_pred,target)test_lossloss.item()test_acc(target_pred.argmax(1)target).type(torch.float).sum().item()test_acc/size test_loss/num_batchesreturntest_acc,test_loss超参数配置与正式训练loss_fnnn.CrossEntropyLoss()# 创建损失函数learn_rate1e-3# 学习率opttorch.optim.Adam(model.parameters(),lrlearn_rate)epochs30train_loss[]train_acc[]test_loss[]test_acc[]forepochinrange(epochs):model.train()epoch_train_acc,epoch_train_losstrain(train_dl,model,loss_fn,opt)model.eval()epoch_test_acc,epoch_test_losstest(test_dl,model,loss_fn)train_acc.append(epoch_train_acc)train_loss.append(epoch_train_loss)test_acc.append(epoch_test_acc)test_loss.append(epoch_test_loss)# 获取当前的学习率lropt.state_dict()[param_groups][0][lr]template(Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E})print(template.format(epoch1,epoch_train_acc*100,epoch_train_loss,epoch_test_acc*100,epoch_test_loss,lr))print(训练完成)可视化训练结果importmatplotlib.pyplotaspltfromdatetimeimportdatetime# 隐藏警告importwarnings warnings.filterwarnings(ignore)# 忽略警告信息plt.rcParams[font.sans-serif][SimHei]# 用来正常显示中文标签plt.rcParams[axes.unicode_minus]False# 用来正常显示负号plt.rcParams[figure.dpi]200# 分辨率# 获取当前时间current_timedatetime.now().strftime(%Y-%m-%d %H:%M:%S)epochs_rangerange(epochs)plt.figure(figsize(12,3))# 左图准确率plt.subplot(1,2,1)plt.plot(epochs_range,train_acc,label训练准确率)plt.plot(epochs_range,test_acc,label测试准确率)plt.legend(loclower right)plt.title(训练与验证准确率)# 右图损失plt.subplot(1,2,2)plt.plot(epochs_range,train_loss,label训练损失)plt.plot(epochs_range,test_loss,label测试损失)plt.legend(locupper right)plt.title(训练与验证损失)# 在整个图的上方添加时间plt.suptitle(f训练完成时间:{current_time},fontsize10,y1.02)plt.tight_layout()plt.show()模型评估与混淆矩阵除了准确率外混淆矩阵的四个象限分别表示左上真阴性TN—— 预测没病实际也没病右上假阳性FP—— 预测有病实际没病误报左下假阴性FN—— 预测没病实际有病漏报危害最大右下真阳性TP—— 预测有病实际也有病print(输入数据Shape为)print(X_test.shape,X_test.shape)print(y_test.shape,y_test.shape)predmodel(X_test.to(device)).argmax(1).cpu().numpy()print(\n输出数据Shape为)print(pred.shape,pred.shape)importnumpyasnpimportmatplotlib.pyplotaspltfromsklearn.metricsimportconfusion_matrix,ConfusionMatrixDisplay# 计算混淆矩阵cmconfusion_matrix(y_test,pred)plt.figure(figsize(4,3))plt.suptitle()sns.heatmap(cm,annotTrue,fmtd,cmapBlues)# 修改字体大小plt.xticks(fontsize10)plt.yticks(fontsize10)plt.title(混淆矩阵,fontsize12)plt.xlabel(预测标签,fontsize10)plt.ylabel(真实标签,fontsize10)# 显示图plt.tight_layout()# 调整布局防止重叠plt.show()单样本预测测试test_XX_test[0].unsqueeze(1)# X_test[0]即我们的输入数据predmodel(test_X.to(device)).argmax(1).item()print(模型预测结果为,pred)print(0不会患心脏病)print(1可能患心脏病)学习总结这次做实验让我对RNN有了简单理解。简单说RNN 就是一个会记事的神经网络。普通神经网络看每个输入都是孤立的但RNN不一样它在看当前这个词或数据的时候还会把过去的信息一起考虑进去。但RNN到底能记住多长的事这次处理的是心脏病预测每个样本就是一堆静态指标没有时间顺序。为了用 RNN硬是把每条数据都孤立根本没用上 RNN 的记忆能力。但真的给它一长串有前后关系的数据它能回溯到多久以前的信息呢