一:“手动实现前馈神经网络解决回归、二分类、多分类任务”实验
1.1“手动实现前馈神经网络解决回归”实验
实验过程:
1.1.1 导入所需要的包
1. import torch 2. import numpy as np 3. import random 4. from IPython import display 5. from matplotlib import pyplot as plt 6. import torch.utils.data as Data
1.1.2自定义数据
1. #自定义数据---训练集 2. num_inputs = 500 3. num_examples = 10000 4. true_w = torch.ones(500,1)*0.0056 5. true_b = 0.028 6. x_features = torch.tensor(np.random.normal(0, 1, (num_examples, num_inputs)), dtype=torch.float) 7. y_labels = torch.mm(x_features,true_w) + true_b 8. y_labels += torch.tensor(np.random.normal(0, 0.01, size=y_labels.size()), dtype=torch.float) 9. #训练集 10. trainfeatures =x_features[:7000] 11. trainlabels = y_labels[:7000] 12. print(trainfeatures.shape) 13. #测试集 14. testfeatures =x_features[7000:] 15. testlabels = y_labels[7000:] 16. print(testfeatures.shape)
注:这里按照ppt的实验要求,进行构建线性数据集,样本的特征和真值其主要是满足高维线性关系,对于样本的特征来说,我这里定义的是服从均值为0,标准差为1的分布,所以相比于分散的数据,其是比较复杂的,这里样本的数量是10000,其中划分为训练集为7000,测试集为3000。
1.1.3读取数据
1. #读取数据 2. batch_size = 50 3. # 将训练数据的特征和标签组合 4. dataset = Data.TensorDataset(trainfeatures, trainlabels) 5. # 把 dataset 放入 DataLoader 6. train_iter = Data.DataLoader( 7. dataset=dataset, # torch TensorDataset format 8. batch_size=batch_size, # mini batch size 9. shuffle=True, # 是否打乱数据 (训练集一般需要进行打乱) 10. num_workers=0, # 多线程来读数据, 注意在Windows下需要设置为0 11. ) 12. # 将测试数据的特征和标签组合 13. dataset = Data.TensorDataset(testfeatures, testlabels) 14. # 把 dataset 放入 DataLoader 15. test_iter = Data.DataLoader( 16. dataset=dataset, # torch TensorDataset format 17. batch_size=batch_size, # mini batch size 18. shuffle=True, # 是否打乱数据 (训练集一般需要进行打乱) 19. num_workers=0, # 多线程来读数据, 注意在Windows下需要设置为0 20. )
注:利用Data.DataLoader读取数据,并设置进行批量读取的大小,后面的实验在读取数据部分都是利用的torch自带的这个模块进行读取,之后不再加以赘述。
1.1.4初始化参数
1. #初始化参数 2. num_hiddens,num_outputs = 256,1 3. 4. W1 = torch.tensor(np.random.normal(0, 0.01, (num_hiddens,num_inputs)), dtype=torch.float32) 5. b1 = torch.zeros(1, dtype=torch.float32) 6. W2 = torch.tensor(np.random.normal(0, 0.01, (num_outputs,num_hiddens)), dtype=torch.float32) 7. b2 = torch.zeros(1, dtype=torch.float32) 8. params =[W1,b1,W2,b2] 9. for param in params: 10. param.requires_grad_(requires_grad=True)
注:可由模型设计部分知道其输入层、隐藏层、输出层分别是500,256,1。这里初始化参数则需要分别初始化输入层和隐藏层之间的W1和b1,以及隐藏层和输出层之间的W2和b2,这里设置的W的初始是服从均值为0,方差为0.01的高斯分布,b的初值是0;然后需要设置梯度更新为true。
1.1.5 定义隐藏层的激活函数
1. def relu(x): 2. x = torch.max(input=x,other=torch.tensor(0.0)) 3. return x
注:这里的激活函数我设计的是ReLU激活函数,因为其x>0部分的不存在梯度消失等问题,对于隐藏的单元的激活函数普遍使用的是ReLU激活函数。
1.1.6定义模型
1. #定义模型 2. def net(X): 3. X = X.view((-1,num_inputs)) 4. H = relu(torch.matmul(X,W1.t())+b1) 5. return torch.matmul(H,W2.t())+b2
注:这里是手动实现模型的关键,首先H得到的是隐藏层经过激活函数后的输出,最后返回全连接得到输出层结果。
1.1.7 定义最小化误差以及随机梯度下降法
1. #定义最小化均方误差 2. loss = torch.nn.MSELoss() 3. 4. #定义随机梯度下降法 5. def SGD(paras,lr,batch_size): 6. for param in params: 7. param.data -= lr * param.grad/batch_size
注:因为是回归问题,这里使用的是torch模块自带的最小化均方误差,然后梯度下降法使用的是小批量随机梯度下降法。
1.1.8 定义训练函数
1. #定义模型训练函数 2. def train(net,train_iter,test_iter,loss,num_epochs,batch_size,params=None,lr=None,optimizer=None): 3. train_ls = [] 4. test_ls = [] 5. for epoch in range(num_epochs): # 训练模型一共需要num_epochs个迭代周期 6. train_l_sum, train_acc_num,n = 0.0,0.0,0 7. # 在每一个迭代周期中,会使用训练数据集中所有样本一次 8. for X, y in train_iter: # x和y分别是小批量样本的特征和标签 9. y_hat = net(X) 10. l = loss(y_hat, y.view(-1,1)) # l是有关小批量X和y的损失 11. #梯度清零 12. if optimizer is not None: 13. optimizer.zero_grad() 14. elif params is not None and params[0].grad is not None: 15. for param in params: 16. param.grad.data.zero_() 17. l.backward() # 小批量的损失对模型参数求梯度 18. if optimizer is None: 19. SGD(params,lr,batch_size) 20. else: 21. optimizer.step() 22. train_labels = trainlabels.view(-1,1) 23. test_labels = testlabels.view(-1,1) 24. train_ls.append(loss(net(trainfeatures),train_labels).item()) 25. test_ls.append(loss(net(testfeatures),test_labels).item()) 26. print('epoch %d, train_loss %.6f,test_loss %f'%(epoch+1, train_ls[epoch],test_ls[epoch])) 27. return train_ls,test_ls
注:这个训练模型主要借鉴的ppt上的训练,在参数设置中,它根据传入的参数值,来区分是手动实现,还是torch模块实现,然后在每次epoch后,重新利用loss计算训练集和测试集的loss,保存到列表中,然后返回。
1.1.9开始训练模型
1. lr = 0.05 2. num_epochs = 500 3. train_loss,test_loss = train(net,train_iter,test_iter,loss,num_epochs,batch_size,params,lr)
注:调用之前的模型函数,传入参数:学习率,迭代次数,批量大小等,进行训练。
1.1.10 绘制训练集和测试集的loss曲线
1. x = np.linspace(0,len(train_loss),len(train_loss)) 2. plt.plot(x,train_loss,label="train_loss",linewidth=1.5) 3. plt.plot(x,test_loss,label="test_loss",linewidth=1.5) 4. plt.xlabel("epoch") 5. plt.ylabel("loss") 6. plt.legend() 7. plt.show()
注:这里也是借鉴ppt的绘图部分,主要是利用matplotlib库,进行绘制,横坐标是迭代次数,纵坐标分别之前保存的训练集误差和测试集误差。
1.1.2“手动实现前馈神经网络解决二分类”实验
实验过程:
1.2.1 导入所需要的包
1. import torch 2. import numpy as np 3. import random 4. from IPython import display 5. from matplotlib import pyplot as plt 6. import torch.utils.data as Data
1.2.2自定义数据
1. #自定义数据---训练集 2. num_inputs = 200 3. #1类 4. x1 = torch.normal(2,1,(10000, num_inputs)) 5. y1 = torch.ones(10000,1) # 标签1 6. x1_train = x1[:7000] 7. x1_test = x1[7000:] 8. #0类 9. x2 = torch.normal(-2,1,(10000, num_inputs)) 10. y2 = torch.zeros(10000,1) # 标签0 11. x2_train = x2[:7000] 12. x2_test = x2[7000:] 13. #合并训练集 14. # 注意 x, y 数据的数据形式一定要像下面一样 (torch.cat 是合并数据)---按行合并 15. trainfeatures = torch.cat((x1_train,x2_train), 0).type(torch.FloatTensor) 16. trainlabels = torch.cat((y1[:7000], y2[:7000]), 0).type(torch.FloatTensor) 17. #合并测试集 18. # 注意 x, y 数据的数据形式一定要像下面一样 (torch.cat 是合并数据)---按行合并 19. testfeatures = torch.cat((x1_test,x2_test), 0).type(torch.FloatTensor) 20. testlabels = torch.cat((y1[7000:], y2[7000:]), 0).type(torch.FloatTensor) 21. print(trainfeatures.shape,trainlabels.shape,testfeatures.shape,testlabels.shape)
注:按实验要求,这里生成了两个数据集,第一类为均值为2,标准差为1,其标签为1,训练集和测试集个数分别是7000,3000;第二类为均值为-2,标准差为1,其标签为0,训练集和测试集个数分别是7000,3000。然后利用torch.cat()操作将两类的训练集和测试集分别合并,从而训练集的大小为14000,测试集的大小为6000。
1.2.3读取数据
1. #读取数据 2. batch_size = 50 3. # 将训练数据的特征和标签组合 4. dataset = Data.TensorDataset(trainfeatures, trainlabels) 5. # 把 dataset 放入 DataLoader 6. train_iter = Data.DataLoader( 7. dataset=dataset, # torch TensorDataset format 8. batch_size=batch_size, # mini batch size 9. shuffle=True, # 是否打乱数据 (训练集一般需要进行打乱) 10. num_workers=0, # 多线程来读数据, 注意在Windows下需要设置为0 11. ) 12. # 将测试数据的特征和标签组合 13. dataset = Data.TensorDataset(testfeatures, testlabels) 14. # 把 dataset 放入 DataLoader 15. test_iter = Data.DataLoader( 16. dataset=dataset, # torch TensorDataset format 17. batch_size=batch_size, # mini batch size 18. shuffle=True, # 是否打乱数据 (训练集一般需要进行打乱) 19. num_workers=0, # 多线程来读数据, 注意在Windows下需要设置为0 20. )
注:因为统一用的是torch中自带的模块 Data.TensorDataset,其过程和上一部分的第1.1.3部分一样。
1.2.4初始化参数
1. #初始化参数 2. num_hiddens,num_outputs = 256,1 3. 4. W1 = torch.tensor(np.random.normal(0, 0.01, (num_hiddens,num_inputs)), dtype=torch.float32) 5. b1 = torch.zeros(1, dtype=torch.float32) 6. W2 = torch.tensor(np.random.normal(0, 0.01, (num_outputs,num_hiddens)), dtype=torch.float32) 7. b2 = torch.zeros(1, dtype=torch.float32) 8. params =[W1,b1,W2,b2] 9. for param in params: 10. param.requires_grad_(requires_grad=True)
注:这部分和4.1.1的第4)部分大致一样,因为网络的大体模型(如:隐藏的个数、隐藏单元的个数、以及最后的输出层神经元个数)是一样的,只是输入层的特征不同而已
1.2.5定义模型
1. #定义模型 2. def net(X): 3. X = X.view((-1,num_inputs)) 4. H = relu(torch.matmul(X,W1.t())+b1) 5. return torch.matmul(H,W2.t())+b2
注:这里的激活函数我设计的是ReLU激活函数,因为其x>0部分的不存在梯度消失等问题,对于隐藏的单元的激活函数普遍使用的是ReLU激活函数。
1.2.6定义交叉熵损失函数和优化器
1. #定义模型 2. def net(X): 3. X = X.view((-1,num_inputs)) 4. H = relu(torch.matmul(X,W1.t())+b1) 5. return torch.matmul(H,W2.t())+b2
注:作为输出层的Sigmoid激活函数,这里我利用torch.nn.BCEWithLogitsLoss()函数,它将Sigmoid函数和交叉熵损失函数合并在了一起计算,相比于单独计算更加稳定了,然后同样是小批量随机梯度下降法进行梯度更新。
1.2.7 定义模型训练函数
1.2.7 定义模型训练函数
1. #定义模型训练函数 2. def train(net,train_iter,test_iter,loss,num_epochs,batch_size,params=None,lr=None,optimizer=None): 3. train_ls = [] 4. test_ls = [] 5. for epoch in range(num_epochs): # 训练模型一共需要num_epochs个迭代周期 6. train_l_sum, train_acc_num,n = 0.0,0.0,0 7. # 在每一个迭代周期中,会使用训练数据集中所有样本一次 8. for X, y in train_iter: # x和y分别是小批量样本的特征和标签 9. y_hat = net(X) 10. l = loss(y_hat, y.view(-1,1)) # l是有关小批量X和y的损失 11. #梯度清零 12. if optimizer is not None: 13. optimizer.zero_grad() 14. elif params is not None and params[0].grad is not None: 15. for param in params: 16. param.grad.data.zero_() 17. l.backward() # 小批量的损失对模型参数求梯度 18. if optimizer is None: 19. SGD(params,lr,batch_size) 20. else: 21. optimizer.step() 22. #计算每个epoch的loss 23. train_l_sum += l.item()*y.shape[0] 24. #train_acc_num += (y_hat.argmax(dim=1)==y).sum().item() 25. n+= y.shape[0] 26. test_labels = testlabels.view(-1,1) 27. train_ls.append(train_l_sum/n) 28. test_ls.append(loss(net(testfeatures),test_labels).item()) 29. print('epoch %d, train_loss %.6f,test_loss %.6f'%(epoch+1, train_ls[epoch],test_ls[epoch])) 30. return train_ls,test_ls
注:这里不同于之前的“手动实现前馈神经网络解决回归”问题,在训练集的误差上,是在每次epoch下的损失和的平均,而回归之前用的是每次epoch之后(即参数更新后)进行的训练集的损失和的平均,我感觉对于回归其loss计算应该也是在每次epoch下的损失和平均,但是对于loss的趋势来说,这样是没有影响的;这里的训练集误差需要注意的是损失函数求得是这个小批量的平均值,而一般最后一个迭代的个数可能会比之前的小,所以这里需要把平均值全部展开,计算所有的损失的和然后除以总的训练集个数,对于测试集的loss,其是在每次epoch梯度更新后计算的,是对于所有测试集的平均值,符合条件,最后存入列表中返回。
1.2.8 开始训练模型
1. lr = 0.01 2. num_epochs = 50 3. train_loss,test_loss = train(net,train_iter,test_iter,loss,num_epochs,batch_size,params,lr)
注:调用模型函数,定义好训练次数、学习率等参数进行训练,返回结果保存到列表中进行画图。这里的loss相比于之前的回归问题比较小,其解释在实验结果部分。
1.2.9绘制训练集和测试集的loss曲线
1. lr = 0.05 2. num_epochs = 500 3. train_loss,test_loss = train(net,train_iter,test_iter,loss,num_epochs,batch_size,params,lr)
注:调用之前的模型函数,传入参数:学习率,迭代次数,批量大小等,进行训练。
1.2.10 绘制训练集和测试集的loss曲线
1. x = np.linspace(0,len(train_loss),len(train_loss)) 2. plt.plot(x,train_loss,label="train_loss",linewidth=1.5) 3. plt.plot(x,test_loss,label="test_loss",linewidth=1.5) 4. plt.xlabel("epoch") 5. plt.ylabel("loss") 6. plt.legend() 7. plt.show()
注:这里也是借鉴ppt的绘图部分,主要是利用matplotlib库,进行绘制,横坐标是迭代次数,纵坐标分别之前保存的训练集误差和测试集误差。
1.3“手动实现前馈神经网络解决多分类”实验
实验过程:
1.3.1 导入所需要的包
1. import torch 2. import numpy as np 3. import random 4. from IPython import display 5. from matplotlib import pyplot as plt 6. import torchvision 7. import torchvision.transforms as transforms
1.3.2 下载MNIST数据集
1. #下载MNIST手写数据集 2. mnist_train = torchvision.datasets.MNIST(root='./Datasets/MNIST', train=True, 3. download=True, transform=transforms.ToTensor()) 4. mnist_test = torchvision.datasets.MNIST(root='./Datasets/MNIST', train=False, 5. download=True, transform=transforms.ToTensor())
注:按实验要求,这里利用torchvision模块下载了数字手写数据集,其中训练集为60000张图片,测试集为10000张图片,其每个图片对应的标签是0-9之间,分别代表手写数字0,1,2,3,4,5,6,7,8,9.
1.3.3读取数据
1. #读取数据 2. batch_size = 32 3. train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, 4. num_workers=0) 5. test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, 6. num_workers=0)
1.3.4初始化参数+定义隐藏层的激活函数
1. #初始化参数 2. num_hiddens,num_outputs = 256,1 3. 4. W1 = torch.tensor(np.random.normal(0, 0.01, (num_hiddens,num_inputs)), dtype=torch.float32) 5. b1 = torch.zeros(1, dtype=torch.float32) 6. W2 = torch.tensor(np.random.normal(0, 0.01, (num_outputs,num_hiddens)), dtype=torch.float32) 7. b2 = torch.zeros(1, dtype=torch.float32) 8. params =[W1,b1,W2,b2] 9. for param in params: 10. param.requires_grad_(requires_grad=True) 11. 12. def relu(x): 13. x = torch.max(input=x,other=torch.tensor(0.0)) 14. return x
注:因为网络的大体模型(如:隐藏的个数、隐藏单元的个数、以及最后的输出层神经元个数)是一样的,只是输入层的特征不同,这里为784;隐藏层同为ReLU激活函数,所以在手动实现前馈神经网络中,主要差距在后面的模型定义中。
1.3.5 定义模型
1. #定义模型 2. def net(X): 3. X = X.view((-1,num_inputs)) 4. H = relu(torch.matmul(X,W1.t())+b1) 5. return torch.matmul(H,W2.t())+b2
1.3.6定义交叉熵损失函数和优化器
1. #定义多分类交叉熵损失函数 2. loss = torch.nn.CrossEntropyLoss() 3. 4. #定义随机梯度下降法 5. def SGD(paras,lr): 6. for param in params: 7. param.data -= lr * param.grad
注:可以看到5)中的定义的模型和之前的是一样的,作为输出层的Softmax激活函数,这里我利用torch.nn. CrossEntropyLoss()函数,它将Softmax函数和交叉熵损失函数合并在了一起计算,同理相比于单独计算更加稳定了,然后同样是小批量随机梯度下降法进行梯度更新。
1.3.7 定义测试集loss以及准确率
1. #测试集loss 2. def evaluate_loss(data_iter,net): 3. acc_sum,loss_sum,n = 0.0,0.0,0 4. for X,y in data_iter: 5. y_hat = net(X) 6. acc_sum += (y_hat.argmax(dim=1)==y).sum().item() 7. l = loss(y_hat,y) # l是有关小批量X和y的损失 8. loss_sum += l.sum().item()*y.shape[0] 9. n+=y.shape[0] 10. return acc_sum/n,loss_sum/n
注:这里不同于之前的二分类和回归问题,在这里我加入对测试集的准确率的判断,利用测试集的预测值和真实值进行比较,计算测试准确的概率,其中对测试集的loss求法.
1.3.8 定义训练函数
1. 定义模型训练函数 2. def train(net,train_iter,test_iter,loss,num_epochs,batch_size,params=None,lr=None,optimizer=None): 3. train_ls = [] 4. test_ls = [] 5. for epoch in range(num_epochs): # 训练模型一共需要num_epochs个迭代周期 6. train_l_sum, train_acc_num,n = 0.0,0.0,0 7. # 在每一个迭代周期中,会使用训练数据集中所有样本一次 8. for X, y in train_iter: # x和y分别是小批量样本的特征和标签 9. y_hat = net(X) 10. l = loss(y_hat, y).sum() # l是有关小批量X和y的损失 11. #梯度清零 12. if optimizer is not None: 13. optimizer.zero_grad() 14. elif params is not None and params[0].grad is not None: 15. for param in params: 16. param.grad.data.zero_() 17. l.backward() # 小批量的损失对模型参数求梯度 18. if optimizer is None: 19. SGD(params,lr) 20. else: 21. optimizer.step() 22. #计算每个epoch的loss 23. train_l_sum += l.item() *y.shape[0] 24. #计算训练样本的准确率 25. train_acc_num += (y_hat.argmax(dim=1)==y).sum().item() 26. #每一个epoch的所有样本数 27. n+= y.shape[0] 28. train_ls.append(train_l_sum/n) 29. test_acc,test_l = evaluate_loss(test_iter,net) 30. test_ls.append(test_l) 31. print('epoch %d, train_loss %.6f,test_loss %f,train_acc %.6f,test_acc %.6f'%(epoch+1, train_ls[epoch],test_ls[epoch],train_acc_num/n,test_acc)) 32. return train_ls,test_ls
注:这里不同于之前的回归和二分类,我在这里引入对训练集和测试集的准确率的计算,即在训练集和测试集上分别判别预测值和真实值是否相等,统计预测准确的数量占总体数量的百分比,并将最后结果打印出来,其他的过程都是一样的,就不加赘述。
1.3.9开始训练模型
1. lr = 0.01 2. num_epochs = 50 3. train_loss,test_loss = train(net,train_iter,test_iter,loss,num_epochs,batch_size,params,lr)
注:调用模型函数,定义好训练次数、学习率等参数进行训练,返回结果保存到列表中进行画图。
1.3.10 绘制训练集和测试集的loss曲线
1. x = np.linspace(0,len(train_loss),len(train_loss)) 2. plt.plot(x,train_loss,label="train_loss",linewidth=1.5) 3. plt.plot(x,test_loss,label="test_loss",linewidth=1.5) 4. plt.xlabel("epoch") 5. plt.ylabel("loss") 6. plt.legend() 7. plt.show()
注:这里也是借鉴ppt的绘图部分,主要是利用matplotlib库,进行绘制,横坐标是迭代次数,纵坐标分别之前保存的训练集误差和测试集误差。