就跟着代码的顺序说问题
先留一些我的数据,可以参考一下数据类型:
第一章 文件读取
%matplotlib inline
import numpy as np
import pandas as pd
import torch
from torch import nn
from d2l import torch as d2l
train_data = pd.read_csv("all_data.csv")
train_data.shape
#(198, 401)
因为我这个例子数据不多,就198条,所以就不专门设立验证集了,用K折交叉验证的方法来验证。
数据最后一列为标签,所以数据的特征列为400,共198条数据,先把训练数据和标签分开。
train_features = train_data.iloc[:,0:-1].values #0:-1 就是取data中的第0列开始倒数第一列结
#束,包括0列不包括倒数第一列
train_labels = train_data.iloc[:,400:]
print(train_features.shape)
print(train_labels.shape)
type(train_features)
#(198, 400)
#(198, 1)
#numpy.ndarray
可以看到训练数据和标签已经分开了,可以查看变量表看看分的是否是正确的,而且现在的数据格式为np.ndarray,到这里文件的读取就已经结束了。
第二章 数据预处理
首先需要对数据进行归一化操作,这个归一化的方法有好几种,看自己需求吧,我的这个归一化的方法是:对于每一个特征值x,(x-该列最小值)/(该列最大值-该列最小值)
def maxminnorm(array):
maxcols=array.max(axis=0)
mincols=array.min(axis=0)
data_shape = array.shape
data_rows = data_shape[0]
data_cols = data_shape[1]
t=np.empty((data_rows,data_cols))
for i in range(data_cols):
t[:,i]=(array[:,i]-mincols[i])/(maxcols[i]-mincols[i])
return t
train_features = maxminnorm(train_features)
type(train_features)
#numpy.ndarray
处理了之后,数据中可能会出现一些无值的情况,也就是特征值为nan,这种情况出现的原因是该列特征值的数值都相同,最大值和最小值相同,相减为0,所以成为了nan。
特征值为nan的需要进行进一步进行填0或者直接删除的操作,nan是没有数值的,如果不处理计算会报错。我是进行填0操作。
train_features = np.nan_to_num(train_features)
#这个将nan填0的函数只适用于numpy格式的数据,如果是其
#他格式的数据,可以自行查询对应格式的填0函数
接下来就要处理数据的标签了,因为是分类任务,所以标签要变成one-hot的编码格式,如果数据标签不是,就需要进行转变
train_labels = torch.tensor(train_labels.values)
#将标签的数据类型从ndarray转成tensor
train_labels = torch.nn.functional.one_hot(train_labels,num_classes=2)
#这个one-hot编码的函数适用于tensor数据类型,所以先把数据转成了tensor类型,
#其他类型的数据可以考虑转成tensor,或者查一查又没有该类型数据的one-hot转换函数
train_labels.shape
#torch.Size([198, 1, 2])
上面的这个num-classes表示的是要表示成几位的编码,2就是【0,1】、【1,0】这样两位的编码,one-hot的函数还有一个要求就是,你标签的数值必须要小于num_classes的值,比如我这个的标签就只能是0或者1,不能有大于2的值。
现在数据的维度变成了198*1*2,正确的应该是198*2,多了一个一维,输出该变量部分。
print(train_labels[:5])
#tensor([[[1, 0]],[[1, 0]],[[1, 0]],[[1, 0]],[[1, 0]]])
单纯只是多了一个框,直接去掉就ok,用squeeze降维函数。
train_labels = torch.squeeze(train_labels, dim=1)
print(train_labels[:5])
#tensor([[1, 0],
[1, 0],
[1, 0],
[1, 0],
[1, 0]])
最后一步就是数据格式的转换了,要用torch的框架,所以就都换成torch的格式,并且要是float32的格式,这是损失函数的格式要求。
train_features = torch.tensor(train_features,dtype=torch.float32)
train_labels = torch.tensor(train_labels,dtype=torch.float32)
第三章 模型构建
首先定义loss以及网络结构,因为是二分类,标签编码是两位,所以输出为2,并且是二分类问题loss采用BCELoss,多分类的话可以用CrossEntropyLoss,最后一层要放一个sigmoid函数,将输出限制在0-1。
loss = nn.BCELoss()
def get_net():
net = nn.Sequential(nn.Linear(400,256),
nn.ReLU(),
nn.Linear(256,2),
nn.Sigmoid()
)
return net
然后定义训练器
def train(net, train_features, train_labels, test_features, test_labels,num_epochs, learning_rate, weight_decay, batch_size):
# 网络,训练集, 训练标签, 测试集, 测试标签, 训练次数, 学习率, 权重衰退 , 批量数目
train_ls, test_ls = [], []
train_iter = d2l.load_array((train_features, train_labels), batch_size) #取小批量样本
# 这里使用的是Adam优化算法
optimizer = torch.optim.Adam(net.parameters(),
lr = learning_rate,
weight_decay = weight_decay)
for epoch in range(num_epochs):
for X, y in train_iter:
optimizer.zero_grad()
l = loss(net(X), y)
l.backward()
optimizer.step()
train_ls.append(loss(net(train_features), train_labels)) #log_rmse返回的是取log后差值的平方,这里用这个作为误差指标
if test_labels is not None:
test_ls.append(loss(net(test_features), test_labels))
return train_ls, test_ls
采用K折交叉验证的方法,需要先对数据进行拆分,下面是数据的拆分函数
def get_k_fold_data(k, i, X, y):
assert k > 1
fold_size = X.shape[0] // k
X_train, y_train = None, None
for j in range(k):
idx = slice(j * fold_size, (j + 1) * fold_size)
X_part, y_part = X[idx, :], y[idx]
if j == i:
X_valid, y_valid = X_part, y_part
elif X_train is None:
X_train, y_train = X_part, y_part
else:
X_train = torch.cat([X_train, X_part], 0)
y_train = torch.cat([y_train, y_part], 0)
return X_train, y_train, X_valid, y_valid
下面是最终的采用K折交叉验证的训练函数
def k_fold(k, X_train, y_train, num_epochs, learning_rate, weight_decay,
batch_size):
train_l_sum, valid_l_sum = 0, 0
for i in range(k):
data = get_k_fold_data(k, i, X_train, y_train)
net = get_net()
train_ls, valid_ls = train(net, *data, num_epochs, learning_rate,
weight_decay, batch_size)
train_l_sum += train_ls[-1]
valid_l_sum += valid_ls[-1]
if i == 0:
d2l.plot(list(range(1, num_epochs + 1)), [train_ls, valid_ls],
xlabel='epoch', ylabel='rmse', xlim=[1, num_epochs],
legend=['train', 'valid'], yscale='log')
print(f'折{i + 1},训练log rmse{float(train_ls[-1]):f}, '
f'验证log rmse{float(valid_ls[-1]):f}')
return train_l_sum / k, valid_l_sum / k
最后进行训练与输出
k, num_epochs, lr, weight_decay, batch_size = 5, 50, 0.001, 0, 10
train_l, valid_l = k_fold(k, train_features, train_labels, num_epochs, lr,
weight_decay, batch_size)
print(f'{k}-折验证: 平均训练log rmse: {float(train_l):f}, '
f'平均验证log rmse: {float(valid_l):f}')