有时我们想训练多个模型,但是又不想一个个的写py文件,那么如何在一个文件中达到训练多个模型的目的呢?以下是个人的一点思路,希望能给大家一些帮助。
完整代码见GitHub
一、多模型训练
多模型训练的具体思路就是在不同的graph中创建模型,一般是写一个类来实现,网上也有很多例子,我这里就直接复制过来了,当然有稍作修改。为了简单说明,我举个例子:假设我有两份数据(
y
=
2
x
y=2x
y=2x和
y
=
3
x
+
1
y=3x+1
y=3x+1),那么需要训练出两个线性模型
y
=
w
x
+
b
y=wx+b
y=wx+b。第一个模型学习到的参数为
w
1
=
2
,
b
1
=
0
w_{1}=2, b_{1}=0
w1=2,b1=0,第二个模型学习到的参数为
w
2
=
3
,
b
2
=
1
w_{2}=3, b_{2}=1
w2=3,b2=1。
首先,创建一个类ImportGraph
,在初始化中建立graph:
class ImportGraph():
def __init__(self):
self.graph = tf.Graph()
self.sess = tf.Session(graph=self.graph)
然后,定义一个训练模型的函数,由于我的模型比较简单,并且模型结构是一样的,所以我把构造模型写在train()
函数中,如果你们训练的多个模型结构是不一样的,可以单独写一个build_model()
的函数来调用。
def train(self, data, label, model_name, save_dir):
'''
参数说明
:param data: 输入数据
:param label: 数据对应的标签
:param model_name: 此次训练的模型名字,不要重复,因为会影响后续合并模型的操作
:param save_dir: 模型保存的路径
'''
features = 1 # 由于我的数据是点的坐标,因此特征和类别数都为1
classes = 1
epoch = 100 # 迭代次数
lr = 0.01 # 学习率
with self.graph.as_default():
xs = tf.placeholder(dtype=tf.float32, shape=[None, features], name='data')
ys = tf.placeholder(dtype=tf.float32, shape=[None, classes], name='label')
weights_name = 'weights' # 为你模型的每一个参数定义一个名字,方便后面的操作
bias_name = 'bias'
with tf.name_scope(model_name):
W = tf.Variable(tf.random_normal(shape=[features, classes]), name=weights_name)
b = tf.Variable(tf.constant(0.1, shape=[classes]), name=bias_name)
out = tf.matmul(xs, W) + b
loss = tf.losses.absolute_difference(ys, out)
tf.add_to_collection('output', out) # 把网络输出output添加到容器中
tf.add_to_collection('loss', loss) # 把网络损失loss添加到容器中
train_step = tf.train.GradientDescentOptimizer(lr).minimize(loss)
init = tf.global_variables_initializer()
self.sess.run(init)
saver = tf.train.Saver()
print('开始训练模型%s\n' % model_name)
for ep in range(epoch):
self.sess.run(train_step, feed_dict={xs: data, ys: label})
if (ep+1) % 10 == 0:
temp_loss = self.sess.run(loss, feed_dict={xs: data, ys: label})
print('epoch %d: %.4f' % (ep, temp_loss))
saver.save(self.sess, save_dir + model_name)
print('模型%s训练完毕,并保存至%s\n' % (model_name, save_dir))
self.sess.close()
紧接着就可以开始训练我们的模型啦!
首先生成数据:
x = np.array([i for i in range(10)])
y1 = np.array(list(map(lambda i: 2*i, x)))
y2 = np.array(list(map(lambda i: 3*i + 1, x)))
x = x.reshape([10, 1])
y1 = y1.reshape([10, 1])
y2 = y2.reshape([10, 1])
此处需要将数据转为列向量,不然会与train()
中定义的占位符形状不匹配。
然后实例化类并且调用train()
函数
ImportGraph().train(x, y1, 'model1', 'models/linear_1/')
ImportGraph().train(x, y2, 'model2', 'models/linear_2/')
这样就完成两个模型的训练啦!
二、加载多模型
要加载多个模型,同样需要类ImportGraph
来初始化不同的graph,然后在不同的graph中加载各个模型。首先在类中添加新的函数load()
def load(self, meta, ckpt, data, label=None):
'''
:param meta: meta文件的路径
:param ckpt: checkpoint所在文件夹的路径
:param data: 数据
:param label: 标签
'''
with self.graph.as_default():
# 从指定路径加载模型到局部图中
print('加载模型参数...')
saver = tf.train.import_meta_graph(meta)
saver.restore(self.sess, tf.train.latest_checkpoint(ckpt))
self.loss = tf.get_collection('loss')
self.output = tf.get_collection('output')
output = self.sess.run(self.output, feed_dict={"data:0": data})[0]
if label is not None:
loss = self.sess.run(self.loss, feed_dict={'data:0': data, 'label:0': label})[0]
else:
loss = None
self.sess.close()
print('计算结果已返回')
return output, loss
因为我在上一步训练模型的时候把loss和网络输出output添加到容器中,所以这里可以通过tf.get_collection()
来获取这两个节点。并且由于我在定义网络的时候给占位符xs
命名为data
,ys
命名为label
,所以这里在喂数据的时候可以使用名称来指定占位符。
现在,让我们用新数据来测试一下模型吧。
x_test = np.reshape(np.array([11, 12, 13, 14, 15]), [-1, 1])
# 加载多个模型
result1, loss1 = ImportGraph().load('models/model1/model1.meta', 'models/model1/', x_test)
result2, loss2 = ImportGraph().load('models/model2/model2.meta', 'models/model2/', x_test)
print('...')
模型预测结果如下:
三、合并模型并再次训练
模型训练好了,我们想把两个模型连在一起使用(前一个模型的输出作为下一个模型的输入),又或者再进一步微调模型,那么该如何做呢?
首先来回顾一下trtain
函数中的一段代码:
with tf.name_scope(model_name):
W = tf.Variable(tf.random_normal(shape=[features, classes]), name=weights_name)
b = tf.Variable(tf.constant(0.1, shape=[classes]), name=bias_name)
其实我们整个网络需要训练的就是W
和b
这两个参数,并且每个参数都有自己的名字model_name/weights_name
或者是model_name/bias_name
,按照我的代码来看,第一个模型的参数名称分别为model1/weights
和model1/bias
。同理,第二个模型参数名称为model2/weights
和model2/bias
。
首先我们得重新构建一个新的模型(即两个模型连接在一起):
# 网络参数设置
features = 1
classes = 1
epoch = 100
lr = 0.05
# 网络输入
xs = tf.placeholder(dtype=tf.float32, shape=[None, features], name='data')
ys = tf.placeholder(dtype=tf.float32, shape=[None, classes], name='label')
with tf.name_scope('model1'):
W1 = tf.Variable(tf.random_normal(shape=[features, classes]), trainable=False, name='weights')
b1 = tf.Variable(tf.constant(0.1, shape=[classes]), trainable=False, name='bias')
out1 = tf.matmul(xs, W1) + b1 # 第一个网络输出out1
with tf.name_scope('model2'):
W2 = tf.Variable(tf.random_normal(shape=[features, classes]), name='weights')
b2 = tf.Variable(tf.constant(0.1, shape=[classes]), name='bias')
out2 = tf.matmul(out1, W2) + b2 # out2=out1*W2+b2
这里需要注意的是,参数的命名要和之前保存的模型名称一样。然后把第一层(也就是model1)输出接到第二层(model2)输入,得到最后的输出out2。至此,我们已经把两个模型连接的架构搭好了,理论上说,此时out2的输出应该是y=3(2x)+1=6x+1
。然后我设置了第一层参数trainable=False
,即如果用于微调,我希望第一层的参数保持不变,只改变第二层的W
和b
。
然后就是定义损失函数,优化目标等:
# 仅用于微调,只用于预测的话以下代码可以不用
loss = tf.losses.absolute_difference(ys, out2)
train_step = tf.train.GradientDescentOptimizer(lr).minimize(loss)
# 保存中间结果
tf.add_to_collection('output', out2)
tf.add_to_collection('loss', loss)
然后接下来就是最重要一步了,获取需要加载的参数名:
variables = tf.contrib.framework.get_variables_to_restore()
model1_vars = [v1 for v1 in variables if re.search('model1', v1.name) is not None]
model2_vars = [v2 for v2 in variables if re.search('model2', v2.name) is not None]
因为要从不同的模型中加载参数,所以要定义两个saver
:
saver1 = tf.train.Saver(model1_vars)
saver2 = tf.train.Saver(model2_vars)
再然后就可以从模型中载入参数了:
with tf.Session() as sess:
saver1.restore(sess, tf.train.latest_checkpoint('models/model1')) # saver1加载model1的参数
saver2.restore(sess, tf.train.latest_checkpoint('models/model2')) # saver2加载model2的参数
# 可以先打印看看是否加载正确
weight1, bias1, weight2, bias2 = sess.run([W1, b1, W2, b2])
print('initial variables: W1=%.4f b1=%.4f W2=%.4f b2=%.4f' % (weight1, bias1, weight2, bias2))
# 进一步训练模型
for ep in range(epoch):
sess.run(train_step, {xs: x, ys: y3})
if (ep+1) % 10 == 0:
weight1, bias1, weight2, bias2, losses = sess.run([W1, b1, W2, b2, loss], {xs: x, ys: y3})
print('ep %3d: %.4f W1=%.4f b1=%.4f W2=%.4f b2=%.4f' % (ep+1, losses, weight1, bias1, weight2, bias2))
print(sess.run(out2, {xs: x}))
由于这里我想进一步微调模型,让模型拟合直线y=4(2x)+3
,即第二层参数W=4,b=3
,生成数据送入模型:
x = np.array([i for i in range(10)])
y3 = np.array(list(map(lambda i: 8*i + 3, x)))
merge_models(x, y3)
通过以下训练过程可以看到,第一层参数确实没改变,W2也接近于4,虽然b2只有1.8,但是可以看出是从1增长的,说明向着正确的方向更新,只不过速度有些慢。
以上就是本次的全部内容,如有错误,欢迎指正~