任务简报
目标:把全连接层的特征保存下来,然后训练SVM分类器
第一步:提取训练数据和验证数据的特征
第二步:训练SVM分类器
第三步:在验证集上进行测试
这是一个隔靴搔痒的答案。此举的真实意图是,用SVM换掉现在的网络中使用的多层感知机(两个全连接层),并寄希望于这个小动作能够带来更好的分类结果。
预备工作
治大国,如烹小鲜,以道莅天下,其鬼不神。 ——《道德经》
在前人的代码上工作,最忌大刀阔斧。此事最温和的解决方案莫过于重建一如当年的工作环境。此事颇有几分艰辛,但尚且用得。随便挑选一个顺手的位置,创建一个复古的virtualenv
virtualenv TF011
cd TF011
source bin/activate
pip install tensorlayer==1.2.8
# 下面这句话取决于你具体把这个古老的TF放在了哪里
pip install tensorflow-0.11.0-cp27-none-linux_x86_64.whl
接下来克隆原始的代码
git clone https://github.com/GuangmingZhu/Conv3D_CLSTM.git
就可以上路了。
提取训练数据和验证数据的特征
这一步的主要思路是,把原来的训练代码中控制网络训练的代码换成用来计算特征值的代码,然后借用原有的绝大多数代码完成工作。这么做可以绕开读取数据这个天大的麻烦。
截胡
先前的代码是仅仅关心最终产物,也就是动作的分类结果的。但是我们现在想要的已经训练好的网络产生的中间特征。所以,在大刀阔斧地开始之前,我们总先要找出访问全连接层的特征的办法。所幸拿到些个东西并不很难,对c3d_clstm.py
的141行稍动手脚
return concat_spp, classes
就可以拿到分类器的前一层——SPP模块计算出的特征。
拿到简单,用却是另外一个故事。在training_isogr_rgb.py
的48行,先为接收特征做点准备工作:
feature_layer, networks = net.c3d_clstm(x, num_classes, False, True)
这里的feature_layer
和networks
都是使用TensorLayer
构造的层。要拿到他们的产出,只消有样学样地复制一下后面一行(training_isogr_rgb.py
第49行)
feature = feature_layer.outputs
贴在下面就好。这里的feature
是TF计算图模型中的一个Tensor。
初始化网络
加载TL由保存的网络参数的代码可以直接从http://tensorlayercn.readthedocs.io/zh/latest/modules/files.html
借来
load_params = tl.files.load_npz(path='', name='isogr_rgb_model_strategy_4.npz')
tl.files.assign_params(sess, load_params, networks)
把这两行放在101行初始化所有变量之后就好。
另外,毕竟我们并不需要训练这个网络,221-223行用于保存训练结果的东西是需要删掉的。
计算特征
使用一个网络远比训练一个网络要省心得多,只要保证所有的数据都被送进网络计算一次就万事大吉了。所以,
- 174行
n_epoch
的可以放心地改成1 - 178行
batch_size
的可以放心地改成1 - 179行
shuffle
可以放心的改成False关掉 - 196-218行留之无用,删掉
接下来是全文的重点,第194行。
_,loss_value,lr_value,acc = sess.run([train_op,cost,lr,networks_accu], feed_dict=feed_dict)
这里先执行了一次训练迭代train_op
,然后计算损失函数的值cost
、更新学习率lr
,并评估了一下目前网络的靠谱程度networks_accu
。然而这四件事情与我们的目标——提取训练数据和验证数据的特征——一点关系都没有。
为了实现最初的目的,我们需要把这段控制网络训练的代码换成计算特征值的代码
feature_value = sess.run(feature, feed_dict=feed_dict)
保存结果
我们需要同时保存特征和标记。到目前为止,特征是feature_value
,标记则在原有的代码里,可以通过feed_dict[y]
拿到。这两个东西都是numpy
格式的,直接使用np.save
就能保存成文件。原始的代码里有一个现成的计数器step
,可以借用来创建不同的文件名。
另外,feature_value
的形状是[1, 26880]
, feed_dict[y]
的形状是[1]
,这显然有些别扭。为了让后面的事情更顺手,在保存的时候可以直接把这个碍事的维度裁掉。
np.save('train_{}.npy'.format(step), (feature_value[0, :], feed_dict[y][0]))
至此,训练集中所有样本的特征值和标记都可以被保存成train_xxx.npy
这样的文件了。
对源文件的251行前后做类似的事情,可以获得验证集中所有数据的特征值和标记,把它们保存成val_xxx.npy
之类的文件,第一步就算大功告成。
训练SVM分类器
第二步,使用刚刚保存的
train_xxx.npy
训练一个SVM分类器。
第三步:在验证集上进行测试
整理数据
现在,看看文件的编号就可以知道一共有多少个训练数据了。不过,为了更省心的训练一个SVM,我们最好先把这些零碎的数据拼起来。
现在,姑且假设一共有2333个训练数据、517个验证数据吧……至少这两个数字足够惹眼,抄代码的时候不会漏掉。
import numpy as np
x_train = np.ndarray([2333, 26880], dtype=np.float32)
y_train = np.ndarray([2333,], dtype=np.float32)
for i in range(2333):
x, y = np.load('train_{}.npy'.format(i))
x_train[i, ...] = x
y_train[i] = y
x_val = np.ndarray([517, 26880], dtype=np.float32)
y_val = np.ndarray([517,], dtype=np.float32)
for i in range(517):
x, y = np.load('val_{}.npy'.format(i))
x_val[i, ...] = x
y_val[i] = y
训练一个SVM并在验证集上进行测试
大概没有比sklearn更简单的方法了吧。在开始使用之前可能需要先安装一下:
pip install -U scikit-learn
随后是一段异常简单(5行)的代码
import sklearn.svm
# SVC 中的 C 是分类器的意思
# ovr, one-over-rest, 要求分类器最后给出输入分别属于各个类别的可能性
svm = sklearn.svm.SVC(decision_function_shape='ovr')
# 训练可能并不很快
svm.fit(x_train, y_train)
# decision_function 是SVM训练好之后得到的决策函数
# argmax 用来找到每个验证数据最可能属于的类别的序号
h_val = svm.decision_function(x_val).argmax(1)
# 准确率 = 被正确分类的样本数目 / 验证集大小
acc = (h_val == y_val).sum() / len(y_val)
print('Accuracy = {}.format(acc))
全文完。