├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── code ├── filtering │ ├── average_word_embedding.py │ ├── filter_problem.py │ ├── identity.py │ ├── semantic_clustering.py │ └── sent2vec.py ├── main.py └── utils │ ├── config.py │ ├── filtering_demo.ipynb │ ├── utils.py │ └── visualization.ipynb ├── docs ├── 3d.png ├── class_diagram.uml ├── cluster_examples.png ├── example_responses.png ├── help.png ├── high_entropy.png ├── metrics_table.png ├── other_datasets.png ├── uml.png └── visu.png ├── requirements.txt └── setup.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask instance folder 57 | instance/ 58 | 59 | # Scrapy stuff: 60 | .scrapy 61 | 62 | # Sphinx documentation 63 | docs/_build/ 64 | 65 | # PyBuilder 66 | target/ 67 | 68 | # IPython Notebook 69 | .ipynb_checkpoints 70 | 71 | # pyenv 72 | .python-version 73 | 74 | # celery beat schedule file 75 | celerybeat-schedule 76 | 77 | # dotenv 78 | .env 79 | 80 | # virtualenv 81 | venv/ 82 | ENV/ 83 | 84 | # Spyder project settings 85 | .spyderproject 86 | 87 | # Rope project settings 88 | .ropeproject 89 | 90 | # ========================= 91 | # Operating System Files 92 | # ========================= 93 | 94 | # OSX 95 | # ========================= 96 | 97 | .DS_Store 98 | .AppleDouble 99 | .LSOverride 100 | 101 | # Thumbnails 102 | ._* 103 | 104 | # Files that might appear in the root of a volume 105 | .DocumentRevisions-V100 106 | .fseventsd 107 | .Spotlight-V100 108 | .TemporaryItems 109 | .Trashes 110 | .VolumeIcon.icns 111 | 112 | # Directories potentially created on remote AFP share 113 | .AppleDB 114 | .AppleDesktop 115 | Network Trash Folder 116 | Temporary Items 117 | .apdisk 118 | 119 | # Windows 120 | # ========================= 121 | 122 | # Windows image file caches 123 | Thumbs.db 124 | ehthumbs.db 125 | 126 | # Folder config file 127 | Desktop.ini 128 | 129 | # Recycle Bin used on file shares 130 | $RECYCLE.BIN/ 131 | 132 | # Windows Installer files 133 | *.cab 134 | *.msi 135 | *.msm 136 | *.msp 137 | 138 | # Windows shortcuts 139 | *.lnk 140 | 141 | *.txt 142 | # Large files and folders 143 | private/ 144 | data/ 145 | responses/ 146 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Richard Krisztian Csaky 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NeuralChatbots-DataFiltering · [![twitter](https://img.shields.io/twitter/url/https/shields.io.svg?style=social)](https://ctt.ac/E_jP6) 2 | [![Paper](https://img.shields.io/badge/Presented%20at-ACL%202019-yellow.svg)](https://www.aclweb.org/anthology/P19-1567) [![Poster](https://img.shields.io/badge/The-Poster-yellow.svg)](https://ricsinaruto.github.io/website/docs/acl_poster_h.pdf) [![Code1](https://img.shields.io/badge/code-chatbot%20training-green.svg)](https://github.com/ricsinaruto/Seq2seqChatbots) [![Code2](https://img.shields.io/badge/code-evaluation-green.svg)](https://github.com/ricsinaruto/dialog-eval) [![documentation](https://img.shields.io/badge/documentation-on%20wiki-red.svg)](https://github.com/ricsinaruto/NeuralChatbots-DataFiltering/wiki) [![blog](https://img.shields.io/badge/Blog-post-black.svg)](https://medium.com/@richardcsaky/neural-chatbots-are-dumb-65b6b40e9bd4) 3 | A lightweight repo for filtering dialog data with entropy-based methods. 4 | 5 | The program **reads the dataset**, runs **clustering** if needed, computes the **entropy** of individual utterances, and then **removes high entropy** utterances based on the threshold, and **saves the filtered dataset** to the output directory. See the [paper](https://www.aclweb.org/anthology/P19-1567) or the [poster](https://ricsinaruto.github.io/website/docs/acl_poster_h.pdf) for more details. 6 | 7 | ## Features 8 | :floppy_disk:   Cluster and filter any dialog data that you provide, or use pre-downloaded datasets 9 | :rocket:   Various parameters can be used to adjust the algorithm 10 | :ok_hand:    Choose between different entropy computation methods 11 | :twisted_rightwards_arrows:   Choose between different clustering and filtering types 12 | :movie_camera:   Visualize clustering and filtering results 13 | 14 | 15 | 16 | ## Setup 17 | Run setup.py which installs required packages and steps you through downloading additional data: 18 | ``` 19 | python setup.py 20 | ``` 21 | You can download all trained models used in [this](https://www.aclweb.org/anthology/P19-1567) paper from [here](https://mega.nz/#!mI0iDCTI!qhKoBiQRY3rLg3K6nxAmd4ZMNEX4utFRvSby_0q2dwU). Each training contains two checkpoints, one for the validation loss minimum and another after 150 epochs. The data and the trainings folder structure match each other exactly. 22 | ## Usage 23 | The main file can be called from anywhere, but when specifying paths to directories you should give them from the root of the repository. 24 | ``` 25 | python code/main.py -h 26 | ``` 27 | 28 | For the complete **documentation** visit the [wiki](https://github.com/ricsinaruto/NeuralChatbots-DataFiltering/wiki). 29 | 30 | ### Cluster Type 31 | * [identity](https://github.com/ricsinaruto/NeuralChatbots-DataFiltering/blob/master/code/filtering/identity.py): In this method there is basically no clustering, the entropy of utterances is calculated based on the conditional probability of utterance pairs. 32 | * [avg-embedding](https://github.com/ricsinaruto/NeuralChatbots-DataFiltering/blob/master/code/filtering/average_word_embedding.py): This clustering type uses average word embedding sentence representations as in [this paper](https://pdfs.semanticscholar.org/3fc9/7768dc0b36449ec377d6a4cad8827908d5b4.pdf). 33 | * [sent2vec](https://github.com/ricsinaruto/NeuralChatbots-DataFiltering/blob/master/code/filtering/sent2vec.py): This clustering type should use [sent2vec](https://github.com/epfml/sent2vec) sentence embeddings, but currently uses any embeddings you provide to it. 34 | 35 | ### Filter Type 36 | * **source**: Filters utterance pairs in which the source utterance's entropy is above the threshold. 37 | * **target**: Filters utterance pairs in which the target utterance's entropy is above the threshold. 38 | * **both**: Filters utterance pairs in which either the source or target utterance's entropy is above the threshold. 39 | 40 | ### [Filtering Demo](https://github.com/ricsinaruto/NeuralChatbots-DataFiltering/blob/master/code/utils/filtering_demo.ipynb) 41 | In this jupyter notebook you can easily try out the identity filtering method implemented in less than 40 lines, and it filters DailyDialog in a couple of seconds (you only need to provide a sources and targets file). In the second part of the notebook there are some cool visualizations for entropy, frequency and sentence length. 42 | 43 | 44 | ### [Visualization](https://github.com/ricsinaruto/NeuralChatbots-DataFiltering/blob/master/code/utils/visualization.ipynb) 45 | Visualize clustering and filtering results by running the [visualization](https://github.com/ricsinaruto/NeuralChatbots-DataFiltering/blob/master/code/utils/visualization.ipynb) jupyter notebook. The notebook is pretty self-explanatory, you just have to provide the directory containing the clustering files. 46 | 47 | 48 | 49 | ## Results & Examples 50 | ### High Entropy Utterances and Clusters from [DailyDialog](https://arxiv.org/abs/1710.03957) 51 | 52 | 53 | A high entropy cluster found by sent2vec. 54 | 55 | ### [Transformer](https://arxiv.org/abs/1706.03762) Trained on [DailyDialog](https://arxiv.org/abs/1710.03957) 56 | For an explanation of the metrics please check [this repo](https://github.com/ricsinaruto/dialog-eval) or the [paper](https://arxiv.org/pdf/1905.05471.pdf). 57 | 58 | 59 | 60 | More examples can be found in the appendix of the [paper](https://arxiv.org/pdf/1905.05471.pdf). 61 | 62 | ### [Transformer](https://arxiv.org/abs/1706.03762) Trained on [Cornell](https://www.cs.cornell.edu/~cristian/Cornell_Movie-Dialogs_Corpus.html) and [Twitter](https://github.com/facebookresearch/ParlAI/tree/master/parlai/tasks/twitter) 63 | For an explanation of the metrics please check [this repo](https://github.com/ricsinaruto/dialog-eval) or the [paper](https://arxiv.org/pdf/1905.05471.pdf). 64 | 65 | 66 | ## Contributing 67 | ##### Check the [issues](https://github.com/ricsinaruto/NeuralChatbots-DataFiltering/issues) for some additions where help is appreciated. Any contributions are welcome :heart: 68 | ##### Please try to follow the code syntax style used in the repo (flake8, 2 spaces indent, 80 char lines, commenting a lot, etc.) 69 | 70 | **New clustering** methods can be added, by subclassing the [FilterProblem](https://github.com/ricsinaruto/NeuralChatbots-DataFiltering/blob/master/code/filtering/filter_problem.py#L48) class, check [Identity](https://github.com/ricsinaruto/NeuralChatbots-DataFiltering/blob/master/code/filtering/identity.py) for a minimal example. Normally you only have to redefine the *clustering* function, which does the clustering of sentences. 71 | 72 | Loading and saving data is taken care of, and you should use the [Cluster](https://github.com/ricsinaruto/NeuralChatbots-DataFiltering/blob/master/code/filtering/filter_problem.py#L24) and [DataPoint](https://github.com/ricsinaruto/NeuralChatbots-DataFiltering/blob/master/code/filtering/filter_problem.py#L9) objects. Use the *data_point* list to get the sentences for your clustering algorithm, and use the *clusters* list to save the results of your clustering. These can also be subclassed if you want to add extra data to your DataPoint and Cluster objects (like a vector). 73 | 74 | Finally add your class to the dictionary in [main](https://github.com/ricsinaruto/NeuralChatbots-DataFiltering/blob/master/code/main.py#L90), and to the command-line argument choices. 75 | 76 | 77 | ## Authors 78 | * **[Richard Csaky](ricsinaruto.github.io)** (If you need any help with running the code: ricsinaruto@hotmail.com) 79 | * **[Patrik Purgai](https://github.com/Mrpatekful)** (clustering part) 80 | 81 | ## License 82 | This project is licensed under the MIT License - see the [LICENSE](https://github.com/ricsinaruto/NeuralChatbots-DataFiltering/blob/master/LICENSE) file for details. 83 | Please include a link to this repo if you use it in your work and consider citing the following paper: 84 | ``` 85 | @inproceedings{Csaky:2019, 86 | title = "Improving Neural Conversational Models with Entropy-Based Data Filtering", 87 | author = "Cs{\'a}ky, Rich{\'a}rd and Purgai, Patrik and Recski, G{\'a}bor", 88 | booktitle = "Proceedings of the 57th Annual Meeting of the Association for Computational Linguistics", 89 | month = jul, 90 | year = "2019", 91 | address = "Florence, Italy", 92 | publisher = "Association for Computational Linguistics", 93 | url = "https://www.aclweb.org/anthology/P19-1567", 94 | pages = "5650--5669", 95 | } 96 | ``` 97 | -------------------------------------------------------------------------------- /code/filtering/average_word_embedding.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import numpy as np 4 | import requests 5 | import zipfile 6 | from collections import Counter 7 | from clint.textui import progress 8 | 9 | from filtering import semantic_clustering 10 | 11 | 12 | class AverageWordEmbedding(semantic_clustering.SemanticClustering): 13 | ''' 14 | Averaged word embeddings clustering method. The meaning vector of the 15 | sentence is created by the weighted average of the word vectors. 16 | ''' 17 | 18 | # Download data from fasttext. 19 | def download_fasttext(self): 20 | # Open the url and download the data with progress bars. 21 | data_stream = requests.get('https://dl.fbaipublicfiles.com/fasttext/' + 22 | 'vectors-english/wiki-news-300d-1M.vec.zip', stream=True) 23 | zipped_path = os.path.join(self.input_dir, 'fasttext.zip') 24 | 25 | with open(zipped_path, 'wb') as file: 26 | total_length = int(data_stream.headers.get('content-length')) 27 | for chunk in progress.bar(data_stream.iter_content(chunk_size=1024), 28 | expected_size=total_length / 1024 + 1): 29 | if chunk: 30 | file.write(chunk) 31 | file.flush() 32 | 33 | # Extract file. 34 | zip_file = zipfile.ZipFile(zipped_path, 'r') 35 | zip_file.extractall(self.input_dir) 36 | zip_file.close() 37 | 38 | # Generate a vocab from data files. 39 | def get_vocab(self, vocab_path): 40 | vocab = [] 41 | 42 | with open(vocab_path, 'w') as file: 43 | for dp in self.data_points['Source']: 44 | vocab.extend(dp.string.split()) 45 | file.write('\n'.join( 46 | [w[0] for w in Counter(vocab).most_common(self.config.vocab_size)])) 47 | 48 | # Download FastText word embeddings. 49 | def get_fast_text_embeddings(self): 50 | vocab_path = os.path.join(self.input_dir, 'vocab.txt') 51 | if not os.path.exists(vocab_path): 52 | print('No vocab file named \'vocab.txt\' found in ' + self.input_dir) 53 | print('Building vocab from data.') 54 | self.get_vocab(vocab_path) 55 | 56 | fasttext_path = os.path.join(self.input_dir, 'wiki-news-300d-1M.vec') 57 | if not os.path.exists(fasttext_path): 58 | self.download_fasttext() 59 | 60 | vocab = [line.strip('\n') for line in open(vocab_path)] 61 | vocab_path = os.path.join(self.input_dir, 'vocab.npy') 62 | 63 | # Save the vectors for words in the vocab. 64 | with open(fasttext_path, errors='ignore') as in_file: 65 | with open(vocab_path, 'w') as out_file: 66 | vectors = {} 67 | for line in in_file: 68 | tokens = line.strip().split() 69 | vectors[tokens[0]] = line 70 | 71 | for word in vocab: 72 | try: 73 | out_file.write(vectors[word]) 74 | except KeyError: 75 | pass 76 | 77 | # Generate the sentence embeddings. 78 | def generate_embeddings(self, tag, vector_path): 79 | ''' 80 | Params: 81 | :tag: Whether it's source or target data. 82 | :vector_path: Path to save the sentence vectors. 83 | ''' 84 | vocab = {} 85 | vocab_path = os.path.join(self.input_dir, 'vocab.npy') 86 | if not os.path.exists(vocab_path): 87 | print('File containing word vectors not found in ' + self.input_dir) 88 | print('The file should be named \'vocab.npy\'') 89 | print('If you would like to use FastText embeddings press \'y\'') 90 | if input() == 'y': 91 | self.get_fast_text_embeddings() 92 | else: 93 | sys.exit() 94 | 95 | # Get the word embeddings. 96 | with open(vocab_path) as v: 97 | for line in v: 98 | tokens = line.strip().split() 99 | vocab[tokens[0]] = [0, np.array(list(map(float, tokens[1:])))] 100 | 101 | embedding_dim = vocab[list(vocab)[0]][1].shape[0] 102 | unique_sentences = set() 103 | word_count = 0 104 | 105 | # Statistics of number of words. 106 | for dp in self.data_points[tag]: 107 | unique_sentences.add(dp.string) 108 | for word in dp.string.split(): 109 | if vocab.get(word): 110 | vocab[word][0] += 1 111 | word_count += 1 112 | 113 | meaning_vectors = [] 114 | sentences = unique_sentences if self.unique else [ 115 | s.string for s in self.data_points[tag]] 116 | # Calculate smooth average embedding. 117 | for s in sentences: 118 | vectors = [] 119 | for word in s.split(): 120 | vector = vocab.get(word) 121 | if vector: 122 | vectors.append(vector[1] * 0.001 / (0.001 + vector[0] / word_count)) 123 | 124 | num_vecs = len(vectors) 125 | if num_vecs: 126 | meaning_vectors.append(np.sum(np.array(vectors), axis=0) / num_vecs) 127 | else: 128 | meaning_vectors.append(np.zeros(embedding_dim)) 129 | 130 | np.save(vector_path, np.array(meaning_vectors).reshape(-1, embedding_dim)) 131 | -------------------------------------------------------------------------------- /code/filtering/filter_problem.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import math 4 | from collections import Counter 5 | 6 | from utils import utils 7 | 8 | 9 | class DataPoint: 10 | ''' 11 | A simple class that handles a string example. 12 | ''' 13 | def __init__(self, string, index): 14 | ''' 15 | Params: 16 | :string: String to be stored. 17 | :index: Number of the line in the file from which this sentence was read. 18 | ''' 19 | self.index = index 20 | self.string = ' '.join(string.strip('\n').split()) 21 | self.cluster_index = 0 22 | 23 | 24 | class Cluster: 25 | ''' 26 | A class to handle one cluster in the clustering problem. 27 | ''' 28 | def __init__(self, medoid): 29 | ''' 30 | Params: 31 | :medoid: Center of the cluster: a DataPoint object. 32 | ''' 33 | self.medoid = medoid 34 | self.elements = [] 35 | self.targets = [] 36 | self.entropy = 0 37 | self.index = 0 38 | 39 | # Append an element to the list of elements in the cluster. 40 | def add_element(self, element): 41 | self.elements.append(element) 42 | 43 | # append an element to the list of targets in the cluster. 44 | def add_target(self, target): 45 | self.targets.append(target) 46 | 47 | 48 | class FilterProblem: 49 | ''' 50 | An abstract class to handle different types of filtering. 51 | ''' 52 | 53 | @property 54 | def DataPointClass(self): 55 | return DataPoint 56 | 57 | @property 58 | def ClusterClass(self): 59 | return Cluster 60 | 61 | def __init__(self, config): 62 | ''' 63 | Params: 64 | :config: Config object storing all arguments. 65 | ''' 66 | self.config = config 67 | self.tag = config.filter_split 68 | self.threshold = config.threshold 69 | self.max_avg_length = config.max_avg_length 70 | self.max_medoid_length = config.max_medoid_length 71 | self.min_cluster_size = config.min_cluster_size 72 | self.unique = config.unique 73 | 74 | self.project_path = os.path.join( 75 | os.path.dirname(os.path.abspath(__file__)), '..', '..') 76 | self.output_dir = os.path.join(self.project_path, config.output_dir) 77 | self.input_dir = os.path.join(self.project_path, config.data_dir) 78 | self.type = config.filter_type 79 | 80 | self.clusters = {'Source': [], 'Target': []} 81 | self.data_points = {'Source': [], 'Target': []} 82 | self.num_clusters = {'Source': config.source_clusters, 83 | 'Target': config.target_clusters} 84 | 85 | self.build('Source') 86 | self.build('Target') 87 | 88 | # Build statistics and full files. 89 | def build(self, tag): 90 | ''' 91 | Params: 92 | :tag: 'Source' or 'Target'. 93 | ''' 94 | splits = ['train', 'dev', 'test'] 95 | try: 96 | files = [open(os.path.join(self.input_dir, split + tag + '.txt')).read() 97 | for split in splits] 98 | except FileNotFoundError: 99 | print('Data files not found in ' + self.input_dir) 100 | print('The following files should be here:') 101 | print('trainSource.txt\ntrainTarget.txt\ndevSource.txt\ndevTarget.txt') 102 | print('testSource.txt\ntestTarget.txt') 103 | sys.exit() 104 | 105 | full_path = os.path.join(self.input_dir, 'full' + tag + '.txt') 106 | if not os.path.exists(full_path): 107 | open(full_path, 'w').write(''.join(files)) 108 | 109 | if tag == 'Source': 110 | self.line_counts = dict(zip(splits, 111 | map(lambda x: len(x.split('\n')), files))) 112 | 113 | # Main method that will run all the functions to do the filtering. 114 | def run(self): 115 | # If we have already done the clustering, don't redo it. 116 | source_data = os.path.join(self.output_dir, 117 | self.tag + 'Source_cluster_elements.txt') 118 | target_data = os.path.join(self.output_dir, 119 | self.tag + 'Target_cluster_elements.txt') 120 | if os.path.exists(source_data) and os.path.exists(target_data): 121 | print('Cluster files are in ' + self.output_dir + ', filtering now.') 122 | self.load_clusters(source_data, target_data) 123 | self.filtering() 124 | 125 | else: 126 | print('No cluster files in ' + self.output_dir + ', clustering now.') 127 | self.read_inputs('Source') 128 | self.read_inputs('Target') 129 | self.clustering('Source') 130 | self.clustering('Target') 131 | self.save_clusters('Source') 132 | self.save_clusters('Target') 133 | self.filtering() 134 | 135 | # Read the data and make it ready for clustering. 136 | def read_inputs(self, tag): 137 | ''' 138 | Params: 139 | :tag: 'Source' or 'Target'. 140 | ''' 141 | file = open(os.path.join(self.input_dir, self.tag + tag + '.txt')) 142 | for i, line in enumerate(file): 143 | self.data_points[tag].append(self.DataPointClass(line, i)) 144 | file.close() 145 | 146 | print('Finished reading ' + tag + ' data.') 147 | 148 | # Load clusters from file. 149 | def load_clusters(self, source_path, target_path): 150 | ''' 151 | Params: 152 | :source_path: Path to source cluster elements. 153 | :target_path: Path to target cluster elements. 154 | ''' 155 | source_clusters = {} 156 | target_clusters = {} 157 | source_data_points = {} 158 | target_data_points = {} 159 | 160 | with open(source_path, 'r') as source_file: 161 | for line in source_file: 162 | # If the data contains these special characters it won't work. 163 | [source_index_center, source_target, _] = line.split('<=====>') 164 | [source_index, source_center] = source_index_center.split(';') 165 | [source, target] = source_target.split('=') 166 | 167 | # Initialize the source and target utterances. 168 | source_data_points[int(source_index)] = self.DataPointClass( 169 | source, int(source_index)) 170 | target_data_points[int(source_index)] = self.DataPointClass( 171 | target, int(source_index)) 172 | 173 | # If this is a new cluster add it to the list. 174 | if source_clusters.get(source_center) is None: 175 | center = self.DataPointClass(source_center, 0) 176 | source_clusters[source_center] = self.ClusterClass(center) 177 | source_clusters[source_center].index = len(source_clusters) - 1 178 | 179 | # Add the elements to the cluster. 180 | source_data_points[int(source_index)].cluster_index = \ 181 | source_clusters[source_center].index 182 | source_clusters[source_center].add_element( 183 | source_data_points[int(source_index)]) 184 | source_clusters[source_center].add_target( 185 | target_data_points[int(source_index)]) 186 | 187 | with open(target_path, 'r') as target_file: 188 | for line in target_file: 189 | [target_index_center, target_source, _] = line.split('<=====>') 190 | [target_index, target_center] = target_index_center.split(';') 191 | [target, source] = target_source.split('=') 192 | 193 | # All elements are already added at this point. 194 | target_data_point = target_data_points[int(target_index)] 195 | source_data_point = source_data_points[int(target_index)] 196 | 197 | # If this is a new cluster add it to the list. 198 | if target_clusters.get(target_center) is None: 199 | center = self.DataPointClass(target_center, 0) 200 | target_clusters[target_center] = self.ClusterClass(center) 201 | target_clusters[target_center].index = len(target_clusters) - 1 202 | 203 | # Add the elements to the cluster. 204 | target_data_point.cluster_index = target_clusters[target_center].index 205 | target_clusters[target_center].add_element(target_data_point) 206 | target_clusters[target_center].add_target(source_data_point) 207 | 208 | # Save the data correctly into self.clusters. 209 | def id(cl): 210 | return sorted(list(cl), key=lambda x: cl[x].index) 211 | self.clusters['Source'] = [source_clusters[i] for i in id(source_clusters)] 212 | self.clusters['Target'] = [target_clusters[i] for i in id(target_clusters)] 213 | 214 | # Cluster sources or targets, should be implemented in subclass. 215 | def clustering(self, tag): 216 | raise NotImplementedError 217 | 218 | # Return a list of indices, showing which clusters should be filtered out. 219 | def get_filtered_indices(self, tag): 220 | ''' 221 | Params: 222 | :tag: Source or Target. 223 | ''' 224 | indices = [] 225 | for num_cl, cluster in enumerate(self.clusters[tag]): 226 | # Build a distribution for the current cluster, based on the targets. 227 | distribution = Counter([t.cluster_index for t in cluster.targets]) 228 | 229 | num_elements = len(cluster.elements) 230 | # Calculate entropy. 231 | entropy = 0 232 | for cl_index in distribution: 233 | if num_elements > 1: 234 | probability = distribution[cl_index] / num_elements 235 | entropy += probability * math.log(probability, 2) 236 | cluster.entropy = -entropy 237 | 238 | avg_length = ( 239 | sum(len(sent.string.split()) for sent in cluster.elements) / 240 | (num_elements if num_elements > 0 else 1)) 241 | medoid_length = len(cluster.medoid.string.split()) 242 | 243 | # Filter based on threshold. 244 | if (cluster.entropy > self.threshold and 245 | avg_length < self.max_avg_length and 246 | medoid_length < self.max_medoid_length): 247 | indices.append(num_cl) 248 | 249 | print('Finished filtering ' + tag + ' data.') 250 | return indices 251 | 252 | # Do the filtering of the dataset. 253 | def filtering(self): 254 | # These are not needed anymore. 255 | self.data_points['Source'].clear() 256 | self.data_points['Target'].clear() 257 | 258 | # Get the filtered indices for both sides. 259 | source_indices = self.get_filtered_indices('Source') 260 | target_indices = self.get_filtered_indices('Target') 261 | 262 | file_dict = {} 263 | # We have to open 6 files in this case. 264 | if self.tag == 'full': 265 | name_list = ['trainS', 'trainT', 'devS', 'devT', 'testS', 'testT'] 266 | file_dict = dict(zip(name_list, self.open_6_files())) 267 | else: 268 | file_dict[self.tag + 'S'] = open( 269 | os.path.join(self.output_dir, self.tag + 'Source.txt'), 'w') 270 | file_dict[self.tag + 'T'] = open( 271 | os.path.join(self.output_dir, self.tag + 'Target.txt'), 'w') 272 | 273 | # Handle all other cases and open files. 274 | if self.type == 'source' or self.type == 'both': 275 | file_dict['source_entropy'] = open( 276 | os.path.join(self.output_dir, 277 | self.tag + 'Source_cluster_entropies.txt'), 'w') 278 | if self.type == 'target' or self.type == 'both': 279 | file_dict['target_entropy'] = open( 280 | os.path.join(self.output_dir, 281 | self.tag + 'Target_cluster_entropies.txt'), 'w') 282 | 283 | # Save data and close files. 284 | self.save_filtered_data(source_indices, target_indices, file_dict) 285 | utils.close_n_files(file_dict) 286 | 287 | # Save the new filtered datasets. 288 | def save_filtered_data(self, source_indices, target_indices, file_dict): 289 | ''' 290 | Params: 291 | :source_indices: Indices of source clusters that will be filtered. 292 | :target_indices: Indices of target clusters that will be filtered. 293 | :file_dict: Dictionary containing all the files that we want to write. 294 | ''' 295 | # Function for writing filtered source or target data to file. 296 | def save_dataset(tag): 297 | for num_cl, cluster in enumerate(self.clusters[tag]): 298 | # Write cluster entropies. 299 | file_dict[tag.lower() + '_entropy'].write( 300 | cluster.medoid.string + ';' + 301 | str(cluster.entropy) + ';' + 302 | str(len(cluster.elements)) + '\n') 303 | 304 | # Check if a cluster is smaller than threshold. 305 | cluster_too_small = len(cluster.elements) < self.min_cluster_size 306 | indices = source_indices if tag == 'Source' else target_indices 307 | 308 | # Make sure that in 'both' case this is only run once. 309 | if ((tag == 'Source' or self.type != 'both') and 310 | (num_cl not in indices or cluster_too_small)): 311 | # Filter one side. 312 | for num_el, element in enumerate(cluster.elements): 313 | target_cl = cluster.targets[num_el].cluster_index 314 | if self.type == 'both': 315 | cluster_too_small = ( 316 | len(self.clusters['Target'][target_cl].elements) < 317 | self.min_cluster_size) 318 | # Check both sides in 'both' case. 319 | if ((target_cl not in target_indices or cluster_too_small) or 320 | self.type != 'both'): 321 | 322 | # Reverse if Target. 323 | source = element.string + '\n' 324 | target = cluster.targets[num_el].string + '\n' 325 | source_string = source if tag == 'Source' else target 326 | target_string = target if tag == 'Source' else source 327 | 328 | # Separate the full case. 329 | if self.tag == 'full': 330 | if element.index < self.line_counts['train']: 331 | file_dict['trainS'].write(source_string) 332 | file_dict['trainT'].write(target_string) 333 | elif element.index < (self.line_counts['train'] + 334 | self.line_counts['dev']): 335 | file_dict['devS'].write(source_string) 336 | file_dict['devT'].write(target_string) 337 | else: 338 | file_dict['testS'].write(source_string) 339 | file_dict['testT'].write(target_string) 340 | else: 341 | file_dict[self.tag + 'S'].write(source_string) 342 | file_dict[self.tag + 'T'].write(target_string) 343 | 344 | # Write source entropies and data to file. 345 | if self.type == 'source' or self.type == 'both': 346 | save_dataset('Source') 347 | # Write target entropies and data to file. 348 | if self.type == 'target' or self.type == 'both': 349 | save_dataset('Target') 350 | 351 | # Save clusters and their elements to files. 352 | def save_clusters(self, tag): 353 | ''' 354 | Params: 355 | :tag: Whether it's source or target data. 356 | ''' 357 | output = open( 358 | os.path.join(self.output_dir, 359 | self.tag + tag + '_cluster_elements.txt'), 'w') 360 | 361 | medoid_counts = [] 362 | rev_tag = 'Target' if tag == 'Source' else 'Source' 363 | 364 | for cluster in self.clusters[tag]: 365 | medoid_counts.append((cluster.medoid.string, len(cluster.elements))) 366 | 367 | # Save together the source and target medoids and elements. 368 | for source, target in zip(cluster.elements, cluster.targets): 369 | output.write( 370 | str(source.index) + ';' + 371 | cluster.medoid.string + '<=====>' + 372 | source.string + '=' + 373 | target.string + '<=====>' + 374 | self.clusters[rev_tag][target.cluster_index].medoid.string + ':' + 375 | str(target.cluster_index) + '\n') 376 | output.close() 377 | 378 | # Save the medoids and the count of their elements, in decreasing order. 379 | output = open(os.path.join(self.output_dir, 380 | self.tag + tag + '_clusters.txt'), 'w') 381 | medoids = sorted(medoid_counts, key=lambda count: count[1], reverse=True) 382 | 383 | for medoid in medoids: 384 | output.write(medoid[0] + ':' + str(medoid[1]) + '\n') 385 | output.close() 386 | 387 | if tag == 'Target': 388 | print('Finished clustering, proceeding with filtering.') 389 | 390 | # Open the 6 files. 391 | def open_6_files(self): 392 | trainS = open(os.path.join(self.output_dir, 'trainSource.txt'), 'w') 393 | trainT = open(os.path.join(self.output_dir, 'trainTarget.txt'), 'w') 394 | devS = open(os.path.join(self.output_dir, 'devSource.txt'), 'w') 395 | devT = open(os.path.join(self.output_dir, 'devTarget.txt'), 'w') 396 | testS = open(os.path.join(self.output_dir, 'testSource.txt'), 'w') 397 | testT = open(os.path.join(self.output_dir, 'testTarget.txt'), 'w') 398 | 399 | return [trainS, trainT, devS, devT, testS, testT] 400 | -------------------------------------------------------------------------------- /code/filtering/identity.py: -------------------------------------------------------------------------------- 1 | from filtering.filter_problem import FilterProblem 2 | 3 | 4 | class Identity(FilterProblem): 5 | ''' 6 | Calculate entropy based on and filter individual utterances. 7 | ''' 8 | 9 | # Do the clustering of sources and targets. 10 | def clustering(self, tag): 11 | ''' 12 | Params: 13 | :tag: Whether it's source or target data. 14 | ''' 15 | rev_tag = 'Target' if tag == 'Source' else 'Source' 16 | 17 | clean_sents = [' '.join(dp.string.split()) for dp in self.data_points[tag]] 18 | sentence_set = list(set(clean_sents)) 19 | 20 | # Build a hash for efficient string searching. 21 | sentence_dict = {} 22 | for data_point, clean_sentence in zip(self.data_points[tag], clean_sents): 23 | if clean_sentence in sentence_dict: 24 | sentence_dict[clean_sentence].append(data_point) 25 | else: 26 | sentence_dict[clean_sentence] = [data_point] 27 | 28 | print(tag + ': ' + str(len(sentence_set)) + ' clusters') 29 | 30 | # Loop through the clusters. 31 | for i, sentence in enumerate(sentence_set): 32 | cl = self.ClusterClass(self.DataPointClass(sentence, 10)) 33 | self.clusters[tag].append(cl) 34 | 35 | # Loop through the data points associated with this sentence. 36 | for data_point in sentence_dict[sentence]: 37 | data_point.cluster_index = i 38 | cl.add_element(data_point) 39 | cl.add_target(self.data_points[rev_tag][data_point.index]) 40 | -------------------------------------------------------------------------------- /code/filtering/semantic_clustering.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | from sklearn.neighbors import BallTree 4 | from sklearn.cluster import MeanShift 5 | 6 | from filtering import filter_problem 7 | from utils.config import Config 8 | 9 | if Config.use_faiss: 10 | try: 11 | from faiss import Kmeans 12 | Config.faiss = True 13 | 14 | except ImportError: 15 | print('Failed to import faiss, using SKLearn clustering instead.') 16 | 17 | if not Config.use_faiss: 18 | from sklearn.cluster import KMeans 19 | 20 | 21 | class DataPoint(filter_problem.DataPoint): 22 | ''' 23 | A simple class that handles a string example. 24 | ''' 25 | def __init__(self, string, index, meaning_vector=None): 26 | ''' 27 | Params: 28 | :string: String to be stored. 29 | :index: Number of the line in the file from which this sentence was read. 30 | :meaning_vector: Numpy embedding vector for the sentence. 31 | ''' 32 | super().__init__(string, index) 33 | self.meaning_vector = meaning_vector 34 | 35 | 36 | class SemanticClustering(filter_problem.FilterProblem): 37 | ''' 38 | Base class for the meaning-based (semantic vector representation) clustering. 39 | The source and target sentences are read into an extended DataPoint object, 40 | that also contains a 'meaning_vector' attribute. This attribute holds 41 | the semantic vector representation of the sentence, which will be used 42 | by the clustering logic. 43 | ''' 44 | 45 | @property 46 | def DataPointClass(self): 47 | return DataPoint 48 | 49 | def __init__(self, *args, **kwargs): 50 | super().__init__(*args, **kwargs) 51 | self.unique_data = {"Source": [], "Target": []} 52 | 53 | def clustering(self, tag): 54 | ''' 55 | Params: 56 | :tag: Whether it's source or target data. 57 | ''' 58 | data_points = self.unique_data if self.unique else self.data_points 59 | centroids = self.calculate_centroids(tag) 60 | 61 | data_point_vectors = np.array( 62 | [data_point.meaning_vector for data_point in 63 | data_points[tag]]).reshape( 64 | -1, data_points[tag][0].meaning_vector.shape[-1]) 65 | 66 | # Get the actual data point for each centroid. 67 | tree = BallTree(data_point_vectors) 68 | _, centroids = tree.query(centroids, k=1) 69 | 70 | # Get the closest centroid for each data point. 71 | tree = BallTree(data_point_vectors[np.array(centroids).reshape(-1)]) 72 | _, labels = tree.query(data_point_vectors, k=1) 73 | labels = labels.reshape(-1) 74 | 75 | # Build the list of clusters. 76 | clusters = {index: self.ClusterClass(data_points[tag][index]) for 77 | index in {labels[_index] for _index in range(len(labels))}} 78 | clusters = [(clusters[cluster_index], cluster_index) for cluster_index in 79 | sorted(list(clusters))] 80 | 81 | label_lookup = {c[1]: i for i, c in enumerate(clusters)} 82 | clusters = [c[0] for c in clusters] 83 | 84 | rev_tag = 'Target' if tag == 'Source' else 'Source' 85 | 86 | # Store the cluster index for each unique sentence. 87 | if self.unique: 88 | cluster_ind_dict = {} 89 | for data_point, cluster_index in zip(data_points[tag], labels): 90 | cluster_ind_dict[data_point.string] = label_lookup[cluster_index] 91 | 92 | # This is different for unique clustering. 93 | for i, data_point in enumerate(self.data_points[tag]): 94 | cl_index = cluster_ind_dict[data_point.string] 95 | data_point.cluster_index = cl_index 96 | clusters[cl_index].add_element(data_point) 97 | clusters[cl_index].add_target(self.data_points[rev_tag][i]) 98 | 99 | # Assign the actual clusters. 100 | else: 101 | for dp, cl_index in zip(self.data_points[tag], labels): 102 | cl_index = label_lookup[cl_index] 103 | dp.cluster_index = cl_index 104 | clusters[cl_index].add_element(dp) 105 | clusters[cl_index].add_target(self.data_points[rev_tag][dp.index]) 106 | 107 | self.clusters[tag] = clusters 108 | 109 | def read_inputs(self, tag): 110 | ''' 111 | Called twice for source and target data. It should implement the 112 | logic of reading the data from Source and Target files into the 113 | data_points list. Each sentence should be wrapped into an appropriate 114 | subclass of the DataPoint class. Source.npy and Target.npy should contain 115 | sentence embeddings, if not they have to be generated in a subclass. 116 | These vectors in the .npy files have to correspond to the loaded sentences. 117 | 118 | Params: 119 | :tag: Whether it's source or target data. 120 | ''' 121 | super().read_inputs(tag) 122 | 123 | vector_path = os.path.join(self.input_dir, self.tag + tag + '.npy') 124 | if not os.path.exists(vector_path): 125 | print('No sentence embeddings found in ' + self.input_dir) 126 | print('They should be named \'fullSource.npy\' and \'fullTarget.npy\',') 127 | print('where each line is a vector corresponding to') 128 | print('sentences in \'fullSource.txt\' and \'fullTarget.txt\'.') 129 | print('Building sentence representations of ' + self.config.cluster_type) 130 | self.generate_embeddings(tag, vector_path) 131 | 132 | # Add vectors to sentences. 133 | sent_vectors = np.load(vector_path) 134 | if not self.unique: 135 | for index, dp in enumerate(self.data_points[tag]): 136 | dp.meaning_vector = sent_vectors[index] 137 | # Create unique data points if necessary. 138 | else: 139 | for i, sent in enumerate(set([s.string for s in self.data_points[tag]])): 140 | self.unique_data[tag].append(self.DataPointClass(sent, 141 | i, 142 | sent_vectors[i])) 143 | 144 | # Has to be implemented by subclass to generate sentence embeddings. 145 | def generate_embeddings(self, tag): 146 | raise NotImplementedError 147 | 148 | # Cluster the data and return the centers. 149 | def calculate_centroids(self, tag): 150 | ''' 151 | Params: 152 | :tag: Whether it's source or target data. 153 | ''' 154 | data_points = self.unique_data if self.unique else self.data_points 155 | matrix = np.stack([dp.meaning_vector for dp in data_points[tag]]) 156 | 157 | if self.config.clustering_method == 'kmeans': 158 | # Kmeans with either the faiss or the sklearn implementation. 159 | if self.config.use_faiss: 160 | kmeans = Kmeans(matrix.shape[1], self.num_clusters[tag], 20, True) 161 | kmeans.train(matrix) 162 | centroids = kmeans.centroids 163 | else: 164 | kmeans = KMeans(n_clusters=self.num_clusters[tag], 165 | random_state=0, 166 | n_jobs=10).fit(matrix) 167 | centroids = kmeans.cluster_centers_ 168 | 169 | else: 170 | mean_shift = MeanShift(bandwidth=self.config.bandwidth, n_jobs=10) 171 | mean_shift.fit(matrix) 172 | centroids = mean_shift.cluster_centers_ 173 | 174 | return centroids 175 | -------------------------------------------------------------------------------- /code/filtering/sent2vec.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from filtering import semantic_clustering 4 | 5 | 6 | class Sent2vec(semantic_clustering.SemanticClustering): 7 | def generate_embeddings(self, tag, vector_path): 8 | print('Currently sent2vec only works with provided sentence embeddings.') 9 | print('Check github.com/epfml/sent2vec for getting sentence embeddings.') 10 | print('Btw any kind of sentence embeddings can be used if they are in the') 11 | print('required format, I recommend github.com/hanxiao/bert-as-service.') 12 | sys.exit() 13 | -------------------------------------------------------------------------------- /code/main.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from utils.config import Config 4 | from filtering.average_word_embedding import AverageWordEmbedding 5 | from filtering.identity import Identity 6 | from filtering.sent2vec import Sent2vec 7 | 8 | 9 | def main(): 10 | config = Config() 11 | parser = argparse.ArgumentParser( 12 | description='Code for filtering methods in: arxiv.org/abs/1905.05471. ' + 13 | 'These arguments can also be set in config.py, ' + 14 | 'and will be saved to the output directory.') 15 | parser.add_argument('-d', '--data_dir', default=config.data_dir, 16 | help='Directory containing the dataset in these files:' + 17 | ' (trainSource.txt, trainTarget.txt, devSource.txt, ' + 18 | 'devTarget.txt, testSource.txt, testTarget.txt, ' + 19 | 'vocab.txt)', 20 | metavar='') 21 | parser.add_argument('-o', '--output_dir', default=config.output_dir, 22 | help='Save here the filtered data and any output', 23 | metavar='') 24 | parser.add_argument('-l', '--load_config', default=config.load_config, 25 | help='Path to load config from file, or leave empty ' + 26 | '(default: %(default)s)', 27 | metavar='') 28 | parser.add_argument('-fs', '--filter_split', default=config.filter_split, 29 | help='Data split to filter, \'full\' filters ' + 30 | 'all splits (choices: %(choices)s)', 31 | metavar='', choices=['full', 'train', 'dev', 'test']) 32 | parser.add_argument('-ct', '--cluster_type', default=config.cluster_type, 33 | help='Clustering method (choices: %(choices)s)', 34 | metavar='', 35 | choices=['identity', 'avg_embedding', 'sent2vec']) 36 | parser.add_argument('-sc', '--source_clusters', 37 | default=config.source_clusters, 38 | help='Number of source clusters in case of Kmeans', 39 | metavar='', type=int) 40 | parser.add_argument('-tc', '--target_clusters', 41 | default=config.target_clusters, 42 | help='Number of target clusters in case of Kmeans', 43 | metavar='', type=int) 44 | parser.add_argument('-u', '--unique', default=config.unique, 45 | help='Whether to cluster only unique sentences ' + 46 | '(default: %(default)s)', 47 | metavar='', type=bool) 48 | parser.add_argument('-vs', '--vocab_size', 49 | default=config.vocab_size, 50 | help='Vocab size, only used if vocab file not given', 51 | metavar='', type=int) 52 | parser.add_argument('-ft', '--filter_type', default=config.filter_type, 53 | help='Filtering way (choices: %(choices)s)', 54 | metavar='', choices=['source', 'target', 'both']) 55 | parser.add_argument('-mins', '--min_cluster_size', 56 | default=config.min_cluster_size, 57 | help='Clusters with fewer elements won\'t get filtered' + 58 | ' (default: %(default)s)', 59 | metavar='', type=int) 60 | parser.add_argument('-t', '--threshold', default=config.threshold, 61 | help='Entropy threshold (default: %(default)s)', 62 | metavar='', type=int) 63 | parser.add_argument('-cm', '--clustering_method', 64 | default=config.clustering_method, 65 | help='Mean shift recommended (choices: %(choices)s)', 66 | metavar='', choices=['kmeans', 'mean_shift']) 67 | parser.add_argument('-bw', '--bandwidth', default=config.bandwidth, 68 | help='Mean shift bandwidth (default: %(default)s)', 69 | metavar='', type=float) 70 | parser.add_argument('-f', '--use_faiss', default=config.use_faiss, 71 | help='Whether to use faiss for GPU based clustering ' + 72 | '(default: %(default)s)', 73 | metavar='', type=bool) 74 | parser.add_argument('-maxal', '--max_avg_length', 75 | default=config.max_avg_length, 76 | help='Clusters with higher average sentence length' + 77 | 'won\'t get filtered (default: %(default)s)', 78 | metavar='', type=int) 79 | parser.add_argument('-maxml', '--max_medoid_length', 80 | default=config.max_medoid_length, 81 | help='Clusters with longer medoids won\'t get filtered' + 82 | ' (default: %(default)s)', 83 | metavar='', type=int) 84 | 85 | parser.parse_args(namespace=config) 86 | if config.load_config: 87 | config.load() 88 | config.save() 89 | 90 | filter_problems = { 91 | 'identity': Identity, 92 | 'avg_embedding': AverageWordEmbedding, 93 | 'sent2vec': Sent2vec, 94 | } 95 | 96 | problem = filter_problems[config.cluster_type](config) 97 | problem.run() 98 | 99 | 100 | if __name__ == "__main__": 101 | main() 102 | -------------------------------------------------------------------------------- /code/utils/config.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | import os 3 | 4 | 5 | # These can also be set as arguments via the command line. 6 | class Config: 7 | data_dir = 'data/DailyDialog/baseline' # Directory containing dataset. 8 | output_dir = 'data/DailyDialog/baseline/filtered_data/avg_embedding' 9 | load_config = None 10 | source_clusters = 0 11 | target_clusters = 0 12 | filter_split = 'full' # Which data split to filter. 13 | cluster_type = 'avg_embedding' 14 | unique = False # Whether to cluster only unique sentences. 15 | vocab_size = 16384 # Only used for average word embeddings. 16 | filter_type = 'both' 17 | min_cluster_size = 2 # Clusters with fewer elements won't get filtered. 18 | threshold = 1.1 # Entropy threshold for filtering. 19 | clustering_method = 'mean_shift' # Kmeans or mean_shift. 20 | bandwidth = 0.7 # Mean shift bandwidth. 21 | use_faiss = False # Whether to use the library for GPU based clustering. 22 | max_avg_length = 15 # Clusters with longer sentences won't get filtered. 23 | max_medoid_length = 50 # Clusters with longer medoids won't get filtered. 24 | project_path = os.path.join( 25 | os.path.dirname(os.path.abspath(__file__)), '..', '..') 26 | 27 | # Save this object to output dir. 28 | def save(self): 29 | out_dir = os.path.join(self.project_path, self.output_dir) 30 | if not os.path.exists(out_dir): 31 | os.makedirs(out_dir) 32 | 33 | file = open(os.path.join(out_dir, 'config'), 'wb') 34 | file.write(pickle.dumps(self.__dict__)) 35 | file.close() 36 | 37 | # Load from output dir. 38 | def load(self): 39 | load_config = os.path.join(self.project_path, self.load_config, 'config') 40 | file = open(load_config, 'rb') 41 | self.__dict__ = pickle.loads(file.read()) 42 | file.close() 43 | -------------------------------------------------------------------------------- /code/utils/utils.py: -------------------------------------------------------------------------------- 1 | # Close n files to write the processed data into. 2 | def close_n_files(files): 3 | for file_name in files: 4 | files[file_name].close() 5 | -------------------------------------------------------------------------------- /code/utils/visualization.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "application/javascript": [ 11 | "IPython.OutputArea.prototype._should_scroll = function(lines) {\n", 12 | " return false;\n", 13 | "}\n" 14 | ], 15 | "text/plain": [ 16 | "" 17 | ] 18 | }, 19 | "metadata": {}, 20 | "output_type": "display_data" 21 | } 22 | ], 23 | "source": [ 24 | "%%javascript\n", 25 | "IPython.OutputArea.prototype._should_scroll = function(lines) {\n", 26 | " return false;\n", 27 | "}" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": { 34 | "colab": { 35 | "base_uri": "https://localhost:8080/", 36 | "height": 122 37 | }, 38 | "colab_type": "code", 39 | "executionInfo": { 40 | "elapsed": 27098, 41 | "status": "ok", 42 | "timestamp": 1562670013345, 43 | "user": { 44 | "displayName": "Richard Csaky", 45 | "photoUrl": "https://lh6.googleusercontent.com/-wOnQ8NuCQqo/AAAAAAAAAAI/AAAAAAAAABQ/GcPUlAm-a98/s64/photo.jpg", 46 | "userId": "14062868679888781538" 47 | }, 48 | "user_tz": -120 49 | }, 50 | "id": "C_bdg07CY0fR", 51 | "outputId": "32f43021-8632-46ff-f5f0-4ca318d6cefe" 52 | }, 53 | "outputs": [], 54 | "source": [ 55 | "# Only run if you want to use google drive inside the notebook.\n", 56 | "from google.colab import drive\n", 57 | "drive.mount('/content/drive')" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": { 63 | "colab_type": "text", 64 | "id": "-4T8h7psXxMI" 65 | }, 66 | "source": [ 67 | "# Parameters" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": 2, 73 | "metadata": { 74 | "cellView": "both", 75 | "colab": {}, 76 | "colab_type": "code", 77 | "id": "obzOs3Y1X6qm" 78 | }, 79 | "outputs": [], 80 | "source": [ 81 | "#@title Path to directory containing filtered files and config file\n", 82 | "DIR = \"data/DailyDialog/baseline/filtered_data/\" #@param {type:\"string\"}" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": { 88 | "colab_type": "text", 89 | "id": "AnLdGG7LSd1B" 90 | }, 91 | "source": [ 92 | "# Setup\n", 93 | "Run some setup code and define the functions that will be used later." 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 3, 99 | "metadata": { 100 | "colab": { 101 | "base_uri": "https://localhost:8080/", 102 | "height": 316 103 | }, 104 | "colab_type": "code", 105 | "executionInfo": { 106 | "elapsed": 652, 107 | "status": "error", 108 | "timestamp": 1562671037191, 109 | "user": { 110 | "displayName": "Richard Csaky", 111 | "photoUrl": "https://lh6.googleusercontent.com/-wOnQ8NuCQqo/AAAAAAAAAAI/AAAAAAAAABQ/GcPUlAm-a98/s64/photo.jpg", 112 | "userId": "14062868679888781538" 113 | }, 114 | "user_tz": -120 115 | }, 116 | "id": "eoZ4rowjSrbu", 117 | "outputId": "0e84048e-681f-41b6-c33f-2d54c9981f7a" 118 | }, 119 | "outputs": [], 120 | "source": [ 121 | "%matplotlib inline\n", 122 | "\n", 123 | "import matplotlib.pyplot as plt\n", 124 | "import os\n", 125 | "import matplotlib.pyplot as plt\n", 126 | "import numpy as np\n", 127 | "import operator\n", 128 | "\n", 129 | "from config import Config\n", 130 | "\n", 131 | "\n", 132 | "# Load config file from specified directory\n", 133 | "Config.load_config = DIR\n", 134 | "config = Config()\n", 135 | "config.load()\n", 136 | "\n", 137 | "plt.rcParams.update({'font.size': 14})" 138 | ] 139 | }, 140 | { 141 | "cell_type": "code", 142 | "execution_count": 4, 143 | "metadata": { 144 | "colab": {}, 145 | "colab_type": "code", 146 | "id": "kejzW0VnSP0L" 147 | }, 148 | "outputs": [], 149 | "source": [ 150 | "# Visualization function for the clustering data.\n", 151 | "def _visualize(file, tag, fig_list):\n", 152 | " '''\n", 153 | " Params:\n", 154 | " :file: Clustering file, from which to read data.\n", 155 | " :tag: Can be 'Source' or 'Target'.\n", 156 | " :fig_list: A list containing the plots, which we will draw.\n", 157 | " '''\n", 158 | " sentence_entropy = []\n", 159 | " entropies_all = []\n", 160 | " entropies = []\n", 161 | " sentence_cl_size = []\n", 162 | " cl_sizes_all = []\n", 163 | " cl_sizes = []\n", 164 | " lengths = []\n", 165 | "\n", 166 | " for line in file:\n", 167 | " [sentence, entropy, cl_size] = line.split(';')\n", 168 | " entropy = float(entropy)\n", 169 | " cl_size = int(cl_size)\n", 170 | "\n", 171 | " # Populate the lists.\n", 172 | " sentence_entropy.append([sentence, entropy])\n", 173 | " entropies_all.extend([entropy] * cl_size)\n", 174 | " entropies.append(entropy)\n", 175 | " sentence_cl_size.append([sentence, cl_size])\n", 176 | " cl_sizes_all.extend([cl_size] * cl_size)\n", 177 | " cl_sizes.append(cl_size)\n", 178 | " lengths.append(len(sentence.split()))\n", 179 | "\n", 180 | " # Draw the plots, and set properties.\n", 181 | " fig_list[0].plot(sorted(entropies_all))\n", 182 | " fig_list[0].set_xlabel('Sentence no.')\n", 183 | " fig_list[0].set_ylabel('Entropy')\n", 184 | " #fig_list[0].axis([0, 90000, -0.2, 9])\n", 185 | "\n", 186 | " fig_list[1].plot(sorted(cl_sizes_all))\n", 187 | " fig_list[1].set_xlabel('Sentence no.')\n", 188 | " fig_list[1].set_ylabel('Cluster size')\n", 189 | " #fig_list[1].axis([0, 90000, -0.2, 500])\n", 190 | "\n", 191 | " fig_list[2].scatter(np.array(cl_sizes), np.array(entropies))\n", 192 | " fig_list[2].set_xlabel('Cluster size')\n", 193 | " fig_list[2].set_ylabel('Entropy')\n", 194 | " #fig_list[2].axis([0, 320, -0.2, 9])\n", 195 | "\n", 196 | " fig_list[3].scatter(np.array(lengths), np.array(entropies))\n", 197 | " fig_list[3].set_xlabel('No. of words in utterance')\n", 198 | " fig_list[3].set_ylabel('Entropy')\n", 199 | " #fig_list[3].axis([-0.2, 30, -0.2, 10])\n", 200 | "\n", 201 | " # Sort the sentence lists.\n", 202 | " sent_ent = sorted(sentence_entropy, key=operator.itemgetter(1), reverse=True)\n", 203 | " sent_cl = sorted(sentence_cl_size, key=operator.itemgetter(1), reverse=True)\n", 204 | " return sent_ent, sent_cl" 205 | ] 206 | }, 207 | { 208 | "cell_type": "code", 209 | "execution_count": 5, 210 | "metadata": { 211 | "colab": {}, 212 | "colab_type": "code", 213 | "id": "jetWP3UaXH6q" 214 | }, 215 | "outputs": [], 216 | "source": [ 217 | "# Main function to visualize clustering/filtering results.\n", 218 | "def data_visualization():\n", 219 | " # Open the clustering files.\n", 220 | " source_cl_entropies = open(os.path.join(config.project_path, config.output_dir, 'fullSource_cluster_entropies.txt'))\n", 221 | " target_cl_entropies = open(os.path.join(config.project_path, config.output_dir, 'fullTarget_cluster_entropies.txt'))\n", 222 | "\n", 223 | " # Set up matplotlib.\n", 224 | " plt.close('all')\n", 225 | " fig, ((ax1, ax2), (ax3, ax4), (ax5, ax6), (ax7, ax8)) = plt.subplots(nrows=4, ncols=2)\n", 226 | " fig.set_size_inches(13, 20)\n", 227 | "\n", 228 | " # Call the actual visualization function for source and target data.\n", 229 | " source_entropies, source_cl_sizes = _visualize(source_cl_entropies,\n", 230 | " 'Source',\n", 231 | " [ax1, ax3, ax5, ax7])\n", 232 | " target_entropies, target_cl_sizes = _visualize(target_cl_entropies,\n", 233 | " 'Target',\n", 234 | " [ax2, ax4, ax6, ax8])\n", 235 | " plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0)\n", 236 | "\n", 237 | " source_cl_entropies.close()\n", 238 | " target_cl_entropies.close()" 239 | ] 240 | }, 241 | { 242 | "cell_type": "code", 243 | "execution_count": 10, 244 | "metadata": { 245 | "colab": {}, 246 | "colab_type": "code", 247 | "id": "hfgy52_TXMQl" 248 | }, 249 | "outputs": [], 250 | "source": [ 251 | "def print_clusters(tag, top_clusters):\n", 252 | " clusters = {}\n", 253 | " cluster_element_lengths = {}\n", 254 | "\n", 255 | " with open(os.path.join(config.project_path, config.output_dir, 'full{}_cluster_elements.txt'.format(tag))) as file:\n", 256 | " for line in file:\n", 257 | " [source, source_cl_target, target_cl] = line.split('<=====>')\n", 258 | "\n", 259 | " if tag == 'Source':\n", 260 | " source_cl = source.split(';')[1]\n", 261 | " target_cl_ind = target_cl.split(':')[1].strip('\\n')\n", 262 | " source = source_cl_target.split('=')[0]\n", 263 | " target = source_cl_target.split('=')[1]\n", 264 | " cluster_element_lengths[source_cl] = \\\n", 265 | " cluster_element_lengths.get(source_cl, 0) + len(source.split())\n", 266 | " clusters[source_cl] = [*clusters.get(source_cl, []), source]\n", 267 | "\n", 268 | " else:\n", 269 | " target_cl = source.split(';')[1]\n", 270 | " target = source_cl_target.split('=')[0]\n", 271 | " cluster_element_lengths[target_cl] = \\\n", 272 | " cluster_element_lengths.get(target_cl, 0) + len(target.split())\n", 273 | " clusters[target_cl] = [*clusters.get(target_cl, []), target]\n", 274 | "\n", 275 | " with open(os.path.join(config.project_path, config.output_dir, 'full{}_cluster_entropies.txt'.format(tag))) as file:\n", 276 | " entropies = {}\n", 277 | " for line in file:\n", 278 | " [medoid, entropy, size] = line.split(';')\n", 279 | " entropies[medoid] = float(entropy)\n", 280 | "\n", 281 | " num_removed = 0\n", 282 | " num_all = 0\n", 283 | " for medoid in cluster_element_lengths:\n", 284 | " num_all += len(clusters[medoid])\n", 285 | " if ((cluster_element_lengths[medoid] / len(clusters[medoid]) if\n", 286 | " len(clusters[medoid]) > 0 else 1) > 1000 or\n", 287 | " len(medoid.split()) > 1000 or\n", 288 | " entropies[medoid] < config.threshold):\n", 289 | " num_removed += len(clusters[medoid])\n", 290 | "\n", 291 | " #print(num_removed / num_all)\n", 292 | " for _, medoid in zip(range(top_clusters),\n", 293 | " sorted(list(clusters), key=lambda x: entropies[x],\n", 294 | " reverse=True)):\n", 295 | " print('=====================================================')\n", 296 | " #print('{}& {} & {} \\\\\\\\'.format(list(set(clusters[medoid]))[0], len(clusters[medoid]), str(entropies[medoid])[:4]))\n", 297 | " print('Center: {}'.format(medoid))\n", 298 | " print('Entropy: {}'.format(entropies[medoid]))\n", 299 | " print('Size: {}'.format(len(clusters[medoid])))\n", 300 | " if len(clusters[medoid]) < 1000:\n", 301 | " print('Elements: \\n{}\\n\\n'.format('\\n'.join(set(clusters[medoid]))))" 302 | ] 303 | }, 304 | { 305 | "cell_type": "markdown", 306 | "metadata": { 307 | "colab_type": "text", 308 | "id": "hItN-dJISP0N" 309 | }, 310 | "source": [ 311 | "# Visualize the clustering.\n", 312 | "Graphs on the left are about source data, and on the right the target data.\n", 313 | "* First the entropy of all the utterances in the dataset is plotted.\n", 314 | "* Second each sentence's cluster's size is plotted (for all sentences in the dataset).\n", 315 | "* Third the entropy and cluster size of all clusters is plotted\n", 316 | "* Finally the relationship between the entropy of an utterance and the number of words in it is plotted." 317 | ] 318 | }, 319 | { 320 | "cell_type": "code", 321 | "execution_count": 15, 322 | "metadata": { 323 | "colab": {}, 324 | "colab_type": "code", 325 | "id": "EKGlzOI6SP0O", 326 | "outputId": "52151bf6-100e-4905-c798-669cd7fd3d67", 327 | "scrolled": true 328 | }, 329 | "outputs": [ 330 | { 331 | "data": { 332 | "image/png": "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\n", 333 | "text/plain": [ 334 | "
" 335 | ] 336 | }, 337 | "metadata": { 338 | "needs_background": "light" 339 | }, 340 | "output_type": "display_data" 341 | } 342 | ], 343 | "source": [ 344 | "data_visualization()" 345 | ] 346 | }, 347 | { 348 | "cell_type": "markdown", 349 | "metadata": { 350 | "colab_type": "text", 351 | "collapsed": true, 352 | "id": "I3m_tPouSP0U" 353 | }, 354 | "source": [ 355 | "# Print some clusters\n", 356 | "Let's see the unique elements of the clusters with highest entropy." 357 | ] 358 | }, 359 | { 360 | "cell_type": "code", 361 | "execution_count": 13, 362 | "metadata": { 363 | "colab": {}, 364 | "colab_type": "code", 365 | "id": "B-y1cyabSP0V", 366 | "outputId": "02f5b184-a729-4089-f788-64d1ef39933d", 367 | "scrolled": true 368 | }, 369 | "outputs": [ 370 | { 371 | "name": "stdout", 372 | "output_type": "stream", 373 | "text": [ 374 | "=====================================================\n", 375 | "Center: so dick how about getting some coffee for tonight ?\n", 376 | "Entropy: 2.3204735011414868\n", 377 | "Size: 23\n", 378 | "Elements: \n", 379 | "i do not care the brand as long as it works well .\n", 380 | "no the steak was recommended but it is not very fresh .\n", 381 | "i have this list of stuff that i need and i only have half the dough .\n", 382 | "half the dough huh . well . how would you like to earn the other half ?\n", 383 | "would you care for a drink before you order ?\n", 384 | "yes i have many things to buy . i would like to choose the cleaning milk first .\n", 385 | "i want to buy the toothpaste the brand of jiajieshi .\n", 386 | "not today honey . do n't eat too much ice cream .\n", 387 | "sounds good . what about shampoo ? i would like to buy the product that prevents scurf .\n", 388 | "yes very . believe it or not it will cost you more than one hundred dollars .\n", 389 | "mom can i have some ice cream ?\n", 390 | "i want to buy more books .\n", 391 | "i just happen to have a question for you guys . why do the chinese cook the vegetables ? you see what i mean is that most vitamin are destroyed when heated .\n", 392 | "is that expensive ?\n", 393 | "i need a packet of cigarettes please .\n", 394 | "yes we charge 50 cents for water .\n", 395 | "mom can i have one more piece of cake ?\n", 396 | "where is your store located ?\n", 397 | "peter go and tidy up your toys now .\n", 398 | "i think i going to need some iced water too . is there an extra charge for that ?\n", 399 | "no sweat . it s a piece of cake .\n", 400 | "may i have a cookie ?\n", 401 | "that 's fine . could you give me some more napkins too ?\n", 402 | "\n", 403 | "\n", 404 | "=====================================================\n", 405 | "Center: come on you can at least try a little besides your cigarette .\n", 406 | "Entropy: 2.265710144725459\n", 407 | "Size: 26\n", 408 | "Elements: \n", 409 | "but i already tried my best .\n", 410 | "wow i ca n't thank you enough .\n", 411 | "thanks a lot . that 's the favor i was going to ask you for .\n", 412 | "i am wiped out . thank you .\n", 413 | "i 'm sorry marry is out right now .\n", 414 | "ok . but i 'm not familiar . i do n't know the beginning part .\n", 415 | "forgive you for what ?\n", 416 | "oh yeah ? are you joking ?\n", 417 | "16 divided by 2 . what 's the answer ?\n", 418 | "i m afraid not . i apologize .\n", 419 | "no ! are you kidding me ?\n", 420 | "jim could you do me a favor ?\n", 421 | "yes i believe they are . here are something that might interest you .\n", 422 | "i 'm afraid there 's been a mistake .\n", 423 | "how much does it cost ?\n", 424 | "i 'm mad ! i said now ! turn off the tv and do it now .\n", 425 | "never ! but thank you for inviting me .\n", 426 | "i 'm sorry sir . what seems to be the trouble ?\n", 427 | "let 's go !\n", 428 | "dad can you just tell me what it means ? i 'm too lazy .\n", 429 | "what time should i call back ?\n", 430 | "you 've got it . tell me a little bit about what you might be wanted .\n", 431 | "thanks for helping me out . i really appreciate it .\n", 432 | "thank you . maybe we can sing a song together . would you like to sing with me ?\n", 433 | "thanks a lot .\n", 434 | "i prefer glossy . how much ?\n", 435 | "\n", 436 | "\n", 437 | "=====================================================\n", 438 | "Center: look next time get yourself some comfy shoes . you re gonna come back again with me aren t you ?\n", 439 | "Entropy: 2.187126559186671\n", 440 | "Size: 67\n", 441 | "Elements: \n", 442 | "would you mind waiting a while ?\n", 443 | "dad . here comes another bus .\n", 444 | "i 'll put all that into the bag for you .\n", 445 | "peter wash your hands first and then have some dessert .\n", 446 | "when is a good time to catch him ?\n", 447 | "i do n't know about all of that but think about it their business gets free publicity !\n", 448 | "did somebody hit you ? or did you just fall ?\n", 449 | "so dick how about getting some coffee for tonight ?\n", 450 | "of course not ! i 'm telling the truth .\n", 451 | "it looks to be a nice day today .\n", 452 | "jessie i m afraid i can t come back home for dinner tonight .\n", 453 | "dad but i do n't want to share a room with peter . he snores every night .\n", 454 | "oh great ! it 's delicious . you see i am already putting on weight . there is one thing i do n't like however msg .\n", 455 | "do you mind if i smoke ?\n", 456 | "it runs every 15 minutes . you must have missed it .\n", 457 | "oh no get off it . it wasn t such a killer class . you just have to get into it . like they say no pain no gain .\n", 458 | "oh do n't let that worry you . if that were true china would n't have such a large population .\n", 459 | "oh i almost forgot . it 's my mum 's birthday saturday . i need to get her some more chanel . could you get me the 1 . 7 ounce bottle of chanel cologne ?\n", 460 | "mom . i have to go school shopping . there 's only one more week left .\n", 461 | "i swear i m going to kill you for this .\n", 462 | "well i want a fillet steak medium but my little girl does n't care for steak . could she have something else instead ?\n", 463 | "are things still going badly with your houseguest ?\n", 464 | "what 's wrong with msg ? it helps to bring out the taste of the food .\n", 465 | "mrs . smith has not checked in yet .\n", 466 | "which do you prefer color or black and white ?\n", 467 | "honey you can ask him to be quite . otherwise you may punish him and tell him to stand out of the room right ?\n", 468 | "what happened peter ? did you have a fight ?\n", 469 | "leo i really think you re beating around the bush with this guy . i know he used to be your best friend in college but i really think it s time to lay down the law .\n", 470 | "ok i 'm sorry it took so long .\n", 471 | "oh honey i 'm so sorry we do n't have enough space for you to have your own room .\n", 472 | "no he 's perfectly harmless . and he 's not afraid of strangers either . here hold him .\n", 473 | "the movie company has to pay them ?\n", 474 | "you can use this . it has special effect for keeping your face moisturized . it has this lotion as a gift attached .\n", 475 | "i used your computer . and i m afraid i ve erased your personal files accidentally .\n", 476 | "can you leave a message for her to call her office ?\n", 477 | "may i sit here ?\n", 478 | "i wonder how all the businesses in the area feel about that .\n", 479 | "he stepped out of the office for a little while .\n", 480 | "isn t he the best instructor ? i think he s so hot . wow ! i really feel energized don t you ?\n", 481 | "sure we are off singing road close to the bank .\n", 482 | "i 'm looking for an engagement ring for my girlfriend . i have an idea of what she likes but i want to surprise her with something special too .\n", 483 | "good evening . welcome to cherry 's . do you have a reservation ?\n", 484 | "getting worse . now he s eating me out of house and home . i ve tried talking to him but it all goes in one ear and out the other . he makes himself at home which is fine . but what really gets me is that yesterday he walked into the living room in the raw and i had company over ! that was the last straw .\n", 485 | "the phone has been ringing off the hook today .\n", 486 | "i am in no particular hurry .\n", 487 | "you are in luck . we just receive a shipment of several different styles of white purses .\n", 488 | "we ca n't go that way the road is blocked for the next few days .\n", 489 | "oh honey you made a mistake .\n", 490 | "i 'm looking for some blush . do you still have some in peach rose ?\n", 491 | "you stepped on my foot !\n", 492 | "ahahah ! what is that thing on your couch ! it just moved !\n", 493 | "sam you ve got to forgive me .\n", 494 | "mom just ten more minutes . the show is going to be over soon .\n", 495 | "they will be ready at noon tomorrow . each negative develops one print right ?\n", 496 | "now we 're going to draw an apple in your sketch book . what do we use ?\n", 497 | "i believe you have charged me twice for the same thing . look the figure of 6 . 5 dollar appears here then again here .\n", 498 | "go straight up zhongshan road and you will see our sign on your right after you pass the museum .\n", 499 | "excuse me i 've been waiting here for 10 minutes . do you know how often does no . 636 run ?\n", 500 | "kata ! you 've got a beautiful singing voice . you hit the high notes perfectly .\n", 501 | "can you connect me to mary . smith hotel room ?\n", 502 | "they must be popular again this season .\n", 503 | "sorry i ca n't sing the song .\n", 504 | "i can t believe it ! i have all my important personal documents stored in that computer . it s no laughing matter .\n", 505 | "you have a pet lizard ? somehow i never would have imagined that .\n", 506 | "oh ! sorry to hear that . this is quite unusual as we have steak from the market every day .\n", 507 | "not back home for dinner again ? that s the third time this week !\n", 508 | "come on you can at least try a little besides your cigarette .\n", 509 | "\n", 510 | "\n", 511 | "=====================================================\n", 512 | "Center: what s wrong with that ? cigarette is the thing i go crazy for .\n", 513 | "Entropy: 2.1571675430303348\n", 514 | "Size: 57\n", 515 | "Elements: \n", 516 | "can i help you sir what do you need ?\n", 517 | "yes sir . i 'll bring it over . have you decided what you 'd like sir ?\n", 518 | "are you sure ?\n", 519 | "yes we shall . what size do you like ?\n", 520 | "great i 'll take one .\n", 521 | "yes it is . and develop them as glossy as possible .\n", 522 | "sure just ask . what can i do for you ?\n", 523 | "dad how can we get to the zoo ?\n", 524 | "how about this one ? it is wellknown for the effect of removing scurf .\n", 525 | "where are you going ?\n", 526 | "i told you i m sorry . what can i do to make it up to you ?\n", 527 | "no mom . i did n't .\n", 528 | "all right . what is your type of skin ?\n", 529 | "what can i do for you ?\n", 530 | "oh ok where do i get off ?\n", 531 | "do i have to memorize it ?\n", 532 | "do you need money or what ?\n", 533 | "welcome . may i help you ?\n", 534 | "this is how you turn on the computer .\n", 535 | "when do you need them sir ?\n", 536 | "well how long will it be ?\n", 537 | "it is difficult .\n", 538 | "i like red .\n", 539 | "yes it is .\n", 540 | "dad can you help me ?\n", 541 | "depends ? depends on what ?\n", 542 | "of course sir no problem .\n", 543 | "let me see . it depends .\n", 544 | "no is it difficult ?\n", 545 | "sure where are you ?\n", 546 | "we can take a bus there .\n", 547 | "er . . . how about this one ?\n", 548 | "how many of you please ?\n", 549 | "so what ? it is not fresh and i 'm not happy about it .\n", 550 | "do i have to change ?\n", 551 | "well the 4 x 6 is fine .\n", 552 | "ok . let 's do it together .\n", 553 | "may i help you find something sir ?\n", 554 | "no . the museum is the terminal of this bus .\n", 555 | "dad may i have a room of my own ?\n", 556 | "dad i want to draw with crayons can i ?\n", 557 | "what for ?\n", 558 | "what kind of food do you like ?\n", 559 | "may i help you madam ?\n", 560 | "may i help you sir ?\n", 561 | "no we do n't .\n", 562 | "let 's see . from here you have to take the 278 bus .\n", 563 | "i see thank you . by the way is this the right bus for the museum ?\n", 564 | "how about this one ?\n", 565 | "do i have a choice ? uh . that 's a no . what can i do ?\n", 566 | "i do n't know how to do it .\n", 567 | "thank you for your compliment . but you are exaggerating . i think you are destined to be a singer . you have the best voice !\n", 568 | "i would like to have a roll developed .\n", 569 | "i think so .\n", 570 | "is it a newcomer ?\n", 571 | "yes i 'd like to . it 's my honor . let 's pick a song .\n", 572 | "yes . thank you .\n", 573 | "\n", 574 | "\n", 575 | "=====================================================\n", 576 | "Center: but your american ?\n", 577 | "Entropy: 2.0\n", 578 | "Size: 4\n", 579 | "Elements: \n", 580 | "have you heard about our special promotion this month ? if you purchase at least 18 dollar 50 cents in any elizabeth arden products you will receive this black poke with a sample of lipstick mascara and two shades of white shadow .\n", 581 | "we have all shapes sizes qualities and price ranges do you know about the four cs of picking a diamond ?\n", 582 | "wow that sounds like a bargain . i 'm running low on facial moisturizer and toner . could you ring those up for me too along with the blush ?\n", 583 | "well my price range is a 5000 dollars to 7000 dollars i 'm looking for a marquise cut on the wide band .\n", 584 | "\n", 585 | "\n", 586 | "=====================================================\n", 587 | "Center: oh no get off it . it wasn t such a killer class . you just have to get into it . like they say no pain no gain .\n", 588 | "Entropy: 1.9877733714879842\n", 589 | "Size: 13\n", 590 | "Elements: \n", 591 | "i 'd be glad to . do you need anything else ?\n", 592 | "dad how do you say this word ?\n", 593 | "coffee ? i don t honestly like that kind of stuff .\n", 594 | "certainly sir .\n", 595 | "you don t have to explain . suit yourself .\n", 596 | "i understand .\n", 597 | "i think that they get a pretty good payoff .\n", 598 | "sword say it sword .\n", 599 | "what s wrong ? didn t you think it was fun ? !\n", 600 | "is everything to your satisfaction ?\n", 601 | "sure . do you need anything else ?\n", 602 | "what does this word mean ?\n", 603 | "anything else ?\n", 604 | "\n", 605 | "\n", 606 | "=====================================================\n", 607 | "Center: oh do n't let that worry you . if that were true china would n't have such a large population .\n", 608 | "Entropy: 1.8220931167465637\n", 609 | "Size: 54\n", 610 | "Elements: \n", 611 | "my car has a problem starting . could you please take a look at it for me ?\n", 612 | "oh that 's right . they 're filming a movie up there are n't they ?\n", 613 | "are you going to the annual party ? i can give you a ride if you need one .\n", 614 | "if you are in a hurry you should take a taxi .\n", 615 | "and do you want the glossy or matted finish ?\n", 616 | "yes a roll of kodak film please .\n", 617 | "would you like to take a look at the menu sir ?\n", 618 | "hurry up will you ?\n", 619 | "i apologize . you have my word i ll spend some time with you on the weekend . i promise .\n", 620 | "the last one is black and white all the rest should need color .\n", 621 | "can you please tell me where you are located ?\n", 622 | "you pay when you pick them up . i do n't need a deposit for just one roll of film .\n", 623 | "could i have my bill please ?\n", 624 | "his name is grunt . come closer and i 'll properly introduce you .\n", 625 | "no he is at work now .\n", 626 | "did you think it was n't real ? that 's my pet lizard .\n", 627 | "i think so . are n't the four cs cut clarity carat and color .\n", 628 | "what s wrong with that ? cigarette is the thing i go crazy for .\n", 629 | "i 'm looking for a white purse as a gift . could you show what you have in stock ?\n", 630 | "can you send a cab to pick me up ?\n", 631 | "may i have his office phone number please ?\n", 632 | "you should get off at the first shi da stop .\n", 633 | "have you got change for a thousand ?\n", 634 | "when will she be back ?\n", 635 | "i 'm not sure . but i 'll get a table ready as fast as i can .\n", 636 | "no problem . do you need another film ?\n", 637 | "sorry i 'm late .\n", 638 | "how was your test ?\n", 639 | "no it 's quite simple . when you get on just ask the bus driver when to pay the fare and where you want to get off .\n", 640 | "fine . let 's get on . oh no judy ! get off the bus quickly !\n", 641 | "i m sorry . our company has just opened . there are always too many things to handle . you know that .\n", 642 | "no honey it 's easy if you know the way .\n", 643 | "excuse me i 'm a little lost . which bus do i take to get to shi da ?\n", 644 | "mom can i have more allowance ?\n", 645 | "does this bus go there ?\n", 646 | "never mind . you can follow me . i 'll sing the first part .\n", 647 | "i 'd like to talk to mr . white please ?\n", 648 | "oh yes that is a beautiful color . it has been very popular blush this season . i have two left .\n", 649 | "yes how do i get to your shop from chilin ?\n", 650 | "wow . this is nice . i 'll take this one . i guess if she does n't like it she can return it right ?\n", 651 | "no problem . do you want 3 x 5 or 4 x 6 ?\n", 652 | "good let 's go for a drive .\n", 653 | "i 'm sorry sir . do you wish to try something else ? that would be on the house of course .\n", 654 | "okay i 'll get the car out of the garage .\n", 655 | "it seems you got here at good time . do you have a bus schedule ?\n", 656 | "yes i do . you can buy a bus schedule in a news stand .\n", 657 | "let 's step in dad .\n", 658 | "mike 's mechanics . can i help you ?\n", 659 | "take a bus then . it will only cost you 5 dollars .\n", 660 | "can i take your order now or do you still want to look at the menu ?\n", 661 | "robert is not available at the moment .\n", 662 | "i hope they will come out well . when should i pick them up ?\n", 663 | "only 15 nt per section . oh look that is your bus .\n", 664 | "would you like a lift home ?\n", 665 | "\n", 666 | "\n", 667 | "=====================================================\n", 668 | "Center: a glass of qingdao beer .\n", 669 | "Entropy: 1.584962500721156\n", 670 | "Size: 3\n", 671 | "Elements: \n", 672 | "yes i would also like some sweet and sour sauce and pepper .\n", 673 | "certainly . how about spaghetti with clams and shrimps .\n", 674 | "do i owe you anything for the sauce pepper and napkins ?\n", 675 | "\n", 676 | "\n", 677 | "=====================================================\n", 678 | "Center: yes sir . i 'll bring it over . have you decided what you 'd like sir ?\n", 679 | "Entropy: 1.584962500721156\n", 680 | "Size: 3\n", 681 | "Elements: \n", 682 | "ok .\n", 683 | "the 4 x6 will be ok . thanks .\n", 684 | "ok thanks . . .\n", 685 | "\n", 686 | "\n", 687 | "=====================================================\n", 688 | "Center: i am wiped out . thank you .\n", 689 | "Entropy: 1.0\n", 690 | "Size: 2\n", 691 | "Elements: \n", 692 | "somebody please answer the phone .\n", 693 | "look next time get yourself some comfy shoes . you re gonna come back again with me aren t you ?\n", 694 | "\n", 695 | "\n" 696 | ] 697 | } 698 | ], 699 | "source": [ 700 | "print_clusters(tag='Source', top_clusters=10)" 701 | ] 702 | }, 703 | { 704 | "cell_type": "code", 705 | "execution_count": null, 706 | "metadata": {}, 707 | "outputs": [], 708 | "source": [] 709 | } 710 | ], 711 | "metadata": { 712 | "colab": { 713 | "collapsed_sections": [ 714 | "hItN-dJISP0N", 715 | "I3m_tPouSP0U" 716 | ], 717 | "name": "visualization.ipynb", 718 | "provenance": [], 719 | "toc_visible": true, 720 | "version": "0.3.2" 721 | }, 722 | "kernelspec": { 723 | "display_name": "Python 3", 724 | "language": "python", 725 | "name": "python3" 726 | }, 727 | "language_info": { 728 | "codemirror_mode": { 729 | "name": "ipython", 730 | "version": 3 731 | }, 732 | "file_extension": ".py", 733 | "mimetype": "text/x-python", 734 | "name": "python", 735 | "nbconvert_exporter": "python", 736 | "pygments_lexer": "ipython3", 737 | "version": "3.6.6" 738 | } 739 | }, 740 | "nbformat": 4, 741 | "nbformat_minor": 1 742 | } 743 | -------------------------------------------------------------------------------- /docs/3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricsinaruto/NeuralChatbots-DataFiltering/2b8a2e848b089ff782ef4c3426af407be3c6f68b/docs/3d.png -------------------------------------------------------------------------------- /docs/cluster_examples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricsinaruto/NeuralChatbots-DataFiltering/2b8a2e848b089ff782ef4c3426af407be3c6f68b/docs/cluster_examples.png -------------------------------------------------------------------------------- /docs/example_responses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricsinaruto/NeuralChatbots-DataFiltering/2b8a2e848b089ff782ef4c3426af407be3c6f68b/docs/example_responses.png -------------------------------------------------------------------------------- /docs/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricsinaruto/NeuralChatbots-DataFiltering/2b8a2e848b089ff782ef4c3426af407be3c6f68b/docs/help.png -------------------------------------------------------------------------------- /docs/high_entropy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricsinaruto/NeuralChatbots-DataFiltering/2b8a2e848b089ff782ef4c3426af407be3c6f68b/docs/high_entropy.png -------------------------------------------------------------------------------- /docs/metrics_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricsinaruto/NeuralChatbots-DataFiltering/2b8a2e848b089ff782ef4c3426af407be3c6f68b/docs/metrics_table.png -------------------------------------------------------------------------------- /docs/other_datasets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricsinaruto/NeuralChatbots-DataFiltering/2b8a2e848b089ff782ef4c3426af407be3c6f68b/docs/other_datasets.png -------------------------------------------------------------------------------- /docs/uml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricsinaruto/NeuralChatbots-DataFiltering/2b8a2e848b089ff782ef4c3426af407be3c6f68b/docs/uml.png -------------------------------------------------------------------------------- /docs/visu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricsinaruto/NeuralChatbots-DataFiltering/2b8a2e848b089ff782ef4c3426af407be3c6f68b/docs/visu.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | sklearn 2 | clint 3 | matplotlib 4 | numpy 5 | requests -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | print('Installing requirements...') 5 | os.system('pip install -r requirements.txt') 6 | 7 | import requests 8 | import zipfile 9 | from clint.textui import progress 10 | 11 | 12 | def download_data(url, zipped_path, extract): 13 | # Open the url and download the data with progress bars. 14 | data_stream = requests.get(url, stream=True) 15 | 16 | with open(zipped_path, 'wb') as file: 17 | total_length = int(data_stream.headers.get('content-length')) 18 | for chunk in progress.bar(data_stream.iter_content(chunk_size=1024), 19 | expected_size=total_length / 1024 + 1): 20 | if chunk: 21 | file.write(chunk) 22 | file.flush() 23 | 24 | # Extract file. 25 | zip_file = zipfile.ZipFile(zipped_path, 'r') 26 | zip_file.extractall(extract) 27 | zip_file.close() 28 | 29 | 30 | print('Do you want to download all datasets used in the paper (116 MB)? (y/n)') 31 | if input() == 'y': 32 | if not os.path.exists('data'): 33 | os.mkdir('data') 34 | download_data('https://ricsinaruto.github.io/website/docs/Twitter.zip', 'data/Twitter.zip', 'data') 35 | download_data('https://ricsinaruto.github.io/website/docs/Cornell.zip', 'data/Cornell.zip', 'data') 36 | download_data('https://ricsinaruto.github.io/website/docs/DailyDialog.zip', 'data/DailyDialog.zip', 'data') 37 | 38 | print('Do you want to download all generated responses on the test set by the different models (7 MB)? (y/n)') 39 | if input() == 'y': 40 | download_data('https://ricsinaruto.github.io/website/docs/responses.zip', 'responses.zip', '') 41 | --------------------------------------------------------------------------------