Skip to content

Commit 9fddc9c

Browse files
committed
First commit
0 parents  commit 9fddc9c

14 files changed

+483853
-0
lines changed

README.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Meet Your Favorite Character: Open-domain Chatbot Mimicking Fictional Characters with only a Few Utterances (NAACL 2022)
2+
3+
- [Paper link](https://arxiv.org/pdf/2204.10825.pdf)
4+
5+
## Env Setup
6+
```
7+
conda create -n [env_name] python=3.8
8+
conda activate [env_name]
9+
10+
conda install cudatoolkit=11.0 pytorch=1.7.1
11+
pip install git+https://github.com/huggingface/transformers
12+
pip install datasets pandas pyarrow sklearn
13+
pip install -r requirements.txt
14+
```
15+
16+
## Run inference
17+
- We assume you run your language model and launch server on `http://${1}/generate`.
18+
- Below is an example for `Dynamic Match`.
19+
20+
```
21+
python3 methods/inference_dynamic_match.py \
22+
--model-file $retriever_model_path \
23+
--megatron-endpoint http://${1}/generate \
24+
--character-name $character_name \
25+
--response-selection-strategy top1 \
26+
--max-num-exemplars 8 \
27+
--evaluate-set resources/dailydialog_test_utterances.tsv \
28+
--all-styles-path resources/all_styles.tsv \
29+
--save-results-path results \
30+
--styles $character
31+
```
32+
33+
## Run Character StyleProb Evaluation
34+
```
35+
srun --gres=gpu:1 eval_scripts/eval_character.sh [jsonl_input_file_path] [character_name] [classfier_model_path]
36+
```
37+
38+
## Run Other Styles StyleProb Evaluation
39+
Styles including `positive`, `negative`, `Modern`, `Shakespearean`, `joy`, `anger`.
40+
```
41+
srun --gres=gpu:1 eval_scripts/eval_style.sh [jsonl_input_file_path] [expected_label] [classfier_model_path]
42+
```
43+
44+
### Note: Example of Jsonl file
45+
```
46+
{"context": ["that's awesome! Do you spend a lot of time there?", "i do! it's a lot of fun but it can be tiring sometimes", "I can imagine. what kind of restaurant do they own?"], "response": "The restaurant the restaurant"}
47+
{"context": ["I got some great news today! My husband got a better paying job offer!", "Holy cow that's awesome!!! What are you going to do with all that extra moneys??", "Not sure yet, but itll help us life more comforatbly! We move to his hometown in November when he gets out of Army!"], "response": "You must be so thrilled. There are so many lonely life out there. He must be thrilled."}
48+
...
49+
```
50+
51+
## Run MaUdE
52+
- Clone [MaUdE Repo](https://github.com/facebookresearch/online_dialog_eval) and setup environment
53+
- Run following script
54+
55+
```
56+
cat maude_inference.sh
57+
58+
>>>
59+
60+
#!/bin/zsh
61+
MODEL_SAVE_DIR=full_runs/
62+
DATA_NAME=convai2
63+
DATA_LOC=$1
64+
FINE_TUNE_MODEL=convai2_data/distilbert_lm
65+
TRAIN_MODE=nce
66+
67+
VERSION=20488119
68+
MODEL_ID=na_all
69+
70+
71+
for DATA_LOC in "$@"
72+
do
73+
python3 codes/inference.py \
74+
--id $MODEL_ID \
75+
--model_save_dir $MODEL_SAVE_DIR \
76+
--model_version $VERSION \
77+
--train_mode nce \
78+
--corrupt_pre $DATA_LOC \
79+
--test_suffix true_response \
80+
--test_column response
81+
done
82+
```
83+
```
84+
srun --gres=gpu:1 maude_inference.sh [jsonl_path]
85+
```
86+
87+
## Citation
88+
89+
If you find our paper or this project helps your research, please kindly consider citing our paper in your publications.
90+
91+
```
92+
@article{han2022meet,
93+
title={Meet Your Favorite Character: Open-domain Chatbot Mimicking Fictional Characters with only a Few Utterances},
94+
author={Han, Seungju and Kim, Beomsu and Yoo, Jin Yong and Seo, Seokjun and Kim, Sangbum and Erdenee, Enkhbayar and Chang, Buru},
95+
journal={arXiv preprint arXiv:2204.10825},
96+
year={2022}
97+
}
98+
```

eval_character.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import argparse
2+
import json
3+
4+
import torch
5+
from scipy.special import softmax
6+
from transformers import AutoModelForSequenceClassification
7+
from transformers import AutoTokenizer
8+
9+
CHARACTERS = [
10+
"BMO",
11+
"Rachel",
12+
"Burke",
13+
"Barney",
14+
"Spock",
15+
"Sheldon",
16+
"Dwight",
17+
"Michael",
18+
"BartSimpson",
19+
"MargeSimpson",
20+
]
21+
CHARACTER_TO_IDX = {c: i for i, c in enumerate(CHARACTERS)}
22+
23+
24+
def transform_input(texts, tokenizer):
25+
result = tokenizer(texts, max_length=256, truncation=True, padding="max_length")
26+
result = {k: torch.LongTensor(v) for k, v in result.items()}
27+
# Optional when you run your model in GPU
28+
result = {k: v.to("cuda:0") for k, v in result.items()}
29+
return result
30+
31+
32+
def run_model(texts, model, tokenizer):
33+
transformed = transform_input(texts, tokenizer)
34+
with torch.no_grad():
35+
logits = model(**transformed).logits
36+
logits = logits.cpu().numpy()
37+
probs = softmax(logits, axis=-1)
38+
return probs
39+
40+
41+
def main(args):
42+
try:
43+
character_idx = CHARACTER_TO_IDX[args.character_name]
44+
except KeyError:
45+
raise ValueError(f"Unsupported character name: {args.character_name}")
46+
47+
with open(args.input_path) as f:
48+
sentences = [json.loads(line.strip())["response"] for line in f]
49+
50+
tokenizer = AutoTokenizer.from_pretrained(args.model_dir)
51+
model = AutoModelForSequenceClassification.from_pretrained(args.model_dir, from_tf=False)
52+
# Optional when you run your model in GPU
53+
model = model.to("cuda:0")
54+
55+
sum_probs = 0.
56+
num_instances = 0.
57+
58+
for start_idx in range(0, len(sentences), args.batch_size):
59+
end_idx = min(start_idx + args.batch_size, len(sentences))
60+
batch = sentences[start_idx: end_idx]
61+
62+
model_preds = run_model(batch, model, tokenizer)
63+
sum_probs += model_preds[:, character_idx].sum()
64+
num_instances += len(model_preds)
65+
66+
avg_prob = sum_probs / num_instances
67+
print(f"Avg prob for predicting as character {args.character_name}: {avg_prob:.8f}")
68+
69+
70+
if __name__ == "__main__":
71+
parser = argparse.ArgumentParser()
72+
parser.add_argument("--model-dir", type=str)
73+
parser.add_argument("--input-path", type=str)
74+
parser.add_argument("--character-name", type=str, choices=CHARACTERS)
75+
parser.add_argument("--batch-size", type=int, default=64)
76+
args = parser.parse_args()
77+
78+
main(args)

eval_scripts/eval_character.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/zsh
2+
3+
EVAL_INPUT_PATH=$1 # Path for input jsonl file containing pairs for evaluation
4+
CHARACTER_NAME=$2
5+
CLASSIFIER_MODEL_DIR=$3
6+
7+
python3 eval_character.py \
8+
--model-dir $CLASSIFIER_MODEL_DIR \
9+
--input-path $EVAL_INPUT_PATH \
10+
--character-name $CHARACTER_NAME

eval_scripts/eval_style.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/zsh
2+
3+
EVAL_INPUT_PATH=$1 # Path for input jsonl file containing pairs for evaluation
4+
EXPECTED_STYLE_LABEL=$2
5+
CLASSIFIER_MODEL_DIR=$3
6+
7+
python3 eval_style.py \
8+
--model-dir $CLASSIFIER_MODEL_DIR \
9+
--input-path $EVAL_INPUT_PATH\
10+
--expected-label $EXPECTED_STYLE_LABEL

eval_style.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import argparse
2+
import json
3+
4+
import numpy as np
5+
import torch
6+
from scipy.special import softmax
7+
from transformers import AutoModelForSequenceClassification
8+
from transformers import AutoTokenizer
9+
10+
11+
def transform_input(texts, tokenizer):
12+
result = tokenizer(texts, max_length=256, truncation=True, padding="max_length")
13+
result = {k: torch.LongTensor(v) for k, v in result.items()}
14+
# Optional when you run your model in GPU
15+
result = {k: v.to("cuda:0") for k, v in result.items()}
16+
return result
17+
18+
19+
def run_model(texts, model, tokenizer):
20+
transformed = transform_input(texts, tokenizer)
21+
with torch.no_grad():
22+
logits = model(**transformed).logits
23+
logits = logits.cpu().numpy()
24+
probs = softmax(logits, axis=-1)
25+
preds = np.argmax(probs, axis=-1)
26+
return preds
27+
28+
29+
def main(args):
30+
LABEL_STR_TO_INT = {
31+
"modern": 0,
32+
"shakespearen": 1,
33+
"negative": 0,
34+
"positive": 1,
35+
"anger": 0,
36+
"joy": 1,
37+
}
38+
expected_label = LABEL_STR_TO_INT[args.expected_label]
39+
40+
with open(args.input_path) as f:
41+
sentences = [json.loads(line.strip())["response"] for line in f]
42+
43+
tokenizer = AutoTokenizer.from_pretrained(args.model_dir)
44+
model = AutoModelForSequenceClassification.from_pretrained(args.model_dir, from_tf=False)
45+
# Optional when you run your model in GPU
46+
model = model.to("cuda:0")
47+
48+
num_right = 0
49+
50+
for start_idx in range(0, len(sentences), args.batch_size):
51+
end_idx = min(start_idx + args.batch_size, len(sentences))
52+
batch = sentences[start_idx: end_idx]
53+
54+
model_preds = run_model(batch, model, tokenizer)
55+
num_right += (model_preds == expected_label).sum()
56+
57+
accuracy = num_right * 100. / len(sentences)
58+
print(f"Accuracy: {accuracy:.6f}%")
59+
60+
61+
if __name__ == "__main__":
62+
parser = argparse.ArgumentParser()
63+
parser.add_argument("--model-dir", type=str)
64+
parser.add_argument("--input-path", type=str)
65+
parser.add_argument("--expected-label", type=str, choices=["modern",
66+
"shakespearen",
67+
"negative",
68+
"positive",
69+
"anger",
70+
"joy"])
71+
parser.add_argument("--batch-size", type=int, default=64)
72+
args = parser.parse_args()
73+
74+
main(args)

0 commit comments

Comments
 (0)