Skip to Content
Course content

109: Word Embeddings in R

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Up until now, we've treated text as a collection of tokens—strings of characters that we clean, count, and filter. But if you're trying to build something that actually understands meaning, you'll quickly hit a wall. Let's say you're analyzing customer reviews for a high-end espresso machine, the "EspressoPro 3000." One customer writes that the coffee is "delicious," and another says it's "tasty." To a computer treating text as simple strings, those two words are as different as "coffee" and "airplane."

The Sparse Matrix Trap

The naive way to handle this is what I call the "Bag of Words" approach—using one-hot encoding or TF-IDF to create a giant matrix where every unique word gets its own column. I've seen plenty of engineers do this because it's intuitive. You just count how many times "delicious" appears in Review A versus Review B.

The problem is that this approach creates a mathematical desert. Your data becomes "sparse," meaning you have a matrix with thousands of columns, most of which are zeros. More importantly, you've stripped away all semantic relationship. In a one-hot encoded world, the distance between "delicious" and "tasty" is exactly the same as the distance between "delicious" and "broken." You're asking your model to learn the entire English language from scratch using only your specific dataset, which is a losing battle unless you have millions of reviews.

# The naive way (simplified)
# Creating a dummy matrix where words are independent dimensions
reviews <- c("The coffee is delicious", "The coffee is tasty")
tokens <- unlist(strsplit(reviews, " "))
unique_tokens <- unique(tokens)
naive_matrix <- matrix(0, nrow = 2, ncol = length(unique_tokens))
# ... filling the matrix ...
# Result: 'delicious' and 'tasty' are orthogonal vectors. 
# No relationship is captured.

Mapping Meaning via Pre-trained Vectors

The better way is to use word embeddings. Instead of giving each word its own column, we represent each word as a dense vector of floating-point numbers (usually 100 to 300 dimensions). These vectors aren't random; they are pre-trained on massive corpora like Wikipedia or Common Crawl using algorithms like Word2Vec or GloVe.

In this vector space, words that appear in similar contexts are placed close together. "Delicious" and "tasty" will have vectors that point in nearly the same direction. When you feed these into a model, the model doesn't have to learn what "tasty" means; it already knows it's a synonym for "delicious" because of the embedding. In R, while we have some native tools, I usually recommend the text package or using reticulate to tap into Python's gensim or spaCy, as the heavy lifting of embedding training happens outside of R's memory model.

# Using a conceptual embedding approach (via the 'text' package)
# install.packages("text")
library(text)

# We can get embeddings for our coffee reviews
# This maps "delicious" and "tasty" to similar coordinates in 300D space
embeddings <- textEmbed(texts = reviews, model = "bert-base-uncased")

# Now, if we calculate cosine similarity, 
# 'delicious' and 'tasty' will have a high score (e.g., 0.85)
# while 'delicious' and 'broken' will be low (e.g., 0.12)

The Trade-off: Precision vs. Memory

Now, you might wonder why we don't always use embeddings. There is a cost. One-hot encoding is computationally cheap and perfectly transparent—you know exactly which word triggered a result. Embeddings are "black boxes." When your model makes a decision based on a 300-dimensional vector, it's much harder to explain why to a stakeholder.

Then there's the memory overhead. Loading a pre-trained GloVe model can eat up several gigabytes of RAM. If you're running a lightweight script on a small VM, the sparse matrix might actually be the more pragmatic choice. But for any serious NLP task—sentiment analysis, document clustering, or semantic search—the "meaning" captured by embeddings far outweighs the memory cost. I'd rather deal with a heavy model than a model that thinks "excellent" and "great" are unrelated concepts.




📋 Practical Task

Semantic Similarity Matrix for EspressoPro Reviews

Your goal is to demonstrate the difference between string matching and semantic embedding. You will create a small set of coffee-related terms and calculate the similarity between them.

  • Create a character vector containing the following words: "espresso", "coffee", "caffeine", "bicycle", and "wrench".
  • Using the text package (or a similar embedding wrapper), generate embeddings for these five words.
  • Calculate a cosine similarity matrix for these vectors.
  • Identify the pair of words with the highest similarity score (excluding the word compared to itself) and the pair with the lowest.
  • Print the final similarity matrix to the console and verify that "espresso" is mathematically closer to "coffee" than it is to "bicycle".
Rating
0 0

There are no comments for now.

to be the first to leave a comment.