我们在学一门编程语言的时候,写的第一个程序往往是hello world。同样,手写数字识别是机器学习中,最入门的项目。
完整代码在文末,中间是代码每个部分的分析 。
机器学习的基本思路 我们需要两个东西:
调用的包 1 2 3 4 5 import torchfrom torch import nn, optimfrom torch.autograd import Variablefrom torch.utils.data import DataLoaderfrom torchvision import datasets, transforms
三层全连接神经网络 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 """ 定义了一个简单的三层全连接神经网络,每一层都是线性的 """ class simpleNet (nn.Module ): """ 初始化函数 :param param1: 类实例对象本身 :param param2: 输入层 :param param3: 隐藏层 :param param4: 输出层 """ def __init__ (self, in_dim, n_hidden_1, out_dim ): super (simpleNet, self).__init__() self.layer1 = nn.Sequential(nn.Linear(in_dim, n_hidden_1), nn.BatchNorm1d(n_hidden_1), nn.ReLU(True )) self.layer2 = nn.Sequential(nn.Linear(n_hidden_1, out_dim)) def forward (self, x ): x = self.layer1(x) x = self.layer2(x) return x
超参数定义 1 2 3 4 BATCH_SIZE = 128 LEARNING_RATE = 0.02 NUM_EPOCHS = 20
以我们这个三层全连接神经网络为例,我们怎么确定隐藏层应该有几个节点呢?这里就需要我们在一堆模型中找出比较好的一款(比如隐藏层是10个节点的那款)。而这个10,其实也是我们定义的一种参数,但是他不会在学习的过程中被修改。就像是一种 框架参数 一样。所以我们把他们叫做超参数(超越参数的参数)。
BATCH_SIZE
我们把一堆数据打成一个包(batch),每次训练的时候就训练一整个包。这个参数是由于深度学习中尝使用SGD(随机梯度下降)产生。batch_size
的大小取值和GPU内存会有关系,数值越大一次性载入数据越多,占用的GPU内存越多。适当增加 batch_size
能够增加训练速度和训练精度(因为梯度下降时震动较小),过小 会导致模型收敛困难。
LEARNING_RATE
学习率,也就是每一轮学习后,对模型的修正程度,太大容易修正过头。
NUM_EPOCH
把所有的训练数据全部迭代遍历的次数。
数据预处理 1 2 3 4 data_tf = transforms.Compose([transforms.ToTensor(), transforms.Normalize([0.5 ], [0.5 ])])
数据集下载 1 2 3 4 5 train_dataset = datasets.MNIST(root='./data' , train=True , transform=data_tf, download=True ) test_dataset = datasets.MNIST(root='./data' , train=False , transform=data_tf) train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True ) test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False )
选用的数据集是MNIST
选择模型 1 2 3 4 model = simpleNet(28 * 28 , 128 , 10 ) if torch.cuda.is_available(): model = model.cuda()
torch.cuda.is_available()
正如其名,判断GPU可不可以使用。如果可以就用GPU加速运算。
定义损失函数 1 2 3 criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=LEARNING_RATE, weight_decay = 1e-4 )
nn.CrossEntropyLoss()
交叉熵损失。公式为:
后面就是一堆数学公式推导了,埋个烂坑吧,以后再细说。
optim.SGD()
SGD(随机梯度下降)会把数据拆分成batch_size
大小的 batch
进行计算。如果整套丢入神经网络训练会消耗很大的计算资源。SGD就像是抽样一样,达成训练优化的目的。
model.parameters()
为该实例中可优化的参数。
lr
学习率。
weight_decay
权重衰减,也叫L2正则化,目的就是为了让权重衰减到更小的值,在一定程度上减少模型过拟 合的问题。
训练模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 for i in range (NUM_EPOCHS): total_loss=0 for data in train_loader: img, label = data img = img.view(img.size(0 ), -1 ) if torch.cuda.is_available(): img = img.cuda() label = label.cuda() out = model(img) loss = criterion(out, label) total_loss += loss.data.item() optimizer.zero_grad() loss.backward() optimizer.step() print ('epoch %d: lost %f' % (i+1 , total_loss))
img = img.view(img.size(0), -1)
说白了就是把多行的 Tensor
拼接成一行。demo:
1 2 3 4 5 6 7 8 9 import torch a = torch.Tensor(2 ,3 ) print (a) print (a.view(1 ,-1 ))
loss.data.item()
.data会返回一个Tensor,.item()返回一个数。demo:
1 2 3 4 5 6 7 8 9 10 11 import torch a = torch.ones([1 ,3 ]) print (a)print (a.data)print (a.data[0 ,1 ])print (a.data[0 ,1 ].item())
loss.backward()
反向传播。计算出loss以后,我们要修改每个节点的权重,用这个函数来计算 层之间节点的新权重。
optimizer.step()
更新所有参数。
模型评估 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 model.eval () eval_loss = 0 eval_acc = 0 for data in test_loader: img, label = data img = img.view(img.size(0 ), -1 ) if torch.cuda.is_available(): img = img.cuda() label = label.cuda() out = model(img) loss = criterion(out, label) eval_loss += loss.data.item()*label.size(0 ) _, pred = torch.max (out, 1 ) num_correct = (pred == label).sum () eval_acc += num_correct.item() print ('Test Loss: {:.6f}, Acc: {:.6f}' .format ( eval_loss / (len (test_dataset)), eval_acc / (len (test_dataset)) ))
附录(python) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 import torchfrom torch import nn, optimfrom torch.autograd import Variablefrom torch.utils.data import DataLoaderfrom torchvision import datasets, transformsclass simpleNet (nn.Module ): """ 定义了一个简单的三层全连接神经网络,每一层都是线性的 """ def __init__ (self, in_dim, n_hidden_1, out_dim ): super (simpleNet, self).__init__() self.layer1 = nn.Sequential(nn.Linear(in_dim, n_hidden_1), nn.BatchNorm1d(n_hidden_1), nn.ReLU(True )) self.layer2 = nn.Sequential(nn.Linear(n_hidden_1, out_dim)) def forward (self, x ): x = self.layer1(x) x = self.layer2(x) return x BATCH_SIZE = 128 LEARNING_RATE = 0.02 NUM_EPOCHS = 20 data_tf = transforms.Compose([transforms.ToTensor(), transforms.Normalize([0.5 ], [0.5 ])]) train_dataset = datasets.MNIST(root='./data' , train=True , transform=data_tf, download=True ) test_dataset = datasets.MNIST(root='./data' , train=False , transform=data_tf) train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True ) test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False ) model = simpleNet(28 * 28 , 128 , 10 ) if torch.cuda.is_available(): model = model.cuda() criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=LEARNING_RATE, weight_decay = 1e-4 ) for i in range (NUM_EPOCHS): total_loss=0 for data in train_loader: img, label = data img = img.view(img.size(0 ), -1 ) if torch.cuda.is_available(): img = img.cuda() label = label.cuda() out = model(img) loss = criterion(out, label) total_loss += loss.data.item() optimizer.zero_grad() loss.backward() optimizer.step() print ('epoch %d: lost %f' % (i+1 , total_loss)) model.eval () eval_loss = 0 eval_acc = 0 for data in test_loader: img, label = data img = img.view(img.size(0 ), -1 ) if torch.cuda.is_available(): img = img.cuda() label = label.cuda() out = model(img) loss = criterion(out, label) eval_loss += loss.data.item()*label.size(0 ) _, pred = torch.max (out, 1 ) num_correct = (pred == label).sum () eval_acc += num_correct.item() print ('Test Loss: {:.6f}, Acc: {:.6f}' .format ( eval_loss / (len (test_dataset)), eval_acc / (len (test_dataset)) ))