tesnsorflow对多模型进行训练、加载、合并操作

有时我们想训练多个模型,但是又不想一个个的写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命名为datays命名为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)

其实我们整个网络需要训练的就是Wb这两个参数,并且每个参数都有自己的名字model_name/weights_name或者是model_name/bias_name,按照我的代码来看,第一个模型的参数名称分别为model1/weightsmodel1/bias。同理,第二个模型参数名称为model2/weightsmodel2/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,即如果用于微调,我希望第一层的参数保持不变,只改变第二层的Wb

然后就是定义损失函数,优化目标等:

# 仅用于微调,只用于预测的话以下代码可以不用
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增长的,说明向着正确的方向更新,只不过速度有些慢。
在这里插入图片描述
以上就是本次的全部内容,如有错误,欢迎指正~

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值