一、准备数据集
- Dataset
其使用需要自定义一个类继承Dataset—》class ImdbDataset(Dataset):
其主要作用是从文件中获取数据,主要有三个方法:
__init__该方法是初始化方法,用来获取所有文件的路径
__getitems__该方法是通过index索引获取到每个文件的内容(包括content,label),此处利用tokenlize对获取到的文本了预处理(处理掉不必要的字符)
__len__该方法是获取整个数据集的长度
注意: 数据集分为训练集和测试集,所以此处在处理数据集的时候,需要标注如 def init(self, train=True): 默认是训练集
# 获取数据集,train,或者test
class ImdbDataset(Dataset):
def __init__(self, train=True):
self.train_data_path = r"aclImdb\train"
self.test_data_path = r"aclImdb\test"
data_path = self.train_data_path if train else self.test_data_path
temp_data_path = [os.path.join(data_path, "pos"), os.path.join(data_path, "neg")]
self.total_file_path = []
for path in temp_data_path:
file_name_list = os.listdir(path)
file_path_list = [os.path.join(path, i) for i in file_name_list if i.endswith(".txt")]
self.total_file_path.extend(file_path_list)
def __getitem__(self, index):
file_path = self.total_file_path[index]
label_str = file_path.split("\\")[-2]
label = 0 if label_str == "neg" else 1
content = open(file_path,encoding="utf-8").read()
tokens = tokenlize(content)
return tokens,label
def __len__(self):
return len(self.total_file_path)
- Dataloader
该模型的作用是,使数据可迭代,可以设置batchsize(多通道),shuffle(每次取数据重排序),使用方法如下:
imdb_dataset = ImdbDataset(train)
# 如果此处要设置batch_size为>1的数字,则需要设置collate_fn
data_loader = DataLoader(imdb_dataset, batch_size=128, shuffle=True,collate_fn=collate_fn)
此处需要注意,只要batch_size>1,可能会爆出这样的错误:RuntimeError: each element in list of batch should be of equal size,其原因在Dataloader的源码,要解决这个问题,可以采取以下两种方式:
(1)考虑先把数据转化为数字序列
(2)考虑自定义一个collate_fn
此处采用第二种方法:
def collate_fn(batch):
# batch是一个列表,其中是一个一个的元组,每个元组是dataset中_getitem__的结果(tokens,label)
# zip(*) 相当于解压缩的意思,zip(*[(1,2),(3,4),(5,6))-->[(1,3,5),(2,4,6)]
content,label = list(zip(*batch))
# label = torch.tensor(label, dtype=torch.int32)
# content = content
# 此处的transform是将文本转化成数值,具体方法在ws里,其中ws是一个被封装的字典(源文件在wordsquence类里,内部有词--》数值,数值--》词,及相应转化方法)该类被封装到pickle类型的文件中,为了方便读取
content = [ws.transform(i,max_len = max_len) for i in content]
# 因为torch.tensor 是float 32类型,此处的content数值是int类型,需要用torch.LongTensor
content = torch.LongTensor(content)
label = torch.LongTensor(label)
# del batch
return content, label
- tokenlize
其作用是处理文本中多余的字符,并进行简单的分词,此处是英文分词
def tokenlize(content):
content = content.replace("'s"," is")
content = content.replace("n't"," not")
content = re.sub("<.*?>"," " ,content)
fileters = ["\(","\)","\t", "\n", "\x97", "\x96", "#", "$", "%","\0x93", "&", "\.", ",","!",":","\"","\'","\?","\x95"]
content = re.sub("|".join(fileters)," ", content)
tokens = [i.strip().lower() for i in content.split()]
# tokens = [item.lower() for item in tokens]
return tokens
二、构建模型
到此处,已经有字典(用于比对的数据,存于ws中),数据集(经过清洗的用于训练和测试的数据,存于get_dataloader(train=True)方法中)
- 定义模型类,继承自torch.nn.Model
class MyModel(nn.Module):
def __init__(self):
super(MyModel, self).__init__()
# Embedding(num_embeddings: int-训练集使用的字典的总长度, embedding_dim: int) --》(长度,用多少向量表示一个词)
self.embedding = nn.Embedding(len(ws),100)
# nn.Linear(input类型,output类型)
self.fc = nn.Linear(max_len*100,2)
def forward(self, input):
# 此处input与dataset中input一致
"""
:param input: [batch_size,max_len]
:return:
"""
x = self.embedding(input) # 经过embedding后,x是三维的,[batch_size,max_len,100]
# 将三维的x变形成二维的,为了进行fc(x)-->linear(input,output)的矩阵计算,此处,x的第二维度需要与input的维度一致
x = x.view([-1,100*max_len])
out = self.fc(x)
# dim = -1 在最后一个维度进行操作
return F.log_softmax(out,dim = -1)
关于这个模型,还有一些不明白,比如nn.embedding的作用,引入后会发生什么变化,linear在此处的作用,其维度与其他数据之间的关联?后期希望能解决!
三、训练模型
- 创建模型对象
- 定义优化器对象,此处使用from torch.optim import Adam
- 定义训练方法
(1)导入参数
(2)梯度设为0
(3)调用模型 output = model(input)
(4)计算损失 loss = F.nll_loss(output,target)
(5)反向传播 loss.backward()
(6)更新参数
model = MyModel()
optimizer = Adam(model.parameters(),0.001)
def train(epoch):
for idx,(input,target) in enumerate(get_dataloader(train = True)):
#梯度设为0
optimizer.zero_grad()
# 传入input,计算output
output = model(input)
loss = F.nll_loss(output,target)
loss.backward()
optimizer.step()
print(loss.item())
if __name__ == '__main__':
# 将训练集训练三次,每一次会重新打乱数据,因为(shuffle = True)
for i in range(3):
train(i)
四、模型评估
def test():
mode = False
# 将模型切换到测试模式
model.eval()
test_dataloader = get_dataloader(mode,batch_size = 1000)
# 停止更新梯度,同时加速和节省显存
loss_list = []
acc_list = []
with torch.no_grad():
for idx, (input, target) in enumerate(test_dataloader):
output = model(input)
cur_loss = F.nll_loss(output, target)
loss_list.append(cur_loss)
# 获取每一行的最大值的位置,max(dim=-1)获取每行的最大值,dim=0获取每列的最大值,该值包括两个部分,最大值和最大值的位置,
# [-1]获取位置
# pred = output.max(dim=-1)[-1]
pred = torch.max(output,dim = -1,keepdim = False)[-1]
cur_acc = pred.eq(target).float().mean()
acc_list.append(cur_acc)
print("准确率,平均损失", np.mean(acc_list), np.mean(loss_list))
- 此处模型评估还不太熟悉,后期补上