|
1 | 1 | //! Word embeddings. |
2 | 2 |
|
| 3 | +use std::collections::HashMap; |
3 | 4 | use std::io::{Read, Seek, Write}; |
4 | 5 | use std::iter::Enumerate; |
5 | 6 | use std::mem; |
6 | 7 | use std::slice; |
7 | 8 |
|
8 | | -use ndarray::{Array1, ArrayViewMut1, Axis, CowArray, Ix1}; |
| 9 | +use ndarray::{s, Array1, Array2, ArrayViewMut1, ArrayViewMut2, Axis, CowArray, Ix1}; |
9 | 10 | use rand::{CryptoRng, RngCore, SeedableRng}; |
10 | 11 | use rand_chacha::ChaChaRng; |
11 | 12 | use reductive::pq::TrainPq; |
@@ -188,6 +189,68 @@ where |
188 | 189 | true |
189 | 190 | } |
190 | 191 |
|
| 192 | + /// Get a batch of embeddings. |
| 193 | + /// |
| 194 | + /// The embeddings of all `words` are computed and returned. This method also |
| 195 | + /// return a `Vec` indicating for each word if an embedding could be found. |
| 196 | + pub fn embedding_batch(&self, words: &[impl AsRef<str>]) -> (Array2<f32>, Vec<bool>) { |
| 197 | + let mut embeddings = Array2::zeros((words.len(), self.dims())); |
| 198 | + let found = self.embedding_batch_into(words, embeddings.view_mut()); |
| 199 | + (embeddings, found) |
| 200 | + } |
| 201 | + |
| 202 | + /// Get a batch of embeddings. |
| 203 | + /// |
| 204 | + /// The embeddings of all `words` are computed and written to `output`. A `Vec` is |
| 205 | + /// returned that indicates for each word if an embedding could be found. |
| 206 | + /// |
| 207 | + /// This method panics when `output` does not have the correct shape. |
| 208 | + pub fn embedding_batch_into( |
| 209 | + &self, |
| 210 | + words: &[impl AsRef<str>], |
| 211 | + mut output: ArrayViewMut2<f32>, |
| 212 | + ) -> Vec<bool> { |
| 213 | + assert_eq!( |
| 214 | + output.len_of(Axis(0)), |
| 215 | + words.len(), |
| 216 | + "Expected embedding matrix for batch size {}, got {}", |
| 217 | + words.len(), |
| 218 | + output.len_of(Axis(0)) |
| 219 | + ); |
| 220 | + assert_eq!( |
| 221 | + output.len_of(Axis(1)), |
| 222 | + self.dims(), |
| 223 | + "Expected embedding matrix for embeddings with dimensionality {}, got {}", |
| 224 | + self.dims(), |
| 225 | + output.len_of(Axis(1)) |
| 226 | + ); |
| 227 | + |
| 228 | + let mut found = vec![false; words.len()]; |
| 229 | + |
| 230 | + let mut word_indices: HashMap<_, Vec<_>> = HashMap::new(); |
| 231 | + for (idx, word) in words.iter().enumerate() { |
| 232 | + let indices = word_indices.entry(word.as_ref()).or_default(); |
| 233 | + indices.push(idx); |
| 234 | + } |
| 235 | + |
| 236 | + for (word, indices) in word_indices { |
| 237 | + // Look up the embedding for the first occurence of the word and |
| 238 | + // then copy to other occurences. |
| 239 | + let idx_first = indices[0]; |
| 240 | + if self.embedding_into(word, output.index_axis_mut(Axis(0), idx_first)) { |
| 241 | + found[idx_first] = true; |
| 242 | + |
| 243 | + for &idx in indices.iter().skip(1) { |
| 244 | + let (first, mut cur) = output.multi_slice_mut((s![idx_first, ..], s![idx, ..])); |
| 245 | + cur.assign(&first); |
| 246 | + found[idx] = true; |
| 247 | + } |
| 248 | + } |
| 249 | + } |
| 250 | + |
| 251 | + found |
| 252 | + } |
| 253 | + |
191 | 254 | /// Get the embedding and original norm of a word. |
192 | 255 | /// |
193 | 256 | /// Returns for a word: |
@@ -716,6 +779,25 @@ mod tests { |
716 | 779 | assert_eq!(target, embeds.embedding("idspispopd").unwrap()); |
717 | 780 | } |
718 | 781 |
|
| 782 | + #[test] |
| 783 | + fn embedding_batch_returns_correct_embeddings() { |
| 784 | + let embeds = test_embeddings(); |
| 785 | + let (lookups, found) = embeds.embedding_batch(&[ |
| 786 | + "Berlin", |
| 787 | + "Bremen", |
| 788 | + "Groningen", |
| 789 | + "Bremen", |
| 790 | + "Berlin", |
| 791 | + "Amsterdam", |
| 792 | + ]); |
| 793 | + |
| 794 | + assert_eq!(lookups.row(0), embeds.embedding("Berlin").unwrap()); |
| 795 | + assert_eq!(lookups.row(1), embeds.embedding("Bremen").unwrap()); |
| 796 | + assert_eq!(lookups.row(3), embeds.embedding("Bremen").unwrap()); |
| 797 | + assert_eq!(lookups.row(4), embeds.embedding("Berlin").unwrap()); |
| 798 | + assert_eq!(found, &[true, true, false, true, true, false]); |
| 799 | + } |
| 800 | + |
719 | 801 | #[test] |
720 | 802 | #[cfg(feature = "memmap")] |
721 | 803 | fn mmap() { |
|
0 commit comments