当前位置:   article > 正文

用Transformer模型实现简单二分类问题_transformer模型 分类问题

transformer模型 分类问题

写在最前:

在系统地学习了Transformer结构后,尝试使用Transformer模型对DNA序列数据实现二分类,好久前就完成了这个实验,一直拖着没有整理,今天系统的记录一下,顺便记录一下自己踩过的坑

(需要数据的可以私聊我)

1、数据说明

两个csv文件,共有三列,第一列是id,第二列每个数据都是一长串dna序列,第三列是它们的label,分别是0和1。

数据的data列有点长,此处截了一部分供大家参考:

2、python库准备

此处Transformer我使用了pytorch的,所以需要事先安装pytorch库,这里是一大踩坑点

我是跟这篇博客下载安装的,因为我的电脑配置也是win10+MX350显卡+CUDA10.2,最后安装在conda环境下安装pytorch:

第一次没有指定pytorch版本结果自动装了最新版,然后在python环境下输入print(torch.cuda.is_available()),结果是false。

第二次:将之前的卸载重新安装,此处指定了版本好像是1.10,结果最后还是false。

第三次:百思不得其解,就找了大量的原因,最后用了下面的命令:pip3 install torch==1.10.1+cu102 torchvision==0.11.2+cu102 torchaudio===0.10.1+cu102 -f https:// download.pytorch.org/whl/cu102/torch_stable.html

然后打印输出:True!!!!!!!

3、实验难点

个人感觉实验中最重要的两部分就是dna序列数据的处理和Transformer模型构建。

3.1 DNA数据处理

数据中第二列data列是dna数据,很长一串,需要进行转换。

(1)我刚开始使用了独热编码,代码如下:

  1. def one_hot_encode_sequence(sequence):
  2. mapping = {'A': [1, 0, 0, 0], 'C': [0, 1, 0, 0], 'G': [0, 0, 1, 0], 'T': [0, 0, 0, 1]}
  3. encoding = [mapping[base] for base in sequence]
  4. return np.array(encoding)
  5. # 对DNA序列进行单热编码
  6. # 每个seq是一个序列,序列中的每个base变成[1,0,0,0],再组合成二维
  7. X = np.array([one_hot_encode_sequence(seq) for seq in data3['data']])
  8. y = data3['label'].values

很好理解,就是将ACGT分别映射成[1,0,0,0]  [0,1,0,0]  [0,0,1,0]  [0,0,0,1],但是这样就多了一个维度,打印一下x的shape看看:

直接变三维数据了,然后后面在Transformer阶段,数据维度转换就出了问题:

我想计算loss,刚开始写了loss = criterion(outputs, labels.view(-1, 1)),但是outputs是torch.Size([1, 1001, 1],labels是torch.Size([32]),这两个数据不知道怎么才能转换成同一shape的数据,当时搞了好久也没成功,所以放弃这个编码方案。。。。

(如果有大佬有方法将这两个的size改成一致,欢迎评论区留言!!)

(2)使用嵌入编码

直接上代码:

  1. # 将DNA序列数据转换为整数编码
  2. def integer_encode(seq):
  3. seq = seq.upper()
  4. encoding = {'A': 0, 'T': 1, 'C': 2, 'G': 3}
  5. return np.array([encoding[x] for x in seq])
  6. X = np.array([integer_encode(seq) for seq in X])
  7. y = np.array(y)

很好理解,再看一下X的维度:

很明显比上面的三维数据要好哈哈哈

好用!爱用!!下次还用!!

3.2 Transformer结构

(1)首先需要嵌入位置信息

直接上代码吧,我喜欢做注释,这个注释也很详细,大家自己看就明白了,其实就对应了Transformer论文中PE的公式:

  1. # 定义PositionalEncoding
  2. class PositionalEncoding(nn.Module):
  3. def __init__(self, d_model, dropout, max_len=5000):
  4. super(PositionalEncoding, self).__init__()
  5. self.dropout = nn.Dropout(p=dropout)
  6. # pe的维度(位置编码最大长度,模型维度)
  7. pe = torch.zeros(max_len, d_model)
  8. # 维度为(max_len, 1):先在[0,max_len]中取max_len个整数,再加一个维度
  9. position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
  10. # 位置编码的除数项:10000^(2i/d_model)
  11. div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-np.log(10000.0) / d_model))
  12. # sin负责奇数;cos负责偶数
  13. pe[:, 0::2] = torch.sin(position * div_term)
  14. pe[:, 1::2] = torch.cos(position * div_term)
  15. #维度变换:(max_len,d_model)→(1,max_len,d_model)→(max_len,1,d_model)
  16. pe = pe.unsqueeze(0).transpose(0, 1)
  17. # 将pe注册为模型缓冲区
  18. self.register_buffer('pe', pe)
  19. def forward(self, x):
  20. # 取pe的前x.size(0)行,即
  21. # (x.size(0),1,d_model) → (x.size(0),d_model),拼接到x上
  22. x = x + self.pe[:x.size(0), :]
  23. return self.dropout(x)

(2)然后就是Transformer结构

  1. # 定义Transformer模型
  2. class TransformerModel(nn.Module):
  3. def __init__(self, input_dim, output_dim, d_model, nhead, num_layers, dim_feedforward, dropout):
  4. super(TransformerModel, self).__init__()
  5. # 创建一个线性变换层,维度input_dim4→d_model
  6. self.embedding = nn.Embedding(input_dim, d_model) # 使用嵌入层
  7. # 生成pe
  8. self.pos_encoder = PositionalEncoding(d_model, dropout)
  9. # 生成一层encoder
  10. encoder_layers = nn.TransformerEncoderLayer(d_model=d_model, nhead=nhead, dim_feedforward=dim_feedforward, dropout=dropout)
  11. # 多层encoder
  12. self.transformer_encoder = nn.TransformerEncoder(encoder_layers, num_layers=num_layers)
  13. # 维度d_model→output_dim
  14. self.fc = nn.Linear(d_model, output_dim)
  15. self.d_model = d_model
  16. def forward(self, src):
  17. src = src.permute(1, 0)
  18. # 缩放
  19. src = self.embedding(src) * np.sqrt(self.d_model)
  20. # 加上位置嵌入
  21. src = self.pos_encoder(src)
  22. output = self.transformer_encoder(src)
  23. # 调整输出形状为(batch, seq_len, d_model)
  24. output = output.permute(1, 0, 2)
  25. # 对所有位置的表示取平均
  26. output = torch.mean(output, dim=1)
  27. # 线性变换
  28. output = self.fc(output)
  29. # 使用sigmoid激活函数
  30. output = torch.sigmoid(output)
  31. return output

4、其余

解决了上面的难点,后面的导包、数据导入预处理划分、数据加载、训练数据、测试数据就不细说了,后面有完整代码大家自己看吧

  1. import torch
  2. import torch.nn as nn
  3. import torch.optim as optim
  4. import numpy as np
  5. import pandas as pd
  6. from sklearn.model_selection import train_test_split
  7. # data1data2是包含DNA序列数据的列表,每个数据集有三列分别是id data label,标签分别为01
  8. data1 = pd.read_csv(r"D:\桌面\研0\Transformer\0.csv")
  9. data2 = pd.read_csv(r"D:\桌面\研0\Transformer\1.csv")
  10. # 合并
  11. data = pd.concat([data1,data2], ignore_index=True)
  12. X = data['data'].values
  13. y = data['label'].values
  14. print("X.shape:",end='')
  15. print(X.shape)
  16. print("y.shape:",end='')
  17. print(y.shape)
  18. # 将DNA序列数据转换为整数编码
  19. def integer_encode(seq):
  20. seq = seq.upper()
  21. encoding = {'A': 0, 'T': 1, 'C': 2, 'G': 3}
  22. return np.array([encoding[x] for x in seq])
  23. X = np.array([integer_encode(seq) for seq in X])
  24. y = np.array(y)
  25. print("====转换成整数编码后====")
  26. print("X.shape:",end='')
  27. print(X.shape)
  28. print("y.shape:",end='')
  29. print(y.shape)
  30. # 划分训练集和测试集
  31. X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
  32. # 创建PyTorch数据集和数据加载器
  33. class DNADataset(torch.utils.data.Dataset):
  34. def __init__(self, X, y):
  35. self.X = torch.tensor(X, dtype=torch.long) # 将输入数据转换为长整型
  36. self.y = torch.tensor(y, dtype=torch.float32)
  37. def __len__(self):
  38. return len(self.X)
  39. def __getitem__(self, idx):
  40. return self.X[idx], self.y[idx]
  41. train_dataset = DNADataset(X_train, y_train)
  42. test_dataset = DNADataset(X_test, y_test)
  43. train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=32, shuffle=True)
  44. test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=32, shuffle=False)
  45. # 定义PositionalEncoding
  46. class PositionalEncoding(nn.Module):
  47. def __init__(self, d_model, dropout, max_len=5000):
  48. super(PositionalEncoding, self).__init__()
  49. self.dropout = nn.Dropout(p=dropout)
  50. # pe的维度(位置编码最大长度,模型维度)
  51. pe = torch.zeros(max_len, d_model)
  52. # 维度为(max_len, 1):先在[0,max_len]中取max_len个整数,再加一个维度
  53. position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
  54. # 位置编码的除数项:10000^(2i/d_model)
  55. div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-np.log(10000.0) / d_model))
  56. # sin负责奇数;cos负责偶数
  57. pe[:, 0::2] = torch.sin(position * div_term)
  58. pe[:, 1::2] = torch.cos(position * div_term)
  59. #维度变换:(max_len,d_model)→(1,max_len,d_model)→(max_len,1,d_model)
  60. pe = pe.unsqueeze(0).transpose(0, 1)
  61. # 将pe注册为模型缓冲区
  62. self.register_buffer('pe', pe)
  63. def forward(self, x):
  64. # 取pe的前x.size(0)行,即
  65. # (x.size(0),1,d_model) → (x.size(0),d_model),拼接到x上
  66. x = x + self.pe[:x.size(0), :]
  67. return self.dropout(x)
  68. # 定义Transformer模型
  69. class TransformerModel(nn.Module):
  70. def __init__(self, input_dim, output_dim, d_model, nhead, num_layers, dim_feedforward, dropout):
  71. super(TransformerModel, self).__init__()
  72. # 创建一个线性变换层,维度input_dim4→d_model
  73. self.embedding = nn.Embedding(input_dim, d_model) # 使用嵌入层
  74. # 生成pe
  75. self.pos_encoder = PositionalEncoding(d_model, dropout)
  76. # 生成一层encoder
  77. encoder_layers = nn.TransformerEncoderLayer(d_model=d_model, nhead=nhead, dim_feedforward=dim_feedforward, dropout=dropout)
  78. # 多层encoder
  79. self.transformer_encoder = nn.TransformerEncoder(encoder_layers, num_layers=num_layers)
  80. # 维度d_model→output_dim
  81. self.fc = nn.Linear(d_model, output_dim)
  82. self.d_model = d_model
  83. def forward(self, src):
  84. src = src.permute(1, 0)
  85. # 缩放
  86. src = self.embedding(src) * np.sqrt(self.d_model)
  87. # 加上位置嵌入
  88. src = self.pos_encoder(src)
  89. output = self.transformer_encoder(src)
  90. # 调整输出形状为(batch, seq_len, d_model)
  91. output = output.permute(1, 0, 2)
  92. # 对所有位置的表示取平均
  93. output = torch.mean(output, dim=1)
  94. # 线性变换
  95. output = self.fc(output)
  96. # 使用sigmoid激活函数
  97. output = torch.sigmoid(output)
  98. return output
  99. # 初始化模型
  100. model = TransformerModel(input_dim=4,
  101. output_dim=1,
  102. d_model=128,
  103. nhead=4,
  104. num_layers=3,
  105. dim_feedforward=256,
  106. dropout=0.1)
  107. # 定义损失函数和优化器
  108. criterion = nn.BCELoss()
  109. optimizer = optim.Adam(model.parameters(), lr=0.001)
  110. # 训练模型
  111. def train(model, iterator, optimizer, criterion):
  112. # iterator是train_loader= DataLoader(train_dataset, batch_size=32)
  113. model.train()
  114. for batch in iterator:
  115. # 初始化,防止梯度爆炸
  116. optimizer.zero_grad()
  117. X, y = batch
  118. predictions = model(X)
  119. # 计算修正损失函数
  120. loss = criterion(predictions.squeeze(), y)
  121. # 计算当前批数据的损失函数对模型参数的梯度
  122. loss.backward()
  123. # 根据梯度更新模型参数
  124. optimizer.step()
  125. # 测试模型
  126. def evaluate(model, iterator, criterion):
  127. print("===========test==========")
  128. model.eval()
  129. epoch_loss = 0
  130. with torch.no_grad():# 此处不需要梯度计算
  131. for batch in iterator:
  132. X, y = batch
  133. predictions = model(X)
  134. loss = criterion(predictions.squeeze(), y)
  135. epoch_loss += loss.item()
  136. # 累加loss,再求平均
  137. return epoch_loss / len(iterator)
  138. # 开始训练
  139. N_EPOCHS = 10
  140. for epoch in range(N_EPOCHS):
  141. print("第%d轮===================================="%(epoch+1))
  142. train(model, train_loader, optimizer, criterion)
  143. test_loss = evaluate(model, test_loader, criterion)
  144. print(f'Epoch: {epoch+1:02}, Test Loss: {test_loss:.3f}')

我设置的epoch是10轮,每轮都会进行训练和测试,其中batch_size是32,每轮都会打印测试的平均loss,具体如下:

  1. Epoch: 01, Test Loss: 0.641
  2. Epoch: 02, Test Loss: 0.640
  3. Epoch: 03, Test Loss: 0.669
  4. Epoch: 04, Test Loss: 0.639
  5. Epoch: 05, Test Loss: 0.638
  6. Epoch: 06, Test Loss: 0.640
  7. Epoch: 07, Test Loss: 0.638
  8. Epoch: 08, Test Loss: 0.641
  9. Epoch: 09, Test Loss: 0.639
  10. Epoch: 10, Test Loss: 0.638

5、写在最后

第一次自行用Transformer模型实现二分类,过程很曲折,不过最后还好成功实现了。

各位大佬们如果有更好的模型实现思路,欢迎在评论区指教!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/235494
推荐阅读
相关标签
  

闽ICP备14008679号