{"id":18887150,"url":"https://github.com/thunlp-mt/promptgating4mctg","last_synced_at":"2025-09-04T08:32:52.950Z","repository":{"id":170359534,"uuid":"640542878","full_name":"THUNLP-MT/PromptGating4MCTG","owner":"THUNLP-MT","description":"This is the repo for our work “An Extensible Plug-and-Play Method for Multi-Aspect Controllable Text Generation” (ACL 2023).","archived":false,"fork":false,"pushed_at":"2023-07-23T08:35:15.000Z","size":622,"stargazers_count":9,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-11-08T07:40:53.895Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/THUNLP-MT.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-05-14T12:51:35.000Z","updated_at":"2024-07-25T13:01:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"59631ccb-667d-4456-877d-750375d6d6ae","html_url":"https://github.com/THUNLP-MT/PromptGating4MCTG","commit_stats":null,"previous_names":["thunlp-mt/promptgating4mctg"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/THUNLP-MT%2FPromptGating4MCTG","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/THUNLP-MT%2FPromptGating4MCTG/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/THUNLP-MT%2FPromptGating4MCTG/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/THUNLP-MT%2FPromptGating4MCTG/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/THUNLP-MT","download_url":"https://codeload.github.com/THUNLP-MT/PromptGating4MCTG/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":231945477,"owners_count":18449893,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T07:34:46.742Z","updated_at":"2024-12-31T05:19:00.110Z","avatar_url":"https://github.com/THUNLP-MT.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PromptGating4MCTG\n\nThis is the repo for our work “[An Extensible Plug-and-Play Method for Multi-Aspect Controllable Text Generation](https://arxiv.org/abs/2212.09387v2)” (ACL 2023).\n\nOur work focuses on **Multi-Aspect Controllable Text Generation (MCTG)** that controls the generated text in multiple aspects (e.g., sentiment, topic, and keywords). Our approach, **Prompt Gating**, is based on parameter efficient tuning methods (e.g., prefix-tuning), which control aspects by inserting trainable prompts or prefixes into the model, referred to as plugins. Specifically, Prompt Gating achieves higher fulfillment of constraints while maintaining the efficiency of training and inference. Each plugin for a single aspect is trained individually and is trained only once. Multi-aspect controlling is realized by synthesizing multiple plugins without training. Please refer to our [paper](https://arxiv.org/abs/2212.09387v2) for more details.\n\n## Contents\n\n* [Examples](#examples)\n* [Method](#method)\n* [Requirements](#requirements)\n* [Quick Start](#quick-start)\n* [Data Preparation](#data-preparation)\n* [Training](#training)\n* [Evaluation](#evaluation)\n* [Checkpoints](#checkpoints)\n* [Citation](#citation)\n\n## Examples\n\n**Text Generation**\n\n| Topic | Sentiment | Generated Text          |\n| ------------- | ----------- |----------- |\n| Mexican     | Positive     | While this is happening i’m going here for dinner for the first time. the food here was very, very good and very tasty!! we ordered a couple of different salads and some tacos. i got a vego beef taco with a spicy sauce (it is very good). i also got an onion rings (it does not have any onions, nor are there many onions in this recipe), and it was delicious!|\n| Asian     | Positive     | Once upon a time i was at the restaurant and had dinner at a friend’s place, the food was great. the chef is very knowledgeable (the owner’s daughter is a sushi professional), but i can only say that the restaurant was very clean and the food was very flavor. |\n| Asian | Negative      |While this is happening, i’ve never been here to the japanese section and have never eaten here before. the place is small and the place is crowded. the food is small, and the service isn’t very friendly. i did not expect to spend a lot of money for a sushi place because it’s pretty small. they have a few places where they can take a large order and they give it to those who pay for the food. also the staff seems to ignore you if you come for sushi.|\n\n**Machine Translation**\n\n| Keywords | Tense | Knowledge (French) | Source (German) | Generated Translation |\n| ------------- | ----------- |----------- |  ----------- |----------- |\n| \"This\", \"bedroom\", \"completely\"| Past | Cette chambre et une autre ont été complètement brûlées. | Dieses und ein weiteres Zimmer brannten vollständig aus. | This and another bedroom burned completely out.|\n| \"attempt\" | Future | Demain, il tentera de s’entraîner avec l’équipe. | Morgen wird er versuchen, mit der Mannschaft zu trainieren. | Tomorrow he will attempt to train with the team.|\n\n## Method\n\n**Overview of our method**:\n\n![method](figs/extensible.png)\nEach plugin for a single aspect is trained individually and is trained only once. The combination of multiple aspects of constraints is realized by concatenating multiple plugins without training (i.e., in a zero-shot manner).\n\n**Model structure**:\n\n\u003ca href=\"url\"\u003e\u003cimg src=\"figs/method.png\" align=\"middle\" width=\"400\"  \u003e\u003c/a\u003e\n\nIt shows the case of inference stage of double-aspect controlled text generation. Blue and purple represent lexical and sentimental constraints respectively. Continuous prompts and contextual contexts are fed into the model and  trainable gates are applied to steer the pretrained model as well as alleviate the mutual interference of plugins.\n\n## Requirements\n\nTwo different virtual environments are required for training and evaluation.\n\n**Training**\n\n```bash\npip install -r requirements_training.txt\n```\n\n**Evaluation**\n\n```bash\npip install -r requirements_eval.txt\n```\n\n## Quick Start\n\nTo quickly start with our code, you can download the [checkpoints](#checkpoints) of our model and run the following command to generate text with constraints.\n\n1. Put the downloaded checkpoints into `codes/checkpoints/`.\n\n2. Merge different prompts and gates that you need into one checkpoint. For example, to generate text with constraints of positive sentiment and usa topic, you need to merge `codes/checkpoints/model-pos.pt` and `codes/checkpoints/model-usa.pt` into one checkpoint, and save it in `codes/checkpoints/test`. You can use scripts in `codes/{thumt,thumt_gen}/scripts/substitute_prompt_gating.py` for each attribute (use `codes/thumt_gen/scripts/substitute_prompt_gating_pre_encoder.py` for tense attribute in the generation task and French constraints in the translation task).\n\n3. Prepare your data for inference like Yelp or WMT dataset in [Data Preparation](#data-preparation)\nand change the paths of files and in `codes/infer_{translation,generation}.sh`.\n\n4. Run the following command to inference with constraints.\n\n    ```bash\n    cd codes\n    bash infer_{translation,generation}.sh\n    ```\n\n    The generated text will be saved in `codes/checkpoints/test`.\n\n\u003e Instructions below are for reproducing the results of our paper.\n\n### Use Prompt Gating in your BART(Enc-Dec) model\n\n1. Find `Segment Embedding`, `Prompt Encoder` and `GatingModule` in `codes/thumt/models/PromptGating.py`. Include them into your code.\n\n    ```Python\n    class SegmentEmbedding(nn.Module):\n        def __init__(self, num_embeddings: int, embedding_dim: int):\n            ...\n\n    class PromptEncoder(nn.Module):\n        def __init__(self, embed_dim, hidden_dim, prompt_num=100, seg_num=2, pri_dim=512):\n            ...\n\n    class GatingModule(nn.Module):\n        def __init__(self, prompt_num: int, embed_dim: int, dropout, hidden_dim: int = 512, pri_dim: int = 512, seg_num: int = 2, layer_num: int = 12) -\u003e None:\n            ...\n    ```\n\n2. Add the segment embeddings to different sources.\n\n    ```Python\n    # Process inputs\n    # Suppose your first input sequence is features[\"source\"] and the rest in features[\"hypothesis\"]\n    seg_mask = torch.cat([torch.zeros(features[\"source\"].shape, dtype=torch.long) if self.src_attached[0] else torch.tensor([], dtype=torch.long), ] +\n        [torch.ones(hyp.shape, dtype=torch.long).fill_(idp+1) if self.src_attached[idp+1] else torch.tensor([], dtype=torch.long) for idp, hyp in enumerate(features[\"hypothesis\"])], axis=1)\n\n    # in BartEncoder.forward\n    # Suppose your first input ids is input_ids[\"src\"] and the rest in input_ids[\"hypo\"]\n    def forward(\n        self, input_ids, attention_mask=None, output_attentions=False, output_hidden_states=False, return_dict=False, \n        prompt_enc=None, gating_enc=None, prefix_enc=None, pre_encode_fr=False, fr_mask=None\n    ):\n        inputs_embeds_list = []\n        if input_ids[\"src\"] is not None:\n            inputs_embeds_list.append(self.embed_tokens(input_ids[\"src\"]) * self.embed_scale + self.embed_positions(input_ids[\"src\"]))\n        if input_ids[\"hypo\"] is not None and len(input_ids[\"hypo\"]) \u003e 0:\n            for hyp in input_ids[\"hypo\"]:\n                inputs_embeds_list.append(self.embed_tokens(hyp) * self.embed_scale + self.embed_positions(hyp))\n\n        input_segment_ids = input_ids[\"seg_mask\"]\n        embed_seg = self.embed_segments(input_segment_ids)\n        x = inputs_embeds + embed_seg\n\n        # Proceed with `x`\n        ...\n    ```\n\n3. Feed prompt generated from `Prompt Encoder` into the model.\n\n    ```Python\n    # in BartEncoder.forward\n    # Suppose your first input ids is input_ids[\"src\"] and the rest in input_ids[\"hypo\"]\n    def forward(\n        self, input_ids, attention_mask=None, output_attentions=False, output_hidden_states=False, return_dict=False, \n        prompt_enc=None, gating_enc=None, prefix_enc=None, pre_encode_fr=False, fr_mask=None\n    ):\n        ...\n        \n        # Proceed with `x`\n        if prompt_enc is not None:\n            prompt = prompt_enc([True for _ in [input_ids[\"src\"]]+input_ids[\"hypo\"]])\n            if prompt is not None:\n                prompt = prompt.expand(x.size()[0], -1, -1)\n                x = torch.cat([prompt, x], axis=1)\n    ```\n\n4. Append gates at each layer.\n\n    ```Python\n    # in EncoderLayer.forward\n    def forward(self, x, encoder_padding_mask, output_attentions=False, attach=None, adding=None, gating=None, prefixkv=None):\n        ...\n\n        if gating is not None:\n            gating = gating.unsqueeze(0).expand(x.size(1), -1, -1).transpose(0, 1)\n            gating = torch.cat([gating, torch.ones([x.size(0)-gating.size(0), x.size(1), x.size(2)])], axis=0)\n            x = x * gating\n\n        if adding is not None:\n            adding = adding.unsqueeze(0).expand(x.size(1), -1, -1).transpose(0, 1)\n            adding = torch.cat([adding, torch.zeros([x.size(0)-adding.size(0), x.size(1), x.size(2)])], axis=0)\n\n            x = adding + x\n        \n        ...\n\n    # in BartEncoder.forward\n    # Suppose `gating_enc` is an instance of `GatingModule`\n    def forward(\n        self, input_ids, attention_mask=None, output_attentions=False, output_hidden_states=False, return_dict=False, \n        prompt_enc=None, gating_enc=None, prefix_enc=None, pre_encode_fr=False, fr_mask=None\n    ):\n\n        ...\n        gating = gating_enc(input_ids['attach'])\n            if gating is not None:\n                adding, gating = gating\n\n        ...\n        # Feeding the input to each encoder layer\n        encoder_states = [] if output_hidden_states else None\n        all_attentions = () if output_attentions else None\n        for ide, encoder_layer in enumerate(self.layers):\n            if output_hidden_states:\n                encoder_states.append(x)\n            # add LayerDrop (see https://arxiv.org/abs/1909.11556 for description)\n            dropout_probability = random.uniform(0, 1)\n            if self.training and (dropout_probability \u003c self.layerdrop):  # skip the layer\n                attn = None\n            else:\n                x, attn = encoder_layer(x, attention_mask,\n                                        output_attentions=output_attentions, \n                                        attach=input_ids['attach'] if 'attach' in input_ids and input_ids['attach'] is not None else None,\n                                        adding=adding[:, ide, :] if adding is not None else None, # Changed Here\n                                        gating=gating[:, ide, :] if gating is not None else None, # Changed Here\n                                        )\n\n            if output_attentions:\n                all_attentions = all_attentions + (attn,)\n\n        ...\n    ```\n\n## Data Preparation\n\n### Yelp for Text Generation\n\n1. Prepare [Yelp](http://tts.speech.cs.cmu.edu/style_models/yelp_reviews.txt) dataset following the guidance on [this repo](https://github.com/facebookresearch/MultipleAttributeTextRewriting/tree/main/data#1-fyelp).\n\n2. Detokenize the raw text and discard the samples with a sentiment score of 3 (neutral).\n\n3. Sample 30k/3k samples for training and validation respectively, and for positive/negative sentiment, Asian/USA/Mexican food respectively. (All other attributes should be balanced in each training/validation set.)  \nTo identify the tense of each review sentence, we use [this repo](https://github.com/ajitrajasekharan/simple_tense_detector). We modified some of the code which is presented in `codes/exp_tense/tense_detector_t.py`.  \nTo extract keywords from each review sentence, we use this command.\n\n    ```bash\n    cat /path/to/yelp/sent_valid.txt | python codes/thumt_gen/scripts/sample_mask.py \u003e /path/to/processed/yelp/mask_sent_valid.txt\n    ```\n\n4. Save these samples into `/path/to/processed/yelp`. The training/validation set of each attribute has two files with text and manual constraints in each line.\n\n    ```txt\n    # pos_sent_valid.txt\n    ...\n    super friendly, courteous, and excellent service. serg and the other young woman who works here are great and checked up on us\n    ...\n\n    # pos_label_valid.txt\n    ...\n    This is a positive review.\n    ...\n    ```\n\n5. Perform sentencepiece tokenization on the text files and save the tokenized files into `/path/to/processed/yelp`. The script is in `codes/spm_gen.sh`.\n\n    ```txt\n    # pos_sent_valid.spm.txt\n    ...\n    super Ġfriendly , Ġcour te ous , Ġand Ġexcellent Ġservice . Ġser g Ġand Ġthe Ġother Ġyoung Ġwoman Ġwho Ġworks Ġhere Ġare Ġgreat Ġand Ġchecked Ġup Ġon Ġus\n    ...\n\n    # pos_label_valid.spm.txt\n    ...\n    This Ġis Ġa Ġpositive Ġreview .\n    ...\n    ```\n\nNow, the files in `/path/to/processed/yelp` should be like this:\n\n```txt\n├── pos_label_train.spm.txt\n├── pos_label_valid.spm.txt\n├── pos_sent_train.spm.txt\n├── pos_sent_valid.spm.txt\n└── {neg,usa,mexi,asian,mask(keywords),future,past,present}_{label,sent}_{train,valid}.spm.txt\n```\n\n### WMT14 DE-EN for Machine Translation\n\n1. Prepare WMT14 DE-EN dataset in parallel format in `/path/to/processed/wmt`.\n\n    ```txt\n    /path/to/processed/wmt\n    ├── train.de\n    └── train.en\n    ```\n\n2. Extract keywords constraints from target sentences using the command below:\n\n    ```bash\n    cat /path/to/processed/wmt/train.en | python codes/thumt/scripts/sample_mask.py \u003e /path/to/processed/wmt/train.en.masked.num.s\n    ```\n\n    Label the tense of each source sentence using [this repo](https://github.com/ajitrajasekharan/simple_tense_detector) as well.\n\n    Translate target sentences to French use any MT model you like. We uses `codes/translate_en_fr/summon_dummy.sh`.\n\n    Repeat the same operation with validation and test set (newstest2013 and newstest2014).\n\n3. Perform sentencepiece tokenization on the text files and save the tokenized files into `/path/to/processed/wmt`. The script is in `codes/spm.sh`.\n\nNow, the files in `/path/to/processed/wmt` should be like this:\n\n```txt\n├── train.spm.en\n├── train.spm.fr\n├── train.spm.de\n└── train.spm.{en.masked.num.s,en.tense}\n```\n\n## Training\n\n### Text Generation\n\n1. Download vocabulary file of BART model into `/path/to/pretrain_BART`. If the file is in json format, then convert it with `codes/pretrain_BART/process_vocab.py`. And also prepare `config.json` and `pytorch_model.bin` in `/path/to/pretrain_BART`.\n\n2. Choose one script of `exp_train_generation_*.sh` and run it.\n\n### Machine Translation\n\n\u003e Because of the large size of the vocabulary of mBART model, we provide a cutted model with vocabulary size of 6w to save memory.\n\n1. Download vocabulary file of mBART model and other files in the [google drive](https://drive.google.com/drive/folders/1wqmM6eR1AG66ano-zbCzYykWoaQvYTJO?usp=sharing) into `/path/to/pretrain_mBART`.\n\n2. Choose one script of `exp_train_translation_*.sh` and run it.\n\n## Inference\n\nAfter merging different prompts and gates, you can perform inference the model on different tasks. The first step of instruction below is for inference, and the other steps are for evaluation.\n\n### Text Generation\n\n1. Process the manual constraints for test set in `/path/to/processed/yelp/infer`, each of the file contains 375 lines of constraint sentences. For example, `neg_label.375.spm.txt` contains 375 lines of `This is a negative review.` and is tokenized.\n\n    Tokenized prefixes used in the paper are in `data/infer_gen/pre_tokens.25.spm.txt`. Positive key words and negative key words for inference are ramdomly sampled and tokenized in `data/infer_gen/maskfix.pos.375.spm.txt` and `data/infer_gen/maskfix.neg.375.spm.txt`.\n\n2. Train a classifier on sentiment and food category. The script is in `codes/train_classifier/train_*.py`. Follow the paper to oversample the raw data and train the classifiers.\n\n3. You still needs to install [this repo](https://github.com/ajitrajasekharan/simple_tense_detector) to evaluate the tense attribute.\n\n4. Run `codes/infer_train_generation_yelp.sh` and remember to change the `src_attached` and `prompt_attached` simultaneously according to the combination of prompts and gates you want to evaluate.\n\n### Machine Translation\n\n1. Run `codes/infer_train_translation.sh` and remember to change the `src_attached` and `prompt_attached` simultaneously according to the combination of prompts and gates you want to evaluate.\n\n2. With the translated results, use `codes/exp_tense/calc_tense_acc.py` to evaluate the accuracy of tense attribute.\n\n## Checkpoints\n\nRemember to check how many prompts and gates are in the checkpoint and merge them into one model if necessary.\n\n### Text Generation\n\nThe checkpoints with prompts and gates of different attributes are in [google drive](https://drive.google.com/drive/folders/1Tg7kZX4h01rb44ld6z65yFaWTUMGDZbn?usp=sharing).\n\nYou can transfer the prompts and gates of each model into another model (or merge different prompts and gates into one model) by using example scripts `codes/thumt/scripts/substitute_prompt_gating.py` or `codes/thumt_gen/scripts/substitute_prompt_gating.py`.\n\n### Machine Translation\n\nThe checkpoints with prompts and gates are in [google drive](https://drive.google.com/drive/folders/1O_hyjwIsV6fNYifvbcgFBYp0HUGGRrPs?usp=sharing).\n\n\u003e The prompts and gates of keywords, tense and French are already merged in the same checkpoint.\n\n## Citation\n\nIf you find this repo helpful, please cite the following:\n\n```bibtex\n@inproceedings{huang2023extensible,\n  title={An Extensible Plug-and-Play Method for Multi-Aspect Controllable Text Generation},\n  author={Xuancheng Huang, Zijun Liu, Peng Li, Tao Li, Maosong Sun, Yang Liu},\n  booktitle={Proceedings of the 61st Annual Meeting of the Association for Computational Linguistics},\n  publisher={Association for Computational Linguistics},\n  year={2023}\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthunlp-mt%2Fpromptgating4mctg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthunlp-mt%2Fpromptgating4mctg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthunlp-mt%2Fpromptgating4mctg/lists"}