前面第一篇文章我们实现了keras和pytorch的入门helloworld:
(12条消息) keras和pytorch深度学习框架的hello world!_caojianhua2018的博客-CSDN博客 https://blog.csdn.net/caojianhua2018/article/details/112339089
对使用keras和pytorch有了一定的认识。接下来我们基于lenet5为骨架的卷积神经网络来实现经典数据集CIFAR-10物体识别实践。
CIFAR-10是一个小型的图片分类数据集,由6万张32x32彩色图像构成,每一张图都对应一个类别,不过这6万张图片总共也就10个类别。百说不如一图,如下显示的10个类别,包括飞机、汽车、鸟、猫、鹿、狗、青蛙、马、轮船和卡车,其中每个类别随机抽取了10张图。
在CIFAT-10数据集中,如果去下载的话会发现整个数据集由5个训练batches,和1个训练batch。每个batch由10000张图像构成。同时类别与类别之间是相互独立的,而且基本上每张图形中识别目标只有一个,就是当前的标签类别。这个数据集可以直接从网站上下载,除了CIFAR-10 外,还有扩展的 CIFAR-100 datasets数据集。链接地址为:
http://www.cs.toronto.edu/~kriz/cifar.htmlwww.cs.toronto.edu
由于这个数据集对于图像识别而言也是非常经典的数据集,在许多深度学习编程框架里都进行了封装,我们在使用的时候可以直接调用下载即可。不过很多情况下由于网速的缘故,下载时间会很长,所以可以直接从其官网上下载下来然后根据说明来加载数据。
下载地址: http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
下载解压后目录显示如下:
加载数据:根据这个数据集的网站说明,使用python的pickle模块来处理,具体的函数为:
def unpickle(file):
import pickle
with open(file, 'rb') as fo:
dict = pickle.load(fo, encoding='bytes')
return dict
很明显,上述函数中只需要传入file文件路径和文件名就可以将数据处理为字典。将上述batch_1文件处理后,得到如下数据:
file = 'data/cifar-10-batches-py/data_batch_1'
data = unpickle(file)
print(data.keys()) #查看键名
print(data[b'batch_label']) #查看该批数据名称
print(len(data[b'filenames'])) #查看filenames长度,这是每个图片的名称
print(len(data[b'labels'])) #查看labels标签长度,这是每个图片对应的标签名称
print(len(data[b'data'])) #查看data图片数据长度,data是每个图片所在的三通道二维像素矩阵
如果要准备训练集,则需要将5个data_batch合起来,组织成训练数据集和训练标签集:
#读取文件函数
def unpickle(file):
import pickle
with open(file, 'rb') as fo:
dict = pickle.load(fo, encoding='bytes')
return dict
#组织训练集数据和标签
train_data,train_labels=[],[]
for i in range(1,6):
file = 'data/cifar-10-batches-py/data_batch_'+str(i)
data = unpickle(file)
for item,label in zip(data[b'data'],data[b'labels']):
train_data.append(item)
train_labels.append(label)
#组织测试集数据和标签
test_data,test_labels=[],[]
file = 'data/cifar-10-batches-py/test_batch'
data = unpickle(file)
for item,label in zip(data[b'data'],data[b'labels']):
test_data.append(item)
test_labels.append(label)
#将数据集列表转化为ndarray格式
x_train,y_train = np.array(train_data),np.array(train_labels)
x_test,y_test = np.array(test_data),np.array(test_labels)
#查看数据维度大小
print("训练集图像数据:",x_train.shape)
print("训练集标签数据:",y_train.shape)
print("测试集图像数据:",x_test.shape)
print("测试集标签数据:",y_test.shape)
执行后终端显示:
获得的图像数据size为3072,因为是3通道,每个通道图像尺寸为32x32,因此每个图像的像素点总数为3x32x32=3072。为便于后续的处理和显示,需要使用numpy的reshape函数将图像数据重新处理一下:
对图像数据进行reshape处理,同时进行归一化
x_train4 = x_train.reshape(x_train.shape[0],3,32,32).astype('float32')
x_train4_norm = x_train4/255
x_test4 = x_test.reshape(x_test.shape[0],3,32,32).astype('float32')
x_test4_norm = x_test4/255
#准备标签数据,进行编码处理
y_train_1hot = np_utils.to_categorical(y_train,10)
y_test_1hot = np_utils.to_categorical(y_test,10)
这样基本数据都准备完成了。不过因为既然是图像,我们应该先可视化一下看一下图像长什么样。有两种方案,一种是直接将上述的图像数据采用cv的imwrite方法保存到本地,另一种就是采用matplotlib来查看,我们选其中一个训练图像来看一下:
#定义显示图像函数
def pltshow(img,label,label_name):
plt.title("label index:"+str(label)+"; label name:"+str(label_name))
plt.imshow(img)
plt.show()
#读取batches.meta元文件获得对应标签名
label_info = unpickle('data/cifar-10-batches-py/batches.meta')
label_names = label_info[b'label_names']
print(label_names)
#显示图像及其标签,x_train4_norm[0][0]是第一个样本的第1个通道图像,y_train[0]为第一个样本对应的标签
pltshow(x_train4_norm[0][0],y_train[0],label_names[y_train[0]])
执行后,显示目前标签序号从0到9分别对应名称为:
[b'airplane', b'automobile', b'bird', b'cat', b'deer', b'dog', b'frog', b'horse', b'ship', b'truck']
训练集里第一个样本图像显示如下:
还可以查看其他的:
很明显,因为像素个数少,同时又是单通道,图像特征并不是非常清晰,如果想看到彩色图则需要重新处理一下,如下代码实现:
#显示第一个样本的彩色图,x_train4[0]表示第一个图像数组
# 图像的存储格式一般为:[长,宽,通道数],需要先对处理过的图像矩阵数组格式变为整型
im = np.array(x_train4[0],dtype=int)
#上一步图像格式为[通道数,长,宽],需要使用transpose实现转置,格式变为[长,宽,通道数]
img = im.transpose((1,2,0))
# 绘制彩图
plt.title("label index:"+str(y_train[0])+"; label name:"+str(label_names[y_train[0]]))
plt.imshow(img)
plt.show()
执行后就可以绘制出第一个样本图像的彩色图了,同样的方式可以绘制其他样本图像了:
不过因为分辨率的缘故,图像并不清晰。此时我们试着将多个图绘制在同一个面上,这样效果就好许多:
#显示彩色图
for i in range(10):
plt.figure(1)
plt.subplot(2,5,i+1)
# # 图像的存储格式一般为:[长,宽,通道数],需要先对处理过的图像矩阵数组格式变为整型
im = np.array(x_train4[i],dtype=int)
# #上一步图像格式为[通道数,长,宽],需要使用transpose实现转置,格式变为[长,宽,通道数]
img = im.transpose((1,2,0))
# 绘制彩图
plt.title("index:"+str(y_train[i])+";name:"+str(label_names[y_train[i]]))
plt.imshow(img)
plt.show()
执行后效果如下:
在选用卷积神经网络模型来实现图形分类检测时,主要依据Lecun大神提出的lenet-5卷积神经网络骨架,也就是由卷积层+全连接两大部分构成。卷积层用于提取图像的特征,然后将特征喂入全连接层开展学习训练。
首先可以设计一下网络结构:
卷积层包括:两次卷积+池化,第一次卷积采用32个卷积核,得到32个特征图,然后进行下采样池化,获得32张16x16的特征图;第二次卷积采用64个卷积核,得到64个特征图,接着池化处理,获得64张8x8的特征图。
全连接层:输入神经元数就是上述64张8x8特征图展平后的64x8x8=4096个特征点,隐层个数设置为1024,输出层为10个神经元,对应10个类别的概率。
卷积核个数、隐层个数都需要经过测试,有些取自于经验,有些参考其他网络模型。目前深度学习发展到AUTOML自动学习阶段,这些基本参数通过自动学习可以获取最优结构。
同时在网络模型中加入dropout、batch normalization、padding不变等优化策略,接下来就可以采用keras和pytorch实现卷积神经网络训练和预测。
keras属于tensorflow的高阶API,可以使用堆叠方式构建网络结构,如下实现:
#第二部分:构建网络模型
model = Sequential()
#1.第一个卷积层:输入图像为32x32x3,卷积核3x3,共32个卷积核,边界策略padding设置为same保持不变,激活函数采用relu
model.add(Conv2D(filters=32,
kernel_size=(3,3),
padding='same',
input_shape=(32,32,3),
activation='relu'))
#2.第一个池化层,大小为2x2
model.add(MaxPooling2D(pool_size=(2,2)))
#3.第二个卷积层:卷积核3x3,共64个卷积核,边界策略padding设置为same保持不变,激活函数采用relu
model.add(Conv2D(filters=64,
kernel_size=(5,5),
padding='same',
activation='relu'))
#4.第二个池化层,大小为2x2
model.add(MaxPooling2D(pool_size=(2,2)))
#5.采用dropout策略
model.add(Dropout(0.25))
#6.拉平特征体成一维向量
model.add(Flatten())
#7.搭建神经网络第一个隐层,120个节点,激活函数使用relu
model.add(Dense(1024,activation='relu'))
model.add(Dropout(0.25))
#8.搭建神经网络输出层,10个节点,激活函数采用softmax
model.add(Dense(10,activation='softmax'))
#查看网络结构图
print(model.summary())
执行后可以得到网络结构摘要如下:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 32, 32, 32) 2432
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 16, 16, 32) 0
_________________________________________________________________
conv2d_1 (Conv2D) (None, 16, 16, 64) 51264
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 8, 8, 64) 0
_________________________________________________________________
dropout (Dropout) (None, 8, 8, 64) 0
_________________________________________________________________
flatten (Flatten) (None, 4096) 0
_________________________________________________________________
dense (Dense) (None, 1024) 4195328
_________________________________________________________________
dropout_1 (Dropout) (None, 1024) 0
_________________________________________________________________
dense_1 (Dense) (None, 10) 10250
=================================================================
Total params: 4,259,274
Trainable params: 4,259,274
Non-trainable params: 0
_________________________________________________________________
None
Process finished with exit code 0
网络结构搭好后,下面可以开始编译和训练了。编译相对简单,设定loss计算方式和优化方法:
#第三部分:对网络模型进行编译
model.compile(loss='categorical_crossentropy',
optimizer='adam',
metrics=['accuracy'])
训练部分这里需要注意以下,由于从本地加载的cifar-10数据集前面在处理的时候将一张图像(3通道)共4096个点用(3,32,32)进行了reshape,我们也看到了实际的图像显示。但进入模型时模型中接收的数据shape则应该为(长,宽,通道数),所以需要使用numpy的transpose方法将原来的(通道数,长,宽)调整为(长,宽,通道数)。
# #第四部分:对网络模型进行训练
train_data = x_train4_norm.transpose((0,2,3,1))
test_data = x_test4_norm.transpose((0,2,3,1))
代码中transpose参数为一个元组,(0,2,3,1)中0表示图像个数位置,共5万个,该位置不能改变,后面的2,3,1中2和3表示图像的长和宽,1表示通道数。也就是需要使用transpose方法将训练集图像调整为【图像个数,图像长,图像宽,通道数】。同样的方式也需要对测试集进行调整。
然后在喂入模型的训练fit函数中进行训练:
model.fit(x=train_data,
y=y_train_1hot,
validation_split=0.2,
epochs=10,
batch_size=300,verbose=2)
参数中设定了10次迭代学习,运行后发现精度不算很高:
Epoch 9/10
134/134 - 35s - loss: 0.6824 - accuracy: 0.7622 - val_loss: 0.8277 - val_accuracy: 0.7163
Epoch 10/10
134/134 - 37s - loss: 0.6291 - accuracy: 0.7807 - val_loss: 0.7921 - val_accuracy: 0.7301
不过可以发现,由于参数量要比lenet5多,所以耗时也相对较长。如果想进一步提高精度,还可以增加卷积层,也就是增加网络的深度。这个我们放在后面的VGG和Googlenet实践中再来阐述。
最后使用测试集中的数据开展测试预测,调用model模型的predict_classes方法获得预测标签:
#第五部分:使用训练好的模型开展测试应用
print("预测结果:",model.predict_classes(test_data[:10]))
print("实际标签:",y_test[:10])
得到预测结果为:
预测结果: [3 8 8 0 6 6 1 6 3 1]
实际标签: [3 8 8 0 6 6 1 6 3 1]
效果还是不错的,在前10个预测样本中精度达到了100%。不过当样本增多时,就会发现误差开始增多了,例如取前30个预测样本预测时效果如下:
预测结果: [3 1 8 8 6 6 1 2 3 1 0 9 5 7 9 8 5 7 8 6 7 2 2 9 4 2 7 0 9 6]
实际标签: [3 8 8 0 6 6 1 6 3 1 0 9 5 7 9 8 5 7 8 6 7 0 4 9 5 2 4 0 9 6]
30个样本,预测结果仅有23个正确,7个错误,预测精度接近80%。
使用pytorch来实现CIFAR-10数据分类预测时,第一步也是需要获得数据集。
(1)加载数据集
可以使用torch封装好的下载来使用:
#下载数据集
train_dataset = torchvision.datasets.CIFAR10(root='data-CIFAR', #存放目录为本地data-cifar文件夹下
train=True, #训练集
transform=transforms.ToTensor(), #转换为tensor
download=True) #同意下载到本地目录下
test_dataset = torchvision.datasets.CIFAR10(root='data-CIFAR',
train=False, #测试集
transform=transforms.ToTensor(),
download=True)
但速度也是超级的慢,所以我们还是需要先下载了数据集,再存到上述的data-CIFAR目录下。这样再运行上面这步获取训练集和测试集时,就会提示:
Files already downloaded and verified
Files already downloaded and verified
也就是说,上面的train_dataset和test_dataset两个数据dataset就准备好了。接下来就可以使用DataLoader模块实现动态的数据抽取加载。
#第一部分:下载数据集到本地
#下载数据集
train_dataset = torchvision.datasets.CIFAR10(root='data-CIFAR', #存放目录为本地data文件夹下
train=True, #训练集
transform=transforms.ToTensor(), #转换为tensor
download=True) #同意下载到本地目录下
test_dataset = torchvision.datasets.CIFAR10(root='data-CIFAR',
train=False, #测试集
transform=transforms.ToTensor(),
download=True)
#装载数据,每批100组数据
train_loader = DataLoader(train_dataset,batch_size=100,shuffle=True)
test_loader = DataLoader(test_dataset,batch_size=100,shuffle=False)
(2)了解数据
主要是了解数据集的准备情况,前面介绍数据集时我们已经知道了数据的内容和构成。这里我们可以再确认一下:
#第二部分:查看数据了解数据
# 由于DataLoader的策略是分批加载,每批100组数据,共500批次。
# 每批数据组由100张原始图像和100个标签对构成。
print("训练集数据组批次总数:",len(train_loader))
#训练集中包括imgs和labels
img_data,label_data = [],[]
for imgs,labels in train_loader:
img_data.append(imgs)
label_data.append(labels)
#查看原始图像信息
print("查看第一批图像数据维度:",img_data[0].shape)
print("第一批里第一张原始图数据维度:",img_data[0][0].shape)
print("第一批里第一个标签数据:",label_data[0][0])
#标签对应的类别名
classes={0:'airplane',1:'automobile',2:'bird',3:'cat',4:'deer',5:'dog',6:'frog',7:'horse',8:'ship',9:'truck'}
#绘制第一批10张图像
for i in range(10):
img = np.array(img_data[0][i]).transpose((1,2,0)) #先进行transpose通道数后移,形成32x32x3维度
plt.figure(1)
plt.subplot(2,5,i+1) #绘制2行5列单元格
plt.imshow(img) #每个单元格绘一幅图
plt.title("name:{}".format(classes[int(label_data[0][i])])) #给每个图增加一个标签说明
plt.show()
运行后效果如下:
(3)构建卷积神经网络模型
这部分过程可以参考上一篇文章或者直接拿过来修改即可,基于上述网络结构的布局,代码参考如下:
#第三部分:创建卷积神经网络模型
class CONvNET(nn.Module):
def __init__(self):
super(CONvNET, self).__init__()
self.conv1 = nn.Conv2d(3, 32, 3, padding=1) #第一个卷积层参数
self.conv2 = nn.Conv2d(32, 64, 3, padding=1) #第二个卷积层参数
self.fc1 = nn.Linear(4096, 120) #第一个全连接层参数
self.fc2 = nn.Linear(120, 10) #输出层参数
def forward(self, x):
x = F.relu(self.conv1(x)) #第一次卷积处理
x = F.max_pool2d(x, 2, 2) #第一次池化
x = F.relu(self.conv2(x)) #第二次卷积处理
x = F.max_pool2d(x, 2, 2) #第二次池化
x = x.view(x.size()[0],-1) #展平处理
x = F.relu(self.fc1(x)) #第一次全连接处理
x = self.fc2(x) #第二次全连接处理
return F.log_softmax(x, dim=1) #输出结果
(4)设定训练和验证过程函数
#第四部分:对训练集进行训练
def train(epoch,model,traindata,optimizer,criterion):
'''
:param model:网络模型
:param data: 输入处理训练集
:param optim: 优化器选择
:param criterion: 计算loss方法
:return:
'''
model.train() #对模型进行训练
for index, (data, target) in enumerate(traindata):
#前向计算过程
optimizer.zero_grad() # 梯度先清零
output = model(data)
loss = criterion(output, target)
#反向梯度优化过程
loss.backward() # 误差反向传播计算
optimizer.step() # 更新梯度
if index % 100 == 0:
# 保存训练模型
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, index * len(data), len(train_loader.dataset),
100. * index / len(train_loader), loss.item()))
#第五部分:使用验证集验证模型
def validate(model,testdata,criterion):
'''
:param model: 网络模型
:param data: 验证集
:param criterion: 计算loss方法
:return:
'''
model.eval()
running_loss = 0
correct = 0
for data,target in testdata:
y_estimate = model(data)
loss = criterion(y_estimate,target)
running_loss+=loss.item()*data.size(0)
pred = y_estimate.data.max(1,keepdim=True)[1]
correct += pred.eq(target.data.view_as(pred)).cpu().sum()
epoch_loss = running_loss / len(testdata)
print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)\n'.format(
epoch_loss, correct, len(test_loader.dataset),
100. * correct / len(test_loader.dataset)))
(5)喂入训练集训练,并保存模型
#第六部分:设定超参数
#创建一个模型net对象,具有lenet5骨架结构
net = CONvNET()
#使用nn模块的交叉熵方法确定loss的计算标准
criterion = nn.CrossEntropyLoss(reduction='sum')
#采用nn模块的optim优化器中的Adam方法
optimizer = optim.Adam(net.parameters(), lr=1e-3)
#第七部分:训练模型并将模型保存下来
# for epoch in range(1,20):
# train(epoch=epoch,model=net,traindata=train_loader,optimizer=optimizer,criterion=criterion)
torch.save(net, 'ownmodel.pkl')
print("training process end....")
(6)基于训练好的模型对测试集进行预测
model = torch.load('ownmodel.pkl')
validate(model=model,testdata=test_loader,criterion=criterion)
最终预测精度接近70%:
下面选择测试集中的小部分数据进行模型验证
#8.选择小批数据预测,每批次4组数据
data_loader_test = torch.utils.data.DataLoader(dataset=test_dataset,
batch_size = 4,
shuffle = True)
#取出其中第一组数据,返回图像数据和标签
X_test, y_test = next(iter(data_loader_test))
#加载保存好的网络模型
model = torch.load('ownmodel.pkl')
#将图像数据传入模型中进行预测
y_estimate = model(X_test)
#将预测tensor向量转化为ndarry
pred_y = torch.max(y_estimate, 1)[1].data.numpy()
#输出结果
print("该组图像预测数字为:",pred_y)
print("该组图像实际数字为:",y_test.numpy())
该组预测结果:
至此,我们使用keras和pytorch完成了CIFAR-10的分类识别,由于设计的卷积神经网络相对较浅,预测精度都不是太高。后续我们再尝试使用googlenet或VGG,使得目标识别精度提高。
上述两种框架CIFAR-10分类识别代码下载地址为:
caoln2003/deeplearninggitee.com
下载后可以直接运行获得结果。如果有什么疑问或者表述不清楚的地方,欢迎下方留言交流