lm_human_preferences/rewards.py (129 lines of code) (raw):
"""Synthetic scores."""
import os
import tensorflow as tf
from mpi4py import MPI
from lm_human_preferences.language import trained_models, model
from lm_human_preferences.utils import core as utils
from lm_human_preferences.utils.core import Schema
# TODO: combine this with TrainedRewardModel
class RewardModelTrainer:
def __init__(
self,
trained_model, *,
scope='reward_model', use_resource=False,
is_root=True,
):
self.trained_model = trained_model
self.hparams = trained_model.hparams()
self.is_root = is_root
self.use_resource = use_resource
self.encoder = self.trained_model.encoding.get_encoder()
self.scope = scope
self.model = model.Model(hparams=self.hparams, scope=f'{scope}/model', scalar_heads=['reward'])
self.built = False
self.padding_token = self.encoder.padding_token
self.get_rewards = utils.graph_function(
queries=Schema(tf.int32, (None, None)),
responses=Schema(tf.int32, (None, None)),
)(self.get_rewards_op)
def get_encoder(self):
return self.encoder
def _build(self, tokens, do_dropout=False, name=None):
with tf.variable_scope(self.scope, reuse=self.built, auxiliary_name_scope=not self.built, use_resource=self.use_resource):
lm_output = self.model(X=tokens, do_dropout=do_dropout, padding_token=self.padding_token)
reward = lm_output['reward'][:, -1]
with tf.variable_scope('reward_norm'):
if not self.built:
self.reward_gain = tf.get_variable('gain', shape=(), initializer=tf.constant_initializer(1))
self.reward_bias = tf.get_variable('bias', shape=(), initializer=tf.constant_initializer(0))
self._reward_gain_p = tf.placeholder(name='gain_p', dtype=tf.float32, shape=())
self._reward_bias_p = tf.placeholder(name='bias_p', dtype=tf.float32, shape=())
self._set_reward_norm = tf.group(self.reward_gain.assign(self._reward_gain_p),
self.reward_bias.assign(self._reward_bias_p))
if reward is not None:
reward = self.reward_gain * reward + self.reward_bias
if not self.built:
self._set_initializers()
self.built = True
return reward
def ensure_built(self):
if self.built:
return
with tf.name_scope('dummy'):
self._build(tokens=tf.zeros([0,0], dtype=tf.int32))
def get_params(self):
self.ensure_built()
return self.model.get_params() + [self.reward_gain, self.reward_bias]
def reset_reward_scale(self):
sess = tf.get_default_session()
sess.run(self._set_reward_norm, feed_dict={self._reward_gain_p: 1, self._reward_bias_p: 0})
def set_reward_norm(self, *, old_mean, old_std, new_mean, new_std):
"""Given old_mean+-old_std of reward_model, change gain and bias to get N(new_mean,new_std)."""
sess = tf.get_default_session()
old_gain, old_bias = sess.run((self.reward_gain, self.reward_bias))
assert old_gain == 1 and old_bias == 0,\
f'set_reward_norm expects gain = 1 and bias = 0, not {old_gain}, {old_bias}'
# gain * N(old_mean,old_std) + bias = N(gain * old_mean, gain * old_std) + bias
# = N(gain * old_mean + bias, gain * old_std)
# gain * old_std = new_std, gain = new_std / old_std
# gain * old_mean + bias = new_mean, bias = new_mean - gain * old_mean
gain = new_std / old_std
bias = new_mean - gain * old_mean
sess.run(self._set_reward_norm, feed_dict={self._reward_gain_p: gain, self._reward_bias_p: bias})
def _set_initializers(self):
"""Change initializers to load a language model from a tensorflow checkpoint."""
# Skip if
# 1. We're not rank 0. Values will be copied from there.
# 2. We want random initialization. Normal initialization will do the work.
if not self.is_root or self.trained_model.name == 'test':
return
with tf.init_scope():
# Initialize!
params = {v.op.name: v for v in utils.find_trainable_variables(self.scope)}
assert params
self.trained_model.init_op(params, new_scope=self.scope)
def get_rewards_op(self, queries, responses):
tokens = tf.concat([queries, responses], axis=1)
return self._build(tokens)
class TrainedRewardModel():
def __init__(self, train_dir, encoding, *, scope='reward_model', comm=MPI.COMM_WORLD):
self.train_dir = train_dir
self.comm = comm
self.encoding = encoding
encoder = encoding.get_encoder()
if train_dir != 'test':
self.hparams = trained_models.load_hparams(os.path.join(train_dir, 'hparams.json'))
assert self.hparams.n_vocab == encoding.n_vocab, f'{self.hparams.n_vocab} != {encoding.n_vocab}'
else:
self.hparams = trained_models.test_hparams()
self.padding_token = encoder.padding_token
self.encoder = encoder
self.scope = scope
self.model = model.Model(hparams=self.hparams, scope=f'{scope}/model', scalar_heads=['reward'])
def _build(self, X):
results = self.model(X=X, padding_token=self.padding_token)
reward = results['reward'][:, -1]
with tf.variable_scope(f'{self.scope}/reward_norm'):
self.reward_gain = tf.get_variable('gain', shape=(), initializer=tf.constant_initializer(1))
self.reward_bias = tf.get_variable('bias', shape=(), initializer=tf.constant_initializer(0))
reward = self.reward_gain * reward + self.reward_bias
self._set_initializers()
return reward
def ensure_built(self):
if self.model.built:
return
with tf.name_scope('dummy'):
self._build(X=tf.zeros([0,0], dtype=tf.int32))
def _set_initializers(self):
"""Change initializers to load a model from a tensorflow checkpoint."""
if self.comm.Get_rank() > 0 or self.train_dir == 'test':
return
assert self.model.built
checkpoint_scope = 'reward_model'
with tf.init_scope():
# Initialize!
params = {v.op.name: v for v in self.get_params()}
checkpoint = tf.train.latest_checkpoint(os.path.join(self.train_dir, 'checkpoints/'))
available = tf.train.list_variables(checkpoint)
unchanged = {}
for name, shape in available:
if not name.startswith(checkpoint_scope + '/'):
# print('skipping', name)
continue
if name.endswith('adam') or name.endswith('adam_1'):
# print('skipping', name)
continue
print('setting', name)
var = params[self.scope + name[len(checkpoint_scope):]]
assert var.shape == shape, 'Shape mismatch: %s.shape = %s != %s' % (var.op.name, var.shape, shape)
unchanged[name] = var
tf.train.init_from_checkpoint(checkpoint, unchanged)
def get_params(self):
return self.model.get_params() + [self.reward_gain, self.reward_bias]
def score_fn(self, queries, responses):
tokens = tf.concat([queries, responses], axis=1)
return self._build(tokens)