美高梅官方网站3045-mgm6608美高梅app下载
从cifar10分类入门深度学习图像分类(Keras)

从cifar10分类入门深度学习图像分类(Keras)

作者:mgm6608美高梅app下载    来源:未知    发布时间:2020-01-26 20:33    浏览量:

之前需要做一个图像分类模型,因为刚入门,拿cifar10数据集练了下手,试了几种优化方案和不同的模型效果,这里就统一总结一下这段学习经历。

对于新手来说,最方便的深度学习框架应该是Keras了,这是一个可以基于Tensorflow、PyTorch等多种深度学习框架的高级框架,用它来搭建和训练模型特别方便,很适合入门快速掌握。这里推荐下Keras之父写的深度学习入门书籍:《Python深度学习》,轻松易懂,而且直接教你一步步用Keras搭建模型,比看西瓜书《机器学习》和花书《深度学习》效率快很多。

cifar10是是一个图像数据集,包含10种类别的32*32大小的图像共60000张。另外还有cifar100,包含100种类别的更多图像。因此,cifar10分类就是一个图像多分类任务。Keras另一个好处在于已经集成了很多常见的数据集和模型,在接口里可以直接调用,当然,为了减小安装包,会在你第一次调用的时候才进行下载,但因为某些因素可能直接下载会失败,因此也可以自己先下载好后再使用,至于下载的方法网上有很多。

因此,本文要说的就是使用Keras框架来开发多种模型和优化方法去训练一个基于cifar10数据集的图像多分类模型。

环境:本文代码运行的环境为Linux,Tensorflow 1.4,Keras 2.1.1,其他的库版本就不说啦,直接安装即可,当然,跑训练最重要的还是最好有一个性能强劲的GPU。

我们一般用深度学习做图片分类的入门教材都是MNIST或者CIFAR-10,因为数据都是别人准备好的,有的甚至是一个函数就把所有数据都load进来了,所以跑起来都很简单,但是跑完了,好像自己还没掌握图片分类的完整流程,因为他们没有经历数据处理的阶段,所以谈不上走过一遍深度学习的分类实现过程。今天我想给大家分享两个比较贴近实际的分类项目,从数据分析和处理说起,以Keras为工具,彻底掌握图像分类任务。

图片 1

目录

  • 简单CNN训练
  • 简单CNN数据增强
  • 基于VGG16-imagenet进行特征提取
  • 基于VGG16-imagenet进行模型微调(fine-tuning)
  • 深层CNN训练
  • 简单Resnet训练

这两个分类项目就是:交通标志分类和票据分类。交通标志分类在无人驾驶或者与交通相关项目都有应用,而票据分类任务就更加贴切生活了,同时该项目也是我现在做的一个大项目中的子任务。这两个分类任务都是很贴近实际的练手好项目,希望经过这两个实际任务可以掌握好Keras这个工具,并且搭建一个用于图像分类的通用框架,以后做其他图像分类项目也可以得心应手。

 向AI转型的程序员都关注了这个号???

简单CNN分类

最简单的图像分类模型就是一个层数较少的CNN啦,至于CNN是什么,这里不介绍了,总之就是一种适合处理图像数据的网络层。先给出简单CNN的网络结构代码:

def quality_classify_model(): model = Sequential() model.add(Conv2D(32, , padding='same', input_shape=) # 卷积层 model.add(Activation # 激活函数 model.add(MaxPooling2D(pool_size= # 最大池化 model.add(Conv2D(32,  model.add(Activation model.add(MaxPooling2D(pool_size= model.add(Conv2D(64, , padding='same')) model.add(Activation model.add(MaxPooling2D(pool_size= model.add) # 全连接层 model.add) model.add(Activation model.add(Dropout # 随机抛弃一半 model.add(Dense(classes_num)) model.add(Activation('softmax')) # 编译模型 opt = keras.optimizers.rmsprop(lr=0.0001, decay=1e-6) model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy']) return model

这网络很简单,首先我们的模型使用Keras的Sequential形式定义,也就是一层一层去定义和add,最后进行编译。我们首先循环了三次“卷积层-激活函数-最大池化”的过程,卷积层就是conv2D,第一个参数表示过滤器数量,第二个参数是卷积核的尺寸,第三个是padding的模式,第四个是输入尺寸,这里我们的输入就是一个32*32的图像,至于那个3,表示图像石油RGB三层颜色构成。Keras的方便又一次体现出来,除了第一层需要我们定义输入尺寸外,后面都不再需要定义了,框架会自行判断上一层的输出尺寸就是下一层的输入尺寸。在卷积层后是一个激活函数,我们使用relu。之后是一个池化层,我们使用最大池化,这些名称的含义可以自行搜索一下。我们连续了三次这样的层定义后,就对接上一个Flatten层,也就是平铺开到一个全连接层,然后就接上一般的神经层了,有64个神经元,然后同样使用relu激活函数,再接入一个Dropout函数,这个表示我们要将64个神经元那一层中的权重随机抛弃一半,这样可以降低过拟合。最后就是接上输出层了,这层的神经元数量我们用分类数来表示,在cifar10中就是10类,最后的激活函数我们用softmax,这个函数适合多分类任务,sigmoid适合二分类任务。

网络搭建完后,需要进行编译,这和代码写完后要编译才能运行的意思差不多,我们定义了损失函数为categorical_crossentropy,优化器是自定义的rmsprop,这是一种很好用的优化器,以及要观察的指标是accuracy,也就是分类准确率。

搭建完模型网络后,我们就可以开始设置训练数据并且开始训练了:

def train(): # 数据载入 (x_train, y_train), (x_test, y_test) = cifar10.load_data() # 多分类标签生成 y_train = keras.utils.to_categorical(y_train, classes_num) y_test = keras.utils.to_categorical(y_test, classes_num) # 生成训练数据 x_train = x_train.astype('float32') x_test = x_test.astype('float32') x_train /= 255 x_test /= 255 # 开始训练 model = quality_classify_model() hist = model.fit(x_train, y_train, batch_size=64, epochs=epochs_num, validation_data=(x_test, y_test), shuffle=True) # 保存模型 model.save('./change_model/cifar10_model.hdf5') model.save_weights('./change_model/cifar10_model_weight.hdf5') # 查看准确率 hist_dict = hist.history print("train acc:") print(hist_dict['acc']) print("validation acc:") print(hist_dict['val_acc'])

训练部分的代码也很直观,首先我们要获取数据,使用cifar10.load_data()接口就可以了,如果没下载过它会自动开始下载数据,如果已经下载过,就会直接获取。然后要根据数据生成多分类的标签,也就是每张图像属于哪个类别,我们的训练集一定要有标签,也就是一定要知道他是什么,才可能训练。为了方便训练,我们将图像数据转成float32的形式(tensorflow处理的基本都是float32类型),并且进行标准化,也就是将原本用0255表示颜色值的方式改成01表示颜色值,这有助于训练。

接着我们调用之前定义的模型,然后用model.fit()开始训练,里面可以加入很多参数,比如训练数据x_train, y_train,批尺寸batch_size(表示每次处理多少张图像后再统一反向传播梯度进行权重优化),训练轮数epochs(每完整过一遍所有数据为一个epoch),validation_data(在训练过程中用于验证的数据,Keras会将cifar10的5W张图像作为训练集,1W张作为验证集),以及可有可无的shuffle,此外还有其他的参数可以自行查官网。这个函数会返回一个数据,里面包含了每一轮训练下来的准确率、损失,是一个词典格式,所以我们获取到后输出训练和验证的准确率。

但是直接看输出的一堆数字可能没什么直观感受,因此可以使用matplotlib库来绘制一个准确率的折线图:

 # 绘图 epochs = range(1, len(train_acc)+1) plt.plot(epochs, train_acc, 'bo', label = 'Training acc') plt.plot(epochs, val_acc, 'r', label = 'Validation acc') plt.title('Training and validation accuracy') plt.legend() plt.savefig("accuracy.png") plt.figure() # 新建一个图 plt.plot(epochs, train_loss, 'bo', label = 'Training loss') plt.plot(epochs, val_loss, 'r', label = 'Validation loss') plt.title('Training and validation loss') plt.legend() plt.savefig("loss.png")

关于如何绘制图像可以参考我这篇博客:Python使用matplotlib库绘图保存

至此,我们就完成了一个模型的训练代码,很简单吧!这全都得益于Keras的方便易用,完整的代码可以看我的github,有帮助的话可以加star,多谢~

基于这个简单CNN的训练我们可以在验证集得到72%的准确率,这对于实用还是差的太远了,还需要继续优化,因此我们考虑第一个优化方案——数据增强。

先说配置环境:

大数据挖掘DT数据分析  公众号: datadw

简单CNN数据增强

由于深度学习的效果很大程度上依赖于数据量,因此如果固定模型不变,效果不佳时一个很重要的优化方案就是增加数据量,但有时候我们无法简单地获取到新的图像数据,比如这个cifar10数据集,就这么多数据,再找不太现实,那怎么办呢?有一种增加数据量的方法叫做数据增强。

Keras自带一种生成相似图像数据的方式,即使用ImageDataGenerator类。简单地说就是这个类可以对原始图像进行水平/竖直移动一定范围、水平/垂直翻转图像、放大图像一定范围等等,达到生成新的同类图像的目的,这种新生成的图像还是属于同样的类别,比如你把一张猫的图像平移15%的距离,它还是一只猫,但是与原图像又不是完全一样,因此也是提升了数据丰富程度的。具体的ImageDataGenerator类使用方法可以看我这篇博客:图像训练样本量少时的数据增强技术。

要使用数据增强,不用改太多东西,只需要在获取数据的时候做一点变动就可以了:

def train(): # 数据载入 (x_train, y_train), (x_test, y_test) = cifar10.load_data() # 多分类标签生成 y_train = keras.utils.to_categorical(y_train, classes_num) y_test = keras.utils.to_categorical(y_test, classes_num) # 生成训练数据 x_train = x_train.astype('float32') x_test = x_test.astype('float32') # x_train /= 255 x_test /= 255 train_datagan = ImageDataGenerator(rescale=1./255, rotation_range=15, width_shift_range=0.15, height_shift_range=0.15, fill_mode='wrap') # test_datagen = ImageDataGenerator(rescale=1./255) model = quality_classify_model() hist = model.fit_generator(train_datagan.flow(x_train, y_train, batch_size=batch_size), steps_per_epoch = x_train.shape[0] // batch_size, epochs = epochs_num, validation_data=(x_test,y_test), shuffle=True) # hist = model.fit(x_train, y_train, batch_size=64, epochs=epochs_num, validation_data=(x_test, y_test), shuffle=True) model.save('./augment_model/cifar10_model.hdf5') model.save_weights('./augment_model/cifar10_model_weight.hdf5')

对比一下上一节的train函数,就可以发现不同点,首先,对于训练数据,我用ImageDataGenerator类返回了一个train_datagan ,用来生成训练的数据,这个ImageDataGenerator中就包含了一些变化的方式,包括标准化(1./255这个即使是一般的训练也都需要)、旋转角度、水平移动、竖直移动。

需要注意的是,我们只只应该对训练数据进行数据增强,对于验证集,不要去变动,因此我们只需要同样做标准化即可。

在开始训练的时候,也从fit函数改成了fit_generator函数,这个函数才能接受ImageDataGenerator类返回的train_datagan作为输入,也就是train_datagan.flow()函数,其中设置了原始数据、批尺寸batch_size,这里同样是每一次处理的数据量,steps_per_epoch 是指每一轮训练需要多少步,epoch还是原来那个epoch,也就是要完整训练多少轮,因此这个steps_per_epoch 就是总数据量除以批尺寸,这是为了让所有图像都有机会被处理到。其他就没什么不同啦。

同样,完整的代码可以看我的github

  1. Python 3.5
  2. Keras==2.0.1,TesnsorFlow后端,CPU训练

基于VGG16-imagenet进行特征提取

训练效果不好还有一种可能性是因为我们的模型不好,毕竟我们就是很简单的一个浅层CNN模型,那怎样的模型才叫好呢?有句话叫站在巨人的肩膀上,而在深度学习方面也有这种做法,而且还有个专有名词,叫做迁移学习。

所谓迁移学习其实就是一种思想,意思就是把某个优秀模型的能力迁移到其他任务中去,在这里,我们要做的就是找某个已经预训练好的效果很好的图像分类模型,基于它来完成我们的图像分类任务。这里我们找的巨人就是在imagenet图像数据集(1000个类别的大数据集)上预训练好的VGG16网络模型。

还记得我们简单CNN的模型结构吧,几个卷积层池化层,然后输入到全连接网络去逐渐分类。后面的全连接网络部分实际上就是个分类器,而前面的卷积层池化层的工作目的就在于提取特征。我们想象一下预训练好的VGG16已经能够较好地完成imagenet数据集的分类任务了,那么它一定是在识别图像上有一定的过人之处,我们就把它识别图像的能力拿过来,在这个基础上只去训练分类器部分,这就叫基于预训练的网络进行特征提取。

要利用预训练好的模型,首先肯定得加载它,Keras也提供了一些常用的预训练好的模型,同cifar10数据集一样,在第一次调用时会下载,如果下载失败,可以参考我这篇博客:keras离线下载模型的存储位置,来自行下载和使用。

如前所说,我们要使用VGG16的卷积基,因此我们自己的模型网络只需要分类器部分就可以了:

def quality_classify_model(): model = Sequential() model.add(Flatten(input_shape=)# 4*4*512 model.add(Dense(256, activation='relu')) model.add(Dropout model.add(Dense(classes_num, activation='softmax')) # 多分类 opt = keras.optimizers.rmsprop(lr=0.0001, decay=1e-6) model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy']) return model

其实就是把前面的卷积基部分删掉了。

然后在训练过程中,我们要加载并调用VGG16模型,去处理我们的图像数据,得到它提取出来的特征,然后再把这个提取出来的特征输入我们的模型网络去训练我们的分类器部分:

def train(): # 数据载入 (x_train, y_train), (x_test, y_test) = cifar10.load_data() # 多分类标签生成 y_train = keras.utils.to_categorical(y_train, classes_num) y_test = keras.utils.to_categorical(y_test, classes_num) # 生成训练数据 x_train = x_train.astype('float32') x_test = x_test.astype('float32') datagan = ImageDataGenerator(rescale=1./255) # 加载预训练好的卷积基 conv_base = VGG16(include_top=False, weights='imagenet') # 用预训练好的卷积基处理训练集提取特征 sample_count = len train_features = np.zeros(shape=(sample_count, 4, 4, 512)) train_labels = np.zeros(shape=(sample_count, classes_num)) train_generator = datagan.flow(x_train, y_train, batch_size=batch_size) i = 0 for inputs_batch, labels_batch in train_generator: features_batch = conv_base.predict(inputs_batch) train_features[i * batch_size :  * batch_size] = features_batch train_labels[i * batch_size :  * batch_size] = labels_batch i += 1 if i * batch_size >= sample_count: break # train_features = np.reshape(train_features, (sample_count, 4*4*512)) # 用预训练好的卷积基处理验证集提取特征 sample_count = len test_generator = datagan.flow(x_test, y_test, batch_size=batch_size) test_features = np.zeros(shape=(sample_count, 4, 4, 512)) test_labels = np.zeros(shape=(sample_count, classes_num)) i = 0 for inputs_batch, labels_batch in test_generator: features_batch = conv_base.predict(inputs_batch) test_features[i * batch_size :  * batch_size] = features_batch test_labels[i * batch_size :  * batch_size] = labels_batch i += 1 if i * batch_size >= sample_count: break # test_features = np.reshape(test_features, (sample_count, 4*4*512)) model = quality_classify_model() # hist = model.fit_generator(train_datagan.flow(x_train, y_train, batch_size=batch_size), steps_per_epoch = 8000, epochs = epochs_num, validation_data=(x_test,y_test), shuffle=True) hist = model.fit(train_features, train_labels, batch_size=batch_size, epochs=epochs_num, validation_data=(test_features, test_labels)) model.save('./extract_features/cifar10_model.hdf5') model.save_weights('./extract_features/cifar10_model_weight.hdf5') hist_dict = hist.history print("train acc:") print(hist_dict['acc']) print("validation acc:") print(hist_dict['val_acc'])

看注释应该比较好理解,与之前不同的就在于我们先把训练和验证数据经过了一遍VGG模型的处理,得到特征向量,然后真正输入我们自己模型做训练的是这些特征向量。

完整的代码可以看我的github

一、交通标志分类

首先是观察数据,看看我们要识别的交通标志种类有多少,以及每一类的图片有多少。打开一看,这个交通标志的数据集已经帮我们分出了训练集和数据集。

图片 2

每个文件夹的名字就是其标签。

图片 3

每一类的标志图片数量在十来张到数十张,是一个小数据集,总的类别是62。

图片 4

图片 5

图片 6

那我们开始以Keras为工具搭建一个图片分类器通用框架。

本文代码、及测试图片在公众号 datadw 里 回复 图片分类  即可获取。

基于VGG16-imagenet进行模型微调(fine-tuning)

迁移学习的另一种做法是不单单只重新训练分类器部分,而是还去训练卷积基的一部分。毕竟卷积基可能由很多个卷积层池化层组成,我们冻结接近输入端的一大部分卷积基,而同时训练靠后的一小部分卷积基和分类器,这就叫做微调(fine-tuning)。这个做法其实和上一节的特征提取差不多,只是我们训练的层多一点而已。Keras支持对部分层进行“冻结”,即不在训练时改变其既有的权重参数,只改变未被冻结的部分,这个做法在我开头推荐的《Python深度学习》书中有详细的介绍,相信Keras官网中也会有相应的例子,这里就不展开说明了(其实是因为我也没做)。

当然,特征提取和微调都可以结合数据增强去做,换句话说,任何的模型优化方案都可以结合数据增强去在数据部分进行优化,至于要使用那些增加数据的方式,得看你的需求上什么变换是合理的。比如如果你要判断图像中物体的完整性,就不能用横移来做变换,因为这可能会将图像中原本完整的物体给移动到不完整了。

搭建CNN

用深度学习做图片分类选的网络肯定是卷积神经网络,但是现在CNN的种类这么多,哪一个会在我们这个标志分类任务表现最好?在实验之前,没有人会知道。一般而言,先选一个最简单又最经典的网络跑一下看看分类效果是的策略是明智的选择,那么LeNet肯定是最符合以上的要求啦,实现简单,又相当经典。那我们先单独写一个lenet.py的文件,然后实现改进版的LeNet类。

图片 7

# import the necessary packages
from keras.models import Sequential
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.layers.core import Activation
from keras.layers.core import Flatten
from keras.layers.core import Dense
from keras import backend as K

class LeNet:
    @staticmethod
    def build(width, height, depth, classes):
        # initialize the model
        model = Sequential()
        inputShape = (height, width, depth)
        # if we are using "channels last", update the input shape
        if K.image_data_format() == "channels_first":   #for tensorflow
            inputShape = (depth, height, width)
        # first set of CONV => RELU => POOL layers
        model.add(Conv2D(20, (5, 5),padding="same",input_shape=inputShape))
        model.add(Activation("relu"))
        model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
        #second set of CONV => RELU => POOL layers
        model.add(Conv2D(50, (5, 5), padding="same"))
        model.add(Activation("relu"))
        model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
        # first (and only) set of FC => RELU layers
        model.add(Flatten())
        model.add(Dense(500))
        model.add(Activation("relu"))

        # softmax classifier
        model.add(Dense(classes))
        model.add(Activation("softmax"))

        # return the constructed network architecture
        return model

其中conv2d表示执行卷积,maxpooling2d表示执行最大池化,Activation表示特定的激活函数类型,Flatten层用来将输入“压平”,用于卷积层到全连接层的过渡,Dense表示全连接层(500个神经元)。


深层CNN训练

实际上,如果你不想弄得那么复杂,又是找模型又是冻结微调的,还有一种方案可以简单粗暴地提升准确率,那就是加深你的网络层数。

实际上我们最开始写的网络是非常浅的,所以效果不好是理所当然的,这一节我们就粗暴地去直接增加网络深度,毕竟这是个深度学习的事儿。我们直接不断地循环增加卷积基部分,增加到40层的CNN:

def quality_classify_model(): model = Sequential() model.add(Conv2D(32, , padding='same', input_shape=) model.add(Activation model.add(Conv2D(32, , padding='same')) model.add(Activation model.add(Conv2D(32, , padding='same')) model.add(Activation model.add(Conv2D(48, , padding='same')) model.add(Activation model.add(Conv2D(48, , padding='same')) model.add(Activation model.add(MaxPooling2D(pool_size= model.add(Dropout model.add(Conv2D(80, , padding='same')) model.add(Activation model.add(Conv2D(80, , padding='same')) model.add(Activation model.add(Conv2D(80, , padding='same')) model.add(Activation model.add(Conv2D(80, , padding='same')) model.add(Activation model.add(Conv2D(80, , padding='same')) model.add(Activation model.add(MaxPooling2D(pool_size= model.add(Dropout model.add(Conv2D(128, , padding='same')) model.add(Activation model.add(Conv2D(128, , padding='same')) model.add(Activation model.add(Conv2D(128, , padding='same')) model.add(Activation model.add(Conv2D(128, , padding='same')) model.add(Activation model.add(Conv2D(128, , padding='same')) model.add(Activation model.add(GlobalMaxPooling2D model.add(Dropout model.add(Dense model.add(Activation model.add(Dropout model.add(Dense(classes_num)) model.add(Activation('softmax')) # model.summary () # 输出网络结构信息 opt = keras.optimizers.Adam(lr=0.0001) model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy']) return model

代码中可以看到,就是简单粗暴的提升层数,但是注意需要不断地增加过滤器的数量,并且适当地dropout一些神经元比例既可以减少计算量,也可以增强训练效果。此外,我们把最后编译用的优化器改成了Adam,Adam的思想和之前用的rmsprop思想差不多,都是在用梯度下降算法找最优解的时候既给出前进方向又去避免震荡地太厉害,可以理解为更稳更快地找到最优解。

训练部分和使用简单CNN时都一样,完整的代码可以看我的github

使用深层CNN后效果拔群,能得到训练集99%、测试集83.5%的准确率,训练到接近200轮后开始出现过拟合。同样,我们还是可以使用数据增强,增强后测试集准确率增长到了89%。

努力了半天,还是没上90%,怎么办?

参数解析器和一些参数的初始化

首先我们先定义好参数解析器。

# set the matplotlib backend so figures can be saved in the background
import matplotlib
matplotlib.use("Agg")

# import the necessary packages
from keras.preprocessing.image import ImageDataGenerator
from keras.optimizers import Adam
from sklearn.model_selection import train_test_split
from keras.preprocessing.image import img_to_array
from keras.utils import to_categorical
from imutils import paths
import matplotlib.pyplot as plt
import numpy as np
import argparse
import random
import cv2
import os
import sys
sys.path.append('..')
from net.lenet import LeNet



def args_parse():
    # construct the argument parse and parse the arguments
    ap = argparse.ArgumentParser()
    ap.add_argument("-dtest", "--dataset_test", required=True,
        help="path to input dataset_test")
    ap.add_argument("-dtrain", "--dataset_train", required=True,
        help="path to input dataset_train")
    ap.add_argument("-m", "--model", required=True,
        help="path to output model")
    ap.add_argument("-p", "--plot", type=str, default="plot.png",
        help="path to output accuracy/loss plot")
    args = vars(ap.parse_args()) 
    return args

我们还需要为训练设置一些参数,比如训练的epoches,batch_szie等。这些参数不是随便设的,比如batch_size的数值取决于你电脑内存的大小,内存越大,batch_size就可以设为大一点。又比如norm_size(图片归一化尺寸)是根据你得到的数据集,经过分析后得出的,因为我们这个数据集大多数图片的尺度都在这个范围内,所以我觉得32这个尺寸应该比较合适,但是不是最合适呢?那还是要通过实验才知道的,也许64的效果更好呢?

# initialize the number of epochs to train for, initial learning rate,
# and batch size
EPOCHS = 35
INIT_LR = 1e-3
BS = 32
CLASS_NUM = 62
norm_size = 32

我们一般用深度学习做图片分类的入门教材都是MNIST或者CIFAR-10,因为数据都是别人准备好的,有的甚至是一个函数就把所有数据都load进来了,所以跑起来都很简单,但是跑完了,好像自己还没掌握图片分类的完整流程,因为他们没有经历数据处理的阶段,所以谈不上走过一遍深度学习的分类实现过程。今天我想给大家分享两个比较贴近实际的分类项目,从数据分析和处理说起,以Keras为工具,彻底掌握图像分类任务。

下一篇:没有了
友情链接: 网站地图
Copyright © 2015-2019 http://www.zen-40.com. mgm美高梅有限公司 版权所有