计算机视觉:图像分类经典模型

  • LeNet
  • AlexNet
  • VGG
  • GoogLeNet
  • ResNet

图像分类是根据图像的语义信息对不同类别图像进行区分,是计算机视觉的核心,是物体检测、图像分割、物体跟踪、行为分析、人脸识别等其他高层次视觉任务的基础。图像分类在许多领域都有着广泛的应用,如:安防领域的人脸识别和智能视频分析等,交通领域的交通场景识别,互联网领域基于内容的图像检索和相册自动归类,医学领域的图像识别等。


本节将基于眼疾分类数据集iChallenge-PM,对图像分类领域的经典卷积神经网络进行剖析,介绍如何应用这些基础模块构建卷积神经网络,解决图像分类问题。涵盖如下卷积神经网络:

  • LeNet:Yan LeCun等人于1998年第一次将卷积神经网络应用到图像分类任务上,在手写数字识别任务上取得了巨大成功。

  • AlexNet:Alex Krizhevsky等人在2012年提出了AlexNet, 并应用在大尺寸图片数据集ImageNet上,获得了2012年ImageNet比赛冠军(ImageNet Large Scale Visual Recognition Challenge,ILSVRC)。

  • VGG:Simonyan和Zisserman于2014年提出了VGG网络结构,是当前最流行的卷积神经网络之一,由于其结构简单、应用性极强而深受广大研究者欢迎。

  • GoogLeNet:Christian Szegedy等人在2014提出了GoogLeNet,并取得了2014年ImageNet比赛冠军。

  • ResNet:Kaiming He等人在2015年提出了ResNet,通过引入残差模块加深网络层数,在ImagNet数据集上的错误率降低到3.6%,超越了人眼识别水平。ResNet的设计思想深刻地影响了后来的深度神经网络的设计。

LeNet

LeNet是最早的卷积神经网络之一。1998年,Yan LeCun第一次将LeNet卷积神经网络应用到图像分类上,在手写数字识别任务中取得了巨大成功。LeNet通过连续使用卷积和池化层的组合提取图像特征,其架构如 图1 所示,这里展示的是作者论文中的LeNet-5模型:

图1:LeNet模型网络结构示意图

  • 第一模块:包含5×5的6通道卷积和2×2的池化。卷积提取图像中包含的特征模式(激活函数使用sigmoid),图像尺寸从32减小到28。经过池化层可以降低输出特征图对空间位置的敏感性,图像尺寸减到14。

  • 第二模块:和第一模块尺寸相同,通道数由6增加为16。卷积操作使图像尺寸减小到10,经过池化后变成5。

  • 第三模块:包含5×5的120通道卷积。卷积之后的图像尺寸减小到1,但是通道数增加为120。将经过第3次卷积提取到的特征图输入到全连接层。第一个全连接层的输出神经元的个数是64,第二个全连接层的输出神经元个数是分类标签的类别数,对于手写数字识别其大小是10。然后使用Softmax激活函数即可计算出每个类别的预测概率。


【提示】:

卷积层的输出特征图如何当作全连接层的输入使用呢?

卷积层的输出数据格式是[N,C,H,W][N, C, H, W][N,C,H,W],在输入全连接层的时候,会自动将数据拉平,

也就是对每个样本,自动将其转化为长度为KKK的向量,

其中K=C×H×WK = C \times H \times WK=C×H×W,一个mini-batch的数据维度变成了N×KN\times KN×K的二维向量。


LeNet在手写数字识别上的应用

LeNet网络的实现代码如下:

# 导入需要的包
import paddle
import paddle.fluid as fluid
import numpy as np
from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear# 定义 LeNet 网络结构
class LeNet(fluid.dygraph.Layer):def __init__(self, num_classes=1):super(LeNet, self).__init__()# 创建卷积和池化层块,每个卷积层使用Sigmoid激活函数,后面跟着一个2x2的池化self.conv1 = Conv2D(num_channels=1, num_filters=6, filter_size=5, act='sigmoid')self.pool1 = Pool2D(pool_size=2, pool_stride=2, pool_type='max')self.conv2 = Conv2D(num_channels=6, num_filters=16, filter_size=5, act='sigmoid')self.pool2 = Pool2D(pool_size=2, pool_stride=2, pool_type='max')# 创建第3个卷积层self.conv3 = Conv2D(num_channels=16, num_filters=120, filter_size=4, act='sigmoid')# 创建全连接层,第一个全连接层的输出神经元个数为64, 第二个全连接层输出神经元个数为分类标签的类别数self.fc1 = Linear(input_dim=120, output_dim=64, act='sigmoid')self.fc2 = Linear(input_dim=64, output_dim=num_classes)# 网络的前向计算过程def forward(self, x):x = self.conv1(x)x = self.pool1(x)x = self.conv2(x)x = self.pool2(x)x = self.conv3(x)x = fluid.layers.reshape(x, [x.shape[0], -1])x = self.fc1(x)x = self.fc2(x)return x

下面的程序使用随机数作为输入,查看经过LeNet-5的每一层作用之后,输出数据的形状

# 输入数据形状是 [N, 1, H, W]
# 这里用np.random创建一个随机数组作为输入数据
x = np.random.randn(*[3,1,28,28])
x = x.astype('float32')
with fluid.dygraph.guard():# 创建LeNet类的实例,指定模型名称和分类的类别数目m = LeNet(num_classes=10)# 通过调用LeNet从基类继承的sublayers()函数,# 查看LeNet中所包含的子层print(m.sublayers())x = fluid.dygraph.to_variable(x)for item in m.sublayers():# item是LeNet类中的一个子层# 查看经过子层之后的输出数据形状try:x = item(x)except:x = fluid.layers.reshape(x, [x.shape[0], -1])x = item(x)if len(item.parameters())==2:# 查看卷积和全连接层的数据和参数的形状,# 其中item.parameters()[0]是权重参数w,item.parameters()[1]是偏置参数bprint(item.full_name(), x.shape, item.parameters()[0].shape, item.parameters()[1].shape)else:# 池化层没有参数print(item.full_name(), x.shape)
# -*- coding: utf-8 -*-# LeNet 识别手写数字import os
import random
import paddle
import paddle.fluid as fluid
import numpy as np# 定义训练过程
def train(model):print('start training ... ')model.train()epoch_num = 5opt = fluid.optimizer.Momentum(learning_rate=0.001, momentum=0.9, parameter_list=model.parameters())# 使用Paddle自带的数据读取器train_loader = paddle.batch(paddle.dataset.mnist.train(), batch_size=10)valid_loader = paddle.batch(paddle.dataset.mnist.test(), batch_size=10)for epoch in range(epoch_num):for batch_id, data in enumerate(train_loader()):# 调整输入数据形状和类型x_data = np.array([item[0] for item in data], dtype='float32').reshape(-1, 1, 28, 28)y_data = np.array([item[1] for item in data], dtype='int64').reshape(-1, 1)# 将numpy.ndarray转化成Tensorimg = fluid.dygraph.to_variable(x_data)label = fluid.dygraph.to_variable(y_data)# 计算模型输出logits = model(img)# 计算损失函数loss = fluid.layers.softmax_with_cross_entropy(logits, label)avg_loss = fluid.layers.mean(loss)if batch_id % 1000 == 0:print("epoch: {}, batch_id: {}, loss is: {}".format(epoch, batch_id, avg_loss.numpy()))avg_loss.backward()opt.minimize(avg_loss)model.clear_gradients()model.eval()accuracies = []losses = []for batch_id, data in enumerate(valid_loader()):# 调整输入数据形状和类型x_data = np.array([item[0] for item in data], dtype='float32').reshape(-1, 1, 28, 28)y_data = np.array([item[1] for item in data], dtype='int64').reshape(-1, 1)# 将numpy.ndarray转化成Tensorimg = fluid.dygraph.to_variable(x_data)label = fluid.dygraph.to_variable(y_data)# 计算模型输出logits = model(img)pred = fluid.layers.softmax(logits)# 计算损失函数loss = fluid.layers.softmax_with_cross_entropy(logits, label)acc = fluid.layers.accuracy(pred, label)accuracies.append(acc.numpy())losses.append(loss.numpy())print("[validation] accuracy/loss: {}/{}".format(np.mean(accuracies), np.mean(losses)))model.train()# 保存模型参数fluid.save_dygraph(model.state_dict(), 'mnist')if __name__ == '__main__':# 创建模型with fluid.dygraph.guard():model = LeNet(num_classes=10)#启动训练过程train(model)

LeNet在眼疾识别数据集iChallenge-PM上的应用

iChallenge-PM是百度大脑和中山大学中山眼科中心联合举办的iChallenge比赛中,提供的关于病理性近视(Pathologic Myopia,PM)的医疗类数据集,包含1200个受试者的眼底视网膜图片,训练、验证和测试数据集各400张。下面我们详细介绍LeNet在iChallenge-PM上的训练过程。


说明:

如今近视已经成为困扰人们健康的一项全球性负担,在近视人群中,有超过35%的人患有重度近视。近视会拉长眼睛的光轴,也可能引起视网膜或者络网膜的病变。随着近视度数的不断加深,高度近视有可能引发病理性病变,这将会导致以下几种症状:视网膜或者络网膜发生退化、视盘区域萎缩、漆裂样纹损害、Fuchs斑等。因此,及早发现近视患者眼睛的病变并采取治疗,显得非常重要。

数据可以从AIStudio下载


查看数据集图片

iChallenge-PM中既有病理性近视患者的眼底图片,也有非病理性近视患者的图片,命名规则如下:

  • 病理性近视(PM):文件名以P开头

  • 非病理性近视(non-PM):

    • 高度近视(high myopia):文件名以H开头

    • 正常眼睛(normal):文件名以N开头

我们将病理性患者的图片作为正样本,标签为1; 非病理性患者的图片作为负样本,标签为0。从数据集中选取两张图片,通过LeNet提取特征,构建分类器,对正负样本进行分类,并将图片显示出来。代码如下所示:

import os
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from PIL import ImageDATADIR = '/home/aistudio/work/palm/PALM-Training400/PALM-Training400'
# 文件名以N开头的是正常眼底图片,以P开头的是病变眼底图片
file1 = 'N0012.jpg'
file2 = 'P0095.jpg'# 读取图片
img1 = Image.open(os.path.join(DATADIR, file1))
img1 = np.array(img1)
img2 = Image.open(os.path.join(DATADIR, file2))
img2 = np.array(img2)# 画出读取的图片
plt.figure(figsize=(16, 8))
f = plt.subplot(121)
f.set_title('Normal', fontsize=20)
plt.imshow(img1)
f = plt.subplot(122)
f.set_title('PM', fontsize=20)
plt.imshow(img2)
plt.show()


定义数据读取器

使用OpenCV从磁盘读入图片,将每张图缩放到224×224224\times224224×224大小,并且将像素值调整到[−1,1][-1, 1][−1,1]之间,代码如下所示:

import cv2
import random
import numpy as np# 对读入的图像数据进行预处理
def transform_img(img):# 将图片尺寸缩放道 224x224img = cv2.resize(img, (224, 224))# 读入的图像数据格式是[H, W, C]# 使用转置操作将其变成[C, H, W]img = np.transpose(img, (2,0,1))img = img.astype('float32')# 将数据范围调整到[-1.0, 1.0]之间img = img / 255.img = img * 2.0 - 1.0return img# 定义训练集数据读取器
def data_loader(datadir, batch_size=10, mode = 'train'):# 将datadir目录下的文件列出来,每条文件都要读入filenames = os.listdir(datadir)def reader():if mode == 'train':# 训练时随机打乱数据顺序random.shuffle(filenames)batch_imgs = []batch_labels = []for name in filenames:filepath = os.path.join(datadir, name)img = cv2.imread(filepath)img = transform_img(img)if name[0] == 'H' or name[0] == 'N':# H开头的文件名表示高度近似,N开头的文件名表示正常视力# 高度近视和正常视力的样本,都不是病理性的,属于负样本,标签为0label = 0elif name[0] == 'P':# P开头的是病理性近视,属于正样本,标签为1label = 1else:raise('Not excepted file name')# 每读取一个样本的数据,就将其放入数据列表中batch_imgs.append(img)batch_labels.append(label)if len(batch_imgs) == batch_size:# 当数据列表的长度等于batch_size的时候,# 把这些数据当作一个mini-batch,并作为数据生成器的一个输出imgs_array = np.array(batch_imgs).astype('float32')labels_array = np.array(batch_labels).astype('float32').reshape(-1, 1)yield imgs_array, labels_arraybatch_imgs = []batch_labels = []if len(batch_imgs) > 0:# 剩余样本数目不足一个batch_size的数据,一起打包成一个mini-batchimgs_array = np.array(batch_imgs).astype('float32')labels_array = np.array(batch_labels).astype('float32').reshape(-1, 1)yield imgs_array, labels_arrayreturn reader# 定义验证集数据读取器
def valid_data_loader(datadir, csvfile, batch_size=10, mode='valid'):# 训练集读取时通过文件名来确定样本标签,验证集则通过csvfile来读取每个图片对应的标签# 请查看解压后的验证集标签数据,观察csvfile文件里面所包含的内容# csvfile文件所包含的内容格式如下,每一行代表一个样本,# 其中第一列是图片id,第二列是文件名,第三列是图片标签,# 第四列和第五列是Fovea的坐标,与分类任务无关# ID,imgName,Label,Fovea_X,Fovea_Y# 1,V0001.jpg,0,1157.74,1019.87# 2,V0002.jpg,1,1285.82,1080.47# 打开包含验证集标签的csvfile,并读入其中的内容filelists = open(csvfile).readlines()def reader():batch_imgs = []batch_labels = []for line in filelists[1:]:line = line.strip().split(',')name = line[1]label = int(line[2])# 根据图片文件名加载图片,并对图像数据作预处理filepath = os.path.join(datadir, name)img = cv2.imread(filepath)img = transform_img(img)# 每读取一个样本的数据,就将其放入数据列表中batch_imgs.append(img)batch_labels.append(label)if len(batch_imgs) == batch_size:# 当数据列表的长度等于batch_size的时候,# 把这些数据当作一个mini-batch,并作为数据生成器的一个输出imgs_array = np.array(batch_imgs).astype('float32')labels_array = np.array(batch_labels).astype('float32').reshape(-1, 1)yield imgs_array, labels_arraybatch_imgs = []batch_labels = []if len(batch_imgs) > 0:# 剩余样本数目不足一个batch_size的数据,一起打包成一个mini-batchimgs_array = np.array(batch_imgs).astype('float32')labels_array = np.array(batch_labels).astype('float32').reshape(-1, 1)yield imgs_array, labels_arrayreturn reader
# 查看数据形状
DATADIR = '/home/aistudio/work/palm/PALM-Training400/PALM-Training400'
train_loader = data_loader(DATADIR, batch_size=10, mode='train')
data_reader = train_loader()
data = next(data_reader)
data[0].shape, data[1].shapeeval_loader = data_loader(DATADIR, batch_size=10, mode='eval')
data_reader = eval_loader()
data = next(data_reader)
data[0].shape, data[1].shape

LeNet 识别眼疾图片

# -*- coding: utf-8 -*-# LeNet 识别眼疾图片import os
import random
import paddle
import paddle.fluid as fluid
import numpy as npDATADIR = '/home/aistudio/work/palm/PALM-Training400/PALM-Training400'
DATADIR2 = '/home/aistudio/work/palm/PALM-Validation400'
CSVFILE = '/home/aistudio/labels.csv'# 定义训练过程
def train(model):with fluid.dygraph.guard():print('start training ... ')model.train()epoch_num = 5# 定义优化器opt = fluid.optimizer.Momentum(learning_rate=0.001, momentum=0.9, parameter_list=model.parameters())# 定义数据读取器,训练数据读取器和验证数据读取器train_loader = data_loader(DATADIR, batch_size=10, mode='train')valid_loader = valid_data_loader(DATADIR2, CSVFILE)for epoch in range(epoch_num):for batch_id, data in enumerate(train_loader()):x_data, y_data = dataimg = fluid.dygraph.to_variable(x_data)label = fluid.dygraph.to_variable(y_data)# 运行模型前向计算,得到预测值logits = model(img)# 进行loss计算loss = fluid.layers.sigmoid_cross_entropy_with_logits(logits, label)avg_loss = fluid.layers.mean(loss)if batch_id % 10 == 0:print("epoch: {}, batch_id: {}, loss is: {}".format(epoch, batch_id, avg_loss.numpy()))# 反向传播,更新权重,清除梯度avg_loss.backward()opt.minimize(avg_loss)model.clear_gradients()model.eval()accuracies = []losses = []for batch_id, data in enumerate(valid_loader()):x_data, y_data = dataimg = fluid.dygraph.to_variable(x_data)label = fluid.dygraph.to_variable(y_data)# 运行模型前向计算,得到预测值logits = model(img)# 二分类,sigmoid计算后的结果以0.5为阈值分两个类别# 计算sigmoid后的预测概率,进行loss计算pred = fluid.layers.sigmoid(logits)loss = fluid.layers.sigmoid_cross_entropy_with_logits(logits, label)# 计算预测概率小于0.5的类别pred2 = pred * (-1.0) + 1.0# 得到两个类别的预测概率,并沿第一个维度级联pred = fluid.layers.concat([pred2, pred], axis=1)acc = fluid.layers.accuracy(pred, fluid.layers.cast(label, dtype='int64'))accuracies.append(acc.numpy())losses.append(loss.numpy())print("[validation] accuracy/loss: {}/{}".format(np.mean(accuracies), np.mean(losses)))model.train()# save params of modelfluid.save_dygraph(model.state_dict(), 'palm')# save optimizer statefluid.save_dygraph(opt.state_dict(), 'palm')# 定义评估过程
def evaluation(model, params_file_path):with fluid.dygraph.guard():print('start evaluation .......')#加载模型参数model_state_dict, _ = fluid.load_dygraph(params_file_path)model.load_dict(model_state_dict)model.eval()eval_loader = data_loader(DATADIR, batch_size=10, mode='eval')acc_set = []avg_loss_set = []for batch_id, data in enumerate(eval_loader()):x_data, y_data = dataimg = fluid.dygraph.to_variable(x_data)label = fluid.dygraph.to_variable(y_data)y_data = y_data.astype(np.int64)label_64 = fluid.dygraph.to_variable(y_data)# 计算预测和精度prediction, acc = model(img, label_64)# 计算损失函数值loss = fluid.layers.sigmoid_cross_entropy_with_logits(prediction, label)avg_loss = fluid.layers.mean(loss)acc_set.append(float(acc.numpy()))avg_loss_set.append(float(avg_loss.numpy()))# 求平均精度acc_val_mean = np.array(acc_set).mean()avg_loss_val_mean = np.array(avg_loss_set).mean()print('loss={}, acc={}'.format(avg_loss_val_mean, acc_val_mean))# 导入需要的包
import paddle
import paddle.fluid as fluid
import numpy as np
from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear# 定义 LeNet 网络结构
class LeNet(fluid.dygraph.Layer):def __init__(self, num_classes=1):super(LeNet, self).__init__()# 创建卷积和池化层块,每个卷积层使用Sigmoid激活函数,后面跟着一个2x2的池化self.conv1 = Conv2D(num_channels=3, num_filters=6, filter_size=5, act='sigmoid')self.pool1 = Pool2D(pool_size=2, pool_stride=2, pool_type='max')self.conv2 = Conv2D(num_channels=6, num_filters=16, filter_size=5, act='sigmoid')self.pool2 = Pool2D(pool_size=2, pool_stride=2, pool_type='max')# 创建第3个卷积层self.conv3 = Conv2D(num_channels=16, num_filters=120, filter_size=4, act='sigmoid')# 创建全连接层,第一个全连接层的输出神经元个数为64, 第二个全连接层输出神经元个数为分类标签的类别数self.fc1 = Linear(input_dim=300000, output_dim=64, act='sigmoid')self.fc2 = Linear(input_dim=64, output_dim=num_classes)# 网络的前向计算过程def forward(self, x, label=None):x = self.conv1(x)x = self.pool1(x)x = self.conv2(x)x = self.pool2(x)x = self.conv3(x)x = fluid.layers.reshape(x, [x.shape[0], -1])x = self.fc1(x)x = self.fc2(x)if label is not None:acc = fluid.layers.accuracy(input=x, label=label)return x, accelse:return xif __name__ == '__main__':# 创建模型with fluid.dygraph.guard():model = LeNet(num_classes=1)train(model)# evaluation(model, params_file_path="palm")

通过运行结果可以看出,在眼疾筛查数据集iChallenge-PM上,LeNet的loss很难下降,模型没有收敛。这是因为MNIST数据集的图片尺寸比较小(28×2828\times2828×28),但是眼疾筛查数据集图片尺寸比较大(原始图片尺寸约为2000×20002000 \times 20002000×2000,经过缩放之后变成224×224224 \times 224224×224),LeNet模型很难进行有效分类。这说明在图片尺寸比较大时,LeNet在图像分类任务上存在局限性。

AlexNet

通过上面的实际训练可以看到,虽然LeNet在手写数字识别数据集上取得了很好的结果,但在更大的数据集上表现却并不好。自从1998年LeNet问世以来,接下来十几年的时间里,神经网络并没有在计算机视觉领域取得很好的结果,反而一度被其它算法所超越,原因主要有两方面,一是神经网络的计算比较复杂,对当时计算机的算力来说,训练神经网络是件非常耗时的事情;另一方面,当时还没有专门针对神经网络做算法和训练技巧的优化,神经网络的收敛是件非常困难的事情。

随着技术的进步和发展,计算机的算力越来越强大,尤其是在GPU并行计算能力的推动下,复杂神经网络的计算也变得更加容易实施。另一方面,互联网上涌现出越来越多的数据,极大的丰富了数据库。同时也有越来越多的研究人员开始专门针对神经网络做算法和模型的优化,Alex Krizhevsky等人提出的AlexNet以很大优势获得了2012年ImageNet比赛的冠军。这一成果极大的激发了产业界对神经网络的兴趣,开创了使用深度神经网络解决图像问题的途径,随后也在这一领域涌现出越来越多的优秀成果。

AlexNet与LeNet相比,具有更深的网络结构,包含5层卷积和3层全连接,同时使用了如下三种方法改进模型的训练过程:

  • 数据增广:深度学习中常用的一种处理方式,通过对训练随机加一些变化,比如平移、缩放、裁剪、旋转、翻转或者增减亮度等,产生一系列跟原始图片相似但又不完全相同的样本,从而扩大训练数据集。通过这种方式,可以随机改变训练样本,避免模型过度依赖于某些属性,能从一定程度上抑制过拟合。

  • 使用Dropout抑制过拟合

  • 使用ReLU激活函数减少梯度消失现象
    AlexNet的具体结构如 图2 所示:

图2:AlexNet模型网络结构示意图

AlexNet在眼疾筛查数据集iChallenge-PM上具体实现的代码如下所示:

# -*- coding:utf-8 -*-# 导入需要的包
import paddle
import paddle.fluid as fluid
import numpy as np
from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear# 定义 AlexNet 网络结构
class AlexNet(fluid.dygraph.Layer):def __init__(self, num_classes=1):super(AlexNet, self).__init__()# AlexNet与LeNet一样也会同时使用卷积和池化层提取图像特征# 与LeNet不同的是激活函数换成了‘relu’self.conv1 = Conv2D(num_channels=3, num_filters=96, filter_size=11, stride=4, padding=5, act='relu')self.pool1 = Pool2D(pool_size=2, pool_stride=2, pool_type='max')self.conv2 = Conv2D(num_channels=96, num_filters=256, filter_size=5, stride=1, padding=2, act='relu')self.pool2 = Pool2D(pool_size=2, pool_stride=2, pool_type='max')self.conv3 = Conv2D(num_channels=256, num_filters=384, filter_size=3, stride=1, padding=1, act='relu')self.conv4 = Conv2D(num_channels=384, num_filters=384, filter_size=3, stride=1, padding=1, act='relu')self.conv5 = Conv2D(num_channels=384, num_filters=256, filter_size=3, stride=1, padding=1, act='relu')self.pool5 = Pool2D(pool_size=2, pool_stride=2, pool_type='max')self.fc1 = Linear(input_dim=12544, output_dim=4096, act='relu')self.drop_ratio1 = 0.5self.fc2 = Linear(input_dim=4096, output_dim=4096, act='relu')self.drop_ratio2 = 0.5self.fc3 = Linear(input_dim=4096, output_dim=num_classes)def forward(self, x):x = self.conv1(x)x = self.pool1(x)x = self.conv2(x)x = self.pool2(x)x = self.conv3(x)x = self.conv4(x)x = self.conv5(x)x = self.pool5(x)x = fluid.layers.reshape(x, [x.shape[0], -1])x = self.fc1(x)# 在全连接之后使用dropout抑制过拟合x= fluid.layers.dropout(x, self.drop_ratio1)x = self.fc2(x)# 在全连接之后使用dropout抑制过拟合x = fluid.layers.dropout(x, self.drop_ratio2)x = self.fc3(x)return x

通过运行结果可以发现,在眼疾筛查数据集iChallenge-PM上使用AlexNet,loss能有效下降,经过5个epoch的训练,在验证集上的准确率可以达到94%左右。

VGG

VGG是当前最流行的CNN模型之一,2014年由Simonyan和Zisserman提出,其命名来源于论文作者所在的实验室Visual Geometry Group。AlexNet模型通过构造多层网络,取得了较好的效果,但是并没有给出深度神经网络设计的方向。VGG通过使用一系列大小为3x3的小尺寸卷积核和pooling层构造深度卷积神经网络,并取得了较好的效果。VGG模型因为结构简单、应用性极强而广受研究者欢迎,尤其是它的网络结构设计方法,为构建深度神经网络提供了方向。

图3 是VGG-16的网络结构示意图,有13层卷积和3层全连接层。VGG网络的设计严格使用3×33\times 33×3的卷积层和池化层来提取特征,并在网络的最后面使用三层全连接层,将最后一层全连接层的输出作为分类的预测。

在VGG中每层卷积将使用ReLU作为激活函数,在全连接层之后添加dropout来抑制过拟合。使用小的卷积核能够有效地减少参数的个数,使得训练和测试变得更加有效。比如使用两层3×33\times 33×3卷积层,可以得到感受野为5的特征图,而比使用5×55 \times 55×5的卷积层需要更少的参数。由于卷积核比较小,可以堆叠更多的卷积层,加深网络的深度,这对于图像分类任务来说是有利的。VGG模型的成功证明了增加网络的深度,可以更好的学习图像中的特征模式。

图3:VGG模型网络结构示意图

VGG在眼疾识别数据集iChallenge-PM上的具体实现如下代码所示:

# -*- coding:utf-8 -*-# VGG模型代码
import numpy as np
import paddle
import paddle.fluid as fluid
from paddle.fluid.layer_helper import LayerHelper
from paddle.fluid.dygraph.nn import Conv2D, Pool2D, BatchNorm, Linear
from paddle.fluid.dygraph.base import to_variable# 定义vgg块,包含多层卷积和1层2x2的最大池化层
class vgg_block(fluid.dygraph.Layer):def __init__(self, num_convs, in_channels, out_channels):"""num_convs, 卷积层的数目num_channels, 卷积层的输出通道数,在同一个Incepition块内,卷积层输出通道数是一样的"""super(vgg_block, self).__init__()self.conv_list = []for i in range(num_convs):conv_layer = self.add_sublayer('conv_' + str(i), Conv2D(num_channels=in_channels, num_filters=out_channels, filter_size=3, padding=1, act='relu'))self.conv_list.append(conv_layer)in_channels = out_channelsself.pool = Pool2D(pool_stride=2, pool_size = 2, pool_type='max')def forward(self, x):for item in self.conv_list:x = item(x)return self.pool(x)class VGG(fluid.dygraph.Layer):def __init__(self, conv_arch=((2, 64), (2, 128), (3, 256), (3, 512), (3, 512))):super(VGG, self).__init__()self.vgg_blocks=[]iter_id = 0# 添加vgg_block# 这里一共5个vgg_block,每个block里面的卷积层数目和输出通道数由conv_arch指定in_channels = [3, 64, 128, 256, 512, 512]for (num_convs, num_channels) in conv_arch:block = self.add_sublayer('block_' + str(iter_id), vgg_block(num_convs, in_channels=in_channels[iter_id], out_channels=num_channels))self.vgg_blocks.append(block)iter_id += 1self.fc1 = Linear(input_dim=512*7*7, output_dim=4096,act='relu')self.drop1_ratio = 0.5self.fc2= Linear(input_dim=4096, output_dim=4096,act='relu')self.drop2_ratio = 0.5self.fc3 = Linear(input_dim=4096, output_dim=1)def forward(self, x):for item in self.vgg_blocks:x = item(x)x = fluid.layers.reshape(x, [x.shape[0], -1])x = fluid.layers.dropout(self.fc1(x), self.drop1_ratio)x = fluid.layers.dropout(self.fc2(x), self.drop2_ratio)x = self.fc3(x)return x
with fluid.dygraph.guard():model = VGG()train(model)

通过运行结果可以发现,在眼疾筛查数据集iChallenge-PM上使用VGG,loss能有效的下降,经过5个epoch的训练,在验证集上的准确率可以达到94%左右。

GoogLeNet







GoogLeNet是2014年ImageNet比赛的冠军,它的主要特点是网络不仅有深度,还在横向上具有“宽度”。由于图像信息在空间尺寸上的巨大差异,如何选择合适的卷积核大小来提取特征就显得比较困难了。空间分布范围更广的图像信息适合用较大的卷积核来提取其特征,而空间分布范围较小的图像信息则适合用较小的卷积核来提取其特征。为了解决这个问题,GoogLeNet提出了一种被称为Inception模块的方案。如 图4 所示:


说明:

  • Google的研究人员为了向LeNet致敬,特地将模型命名为GoogLeNet
  • Inception一词来源于电影《盗梦空间》(Inception)

图4:Inception模块结构示意图

图4(a)是Inception模块的设计思想,使用3个不同大小的卷积核对输入图片进行卷积操作,并附加最大池化,将这4个操作的输出沿着通道这一维度进行拼接,构成的输出特征图将会包含经过不同大小的卷积核提取出来的特征。Inception模块采用多通路(multi-path)的设计形式,每个支路使用不同大小的卷积核,最终输出特征图的通道数是每个支路输出通道数的总和,这将会导致输出通道数变得很大,尤其是使用多个Inception模块串联操作的时候,模型参数量会变得非常大。为了减小参数量,Inception模块使用了图(b)中的设计方式,在每个3x3和5x5的卷积层之前,增加1x1的卷积层来控制输出通道数;在最大池化层后面增加1x1卷积层减小输出通道数。基于这一设计思想,形成了上图(b)中所示的结构。下面这段程序是Inception块的具体实现方式,可以对照图(b)和代码一起阅读。


提示:

可能有读者会问,经过3x3的最大池化之后图像尺寸不会减小吗,为什么还能跟另外3个卷积输出的特征图进行拼接?这是因为池化操作可以指定窗口大小kh=kw=3k_h = k_w = 3kh​=kw​=3,pool_stride=1和pool_padding=1,输出特征图尺寸可以保持不变。


Inception模块的具体实现如下代码所示:

class Inception(fluid.dygraph.Layer):def __init__(self, c1, c2, c3, c4, **kwargs):'''Inception模块的实现代码,c1,  图(b)中第一条支路1x1卷积的输出通道数,数据类型是整数c2,图(b)中第二条支路卷积的输出通道数,数据类型是tuple或list, 其中c2[0]是1x1卷积的输出通道数,c2[1]是3x3c3,图(b)中第三条支路卷积的输出通道数,数据类型是tuple或list, 其中c3[0]是1x1卷积的输出通道数,c3[1]是3x3c4,  图(b)中第一条支路1x1卷积的输出通道数,数据类型是整数'''super(Inception, self).__init__()# 依次创建Inception块每条支路上使用到的操作self.p1_1 = Conv2D(num_filters=c1, filter_size=1, act='relu')self.p2_1 = Conv2D(num_filters=c2[0], filter_size=1, act='relu')self.p2_2 = Conv2D(num_filters=c2[1], filter_size=3, padding=1, act='relu')self.p3_1 = Conv2D(num_filters=c3[0], filter_size=1, act='relu')self.p3_2 = Conv2D(num_filters=c3[1], filter_size=5, padding=2, act='relu')self.p4_1 = Pool2D(pool_size=3, pool_stride=1,  pool_padding=1, pool_type='max')self.p4_2 = Conv2D(num_filters=c4, filter_size=1, act='relu')def forward(self, x):# 支路1只包含一个1x1卷积p1 = self.p1_1(x)# 支路2包含 1x1卷积 + 3x3卷积p2 = self.p2_2(self.p2_1(x))# 支路3包含 1x1卷积 + 5x5卷积p3 = self.p3_2(self.p3_1(x))# 支路4包含 最大池化和1x1卷积p4 = self.p4_2(self.p4_1(x))# 将每个支路的输出特征图拼接在一起作为最终的输出结果return fluid.layers.concat([p1, p2, p3, p4], axis=1)

GoogLeNet的架构如 图5 所示,在主体卷积部分中使用5个模块(block),每个模块之间使用步幅为2的3 ×3最大池化层来减小输出高宽。

  • 第一模块使用一个64通道的7 × 7卷积层。
  • 第二模块使用2个卷积层:首先是64通道的1 × 1卷积层,然后是将通道增大3倍的3 × 3卷积层。
  • 第三模块串联2个完整的Inception块。
  • 第四模块串联了5个Inception块。
  • 第五模块串联了2 个Inception块。
  • 第五模块的后面紧跟输出层,使用全局平均池化层来将每个通道的高和宽变成1,最后接上一个输出个数为标签类别数的全连接层。

说明:
在原作者的论文中添加了图中所示的softmax1和softmax2两个辅助分类器,如下图所示,训练时将三个分类器的损失函数进行加权求和,以缓解梯度消失现象。这里的程序作了简化,没有加入辅助分类器。


图5:GoogLeNet模型网络结构示意图

GoogLeNet的具体实现如下代码所示:

# -*- coding:utf-8 -*-# GoogLeNet模型代码
import numpy as np
import paddle
import paddle.fluid as fluid
from paddle.fluid.layer_helper import LayerHelper
from paddle.fluid.dygraph.nn import Conv2D, Pool2D, BatchNorm, Linear
from paddle.fluid.dygraph.base import to_variable# 定义Inception块
class Inception(fluid.dygraph.Layer):def __init__(self, c0,c1, c2, c3, c4, **kwargs):'''Inception模块的实现代码,c1,  图(b)中第一条支路1x1卷积的输出通道数,数据类型是整数c2,图(b)中第二条支路卷积的输出通道数,数据类型是tuple或list, 其中c2[0]是1x1卷积的输出通道数,c2[1]是3x3c3,图(b)中第三条支路卷积的输出通道数,数据类型是tuple或list, 其中c3[0]是1x1卷积的输出通道数,c3[1]是3x3c4,  图(b)中第一条支路1x1卷积的输出通道数,数据类型是整数'''super(Inception, self).__init__()# 依次创建Inception块每条支路上使用到的操作self.p1_1 = Conv2D(num_channels=c0, num_filters=c1, filter_size=1, act='relu')self.p2_1 = Conv2D(num_channels=c0, num_filters=c2[0], filter_size=1, act='relu')self.p2_2 = Conv2D(num_channels=c2[0], num_filters=c2[1], filter_size=3, padding=1, act='relu')self.p3_1 = Conv2D(num_channels=c0, num_filters=c3[0], filter_size=1, act='relu')self.p3_2 = Conv2D(num_channels=c3[0], num_filters=c3[1], filter_size=5, padding=2, act='relu')self.p4_1 = Pool2D(pool_size=3, pool_stride=1,  pool_padding=1, pool_type='max')self.p4_2 = Conv2D(num_channels=c0, num_filters=c4, filter_size=1, act='relu')def forward(self, x):# 支路1只包含一个1x1卷积p1 = self.p1_1(x)# 支路2包含 1x1卷积 + 3x3卷积p2 = self.p2_2(self.p2_1(x))# 支路3包含 1x1卷积 + 5x5卷积p3 = self.p3_2(self.p3_1(x))# 支路4包含 最大池化和1x1卷积p4 = self.p4_2(self.p4_1(x))# 将每个支路的输出特征图拼接在一起作为最终的输出结果return fluid.layers.concat([p1, p2, p3, p4], axis=1)  class GoogLeNet(fluid.dygraph.Layer):def __init__(self):super(GoogLeNet, self).__init__()# GoogLeNet包含五个模块,每个模块后面紧跟一个池化层# 第一个模块包含1个卷积层self.conv1 = Conv2D(num_channels=3, num_filters=64, filter_size=7, padding=3, act='relu')# 3x3最大池化self.pool1 = Pool2D(pool_size=3, pool_stride=2,  pool_padding=1, pool_type='max')# 第二个模块包含2个卷积层self.conv2_1 = Conv2D(num_channels=64, num_filters=64, filter_size=1, act='relu')self.conv2_2 = Conv2D(num_channels=64, num_filters=192, filter_size=3, padding=1, act='relu')# 3x3最大池化self.pool2 = Pool2D(pool_size=3, pool_stride=2,  pool_padding=1, pool_type='max')# 第三个模块包含2个Inception块self.block3_1 = Inception(192, 64, (96, 128), (16, 32), 32)self.block3_2 = Inception(256, 128, (128, 192), (32, 96), 64)# 3x3最大池化self.pool3 = Pool2D(pool_size=3, pool_stride=2,  pool_padding=1, pool_type='max')# 第四个模块包含5个Inception块self.block4_1 = Inception(480, 192, (96, 208), (16, 48), 64)self.block4_2 = Inception(512, 160, (112, 224), (24, 64), 64)self.block4_3 = Inception(512, 128, (128, 256), (24, 64), 64)self.block4_4 = Inception(512, 112, (144, 288), (32, 64), 64)self.block4_5 = Inception(528, 256, (160, 320), (32, 128), 128)# 3x3最大池化self.pool4 = Pool2D(pool_size=3, pool_stride=2,  pool_padding=1, pool_type='max')# 第五个模块包含2个Inception块self.block5_1 = Inception(832, 256, (160, 320), (32, 128), 128)self.block5_2 = Inception(832, 384, (192, 384), (48, 128), 128)# 全局池化,尺寸用的是global_pooling,pool_stride不起作用self.pool5 = Pool2D(pool_stride=1, global_pooling=True, pool_type='avg')self.fc = Linear(input_dim=1024, output_dim=1, act=None)def forward(self, x):x = self.pool1(self.conv1(x))x = self.pool2(self.conv2_2(self.conv2_1(x)))x = self.pool3(self.block3_2(self.block3_1(x)))x = self.block4_3(self.block4_2(self.block4_1(x)))x = self.pool4(self.block4_5(self.block4_4(x)))x = self.pool5(self.block5_2(self.block5_1(x)))x = fluid.layers.reshape(x, [x.shape[0], -1])x = self.fc(x)return x

通过运行结果可以发现,使用GoogLeNet在眼疾筛查数据集iChallenge-PM上,loss能有效的下降,经过5个epoch的训练,在验证集上的准确率可以达到95%左右。

ResNet

ResNet是2015年ImageNet比赛的冠军,将识别错误率降低到了3.6%,这个结果甚至超出了正常人眼识别的精度。







通过前面几个经典模型学习,我们可以发现随着深度学习的不断发展,模型的层数越来越多,网络结构也越来越复杂。那么是否加深网络结构,就一定会得到更好的效果呢?从理论上来说,假设新增加的层都是恒等映射,只要原有的层学出跟原模型一样的参数,那么深模型结构就能达到原模型结构的效果。换句话说,原模型的解只是新模型的解的子空间,在新模型解的空间里应该能找到比原模型解对应的子空间更好的结果。但是实践表明,增加网络的层数之后,训练误差往往不降反升。

Kaiming He等人提出了残差网络ResNet来解决上述问题,其基本思想如 图6所示。

  • 图6(a):表示增加网络的时候,将xxx映射成y=F(x)y=F(x)y=F(x)输出。
  • 图6(b):对图6(a)作了改进,输出y=F(x)+xy=F(x) + xy=F(x)+x。这时不是直接学习输出特征yyy的表示,而是学习y−xy-xy−x。
    • 如果想学习出原模型的表示,只需将F(x)F(x)F(x)的参数全部设置为0,则y=xy=xy=x是恒等映射。
    • F(x)=y−xF(x) = y - xF(x)=y−x也叫做残差项,如果x→yx\rightarrow yx→y的映射接近恒等映射,图6(b)中通过学习残差项也比图6(a)学习完整映射形式更加容易。

图6:残差块设计思想
图6(b)的结构是残差网络的基础,这种结构也叫做残差块(Residual block)。输入$x$通过跨层连接,能更快的向前传播数据,或者向后传播梯度。残差块的具体设计方案如 图7 所示,这种设计方案也成称作瓶颈结构(BottleNeck)。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200821154110621.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwNTA3ODU3,size_16,color_FFFFFF,t_70#pic_center)
图7:残差块结构示意图

下图表示出了ResNet-50的结构,一共包含49层卷积和1层全连接,所以被称为ResNet-50。

图8:ResNet-50模型网络结构示意图

ResNet-50的具体实现如下代码所示:

# -*- coding:utf-8 -*-# ResNet模型代码
import numpy as np
import paddle
import paddle.fluid as fluid
from paddle.fluid.layer_helper import LayerHelper
from paddle.fluid.dygraph.nn import Conv2D, Pool2D, BatchNorm, Linear
from paddle.fluid.dygraph.base import to_variable# ResNet中使用了BatchNorm层,在卷积层的后面加上BatchNorm以提升数值稳定性
# 定义卷积批归一化块
class ConvBNLayer(fluid.dygraph.Layer):def __init__(self,num_channels,num_filters,filter_size,stride=1,groups=1,act=None):"""num_channels, 卷积层的输入通道数num_filters, 卷积层的输出通道数stride, 卷积层的步幅groups, 分组卷积的组数,默认groups=1不使用分组卷积act, 激活函数类型,默认act=None不使用激活函数"""super(ConvBNLayer, self).__init__()# 创建卷积层self._conv = Conv2D(num_channels=num_channels,num_filters=num_filters,filter_size=filter_size,stride=stride,padding=(filter_size - 1) // 2,groups=groups,act=None,bias_attr=False)# 创建BatchNorm层self._batch_norm = BatchNorm(num_filters, act=act)def forward(self, inputs):y = self._conv(inputs)y = self._batch_norm(y)return y# 定义残差块
# 每个残差块会对输入图片做三次卷积,然后跟输入图片进行短接
# 如果残差块中第三次卷积输出特征图的形状与输入不一致,则对输入图片做1x1卷积,将其输出形状调整成一致
class BottleneckBlock(fluid.dygraph.Layer):def __init__(self,num_channels,num_filters,stride,shortcut=True):super(BottleneckBlock, self).__init__()# 创建第一个卷积层 1x1self.conv0 = ConvBNLayer(num_channels=num_channels,num_filters=num_filters,filter_size=1,act='relu')# 创建第二个卷积层 3x3self.conv1 = ConvBNLayer(num_channels=num_filters,num_filters=num_filters,filter_size=3,stride=stride,act='relu')# 创建第三个卷积 1x1,但输出通道数乘以4self.conv2 = ConvBNLayer(num_channels=num_filters,num_filters=num_filters * 4,filter_size=1,act=None)# 如果conv2的输出跟此残差块的输入数据形状一致,则shortcut=True# 否则shortcut = False,添加1个1x1的卷积作用在输入数据上,使其形状变成跟conv2一致if not shortcut:self.short = ConvBNLayer(num_channels=num_channels,num_filters=num_filters * 4,filter_size=1,stride=stride)self.shortcut = shortcutself._num_channels_out = num_filters * 4def forward(self, inputs):y = self.conv0(inputs)conv1 = self.conv1(y)conv2 = self.conv2(conv1)# 如果shortcut=True,直接将inputs跟conv2的输出相加# 否则需要对inputs进行一次卷积,将形状调整成跟conv2输出一致if self.shortcut:short = inputselse:short = self.short(inputs)y = fluid.layers.elementwise_add(x=short, y=conv2)layer_helper = LayerHelper(self.full_name(), act='relu')return layer_helper.append_activation(y)# 定义ResNet模型
class ResNet(fluid.dygraph.Layer):def __init__(self, layers=50, class_dim=1):"""layers, 网络层数,可以是50, 101或者152class_dim,分类标签的类别数"""super(ResNet, self).__init__()self.layers = layerssupported_layers = [50, 101, 152]assert layers in supported_layers, \"supported layers are {} but input layer is {}".format(supported_layers, layers)if layers == 50:#ResNet50包含多个模块,其中第2到第5个模块分别包含3、4、6、3个残差块depth = [3, 4, 6, 3]elif layers == 101:#ResNet101包含多个模块,其中第2到第5个模块分别包含3、4、23、3个残差块depth = [3, 4, 23, 3]elif layers == 152:#ResNet50包含多个模块,其中第2到第5个模块分别包含3、8、36、3个残差块depth = [3, 8, 36, 3]# 残差块中使用到的卷积的输出通道数num_filters = [64, 128, 256, 512]# ResNet的第一个模块,包含1个7x7卷积,后面跟着1个最大池化层self.conv = ConvBNLayer(num_channels=3,num_filters=64,filter_size=7,stride=2,act='relu')self.pool2d_max = Pool2D(pool_size=3,pool_stride=2,pool_padding=1,pool_type='max')# ResNet的第二到第五个模块c2、c3、c4、c5self.bottleneck_block_list = []num_channels = 64for block in range(len(depth)):shortcut = Falsefor i in range(depth[block]):bottleneck_block = self.add_sublayer('bb_%d_%d' % (block, i),BottleneckBlock(num_channels=num_channels,num_filters=num_filters[block],stride=2 if i == 0 and block != 0 else 1, # c3、c4、c5将会在第一个残差块使用stride=2;其余所有残差块stride=1shortcut=shortcut))num_channels = bottleneck_block._num_channels_outself.bottleneck_block_list.append(bottleneck_block)shortcut = True# 在c5的输出特征图上使用全局池化self.pool2d_avg = Pool2D(pool_size=7, pool_type='avg', global_pooling=True)# stdv用来作为全连接层随机初始化参数的方差import mathstdv = 1.0 / math.sqrt(2048 * 1.0)# 创建全连接层,输出大小为类别数目self.out = Linear(input_dim=2048, output_dim=class_dim,param_attr=fluid.param_attr.ParamAttr(initializer=fluid.initializer.Uniform(-stdv, stdv)))def forward(self, inputs):y = self.conv(inputs)y = self.pool2d_max(y)for bottleneck_block in self.bottleneck_block_list:y = bottleneck_block(y)y = self.pool2d_avg(y)y = fluid.layers.reshape(y, [y.shape[0], -1])y = self.out(y)return y

通过运行结果可以发现,使用ResNet在眼疾筛查数据集iChallenge-PM上,loss能有效的下降,经过5个epoch的训练,在验证集上的准确率可以达到95%左右。

计算机视觉:基于眼疾分类数据集iChallenge-PM图像分类经典模型剖析(LeNet,AlexNet,VGG,GoogLeNet,ResNet)相关推荐

  1. ML之FE:基于波士顿房价数据集利用LightGBM算法进行模型预测然后通过3σ原则法(计算残差标准差)寻找测试集中的异常值/异常样本

    ML之FE:基于波士顿房价数据集利用LightGBM算法进行模型预测然后通过3σ原则法(计算残差标准差)寻找测试集中的异常值/异常样本 目录 基于波士顿房价数据集利用LiR和LightGBM算法进行模 ...

  2. R语言xgboost模型构建:基于prima糖尿病数据集

    R语言xgboost模型构建:基于prima糖尿病数据集 目录 R语言xgboost模型构建:基于prima糖尿病数据集

  3. 图像分类经典项目:基于开源数据集Fashion-MNIST的应用实践

    Datawhale 作者:何新,Datawhale优秀学习者 简介:何新,武汉理工大学硕士 https://github.com/whut2962575697 图像分类是计算机视觉和数字图像处理的一个 ...

  4. ML之xgboostGBM:基于xgboostGBM算法对HiggsBoson数据集(Kaggle竞赛)训练(两模型性能PK)实现二分类预测

    ML之xgboost&GBM:基于xgboost&GBM算法对HiggsBoson数据集(Kaggle竞赛)训练(两模型性能PK)实现二分类预测 目录 输出结果 设计思路 核心代码 输 ...

  5. Keras之DNN:基于Keras(sigmoid+linear+mse+predict)利用DNN实现回归预测——DIY多分类数据集预测新数据点

    Keras之DNN:基于Keras(sigmoid+linear+mse+predict)利用DNN实现回归预测--DIY多分类数据集&预测新数据点 目录 输出结果 实现代码 输出结果 实现代 ...

  6. Keras之DNN:基于Keras(sigmoid+binary_crossentropy+predict_proba)利用DNN实现分类预测概率——DIY二分类数据集预测新数据点

    #Keras之DNN:基于Keras(sigmoid+binary_crossentropy+predict_proba)利用DNN实现分类预测概率--DIY二分类数据集&预测新数据点 目录 ...

  7. Keras之DNN::基于Keras(sigmoid+binary_crossentropy+predict_classes)利用DNN实现二分类——DIY二分类数据集预测新数据点

    Keras之DNN::基于Keras(sigmoid+binary_crossentropy+predict_classes)利用DNN实现二分类--DIY二分类数据集&预测新数据点 目录 输 ...

  8. 【深度学习】近几年,关于基于Imagenet数据集图像分类的模型总结

    「@Author:Runsen」 在过去的几年里,许多深度学习模型涌现出来,例如层的类型.超参数等.在本系列中,我将回顾几个最显着的 deeplearn 图像分类的模型. AlexNet (2012 ...

  9. 近几年,关于基于Imagenet数据集图像分类的模型总结

    @Author:Runsen 在过去的几年里,许多深度学习模型涌现出来,例如层的类型.超参数等.在本系列中,我将回顾几个最显着的 deeplearn 图像分类的模型. 文章目录 AlexNet (20 ...

最新文章

  1. 论文翻译 | LS-Net:单目双目视觉的非线性最小二乘学习算法
  2. 【错误记录】安装 Visual Studio 2013 中文语言包报错 ( 需要 Microsoft Visual Studio Test Agent , 但此计算机上并为安装它 )
  3. 使用jackson转换类型时报Unrecognized field
  4. 素描的几大基础知识点_2020年让您感到惊奇的5大素描资源
  5. 我更看好rust飞鸽传书
  6. 【Linux系列】centos7中防火墙相关命令
  7. mysql查询耗时_一种数据库高耗时查询的自动取消方法与流程
  8. Java 编程下的并发线程之间的同步代码块死锁
  9. hadoop3.1集成tez和tez-ui
  10. HBuilder原生功能概述
  11. DWM1000 收发RXLED TXLED控制代码修改
  12. 千万别把有本事挣钱的人,当成臭奸商
  13. python安装tensorflow失败解决办法_pip安装tensorflow总是失败怎么办?
  14. 对于elemnet-ui的el-upload的使用总结(移除上传文件判断,action等问题)
  15. Outlook邮箱注册详解教程
  16. matlab如何打开flt文件,科学网—grb文件的读取 (更新) - 张凌的博文
  17. Win10麦克风显示这个设备正常但是没声音怎么解决
  18. iPhone 8价格已破发!苹果无奈只能减产
  19. php 继承 父类使用子类,在PHP中使用 来 实现子类和父类之间的继承 。
  20. vb.net 教程 3-4 窗体编程 公共控件2 radiobutton ComboBox

热门文章

  1. 7-36 旅游规划 (25 分(Dijkstra)
  2. 事务日志已满,原因为“ACTIVE_TRANSACTION”
  3. 优化DOTTEXT之一:缓存配置项
  4. ad13批量安装元件库_ad16如何批量添加封装库?
  5. python章节总结_《Python深度学习》第一章总结
  6. 设置行内元素宽高和背景色后,行内元素文本不水平垂直居中解决方案
  7. Java使用预定格式获取时间字符串
  8. android 字符串转bitmap,android – 如何将Base64字符串转换为BitMap图像显示在ImageView?...
  9. 如何利用计算机网络辅助自己,如何借助计算机网络辅助大学英语写作教学.doc...
  10. 为什么恋舞显示连接服务器失败,恋舞模式全攻略与技巧