官方代码中的官方代码“具有隐性语言q学习的自然语言生成的离线RL”
项目网站| arxiv

从此处的Google Drive文件夹下载data.zip and outputs.zip 。将下载和未拉紧的文件夹, data/和outputs/放在存储库的根部。 data/包含我们所有任务的预处理数据,并且outputs/包含我们的reddit注释的检查点,请奖励奖励。
此存储库是为Python 3.9.7设计的
pip install -r requirements.txt
export PYTHONPATH= " $PWD /src/ "要运行视觉对话实验,您需要按照此处的说明来为Local主持人提供视觉对话环境。
使用毒性过滤器奖励进行Reddit评论实验:
export OPENAI_API_KEY=your_API_keyscripts/包含所有实验脚本。在scripts/ ::
python script_name.py选修的:
python script_name.py eval.bsize=5 train.lr=1e-6 wandb.use_wandb=falsepython -m torch.distributed.launch --nproc_per_node [N_GPUs] --use_env script_name.py arg1=a arg2=b默认情况下,所有培训脚本都会记录到WANDB。要关闭此问题,请在培训配置中设置wandb.use_wandb=false 。
在这里,我概述了推荐的工作流程,用于培训离线RL代理。假设我想训练一堆不同的离线RL代理,以产生毒性奖励的红色评论。
我将首先在数据上训练BC模型:
cd scripts/train/toxicity/
python train_bc.py然后将此BC检查点转换为与离线RL模型兼容的一个:
cd ../data/
python convert_bc.py --load ../../outputs/toxicity/conditional_toxicity_official_bc_test1/model.pkl --save ../../outputs/toxicity/conditional_toxicity_official_bc_test1/model_converted.pkl然后编辑离线RL配置为训练以下检查的检查点:
cd ../train/
python train_iql.py model.load.checkpoint_path=outputs/toxicity/model_converted.pkl model.load.strict_load=false train.loss.awac_weight=0.0但是,这只是一个工作流程,您也可以通过设置train.loss.awac_weight=1.0在培训配置中与离线RL代理同时训练BC模型。
data/文件夹中预处理。scripts/包含所有用于运行培训,评估和数据预处理步骤的脚本。脚本被组织成与所使用的数据集相对应的子文件夹。config/ contrains .yaml configs的每个脚本。此存储库使用Hydra管理配置。配置被组织成对应于所用数据集的子文件夹。大多数配置文件的命名与其相应的脚本相同,但是如果您不确定哪个配置对应于脚本,请选中line @hydra.main(config_path="some_path", config_name="some_name")以查看脚本对应的配置文件。src/包含所有核心实现。有关所有模型实现,请参见src/models/ 。有关所有基本数据处理和MDP抽象代码,请参见src/data/ 。有关各种实用程序功能,请参见src/utils/ 。请参阅src/wordle/ , src/visdial和src/toxicity/有关所有词,视觉对话和reddit评论数据集的特定代码。ILQL被称为iql 。 每个脚本都与配置文件关联。配置文件指定脚本及其相应的超参数将加载哪些模型,数据集和评估器。有关一个示例,请参见configs/toxicity/train_iql.yaml 。
每个可能的模型,数据集或评估器对象都有其自己的配置文件,该文件指定该对象的默认值和一个特殊name属性,该属性告诉Config Manager加载什么类。有关一个示例,请参见configs/toxicity/model/per_token_iql.yaml 。
文件src/load_objects.py , src/wordle/load_objects.py , src/visdial/load_objects.py和src/toxicity/load_objects.py定义了每个对象是如何从其相应配置中加载的。每个加载对象函数上方的@register('name')标签链接到配置中的name属性。
您可能会注意到与配置中的某些对象关联的特殊cache_id属性。例如,请参见configs/toxicity/train_iql.yaml中的train_dataset 。此属性告诉Config Manager来缓存它加载与此ID关联的第一个对象,然后使用此cache_id返回此缓存的对象以进行后续对象配置。
对于所有配置,请使用相对于repo root的路径。
我们存储库中的每个任务(Wordle,Visual对话和Reddit评论)都实现了一些基础类。实施后,所有离线RL算法都可以以插件方式应用于任务。请参阅“创建自己的任务”部分,以概述为了创建自己的任务应实现的内容。在下面,我们概述了使它成为可能的关键抽象。
data.language_environment.Language_Environment - 代表一个策略可以与之互动的任务POMDP环境。它具有类似健身房的界面。data.language_environment.Policy - 表示可以与环境交互的策略。 src/models/中的每个离线RL算法都有相应的策略。data.language_environment.Language_Observation - 代表环境返回并作为策略输入的文本观察。data.language_environment.interact_environment - 一种函数,它可以在环境,策略和当前观察过程中运行并运行环境互动循环。如果未提供当前观察结果,它将通过重置环境自动获取初始状态。data.rl_data.DataPoint - 定义标准化的数据格式,该格式被馈送为所有任务上所有离线RL代理的输入。这些数据结构是从给定的Language_Observation自动创建的。data.rl_data.TokenReward - 定义在每个令牌上给出的奖励功能,可用于学习更多细粒度控制。这是在环境奖励之上提供的,它不是在每个令牌上都提供,而是在每次互动之后提供的。在我们的所有实验中,我们将此奖励设置为常数0,因此没有效果。data.tokenizer.Tokenizer - 指定如何将字符串转换为代币序列,然后将其作为输入作为语言模型的输入。data.rl_data.RL_Dataset - 定义一个数据集对象,该对象返回DataPoint对象,用于训练离线RL代理。 RL_Dataset有两个版本:List_RL_DatasetIterable_RL_Dataset
在这里,我们概述并记录Wordle任务的所有组件。
示例脚本中的许多内容都是由配置管理器自动完成的,并且可以通过更改配置来编辑相应的参数。但是,如果您想使用配置绕过并将Wordle任务与自己的代码库一起使用,则可以在下面引用下面的脚本和文档以进行此操作。
一个简单的示例脚本,用于在命令行中播放Wordle。
from wordle . wordle_env import WordleEnvironment
from wordle . wordle_game import Vocabulary
from wordle . policy import UserPolicy
from data . language_environment import interact_environment
from utils . misc import convert_path
game_vocab = Vocabulary . from_file ( convert_path ( 'data/wordle/word_lists/wordle_official.txt' ))
env = WordleEnvironment ( game_vocab )
policy = UserPolicy ()
interact_environment ( env , policy )src/wordle/wordle_game.pysrc/wordle/wordle_env.pysrc/wordle/policy.pysrc/wordle/wordle_dataset.py为了使游戏成为有效的MDP,环境代表了基本状态作为一组已知的字母约束,并使用它们来过滤词汇,以符合每回合满足所有这些约束的单词。然后从此过滤的单词列表中选择一个随机单词,并用于确定环境返回的颜色过渡。这些新的颜色转换然后更新已知字母约束的集合。
Wordle环境带有单词列表。在data/wordle/word_lists/中给出了一些单词列表,但可以随意制作自己的单词列表。
包含的单词列表是:
如上所述,单词列表通过Vocabulary对象加载到环境中。
from wordle . wordle_game import Vocabulary
from utils . misc import convert_path
vocab = Vocabulary . from_file ( convert_path ( 'data/wordle/word_lists/wordle_official.txt' ))词汇不仅存储单词列表,还可以跟踪符合给定状态下所有已知字母约束的过滤单词列表。此列表用于计算环境中的过渡,并由一些手工制作的政策使用。
实时生产这些过滤列表可以减慢环境交互过程。这通常不应该是一个问题,但是如果您想快速从政策中综合大量数据,那么这可能会成为瓶颈。为了克服这一点,所有Vocabulary对象都存储一个cache参数,该参数缓存了与给定状态关联的过滤单词列表。 vocab.cache.load(f_path)和vocab.cache.dump()启用加载和保存此缓存。例如, data/wordle/vocab_cache_wordle_official.pkl是Wordle_official.txt Word列表的大量缓存。
除了存储缓存外, Vocabulary对象还在src/wordle/wordle_game.py中实现以下方法:
__init__ def __init__ ( self , all_vocab : List [ str ],
wordle_state : Optional [ WordleState ],
cache : Optional [ Cache ] = None ,
fill_cache : bool = True ) -> None输入:
all_vocab: List[str] - 单词列表。wordle_state: Optional[WordleState] - 一种生成过滤的单词列表的状态,如果没有提供状态,则不会过滤单词。cache: Optional[Cache]=None - 如上所述,用于过滤的词汇的缓存。fill_cache: bool=True - 是否添加到缓存中。返回: None
from_file def from_file ( cls , vocab_file : str , fill_cache : bool = True ) -> Vocabulary输入:
vocab_file: str - 从中加载单词的文件。该方法仅选择5个字母的单词。fill_cache: bool=True - 是否添加到缓存中。回报: Vocabulary
filtered_vocab_size def filtered_vocab_size ( self ) -> int回报:被过滤的词汇的大小
all_vocab_size def all_vocab_size ( self ) -> int回报:完整未经过滤的词汇的大小
get_random_word_filtered def get_random_word_filtered ( self ) -> str返回:从过滤列表中的随机单词。
get_random_word_all def get_random_word_all ( self ) -> str返回:完整未经过滤列表中的随机单词。
update_vocab def update_vocab ( self , wordle_state : WordleState ) -> Vocabulary输入:
wordle_state: WordleState - 一个Wordle状态对象,代表已知字母约束的集合。返回:一个新的Vocabulary对象,根据wordle_state进行过滤。
__str__ def __str__ ( self ) -> str返回:用于打印到终端的过滤单词列表的字符串表示。
WordleEnvironment将词汇对象作为输入,它定义了环境中可能的正确单词。
from wordle . wordle_env import WordleEnvironment
from wordle . wordle_game import Vocabulary
from utils . misc import convert_path
vocab = Vocabulary . from_file ( convert_path ( 'data/wordle/word_lists/wordle_official.txt' ))
env = WordleEnvironment ( vocab )
initial_obs = env . reset ()
next_obs , reward , terminal = env . step ( "snake" )如上所示,环境在src/wordle/wordle_env.py中实现类似健身房的界面:
__init__ def __init__ ( self , vocab : Vocabulary ) -> None输入:
vocab: Vocabulary - 环境的词汇。返回: None
step def step ( self , action : str ) -> Tuple [ WordleObservation , float , bool ]输入:
action: Vocabulary - 一串文本,代表代理在环境中的动作。回报:(观察,奖励,终端)元组。
reset def reset ( self ) -> WordleObservation回报:观察。
is_terminal def is_terminal ( self ) -> bool回报:布尔值表示交互是否已终止。
我们实施一套手工制作的文字策略,这些策略涵盖了一系列游戏级别。所有这些都在src/wordle/policy.py中实现。在这里,我们描述每个人:
UserPolicy from wordle . policy import UserPolicy
policy = UserPolicy ( hint_policy = None , vocab = None )描述:
让您在终端玩。
输入:
hint_policy: Optional[Policy] - 如果您想提示要使用什么单词,请查询另一个策略。vocab: Optional[Union[str, Vocabulary]] - 可猜测的单词的Vocabulary 。如果未指定,则任何5个字母的字符序列都是有效的猜测。 StartWordPolicy from wordle . policy import StartWordPolicy
policy = StartWordPolicy ()描述:
仅适用于第一个单词。从精选的高质量起始单词列表中随机选择一个单词。
输入:
start_words: Optional[List[str]]=None - 覆盖策划的开始单词的列表。 OptimalPolicy from wordle . policy import OptimalPolicy
policy = OptimalPolicy ()描述:
从近视上扮演最高信息列表中符合所有已知字母约束的最高信息。该策略实际上并不是最佳的,因为最佳游戏是NP-HARD。但是它的效果非常高,可以用作性能的近似上限。该策略的计算非常慢,并且在单词列表的大小中进行性能二次。为了保存计算, self.cache.load(f_path)和self.cache.dump()允许您加载和保存缓存。例如, data/wordle/optimal_policy_cache_wordle_official.pkl在wordle_official.txt word列表上代表此策略的缓存。
输入:
start_word_policy: Optional[Policy]=None - 由于第一个单词通常是最昂贵的计算信息增益,因此您可以指定只要求第一个单词的其他策略。progress_bar: bool=False - 由于计算可能需要这么长时间,因此我们为您提供每个呼叫self.act的进度条的选择。 RepeatPolicy from wordle . policy import RepeatPolicy
policy = RepeatPolicy ( start_word_policy = None , first_n = 2 )描述:
随机重复已经使用的first_n单词之一。这是最大程度的次优政策,因为除非在第一个单词上幸运,否则它永远无法获胜。
输入:
start_word_policy: Optional[Policy] - 用于选择第一个单词的策略。如果None ,则随机从环境的词汇中选择一个单词。first_n: Optional[int] - 该策略将历史记录中的第first_n单词随机选择下一个单词。如果None ,则它将从完整的历史记录中随机选择。 RandomMixturePolicy from wordle . policy import RandomMixturePolicy
policy = RandomMixturePolicy ( prob_smart = 0.5 , vocab = None )描述:
从概率(1 - prob_smart)中从单词列表中完全随机选择一个单词,并从单词列表中选择一个随机单词,该单词符合所有已知字母约束,并具有概率prob_smart 。
输入:
prob_smart: float - 选择一个符合所有已知字母约束的单词的概率,而不是完全随机。vocab: Optional[Union[str, Vocabulary]] - 可从中选择的单词列表。如果None ,则策略默认为环境单词列表。 WrongPolicy from wordle . policy import WrongPolicy
from wordle . wordle_game import Vocabulary
vocab = Vocabulary . from_file ( 'data/wordle/word_lists/wordle_official.txt' )
policy = WrongPolicy ( vocab )描述:
从单词列表中随机选择一个单词,该单词无法满足所有已知的字母约束,因此不能是正确的单词。如果单词列表中的所有单词都符合字母约束,则它将从列表中随机选择一个单词。该政策是高度最佳的。
输入:
vocab: Union[str, Vocabulary] - 一个单词列表可供选择。 MixturePolicy from wordle . policy import MixturePolicy , OptimalPolicy , RandomMixturePolicy
policy1 = OptimalPolicy ()
policy2 = RandomMixturePolicy ( prob_smart = 0.5 , vocab = None )
policy = MixturePolicy ( prob1 = 0.5 , policy1 = policy1 , policy2 = policy2 )描述:
混合两个给定的政策。从policy1中选择具有概率prob1选择,然后从概率(1 - prob1)中从policy2中进行选择。
输入:
prob1: float - 从policy1中选择操作的概率1。policy1: Policy - 第一个选择行动的政策。选择具有概率prob1选择。policy1: Policy - 第二种选择行动的政策。以概率(1 - prob1)选择。 MonteCarloPolicy from wordle . policy import MonteCarloPolicy
sample_policy = RandomMixturePolicy ( prob_smart = 0.5 , vocab = None )
policy = MonteCarloPolicy ( n_samples = 5 , sample_policy = sample_policy )描述:
采取政策,在环境中运行蒙特卡洛推出的n_samples ,并选择在推出过程中获得最高平均奖励的下一个动作。
输入:
n_samples: int - 要执行的蒙特卡洛推出数量。sample_policy: Policy - 采样推出的政策。 
上述任何策略均可用于生成数据集,该数据集可用于训练离线RL代理。我们在src/wordle/wordle_dataset.py中实现了两种合成数据集:
wordle.wordle_dataset.WordleListDataset - 从文件加载Wordle游戏。wordle.wordle_dataset.WordleIterableDataset - 从给定的策略中示例Wordle Games。WordleListDataset :从类似文件中加载Wordle数据集:
from wordle . wordle_dataset import WordleListDataset
from data . rl_data import ConstantTokenReward
data = WordleListDataset . from_file (
file_path = 'data/wordle/expert_wordle_100k.pkl' ,
max_len = None ,
vocab = None ,
token_reward = ConstantTokenReward ( 0.0 ),
)
for i in range ( data . size ()):
item = data . get_item ( i )__init__ def __init__ ( self , items : List [ Tuple [ WordleObservation , Optional [ Dict [ str , Any ]]]], max_len : Optional [ int ], token_reward : TokenReward ) -> None输入:
items: List[Tuple[WordleObservation, Optional[Dict[str, Any]]]] - 以(WordLeobservation,metadata_dict)形式的数据列表。元数据_Dict是任何形式的元数据,您可能想存储在数据标记中的任何一种元数据。max_len: Optional[int] - 数据集中的最大序列长度将截断所有令牌序列到此长度。如果None ,则不会截断序列。token_reward: TokenReward - 用于应用序列的令牌级别的奖励。对于所有实验,我们使用持续的0奖励。返回: None
from_file def from_file ( cls , file_path : str , max_len : Optional [ int ], vocab : Optional [ Vocabulary ], token_reward : TokenReward ) -> WordleListDataset输入:
file_path: str - 数据腌制文件的路径。max_len: Optional[int] - 数据集中的最大序列长度将截断所有令牌序列到此长度。如果None ,则不会截断序列。vocab: Optional[Vocabulary] - 在不同的环境词汇下模拟数据集。如果None ,则默认使用用于创建数据集的相同词汇。token_reward: TokenReward - 用于应用序列的令牌级别的奖励。对于所有实验,我们使用持续的0奖励。返回: WordleListDataset对象。
get_item def get_item ( self , idx : int ) -> DataPoint输入:
idx: int - 数据集中的索引。返回: DataPoint对象。
size def size ( self ) -> int返回:数据集的大小。
scripts/data/wordle/中的以下脚本可用于合成Wordle数据。
| 脚本 | 描述 |
|---|---|
generate_data.py | 从配置中指定的给定策略中采样许多游戏,并将其保存到文件中。 |
generate_data_mp.py | 与generate_data.py相同,除了在多个过程中并行示例游戏。 |
generate_adversarial_data.py | 合成了我们论文第5节中描述的数据集,该数据集旨在证明单步RL方法和多步中的数据集。 |
generate_adversarial_data_mp.py | 除了在多个进程上并行示例游戏以外,与generate_adversarial_data.py相同。 |
generate_data_branch.py | 从给定的“专家”策略中采样游戏,然后从游戏中的每个动作中进行采样,“次优”策略分支机构不再采样许多新游戏。 |
generate_data_branch_mp.py | 除了在多个进程上并行示例游戏以外,与generate_data_branch.py相同。 |
一些提供的合成Wordle数据集在data/wordle/中。
| 文件 | 描述 |
|---|---|
expert_wordle_100k_1.pkl | 从OptimalPolicy取样了100K游戏。 |
expert_wordle_100k_2.pkl | 另一个从OptimalPolicy取样的100K游戏。 |
expert_wordle_adversarial_20k.pkl | 我们论文第5节中描述的数据集旨在证明单步RL方法和多步中的数据集。 |
expert_wordle_branch_100k.pkl | 使用generate_data_branch.py从OptimalPolicy采样了100K游戏,并从WrongPolicy中采样了分支。 |
expert_wordle_branch_150k.pkl | 另外150k游戏使用generate_data_branch.py从OptimalPolicy进行了采样,并从WrongPolicy中采样了分支。 |
expert_wordle_branch_2k_10sub.pkl | 使用generate_data_branch.py从OptimalPolicy采样的2K游戏,每个动作中的10个分支从WrongPolicy采样,因此比expert_wordle_branch_100k.pkl中的次优数据要多得多。 |
expert_wordle_branch_20k_10sub.pkl | 与expert_wordle_branch_2k_10sub.pkl相同,除了20K游戏而不是2K游戏。 |
WordleIterableDataset :从类似策略中生成Wordle数据采样:
from wordle . wordle_dataset import WordleIterableDataset
from wordle . policy import OptimalPolicy
from data . rl_data import ConstantTokenReward
policy = OptimalPolicy ()
vocab = Vocabulary . from_file ( 'data/wordle/word_lists/wordle_official.txt' )
data = WordleIterableDataset (
policy = policy ,
vocab = vocab ,
max_len = None ,
token_reward = ConstantTokenReward ( 0.0 ),
)
while True :
item = data . sample_item ()__init__ def __init__ ( self , policy : Policy , vocab : Vocabulary , max_len : Optional [ int ], token_reward : TokenReward ) -> None输入:
policy: Policy - 从中采样的政策。vocab: Vocabulary - 环境的词汇。max_len: Optional[int] - 数据集中的最大序列长度将截断所有令牌序列到此长度。如果None ,则不会截断序列。token_reward: TokenReward - 用于应用序列的令牌级别的奖励。对于所有实验,我们使用持续的0奖励。返回: None
sample_item def sample_item ( self ) -> DataPoint返回: DataPoint对象。
我们有大量的数据集,其中包含超过200k的Wordle游戏推文:

我们可以在这些颜色过渡方块上翻新单词,以创建一个真正的Wordle游戏数据集。
RAW Tweet数据在data/wordle/tweets.csv中给出,但是为了可用,需要将实际单词翻新到Tweets中的颜色正方形上。执行此改装过程需要执行一个预处理脚本,该脚本缓存了词汇列表下可能发生的所有可能发生的颜色转换: guess_vocab (一组可猜测的单词)和correct_vocab (环境中一组可能的正确单词)。结果是一个数据结构wordle.wordle_dataset.WordleHumanDataset此脚本是scripts/data/wordle/build_human_datastructure.py 。将脚本称为:
cd scripts/data/wordle/
python build_human_datastructure.py --guess_vocab=../../../data/wordle/word_lists/wordle_official.txt --correct_vocab=../../../data/wordle/word_lists/wordle_official.txt --tweets_file=../../../data/wordle/tweets.csv --output_file=../../../data/wordle/random_human_tweet_data.json脚本的args:
--guess_vocab指定了一组猜猜单词。--correct_vocab指定环境中可能的正确单词的集合。--tweets_file指定推文的原始CSV文件--output_file指定在哪里转储输出。我们已经在一些单词列表上运行预处理,结果保存在data/wordle/中。
| 单词列表 | 预处理的推文数据文件 |
|---|---|
wordle_official.txt | random_human_tweet_data.json |
wordle_official_800.txt | random_human_tweet_data_800.json |
wordle_official_400.txt | random_human_tweet_data_400.json |
wordle_official_200.txt | random_human_tweet_data_200.json |
tweet_words.txt | human_tweet_data_true_word.json |
给定这些文件之一,您可以加载Wordle Tweet数据集这样:
from wordle . wordle_dataset import WordleHumanDataset
data = WordleHumanDataset . from_file ( 'data/wordle/random_human_tweet_data_200.json' )
print ( data . sample_item ())我们在实验中使用了'data/wordle/random_human_tweet_data_200.json' 。
WordleHumanDataset : __init__ def __init__ ( self , games : List [ Tuple [ str , List [ str ]]], transitions : Dict [ str , Dict [ str , List [ str ]]], use_true_word : bool , max_len : Optional [ int ], token_reward : TokenReward , game_indexes : Optional [ List [ int ]], top_p : Optional [ float ]) -> None输入:
games: List[Tuple[str, List[str]]] - 表单的单元列表(correct_wordle_word, wordle_transitions_list) ,其中wordle_transitions_list是一个过渡的列表,指示了tweet中的颜色["<b><b><y><y><b>", "<g><b><b><b><b>", "<g><g><y><b><b>", "<g><g><g><g><g>"] 。transitions: Dict[str, Dict[str, List[str]]] - 将正确的单词映射到另一个dict映射的dict映射可能被该词可能引起的颜色过渡到可能播放的单词列表,这些颜色过渡可能会播放以引起该过渡。该数据结构用于在推文上翻新单词。use_true_word: bool - 如果为True ,请使用Tweet中的地面真相正确单词,否则修改有效的单词列表中的任何正确单词。max_len: Optional[int] - 数据集中的最大序列长度将截断所有令牌序列到此长度。如果None ,则不会截断序列。token_reward: TokenReward - 用于应用序列的令牌级别的奖励。对于所有实验,我们使用持续的0奖励。game_indexes: Optional[List[int]] - 索引列表以创建推文的拆分。如果None ,将使用数据中的所有项目。我们有data/wordle/human_eval_idxs.json和data/wordle/human_train_idxs.json创建的作为随机选择的火车和评估拆分。top_p: Optional[float] - top_p的过滤器执行数据的百分比。如果None ,则不会过滤数据。与%BC模型一起使用。返回: None
from_file def from_file ( cls , file_path : str , use_true_word : bool = False , max_len : Optional [ int ] = None , token_reward : Optional [ TokenReward ] = None , top_p : Optional [ float ] = None ) -> WordleHumanDataset输入:
file_path: str - JSON文件的路径以加载数据。use_true_word: bool - 如果为True ,请使用Tweet中的地面真相正确单词,否则修改有效的单词列表中的任何正确单词。max_len: Optional[int] - 数据集中的最大序列长度将截断所有令牌序列到此长度。如果None ,则不会截断序列。token_reward: TokenReward - 用于应用序列的令牌级别的奖励。对于所有实验,我们使用持续的0奖励。game_indexes: Optional[List[int]] - 索引列表以创建推文的拆分。如果None ,将使用数据中的所有项目。我们有data/wordle/human_eval_idxs.json和data/wordle/human_train_idxs.json创建的作为随机选择的火车和评估拆分。top_p: Optional[float] - top_p的过滤器执行数据的百分比。如果None ,则不会过滤数据。与%BC模型一起使用。返回: WordleHumanDataset对象。
sample_item def sample_item ( self ) -> DataPoint返回: DataPoint对象。
培训脚本在scripts/train/wordle/中。
| 脚本 | 描述 |
|---|---|
train_bc.py | 训练卑诗省代理商。 |
train_iql.py | 培训ILQL代理。 |
评估脚本在scripts/eval/wordle/中。
| 脚本 | 描述 |
|---|---|
eval_policy.py | 在Wordle环境中评估BC或ILQL代理。 |
eval_q_rank.py | 一个评估脚本,用于比较在我们论文第5节中所述的合成数据集上训练的代理的Q值的相对等级,该脚本旨在证明单步RL和多步rl之间的差异。 |
distill_policy_eval.py | 打印出具有错误栏的eval_policy.py的结果。 |
在这里,我们概述了如何在代码库中加载视觉对话数据以及如何执行环境。有关如何设置视觉对话环境的远程组件,请参见上面的设置部分。数据和环境对象是由配置管理器自动加载的,但是如果要绕过配置系统并将环境与您自己的代码库一起使用,则应该如何加载,执行和配置这些对象。下面描述的相同设置也可以在配置中修改。
如何加载视觉对话环境的一个示例:
from visdial . visdial_env import VDEnvironment
from visdial . visdial_base import VisDialogueData
from visdial . visdial_dataset import VisDialListDataset
from data . rl_data import ConstantTokenReward
from utils . misc import convert_path
data = VisDialogueData (
data_path = convert_path ( 'data/vis_dialogue/raw/visdial_0.5/visdial_0.5_train.json' ),
img_feat_path = convert_path ( 'data/vis_dialogue/processed/visdial_0.5/data_img.h5' ),
split = 'train' ,
reward_cache = convert_path ( 'data/vis_dialogue/processed/visdial_0.5/train_rank_reward_cache1.json' ),
yn_reward_kind = 'none'
)
list_data = VisDialListDataset (
data = data ,
max_len = None ,
token_reward = ConstantTokenReward ( 0.0 )
)
env = VDEnvironment (
dataset = list_data ,
url = 'http://localhost:5000/step_rank' ,
yn_reward = - 2.0 ,
yn_reward_kind = 'none'
)
print ( env . reset ())上面的脚本对应于我们为“标准”奖励实验配置数据集和环境的方式,但是如果您想以不同的方式配置数据集,则可以修改许多参数。除了更改数据集拆分外,这些论点还可以更改任务或奖励。下面我们描述了VisDialogueData , VisDialListDataset和VDEnvironment take的所有不同可配置参数。
我们记录了VisDialogueData , VisDialListDataset和VDEnvironment的参数和方法,因此您知道如何自己配置环境。
VisDialogueData :用src/visdial/visdial_base.py实现的VisDialogueData存储任务的对话和奖励集。
__init__ def __init__ ( self , data_path : str , img_feat_path : str , split : str , reward_cache : Optional [ str ] = None , norm_img_feats : bool = True , reward_shift : float = 0.0 , reward_scale : float = 1.0 , addition_scenes : Optional [ List [ Scene ]] = None , mode : str = 'env_stops' , cutoff_rule : Optional [ CutoffRule ] = None , yn_reward : float = - 2.0 , yn_reward_kind : str = 'none' ) -> None输入:
data_path: str - 对话数据的路径。应该是:data/vis_dialogue/raw/visdial_0.5/visdial_0.5_train.jsondata/vis_dialogue/raw/visdial_0.5/visdial_0.5_val.jsondata/vis_dialogue/raw/visdial_0.5/visdial_0.5_test.jsonimg_feat_path: str - 用于计算每个对话的奖励的图像功能的路径。应始终是data/vis_dialogue/processed/visdial_0.5/data_img.h5 。split: str - train , val或test之一。指示要使用的图像功能的哪些数据集拆分。应与data_path拆分一致。reward_cache: Optional[str]=None - 存储每个对话的奖励的位置。如果None ,它将将所有奖励设置为None 。我们为两个奖励功能提供缓存:data/vis_dialogue/processed/visdial_0.5/[split]_rank_reward_cache1.json ,其中[split]由train , val或test之一代替。data/vis_dialogue/processed/visdial_0.5/[split]_reward_cache2.json ,其中[split]被train , val或test中的一个替换。norm_img_feats: bool=True - 是否将图像功能归一化。reward_shift: float=0.0 - 将奖励转移到此数量中。reward_scale: float=1.0 - 将奖励缩小为此数量。addition_scenes: Optional[List[Scene]]=None - 将其他数据注入数据集。mode: str='env_stops' - ['agent_stops', 'env_stops', '10_stop']之一。控制任务的某些属性。我们使用env_stopsmode='env_stops' ,请根据cutoff_rule提早停止环境交互。mode='agent_stops' ,则代理在操作过程中通过生成特殊<stop>令牌来停止交互;每次可能的操作之后,通过放置<stop>来增加数据。mode='10_stop' ,则播放在10轮交互后总是停止,这是Visual对话数据集中的标准配置。cutoff_rule: Optional[CutoffRule]=None - 仅当mode='env_stops'时应用。实现一个函数,该功能确定环境何时应尽早停止相互作用。我们在所有实验中使用visdial.visdial_base.PercentileCutoffRule(1.0, 0.5)的默认值。yn_reward: float=-2.0 - 询问是/否问题应添加的奖励罚款。yn_reward_kind: str='none' - 指定字符串匹配的启发式,用于确定是否问是/否问题。应该是['none', 'soft', 'hard', 'conservative']之一。'none' :不要惩罚是/否问题。这与我们论文中的standard奖励相对应。'soft' :如果响应包含"yes"或"no"作为子字符串,则惩罚问题。'hard' :如果响应与字符串"yes"或"no"完全匹配,则惩罚问题。这对应于我们论文中的"y/n"奖励。'conservative' :如果响应满足几种匹配的启发式方法之一,则惩罚一个问题。这与我们论文中的"conservative y/n"奖励相对应。返回: None
__len__ def __len__ ( self ) -> int返回:数据集的大小。
__getitem__ def __getitem__ ( self , i : int ) -> Scene输入:
i: int - 数据集索引。返回:数据集中的项目。
VisDialListDataset :用src/visdial/visdial_dataset.py实施的VisDialListDataset围绕VisDialogueData进行包裹,并将其转换为DataPoint格式,可用于训练离线RL代理。
__init__ def __init__ ( self , data : VisDialogueData , max_len : Optional [ int ], token_reward : TokenReward , top_p : Optional [ float ] = None , bottom_p : Optional [ float ] = None ) -> None输入:
data: VisDialogueData - 一个存储所有原始数据的视觉对话数据对象。max_len: Optional[int] - 数据集中的最大序列长度将截断所有令牌序列到此长度。如果None ,则不会截断序列。token_reward: TokenReward - 用于应用序列的令牌级别的奖励。对于所有实验,我们使用持续的0奖励。top_p: Optional[float] - top_p的过滤器执行数据的百分比。如果None ,则不会过滤数据。与%BC模型一起使用。bottom_p: Optional[float] - bottom_p的过滤器执行数据的百分比。如果None ,则不会过滤数据。返回: None
size def size ( self ) -> int返回:数据集的大小。
get_item def get_item ( self , idx : int ) -> DataPoint输入:
i: int - 数据集索引。返回:数据集中的DataPoint 。
VDEnvironment :在src/visdial/visdial_env.py中实施的VDEnvironment定义了视觉对话环境,我们的离线RL代理在评估时与之相互作用。环境涉及连接到Localhost服务器,设置部分描述了如何旋转。
__init__ def __init__ ( self , dataset : RL_Dataset , url : str , reward_shift : float = 0.0 , reward_scale : float = 1.0 , actor_stop : bool = False , yn_reward : float = - 2.0 , yn_reward_kind : str = 'none' ) -> None输入:
dataset: RL_Dataset - 采用RL_Dataset ;如上所述,特别是VisDialListDataset 。该数据集用于选择初始状态。url: str - 踏上环境的URL。请按照“设置”部分中的说明进行指令,以了解如何初始化与此URL相对应的Local -Host Web服务器。reward_shift: float=0.0 - 将奖励转移到此数量中。reward_scale: float=1.0 - 将奖励缩小为此数量。actor_stop: bool=False - 允许演员通过生成特殊<stop>令牌提早停止交互。yn_reward: float=-2.0 - 询问是/否问题应添加的奖励罚款。yn_reward_kind: str='none' - 指定字符串匹配的启发式,用于确定是否问是/否问题。应该是['none', 'soft', 'hard', 'conservative']之一。'none' :不要惩罚是/否问题。这与我们论文中的standard奖励相对应。'soft' :如果响应包含"yes"或"no"作为子字符串,则惩罚问题。'hard' :如果响应与字符串"yes"或"no"完全匹配,则惩罚问题。这对应于我们论文中的"y/n"奖励。'conservative' :如果响应满足几种匹配的启发式方法之一,则惩罚一个问题。这与我们论文中的"conservative y/n"奖励相对应。返回: None
step def step ( self , action : str ) -> Tuple [ WordleObservation , float , bool ]输入:
action: Vocabulary - 环境的词汇回报:(观察,奖励,终端)元组。
reset def reset ( self ) -> WordleObservation回报:观察
is_terminal def is_terminal ( self ) -> bool回报:布尔值表示交互是否已终止。
培训脚本在scripts/train/vis_dial/中。
| 脚本 | 描述 |
|---|---|
train_bc.py | 训练卑诗省代理商。 |
train_chai.py | 训练柴特工。 |
train_cql.py | 培训CQL代理。 |
train_dt.py | 训练决策变压器代理。 |
train_iql.py | 培训ILQL代理。 |
train_psi.py | 训练 |
train_utterance.py | 培训讲话级的ILQL代理。 |
评估脚本在scripts/eval/vis_dial/中。
| 脚本 | 描述 |
|---|---|
eval_policy.py | 在视觉对话环境中评估代理。 |
top_advantage.py | 找到模型下具有最大和最小优势的问题。 |
distill_policy_eval.py | 打印出具有错误栏的eval_policy.py的结果。 |
在这里,我们概述了如何在代码库中加载Reddit注释数据以及如何执行环境。有关如何设置毒性过滤器奖励,请参见上面的设置部分。 The data and environment objects are loaded automatically by the config manager, but if you want to by-pass the config system and use the task with your own codebase, here's how you should load, execute, and configure these objects. The same settings described below can all be modified in the configs as well.
An example of how to load the Reddit comment environment:
from toxicity . toxicity_env import ToxicityEnvironment
from toxicity . reddit_comments_base import RedditData
from toxicity . reward_fs import toxicity_reward
from utils . misc import convert_path
idxs = json . load ( open ( convert_path ( 'data/reddit_comments/train_idxs.json' ), 'r' ))
data = RedditData (
path = convert_path ( 'data/reddit_comments/' ),
indexes = idxs ,
reward_f = toxicity_reward
)
env = ToxicityEnvironment (
data = data ,
reward_f = toxicity_reward
)
print ( env . reset ())
The above script corresponds to how we configured the environment for our toxicity reward experiments, but if you want to configure the environment differently, there are a few arguments you can modify. These arguments can also change the task or reward. Below we describe all the different configurable parameters that our reward functions, RedditData , ToxicityListDataset , and ToxicityEnvironment take.
We document the parameters and methods for our different Reddit comment reward functions, RedditData , ToxicityListDataset , and ToxicityEnvironment , so that you know how to configure the environment yourself.
Here we outline the 4 main reward functions we use for our Reddit comment task. Each of these rewards is implemented in src/toxicity/reward_fs.py .
toxicity_reward from toxicity . reward_fs import toxicity_reward
reward_f = toxicity_reward ()描述:
The "toxicity" reward from our paper, which queries the GPT-3 toxicity filter. It assigns a value of "0" to non-toxic comments, a value of "1" to moderately toxic comments, and a value of "2" to very toxic comments.
toxicity_noised_reward from toxicity . reward_fs import toxicity_noised_reward
reward_f = toxicity_noised_reward ()描述:
The "noised toxicity" reward from our paper, which is the same as toxicity_noised_reward but induces additional noise. Specifically, it re-assigns comments labeled as "1" (moderately toxic) to either "0" (non-toxic) or "2" (extremely toxic) with equal probability.
score_human_reward from toxicity . reward_fs import score_human_reward
from utils . misc import convert_path
reward_f = score_human_reward (
reddit_path = convert_path ( 'data/reddit_comments/' ),
indexes = None
)描述:
The "upvotes real" reward from our paper, which gives a reward of +1 for positive upvote comments and -1 for negative upvote comments. This uses the ground truth upvotes in the data, so it only applies to comments in the dataset and cannot be used for evaluation. If you input a string not present in the data, it will error. The arguments to this function specify what data to load.
输入:
reddit_path: str – a path to the data.indexes: List[int] – a split of indexes in the data to use. If None , it considers all the data. model_reward from toxicity . reward_fs import score_human_reward
from toxicity . reddit_comments_base import RedditData
from toxicity . toxicity_dataset import ToxicityListDataset
from toxicity . reward_model import RobertaBinaryRewardModel
from utils . rl_data import ConstantTokenReward
from utils . misc import convert_path
data = RedditData (
path = convert_path ( 'data/reddit_comments/' ),
indexes = None ,
reward_f = None
)
listdata = ToxicityListDataset (
data = data ,
max_len = 512 ,
token_reward = ConstantTokenReward ( 0.0 )
)
model = RobertaBinaryRewardModel (
data = listdata ,
device = 'cuda' ,
roberta_kind = 'roberta-base' ,
freeze_roberta = False ,
reward_cuttoff = 0.0
)
model . load_state_dict ( torch . load ( convert_path ( 'outputs/toxicity/upvote_reward/model.pkl' ), map_location = 'cpu' ))
reward_f = score_human_reward ( model = model )描述:
The "upvotes model" reward from our paper, which gives a reward of +1 if the given model predicts that the comment will get a positive number of upvotes and a reward of -1 otherwise. The model checkpoint we used for our experiments is at: outputs/toxicity/upvote_reward/model.pkl
输入:
model: RewardModel : the reward model implemented in src/toxicity/reward_model.py . The model should be first trained and loaded from a pytorch checkpoint.RedditData : RedditData , implemented in src/toxicity/reddit_comments_base.py , stores the raw Reddit comments data.
__init__ def __init__ ( self , path : str , indexes : Optional [ List [ int ]], reward_f : Optional [ Callable [[ str ], float ]], reward_cache : Optional [ Cache ] = None , reward_shift : float = 0.0 , reward_scale : float = 1.0 ) -> None输入:
path: str – the path to the Reddit data.indexes: Optional[List[int]] – a list of indexes to create a split of the data. Randomly selected, training, validation, and test splits are in the json files:data/reddit_comments/train_idxs.jsondata/reddit_comments/eval_idxs.jsondata/reddit_comments/test_idxs.jsonreward_f: Optional[Callable[[str], float]] – the reward function to use.reward_cache: Optional[Cache]=None – a cache of reward values, so you don't have to recompute them everytime.reward_shift: float=0.0 – shift the reward by this amount.reward_scale: float=1.0 – scale the reward by this amount.返回: None
__len__ def __len__ ( self ) -> intReturns: the size of the dataset.
__getitem__ def __getitem__ ( self , idx : int ) -> Scene输入:
idx: int – the dataset index.Returns: an item from the dataset.
ToxicityListDataset : ToxicityListDataset , implemented in src/toxicity/toxicity_dataset.py , wraps around RedditData and converts it into a DataPoint format that can be used to train offline RL agents.
__init__ def __init__ ( self , data : RedditData , max_len : Optional [ int ], token_reward : TokenReward , cuttoff : Optional [ float ] = None , resample_timeout : float = 0.0 , include_parent : bool = True ) -> None输入:
data: RedditData – a Reddit comment data object that stores all the raw data.max_len: Optional[int] – the maximum sequence length in the dataset, will truncate all token sequences to this length. If None , then sequences will not be truncated.token_reward: TokenReward – the token-level reward to apply to the sequences. We use a constant reward of 0 per-token for all experiments.cuttoff: Optional[float]=None – filter out all comments from the dataset with reward less than cuttoff . If None , no data will be filtered. Used with %BC models.resample_timeout: float=0.0 – when cuttoff is not equal to None , comments are stochastically sampled iid from the dataset, like an iterable, even though the dataset has a list-type interface. It uniformly re-samples from the dataset until it finds a comment with a reward that satisfies the cuttoff. In the case of the "toxicity" reward, this re-sampling can cause rate-limit errors on the GPT-3 API, so we allow you to add a resample_timeout to fix this issue: a timeout of roughly 0.05 should fix rate-limit issues.include_parent: bool=True – whether to condition on the parent comment in the thread. If False , models will be trained to generate comments unconditionally.返回: None
size def size ( self ) -> intReturns: the size of the dataset.
get_item def get_item ( self , idx : int ) -> DataPoint输入:
i: int – the dataset index. Returns: a DataPoint from the dataset.
ToxicityEnvironment : ToxicityEnvironment , implemented in src/toxicity/toxicity_env.py , defines the Reddit comment generation environment, which our offline RL agents interact with at evaluation time.
__init__ def __init__ ( self , data : RedditData , reward_f : Optional [ Callable [[ str ], float ]], reward_shift : float = 0.0 , reward_scale : float = 1.0 , include_parent : bool = True ) -> None输入:
data: RedditData – the dataset used to select initial state parent comments to condition on.reward_f: Optional[Callable[[str], float]] – the reward function to use.reward_shift: float=0.0 – shift the reward by this amount.reward_scale: float=1.0 – scale the reward by this amount.include_parent: bool=True – specifies whether to condition on the previous comment or post in the Reddit thread.返回: None
step def step ( self , action : str ) -> Tuple [ WordleObservation , float , bool ]输入:
action: Vocabulary – the environment's vocabularyReturns: an (observation, reward, terminal) tuple.
reset def reset ( self ) -> WordleObservationReturns: an observation
is_terminal def is_terminal ( self ) -> boolReturns: a boolean indicating if the interaction has terminated.
Training scripts are in scripts/train/toxicity/ .
| 脚本 | 描述 |
|---|---|
train_bc.py | Train a BC agent. |
train_iql.py | Train an ILQL agent. |
train_upvote_reward.py | Train the upvote reward model. |
Evaluation scripts are in scripts/eval/toxicity/ .
| 脚本 | 描述 |
|---|---|
eval_policy.py | Evaluate an agent in the Reddit comments environment. |
distill_policy_eval.py | Prints out the result of eval_policy.py with error bars. |
All tasks – Wordle, Visual Dialogue, Reddit – have a corresponding environment and dataset implemented in the codebase, as described above. And all offline RL algorithms in the codebase are trained, executed, and evaluated on one of these given environments and datasets.
You can similarly define your own tasks that can easily be run on all these offline RL algorithms. This codebase implements a simple set of RL environment abstractions that make it possible to define your own environments and datasets that can plug-and-play with any of the offline RL algorithms.
All of the core abstractions are defined in src/data/ . Here we outline what needs to be implemented in order to create your own tasks. For examples, see the implementations in src/wordle/ , src/vis_dial/ , and src/toxicity/ .
All tasks must implement subclasses of: Language_Observation and Language_Environment , which are in src/data/language_environment.py .
Language_Observation :This class represents the observations from the environment that will be input to your language model.
A Language_Observation must define the following two functions.
to_sequence def to_sequence ( self ) -> Tuple [ List [ str , Optional [ float ]], bool ]:描述:
A function which converts the observation object into a standard format that can be input to the language model and used for training.
返回:
__str__ def __str__ ( self ) -> str :描述:
This is only used to print the observation to the terminal. It should convert the observation into some kind of string that is interpretable by a user.
Returns: a string.
Language_Environment :This class represents a gym-style environment for online interaction, which is only used for evaluation.
A Language_Environment must define the following three functions.
step def step ( self , action : str ) -> Tuple [ Language_Observation , float , bool ]:描述:
Just like a standard gym environment, given an action in the form of a string, step the environment forward.
Returns: a tuple of (Language_Observation, reward, terminal).
reset def reset ( self ) -> Language_Observation :描述:
This resets the environment to an initial state.
Returns: the corresponding initial Language_Observation
is_terminal def is_terminal ( self ) -> bool :描述:
Outputs whether the environment has reached a terminal state.
Returns: a boolean indicating if the environment has reached a terminal state.
All tasks must implement subclasses of either List_RL_Dataset or Iterable_RL_Dataset or both, which are defined in src/data/rl_data.py .
List_RL_Dataset :This class represents a list dataset (or an indexable dataset of finite length) that can be used to train offline RL agents.
A List_RL_Dataset must define the following two functions.
get_item def get_item ( self , idx : int ) -> DataPoint描述:
This gets an item from the dataset at a given index.
Returns: a DataPoint object from the dataset.
size def size ( self ) -> int描述:
Returns the size of the dataset.
Returns: the dataset's size.
Iterable_RL_Dataset :This class represents an iterable dataset (or a non-indexable dataset that stochastically samples datapoints iid) that can be used to train offline RL agents.
A Iterable_RL_Dataset must define the following function.
sample_item def sample_item ( self ) -> DataPoint描述:
Samples a datapoint from the dataset.
Returns: a DataPoint object from the dataset.