总述《tensorflow 迁移学习》

迁移学习方法多样,对于新手来说很不友好,本文总结迁移学习的各种情况,并在TensorFlow中对AlexNet进行迁移学习以对德国交通标志进行分类。

1. 迁移学习的四种情况

在迁移学习中,有四种情况决定着该如何训练pre-trained neural network以便将其应用于新的问题。而这取决于两个因素:新数据集的大小以及新数据集与原始数据集(指训练pre-trained neural network使用的数据集)的相似程度。

下面介绍一个三卷积三全连接层的网络,将其作为pre-trained neural network,来看四种情况下如何对其进行修改:

假设该训练好的网络第一层卷积用于检测边沿信息,第二层用于检测形状信息,第三层用于检测更为高级的特征。三个全连接层对高级特征组合后进行softmax分类。

1.1 新数据集小并且与原始数据集相似

在这种情况下(Feature extraction):

  • 先去掉pre-trained neural network的最后一个全连接层。
  • 新增加符合新数据集类别个数的全连接层。
  • 其他预训练层的权值不动,只随机初始化新增加层的权值。
  • 使用新数据集来训练新的全连接层。

为什么这样修改呢?因为新数据集小,首要考虑的一点就是防止过拟合,所有就保持原网络层的权值不动。另外因为数据集之间是相似的,那么也就可以认为网络之前在旧数据集上学得的高级特征(权值)也可用于新数据集。

1.2 新数据集小且数据集间不相似

在这种情况下:

  • 只保留前几层卷积层,后面的其它层全部去掉。
  • 然后添加一个新的全连接层,其神经元个数符合新数据集的类别数。
  • 其他预训练层的权值不动,只随机初始化新增加层的权值。
  • 使用新数据集来训练新的全连接层。

因为数据集小,所以首要考虑的还是防止发生过拟合,所以原网络层的权值不动。但是由于数据集已经不相似了,原网络中深层卷积所检测的高级特征就不能应用于新数据集了,所以就将其去掉。但由于原网络一般都在大量的训练集上训练,所以低级特征的检测,也就是前几层卷积还是可以用于新数据集的。

1.3 新数据集大且数据集间相似

在这种情况下(Fine tune):

  • 先去掉pre-trained neural network的最后一个全连接层,然后添加一个新的全连接层,其神经元个数符合新数据集的类别数。
  • 随机初始化新添加的全连接层的权值。
  • 将预训练网络其它层的权值作为初始值。
  • 在新数据集上训练网络。

此时,因为数据量大,并不容易发生过拟合,所以可以重训练整个网络。此外由于数据集相似,原网络层检测到的特征可用于新数据集,所以网络层上的原权值可以用来作为初始值,这样可以加快训练的速度。

1.4 新数据集大且数据集间不相似

这时可以:

  • 先去掉pre-trained neural network的最后一个全连接层,然后添加一个新的全连接层,其神经元个数符合新数据集的类别数。
  • 随机初始化全部层的权值,然后再新数据集上训练。
  • 或者,像1.3中一样,将原网络权值作为初始值进行训练。虽然此时数据集并不相同,但是使用原网络权值也可能会加快训练速度。如果这样不行,那就重新随机初始化权值。

Feature extraction就是只训练新增加层的权值,其他层的权值保留不动。
Finetune就是使用原网络层的权值作为初始值,训练整个网络。

2. 使用TensorFlow对AlexNet进行特征提取

这一节介绍如何对AlexNet进行特征提取(Feature extraction),将其应用于德国交通标志数据集的识别。

2.1 AlexNet简介

AlexNet的论文在这里

AlexNet为八层网络,包括五个卷积层和三个全连接层,最后接一个分1000类的softmax。其第一次使用ReLu作为激励函数,为了防止过拟合,在全连接层中还加入了dropout。论文中还介绍了一个regularize技术,即Local Response Normalization,是对卷积层的输出特征图按channel做regularize(并没有改变特征图的维度),使用后可提升网络的泛化能力,吴恩达老师说现在这个很少用了。使用了两块当时(2012)最好的GTX 580 3GB GPU以加速训练,两块GPU只在特定层进行内存通信。将特征图按channel分为两部分,分别存到两个GPU中。同样的将一层中的kernel数目也分为两部分,分别存到两个GPU中。当GPU通信时,相当于正常的卷积运算,即kernel的channel与输入特征图的channel相同。而当GPU不通信时,相当于单个GPU内的kernel只对该GPU内的部分特征图做卷积运算,此时kernel的channel只等于这部分特征图的channel,即总体特征图channel的一半。整体网络结构如下图:

第一层、第二层和第五层卷积后接了max-pooling,只有在第三层卷积和所有全连接层中,两GPU才相互通信。普遍认为,论文中对输入图片的大小描述错误,如果想要得到输出为48x48的特征图,对于kernel size=11,stride=4,而且没有padding的情况下,只有输入为227x227才对。此外,除第一层卷积之外,其他层卷积在运算前应该是都做了same-padding操作,但论文中并没有说。

作者分别使用了ILSVRC-2010和ILSVRC-2012训练集训练了两个版本的网络,为什么用了两个训练集呢?因为当时只有ILSVRC-2010的测试集数据有label,而ILSVRC-2012的测试集数据没有label,训练的效果可看论文。ILSVRC使用了ImageNet数据集的子集,有1000个类别,每个类别大概有1000张图片。由于每张图片的分辨率都不相同,所以论文中对各图片下采样到256x256像素,训练时会对其随机采227x227的patches,以进行data augment。只对数据进行了一种预处理,即使训练集上各像素点的均值为零。

还有一些训练上的细节,如如何增强的数据集、训练的过程可看论文。模型具体的效果可看论文,反正是ILSVRC-2012的冠军,错误率为15%。

2.2 AlexNet的TensorFlow实现

AlexNet的TensorFlow实现代码及权值来自这里

下面的代码略有不同,只是将AlexNet的前向传播封装到一个函数中,并且提供了一个参数用于特征提取,注意这里在卷积运算前没有使用tf.pad来做padding,而是使用卷积运算API和max-pool API中的padding参数来设置,而TensorFlow中的padding操作略有区别,可看这里。这就使第一层和第二层卷积后的特征图尺寸略有不同:

"""
File - alexnet.py
改代码完全对应了AlexNet的结构,具体结构要看论文。
"""
import numpy as np
import tensorflow as tf

# npy的数组中只有一个字典,使用item()方法将其提取出来
# 字典的key为各层名,对应的value为一两元素列表
# 第一个为权值,第二个为偏置
net_data = np.load(“bvlc-alexnet.npy”, encoding=“latin1”).item()

# 这个group是什么意思呢?这就要看AlexNet的结构了。
# AlexNet使用了两块GPU来训练。
# 但两块GPU不是在所有卷积层上都传递信息
# 而只在特定层上传递信息。
# 没传递信息时,就相当于两个并行的卷积层,此时group=2,某一kernel只在一个GPU中做卷积运算
# 传递信息时,就是一个卷积层,此时group=1,某一kernel在两块GPU中都做卷积运算。
def conv(input, kernel, biases, k_h, k_w, c_o, s_h, s_w, padding=“VALID”, group=1):
‘’’
From https://github.com/ethereon/caffe-tensorflow
‘’’

c_i = input.get_shape()[-1]
assert c_i % group == 0
assert c_o % group == 0
convolve = lambda i, k: tf.nn.conv2d(i, k, [1, s_h, s_w, 1], padding=padding)

<span class="hljs-keyword">if</span> tf.__version__ &lt; <span class="hljs-string">"1.0.0"</span>:
    <span class="hljs-keyword">if</span> group == <span class="hljs-number">1</span>:
        conv = convolve(input, kernel)
    <span class="hljs-keyword">else</span>:
        input_groups = tf.split(<span class="hljs-number">3</span>, group, input)
        kernel_groups = tf.split(<span class="hljs-number">3</span>, group, kernel)
        output_groups = [convolve(i, k) <span class="hljs-keyword">for</span> i, k <span class="hljs-keyword">in</span> zip(input_groups, kernel_groups)]
        conv = tf.concat(<span class="hljs-number">3</span>, output_groups)
<span class="hljs-keyword">else</span>:
    <span class="hljs-keyword">if</span> group == <span class="hljs-number">1</span>:
        conv = convolve(input, kernel)
    <span class="hljs-keyword">else</span>:
        input_groups = tf.split(input, group, <span class="hljs-number">3</span>)
        kernel_groups = tf.split(kernel, group, <span class="hljs-number">3</span>)
        output_groups = [convolve(i, k) <span class="hljs-keyword">for</span> i, k <span class="hljs-keyword">in</span> zip(input_groups, kernel_groups)]
        conv = tf.concat(output_groups, <span class="hljs-number">3</span>)
<span class="hljs-keyword">return</span> tf.reshape(tf.nn.bias_add(conv, biases), [<span class="hljs-number">-1</span>] + conv.get_shape().as_list()[<span class="hljs-number">1</span>:])

# 为AlexNet的前向传播过程
# 如果feature_extract=True,则返回倒数第二个全连接层的输出
# 可用于后面的特征提取。
def AlexNet(features, feature_extract=False):
“”"
Builds an AlexNet model, loads pretrained weights
“”"

# conv1
# conv(11, 11, 96, 4, 4, padding=‘VALID’, name=‘conv1’)
k_h = 11
k_w = 11
c_o = 96
s_h = 4
s_w = 4
# 使用训练好的权值初始化。
conv1W = tf.Variable(net_data[“conv1”][0])
conv1b = tf.Variable(net_data[“conv1”][1])
# input:(227,227,3)
conv1_in = conv(features, conv1W, conv1b, k_h, k_w, c_o, s_h, s_w, padding=“SAME”, group=1)
# output:(57,57,96)
conv1 = tf.nn.relu(conv1_in)

<span class="hljs-comment"># lrn1</span>
<span class="hljs-comment"># lrn(2, 2e-05, 0.75, name='norm1')</span>
radius = <span class="hljs-number">2</span>
alpha = <span class="hljs-number">2e-05</span>
beta = <span class="hljs-number">0.75</span>
bias = <span class="hljs-number">1.0</span>
lrn1 = tf.nn.local_response_normalization(conv1, depth_radius=radius, alpha=alpha, beta=beta, bias=bias)

<span class="hljs-comment"># maxpool1</span>
<span class="hljs-comment"># max_pool(3, 3, 2, 2, padding='VALID', name='pool1')</span>
k_h = <span class="hljs-number">3</span>
k_w = <span class="hljs-number">3</span>
s_h = <span class="hljs-number">2</span>
s_w = <span class="hljs-number">2</span>
padding = <span class="hljs-string">'VALID'</span>
<span class="hljs-comment"># input: (57,57,96)</span>
maxpool1 = tf.nn.max_pool(lrn1, ksize=[<span class="hljs-number">1</span>, k_h, k_w, <span class="hljs-number">1</span>], strides=[<span class="hljs-number">1</span>, s_h, s_w, <span class="hljs-number">1</span>], padding=padding)
<span class="hljs-comment"># output: (28,28,96)</span>

<span class="hljs-comment"># conv2</span>
<span class="hljs-comment"># conv(5, 5, 256, 1, 1, group=2, name='conv2')</span>
k_h = <span class="hljs-number">5</span>
k_w = <span class="hljs-number">5</span>
c_o = <span class="hljs-number">256</span>
s_h = <span class="hljs-number">1</span>
s_w = <span class="hljs-number">1</span>
group = <span class="hljs-number">2</span>
conv2W = tf.Variable(net_data[<span class="hljs-string">"conv2"</span>][<span class="hljs-number">0</span>])
conv2b = tf.Variable(net_data[<span class="hljs-string">"conv2"</span>][<span class="hljs-number">1</span>])
<span class="hljs-comment"># input: (28,28,96)</span>
conv2_in = conv(maxpool1, conv2W, conv2b, k_h, k_w, c_o, s_h, s_w, padding=<span class="hljs-string">"SAME"</span>, group=group)
<span class="hljs-comment"># output: (28,28,256)</span>
conv2 = tf.nn.relu(conv2_in)

<span class="hljs-comment"># lrn2</span>
<span class="hljs-comment"># lrn(2, 2e-05, 0.75, name='norm2')</span>
radius = <span class="hljs-number">2</span>
alpha = <span class="hljs-number">2e-05</span>
beta = <span class="hljs-number">0.75</span>
bias = <span class="hljs-number">1.0</span>
lrn2 = tf.nn.local_response_normalization(conv2, depth_radius=radius, alpha=alpha, beta=beta, bias=bias)

<span class="hljs-comment"># maxpool2</span>
<span class="hljs-comment"># max_pool(3, 3, 2, 2, padding='VALID', name='pool2')</span>
k_h = <span class="hljs-number">3</span>
k_w = <span class="hljs-number">3</span>
s_h = <span class="hljs-number">2</span>
s_w = <span class="hljs-number">2</span>
padding = <span class="hljs-string">'VALID'</span>
<span class="hljs-comment"># input: (28,28,256)</span>
maxpool2 = tf.nn.max_pool(lrn2, ksize=[<span class="hljs-number">1</span>, k_h, k_w, <span class="hljs-number">1</span>], strides=[<span class="hljs-number">1</span>, s_h, s_w, <span class="hljs-number">1</span>], padding=padding)
<span class="hljs-comment"># output: (13,13,256)</span>

<span class="hljs-comment"># conv3</span>
<span class="hljs-comment"># conv(3, 3, 384, 1, 1, name='conv3')</span>
k_h = <span class="hljs-number">3</span>
k_w = <span class="hljs-number">3</span>
c_o = <span class="hljs-number">384</span>
s_h = <span class="hljs-number">1</span>
s_w = <span class="hljs-number">1</span>
group = <span class="hljs-number">1</span>
conv3W = tf.Variable(net_data[<span class="hljs-string">"conv3"</span>][<span class="hljs-number">0</span>])
conv3b = tf.Variable(net_data[<span class="hljs-string">"conv3"</span>][<span class="hljs-number">1</span>])
<span class="hljs-comment"># input: (13,13,256)</span>
conv3_in = conv(maxpool2, conv3W, conv3b, k_h, k_w, c_o, s_h, s_w, padding=<span class="hljs-string">"SAME"</span>, group=group)
<span class="hljs-comment"># output: (13,13,384)</span>
conv3 = tf.nn.relu(conv3_in)

<span class="hljs-comment"># conv4</span>
<span class="hljs-comment"># conv(3, 3, 384, 1, 1, group=2, name='conv4')</span>
k_h = <span class="hljs-number">3</span>
k_w = <span class="hljs-number">3</span>
c_o = <span class="hljs-number">384</span>
s_h = <span class="hljs-number">1</span>
s_w = <span class="hljs-number">1</span>
group = <span class="hljs-number">2</span>
conv4W = tf.Variable(net_data[<span class="hljs-string">"conv4"</span>][<span class="hljs-number">0</span>])
conv4b = tf.Variable(net_data[<span class="hljs-string">"conv4"</span>][<span class="hljs-number">1</span>])
<span class="hljs-comment"># input: (13,13,384)</span>
conv4_in = conv(conv3, conv4W, conv4b, k_h, k_w, c_o, s_h, s_w, padding=<span class="hljs-string">"SAME"</span>, group=group)
<span class="hljs-comment"># output: (13,13,384)</span>
conv4 = tf.nn.relu(conv4_in)

<span class="hljs-comment"># conv5</span>
<span class="hljs-comment"># conv(3, 3, 256, 1, 1, group=2, name='conv5')</span>
k_h = <span class="hljs-number">3</span>
k_w = <span class="hljs-number">3</span>
c_o = <span class="hljs-number">256</span>
s_h = <span class="hljs-number">1</span>
s_w = <span class="hljs-number">1</span>
group = <span class="hljs-number">2</span>
conv5W = tf.Variable(net_data[<span class="hljs-string">"conv5"</span>][<span class="hljs-number">0</span>])
conv5b = tf.Variable(net_data[<span class="hljs-string">"conv5"</span>][<span class="hljs-number">1</span>])
<span class="hljs-comment"># input: (13,13,384)</span>
conv5_in = conv(conv4, conv5W, conv5b, k_h, k_w, c_o, s_h, s_w, padding=<span class="hljs-string">"SAME"</span>, group=group)
<span class="hljs-comment"># output: (13,13,256)</span>
conv5 = tf.nn.relu(conv5_in)

<span class="hljs-comment"># maxpool5</span>
<span class="hljs-comment"># max_pool(3, 3, 2, 2, padding='VALID', name='pool5')</span>
k_h = <span class="hljs-number">3</span>
k_w = <span class="hljs-number">3</span>
s_h = <span class="hljs-number">2</span>
s_w = <span class="hljs-number">2</span>
padding = <span class="hljs-string">'VALID'</span>
<span class="hljs-comment"># input: (13,13,256)</span>
maxpool5 = tf.nn.max_pool(conv5, ksize=[<span class="hljs-number">1</span>, k_h, k_w, <span class="hljs-number">1</span>], strides=[<span class="hljs-number">1</span>, s_h, s_w, <span class="hljs-number">1</span>], padding=padding)
<span class="hljs-comment"># output: (6,6,256)</span>

<span class="hljs-comment"># fc6, 4096</span>
fc6W = tf.Variable(net_data[<span class="hljs-string">"fc6"</span>][<span class="hljs-number">0</span>])
fc6b = tf.Variable(net_data[<span class="hljs-string">"fc6"</span>][<span class="hljs-number">1</span>])
flat5 = tf.reshape(maxpool5, [<span class="hljs-number">-1</span>, int(np.prod(maxpool5.get_shape()[<span class="hljs-number">1</span>:]))])
fc6 = tf.nn.relu(tf.matmul(flat5, fc6W) + fc6b)

<span class="hljs-comment"># fc7, 4096</span>
fc7W = tf.Variable(net_data[<span class="hljs-string">"fc7"</span>][<span class="hljs-number">0</span>])
fc7b = tf.Variable(net_data[<span class="hljs-string">"fc7"</span>][<span class="hljs-number">1</span>])
fc7 = tf.nn.relu(tf.matmul(fc6, fc7W) + fc7b)

<span class="hljs-keyword">if</span> feature_extract:
    <span class="hljs-keyword">return</span> fc7

<span class="hljs-comment"># fc8, 1000</span>
fc8W = tf.Variable(net_data[<span class="hljs-string">"fc8"</span>][<span class="hljs-number">0</span>])
fc8b = tf.Variable(net_data[<span class="hljs-string">"fc8"</span>][<span class="hljs-number">1</span>])

logits = tf.matmul(fc7, fc8W) + fc8b
probabilities = tf.nn.softmax(logits)

<span class="hljs-keyword">return</span> probabilities

如果要使用AlexNet对图片进行分类,只要在代码中导入该函数(AlexNet),在Session中run就可以了。使用如下:

"""
File - image_inference.py
"""
import time
import tensorflow as tf
import numpy as np
from scipy.misc import imread
# 这里的class_name是一个1000元素列表
# 对应着AlexNet分的1000个类别名
from caffe_classes import class_names
from alexnet import AlexNet

# AlexNet的输入图片为(227,227,3)
x = tf.placeholder(tf.float32, (None, 227, 227, 3))

# 这里禁止feature extraction
# 调用AlexNet,构建图上的Ops
probs = AlexNet(x, feature_extract=False)
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)

# 这里读入两张图片,并使均值为0
im1 = (imread(“poodle.png”)[:, :, :3]).astype(np.float32)
im1 = im1 - np.mean(im1)
im2 = (imread(“weasel.png”)[:, :, :3]).astype(np.float32)
im2 = im2 - np.mean(im2)

# 对图片进行分类
t = time.time()
output = sess.run(probs, feed_dict={x: [im1, im2]})

# 输出分类的结果
for input_im_ind in range(output.shape[0]):
inds = np.argsort(output)[input_im_ind, :]
print(“Image”, input_im_ind)
for i in range(5):
# np.argsort()是升序排列,所以从-1开始索引
# 输出最高的5个概率
print("%s: %.3f" % (class_names[inds[-1 - i]], output[input_im_ind, inds[-1 - i]]))
print()

print(“Time: %.3f seconds” % (time.time() - t))

2.3 将AlexNet应用于交通标志分类

AlexNet的输入图片为(227,227,3),而交通标志为(32,32,3)。这里要去掉AlexNet的最后一层的1000分类,新添加输出43类的全连接层。其它层的权值固定,只训练最后一层的权值。文件alexnet.py中的内容无需改变,训练代码如下:

"""
File - train_feature_extraction.py
"""
import pickle
import time
import numpy as np
import tensorflow as tf
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle

from alexnet import AlexNet

# 交通标志共43类
nb_classes = 43
epochs = 15
batch_size = 128

with open(’./train.p’, ‘rb’) as f:
data = pickle.load(f)

X_train, X_val, y_train, y_val = train_test_split(data[‘features’], data[‘labels’], test_size=0.33, random_state=0)

features = tf.placeholder(tf.float32, (None, 32, 32, 3))
labels = tf.placeholder(tf.int64, None)
# 因为只训练最后一个全连接层,所以只在其上加dropout
# 这个placeholder用于控制keep_prob
keep_prob = tf.placeholder(tf.float32, (None))
resized = tf.image.resize_images(features, (227, 227))

# Returns the second final layer of the AlexNet model,
# this allows us to redo the last layer for the traffic signs
# model.
fc7 = AlexNet(resized, feature_extract=True)
fc7 = tf.stop_gradient(fc7)
fc7 = tf.nn.dropout(fc7, keep_prob)

shape = (fc7.get_shape().as_list()[-1], nb_classes)
fc8W = tf.Variable(tf.truncated_normal(shape, stddev=1e-2))
fc8b = tf.Variable(tf.zeros(nb_classes))
logits = tf.nn.xw_plus_b(fc7, fc8W, fc8b)

cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=labels)
loss_op = tf.reduce_mean(cross_entropy)
opt = tf.train.AdamOptimizer()
# 指定优化变量列表
train_op = opt.minimize(loss_op, var_list=[fc8W, fc8b])
init_op = tf.global_variables_initializer()

preds = tf.argmax(logits, 1)
accuracy_op = tf.reduce_mean(tf.cast(tf.equal(preds, labels), tf.float32))

def eval_on_data(X, y, sess):
total_acc = 0
total_loss = 0
for offset in range(0, X.shape[0], batch_size):
end = offset + batch_size
X_batch = X[offset:end]
y_batch = y[offset:end]

    loss, acc = sess.run([loss_op, accuracy_op], feed_dict={features: X_batch, labels: y_batch, keep_prob:<span class="hljs-number">1.0</span>})
    total_loss += (loss * X_batch.shape[<span class="hljs-number">0</span>])
    total_acc += (acc * X_batch.shape[<span class="hljs-number">0</span>])

<span class="hljs-keyword">return</span> total_loss/X.shape[<span class="hljs-number">0</span>], total_acc/X.shape[<span class="hljs-number">0</span>]

with tf.Session() as sess:
sess.run(init_op)

print(<span class="hljs-string">"Start training! "</span>)
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(epochs):
    <span class="hljs-comment"># training</span>
    X_train, y_train = shuffle(X_train, y_train)
    t0 = time.time()
    <span class="hljs-keyword">for</span> offset <span class="hljs-keyword">in</span> range(<span class="hljs-number">0</span>, X_train.shape[<span class="hljs-number">0</span>], batch_size):
        end = offset + batch_size

        <span class="hljs-comment"># 本来想设置dropout防止过拟合的,结果发现不设置的效果更好。</span>
        <span class="hljs-comment"># dropout虽然可以防止过拟合,但也会降低训练速度。</span>
        <span class="hljs-comment"># 所以不应该先设置,等发生了过拟合后再解决过拟合问题</span>
        sess.run(train_op, feed_dict={features: X_train[offset:end], labels: y_train[offset:end], keep_prob:<span class="hljs-number">1.0</span>})

    val_loss, val_acc = eval_on_data(X_val, y_val, sess)

    val_losst, val_acct = eval_on_data(X_train, y_train, sess)

    print(<span class="hljs-string">"Epoch"</span>, i+<span class="hljs-number">1</span>)
    print(<span class="hljs-string">"Time: %.3f seconds"</span> % (time.time() - t0))
    print(<span class="hljs-string">"Train Loss ="</span>, val_losst)
    print(<span class="hljs-string">"Train Accuracy ="</span>, val_acct)
    print(<span class="hljs-string">"Validation Loss ="</span>, val_loss)
    print(<span class="hljs-string">"Validation Accuracy ="</span>, val_acc)
    print(<span class="hljs-string">""</span>)

w, b= sess.run([fc8W, fc8b])
para = dict(fc8=[w,b])
np.save(<span class="hljs-string">'fc8.npy'</span>, np.array(para))
print(<span class="hljs-string">"Done!!!!!!"</span>)

训练网络,15个epoch后,在验证集上可达到97%的正确率。

      </div>

猜你喜欢

转载自blog.csdn.net/qq_38742161/article/details/86621666