写在前面
这篇博客只是记录一下在训练神经网络中的一些问题和熟悉一下流程,毕竟好久没有训练一个模型了。课程正好需要我们去进行训练,我就选择了三个比较经典的模型进行预测。过程分为:处理数据,划分训练集和预测集,定义模型和优化器,生成混淆矩阵。
整个部分比较简单,使用了GPU进行训练,主要是熟悉训练网络的流程和框架。
处理数据
首先是读入数据,使用了pandas读数据
csv_data = './data/high_diamond_ranked_10min.csv' # 数据路径
data_df = pd.read_csv(csv_data, sep=',') # 读入csv文件为pandas的DataFrame
data_df = data_df.drop(columns='gameId') # 舍去对局标号列
之后对数据中的特征进行处理,主要是舍弃了一些不太重要的特征
drop_features = ['blueGoldDiff', 'redGoldDiff',
'blueExperienceDiff', 'redExperienceDiff',
'blueCSPerMin', 'redCSPerMin',
'blueGoldPerMin', 'redGoldPerMin'] # 需要舍去的特征列
df = data_df.drop(columns=drop_features) # 舍去特征列
info_names = [c[3:] for c in df.columns if c.startswith('red')] # 取出要作差值的特征名字(除去red前缀)
for info in info_names: # 对于每个特征名字
df['br' + info] = df['blue' + info] - df['red' + info] # 构造一个新的特征,由蓝色特征减去红色特征,前缀为br
# 其中FirstBlood为首次击杀最多有一只队伍能获得,brFirstBlood=1为蓝,0为没有产生,-1为红
df = df.drop(columns=['blueFirstBlood', 'redFirstBlood']) # 原有的FirstBlood可删除
之后将特征数值归一化,这里采用了减平均值除以标准差的形式。
discrete_df = df.values.copy()
row,col = discrete_df.shape
for i in range(1,col):
avg = np.mean(discrete_df[:,i])
std = np.std(discrete_df[:,i])
discrete_df[:,i] = (discrete_df[:,i]-avg)/std
划分训练集和测试集
这里要注意将输入和输出的type转为float32(初始是float64,不修改会和模型中的参数发生不匹配),并且需要将DataFrame转为array(这里博主踩坑了很久,记得修改)。另外all_x的第一列就是标签,需要删除第一列的元素。
all_y = df['blueWins'].values.astype(np.float32) # 所有标签数据
all_x = discrete_df.astype(np.float32)[:,1:] # 所有原始特征值,pandas的DataFrame.values取出为numpy的array矩阵
# 划分训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(all_x, all_y, test_size=0.2, random_state=RANDOM_SEED)
接下来是定义超参数。为了训练方便,我们都使用TensorDataset和DataLoader进行数据读取,同时还可以使用cuda加速。(使用cuda加速需要cuda版本的输入和输出,以及模型)
input_size = 43 # 输入特征的维度
hidden_size = 64 # 隐藏层的神经元数量
num_classes = 2 # 二分类问题,所以输出层有2个神经元(对应于两个类别)
num_epochs = 100 # 训练周期数
batch_size = 64 # 批处理大小
learning_rate = 0.013 # 学习率
train_dataset = TensorDataset(torch.tensor(x_train), torch.tensor(y_train))
train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_dataset = TensorDataset(torch.tensor(x_test), torch.tensor(y_test))
test_dataloader = DataLoader(test_dataset, batch_size=batch_size, shuffle=True)
定义模型
MLP
class SimpleNN(nn.Module):
def __init__(self, input_size, hidden_size, num_classes):
super(SimpleNN, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size) # 输入层到隐藏层的线性变换
self.relu = nn.ReLU() # 非线性激活函数ReLU
self.fc2 = nn.Linear(hidden_size, 16) # 隐藏层到输出层的线性变换
self.fc3 = nn.Linear(16,num_classes)
def forward(self, x):
out = self.fc1(x)
out = self.relu(out)
out = self.fc2(out)
out = self.relu(out)
out = self.fc3(out)
return out
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检查是否有可用的GPU,并设置设备为GPU或CPU
model = SimpleNN(input_size,hidden_size,num_classes)
model = model.to(device) # 将模型移动到选定的设备上(GPU或CPU)
Attention
class AttentionModel(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(AttentionModel, self).__init__()
# 定义注意力权重计算
self.attention = nn.Sequential(
nn.Linear(input_size, hidden_size),
nn.Tanh(),
nn.Linear(hidden_size, 1),
nn.Softmax(dim=1)
)
# 定义分类器
self.classifier = nn.Sequential(
nn.Linear(input_size, output_size),
nn.Sigmoid() # 适用于二分类任务
)
def forward(self, x):
# x:[32,43]
# 计算注意力权重
attention_weights = self.attention(x)
# attendtion:[32,1]
# 使用注意力权重加权输入特征
attended_input = x * attention_weights
# attended:[32*43]
# 使用分类器进行预测
output = self.classifier(attended_input)
return output
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检查是否有可用的GPU,并设置设备为GPU或CPU
model = AttentionModel(input_size,hidden_size,num_classes)
model = model.to(device) # 将模型移动到选定的设备上(GPU或CPU)
CNN
class CNN(nn.Module):
def __init__(self,input_size,hidden_size,num_classes):
super(CNN, self).__init__()
self.conv1 = nn.Conv1d(1, 32, kernel_size=3, stride=1, padding=1)
self.conv2 = nn.Conv1d(32, 64, kernel_size=3, stride=1, padding=1)
self.conv3 = nn.Conv1d(64, 128, kernel_size=3, stride=1, padding=1)
self.fc1 = nn.Linear(5504, 256) # 根据实际特征大小调整
self.fc2 = nn.Linear(256, num_classes)
def forward(self, x):
x = x.reshape(-1,1,43)
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
x = F.relu(self.conv3(x))
x = x.view(-1, 5504) # 展平操作,根据实际特征大小调整
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu") # 检查是否有可用的GPU,并设置设备为GPU或CPU
model = CNN(input_size,hidden_size,num_classes)
model = model.to(device) # 将模型移动到选定的设备上(GPU或CPU)
训练和测试
先定义一下优化器和损失函数
criterion = nn.CrossEntropyLoss() # 二分类问题使用交叉熵损失函数
optimizer = optim.SGD(model.parameters(),momentum=0.9,lr=learning_rate) # 使用随机梯度下降优化器
然后进行训练,输出训练loss和测试loss。注意测试数据不要用来进行参数优化
for epoch in range(num_epochs):
train_loss = 0.0
for i, (features, labels) in enumerate(train_dataloader):
features = features.to(device) # 将数据移动到GPU上(如果有可用)
labels = labels.to(device) # 将标签移动到GPU上(如果有可用)
outputs = model(features) # 前向传播,计算输出值
loss = criterion(outputs, labels.long()) # 计算损失值
train_loss += loss.item()
optimizer.zero_grad() # 清空梯度缓存
loss.backward() # 反向传播,计算梯度值并更新权重
optimizer.step() # 使用优化器更新权重和偏置项
test_loss = 0.0
for j, (features, labels) in enumerate(test_dataloader):
features = features.to(device) # 将数据移动到GPU上(如果有可用)
labels = labels.to(device) # 将标签移动到GPU上(如果有可用)
outputs = model(features)
loss = criterion(outputs, labels.long()) # 计算损失值
test_loss += loss.item()
print(f"Epoch {epoch+1}/{num_epochs}, Train Loss: {train_loss/i}, Test Loss: {test_loss/j}") # 打印当前训练周期的损失值
生成混淆矩阵
这里我使用了数据集和测试集一起生成混淆矩阵。但我觉得模型在训练的时候可能会出现过拟合情况,所以这样得到的混淆矩阵可能会过于准确,仅使用测试集可能效果会更真实。
from sklearn.metrics import confusion_matrix # 生成混淆矩阵函数
import matplotlib.pyplot as plt
pred_output = model(torch.tensor(all_x).to(device)).cpu().detach()
y_true = []
y_pred = []
for i,pred_label in enumerate(pred_output):
y_true.append(all_y[i])
y_pred.append(np.argmax(pred_label))
def draw_confusion_matrix(label_true, label_pred, label_name, title="Confusion Matrix", pdf_save_path=None, dpi=300):
cm = confusion_matrix(y_true=label_true, y_pred=label_pred, normalize='true')
plt.imshow(cm, cmap='Blues')
plt.title(title)
plt.xlabel("Predict label")
plt.ylabel("Truth label")
plt.yticks(range(label_name.__len__()), label_name)
plt.xticks(range(label_name.__len__()), label_name, rotation=45)
plt.tight_layout()
plt.colorbar()
for i in range(label_name.__len__()):
for j in range(label_name.__len__()):
color = (1, 1, 1) if i == j else (0, 0, 0) # 对角线字体白色,其他黑色
value = float(format('%.2f' % cm[j, i]))
plt.text(i, j, value, verticalalignment='center', horizontalalignment='center', color=color)
# plt.show()
if not pdf_save_path is None:
plt.savefig(pdf_save_path, bbox_inches='tight', dpi=300)
plt.close()
draw_confusion_matrix(y_true,y_pred,[0,1],"Attention","./result/MLP+SDG.png")
注意问题
- 调试输入的维度,需要清楚模块如何对输入进行维度调整的
- SGD当加入momentum时容易过拟合,Adam则下降速度很慢,需要调大learning rate
- DataFrame转为array,使用obj.values
- tensor类型的to.(device),在GPU训练中不可缺少
- 采用什么数据生成混淆矩阵
- 在进行多个模型训练、性能对比的时候,要注意我们将相同的训练数据喂给多个模型,而不是打乱一次数据喂一个模型。考试的时候每个人拿到的是不同的卷子,那样成绩有可比性吗?