Skip to content

Commit c79324d

Browse files
danieldkDaniël de Kok
authored andcommitted
Add batch embedding lookups
This change adds the embedding_batch and embedding_batch_into methods. These methods look up the embeddings for multiple words. The motivation is threefold: 1. It provides the convenience of looking up multiple embeddings at once. 2. Words which occur more than once in the batch only need to be looked up once. 3. These method can avoid some allocations if the downstream user is concatenating the embeddings in a batch anyway.
1 parent a652bc2 commit c79324d

1 file changed

Lines changed: 83 additions & 1 deletion

File tree

src/embeddings.rs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
//! Word embeddings.
22
3+
use std::collections::HashMap;
34
use std::io::{Read, Seek, Write};
45
use std::iter::Enumerate;
56
use std::mem;
67
use std::slice;
78

8-
use ndarray::{Array1, ArrayViewMut1, Axis, CowArray, Ix1};
9+
use ndarray::{s, Array1, Array2, ArrayViewMut1, ArrayViewMut2, Axis, CowArray, Ix1};
910
use rand::{CryptoRng, RngCore, SeedableRng};
1011
use rand_chacha::ChaChaRng;
1112
use reductive::pq::TrainPq;
@@ -188,6 +189,68 @@ where
188189
true
189190
}
190191

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+
191254
/// Get the embedding and original norm of a word.
192255
///
193256
/// Returns for a word:
@@ -716,6 +779,25 @@ mod tests {
716779
assert_eq!(target, embeds.embedding("idspispopd").unwrap());
717780
}
718781

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+
719801
#[test]
720802
#[cfg(feature = "memmap")]
721803
fn mmap() {

0 commit comments

Comments
 (0)