├── .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 · [](https://ctt.ac/E_jP6)
2 | [](https://www.aclweb.org/anthology/P19-1567) [](https://ricsinaruto.github.io/website/docs/acl_poster_h.pdf) [](https://github.com/ricsinaruto/Seq2seqChatbots) [](https://github.com/ricsinaruto/dialog-eval) [](https://github.com/ricsinaruto/NeuralChatbots-DataFiltering/wiki) [](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 |
--------------------------------------------------------------------------------