Show HN: Word2vec Algorithm in ~100sloc with NumPy (github.com)
Here's a small demonstration of the fundamental aspects of the word-to-vec
algorithm. It's implemented in a single python script and depends only on a single text file for training.
It's not meant to be blazingly fast or anything, just a toy example to aid my understanding of how word vectors might be learnt from a corpus.
30 comments
[ 3.4 ms ] story [ 48.4 ms ] threadCould you explain why lines are processed independently?
Does it make a difference that some lines have fewer words than others?I would think that
using the sliding_window and flatting recipes at https://docs.python.org/3/library/itertools.html#itertools-r... would be more appropriate. Though using it directly would not handle the leading/trailing partial windows.I liked seeing the 'η' - I haven't yet used non-ASCII in my code!
The lines are processed independently as they are separate sentences. One line may be from one source, and the next from a completely different source. The problem with simply concatenating them is that the target words on the boundaries between sentences can end up with context words from unrelated sentences.
>I liked seeing the 'η' - I haven't yet used non-ASCII in my code!
Then you may enjoy this april fool's python issue I made https://github.com/python/cpython/issues/103172
Yeah, I like using utf-8 chars for mathematical symbols, it makes my brain hurt a little less when mapping between the two! I also like using ŷ for predictions in ML contexts as that's the canonical symbol used in the literature.
Ahh! I didn't understand that about the corpus. Thanks!
There is a min_frequency threshold for including words
Wouldn’t you want a max_frequency instead?
A lot of times infrequent words are more important than the frequent ones (eg titles vs articles)
It is also worth discussing the difference between the training corpus (the data that the word vectors are found from) and the inference corpus (the data that the word vectors are used on). If a word is uncommon in the inference corpus, but is common enough to be trained on the training corpus, then a vector is still generated and useful for the inference task.
Is the context considered “all appearances of the word”, or “text around the word”?
Also, are all positions/“areas” of the corpus considered evenly important except for their frequency?
In other words, ie position within the corpus considered at all? (eg. Usually titles are more relevant to a document then a random sentence or word in the middle of it)
For example in the sentence:
> Usually titles are more relevant to a document then a random sentence or word in the middle of it
If 'document' is the current target word, we select ["relevant", "to", "a"] and ["then, "a", "random"] as our left and right contexts. These context words and our target word, ["document"] form our 'positive' examples that we want to move closer together in the embedding space.
Position in the text is not used in this algorithm. You're right about titles being more relevant to the meaning of a document but the document isn't important: it's just a resource that we're using to learn which words occur together (and thus are semantically related). The algorithm steps through every single word as a target and thus will inevitably utilize words that were titles just as much as words in any other part of the text:)
Hope that helps!
Yes, very much
Thank you for such a great explanation, super clear :)
A word having many meanings would otherwise means its vectors are more "smeared" out and less accurate I guess. Although, I guess that might be good for these algorithms.
In practice, as you intuit, contextual embeddings are in fact useful for obtaining more precise word vectors. The example given in[0] is BERT's embeddings, though I would imagine that most NLP applications nowadays all use contextual embeddings since they're a strict improvement over static embeddings.
[0]. https://web.stanford.edu/~jurafsky/slp3/6.pdf (search for 'static embeddings')
So in the scenario that these are products which have IDs, and the container is a document that mentions these products together, you can reduce it to a key-value map of documentID-[list of product IDs], and then by selecting the vectors that have cosine similarity of .95-0.9999, you can get the object similar to that product but not that product. Using contextualized vectors, in the best case would give you things similar as well as the query product, but for every single instance, not just the single abstract entity.
With a high dimension vector (>50d, more usually 300d or 500d) one or two dimensions together will pick up a concept.
This is why word algebra like
Paris - France + Russia ~ Moscow
works with original Word2Vec vectors.
Language models are better at picking up context-dependent meaning, but word vectors still have good enough performance for many tasks.
The two changes I can imagine are: a) handling randomness requires more explicit rng estate handling, and b) wrapping the inner loop in a function so that it can be JIT-compiled.
The advantage to doing this is that you can then run your code on a GPU, getting you most of the way to 'blazingly fast.'
Fortran, on the other hand,...
Pithy readable implementations of core ideas have a lot of value. I don't see much value in code golfing besides having fun :)
The iterative code is busy and long, but allows to track exactly how the pretty trivial calculation is happening, down to elementary(-ish) operations.
The comprehension-based code is more declarative; it succinctly shows what is happening, in almost plain English, without the minute details cluttering out the purpose of the code.
For anyone who is not a Python beginner, but is an ML beginner, the shorter version is much more approachable, as it puts the subject matter more front-and-center.
(Imagine that every matrix multiplication would be written using explicit loops, instead of one "multiply" operation. Would it clarify linear algebra for you, or the other way around?)
It certainly depends on the audience. Interestingly, I had the opposite conclusion about Python beginners in my head before reaching this line!
I think it's more about the learner's prior background. Lately, I've mostly been helping friends who do a lot of scientific computing get started in ML. For that audience, the "nested loops" presentation is typically much easier to grok.
> (Imagine that every matrix multiplication would be written using explicit loops, instead of one "multiply" operation. Would it clarify linear algebra for you, or the other way around?)
Obviously "every" would be terrible. But there's a real question here if we flip "every" to "first". For a work-a-day mathematician who doesn't write code often, certainly not! For a work-a-day programmer who didn't take or doesn't remember linear algebra, the loopy version is probably worth showing once before moving on.
On a related note: I sometimes find folds easier to understand than loops. Other times find loops easier to understand than folds. I'm not particularly sure why. Probably having both versions stashed away and either exercising judgement based on the learner at hand -- or just showing both -- is the best option.
Anyways, now I have two ways of saying the same thing, which is always nice to have when teaching.
I've been using gensim for a few years for topic modeling. It works great with pyLDAvis for visualizations.
How does your implementation differ from the one by Radim?
I've used gensim's one and it seems great for practical use but I wanted to build an understanding of the underlying algorithm, so built my own from scratch. I was hoping others might learn from it, but it's not really a practical approach to constructing w2v embeddings.
How can you train the embedding without that? And if you can, is it a good or bad idea to prepare your embedding using this script first then chuck those weights into a bigger model (I think I know what the answer will be…)