Text Analytics


Text preprocessing, feature representation (one-hot, count, TF-IDF, word embeddings, N-grams, hashing), similarity, POS tagging, entity extraction, topic extraction, spam-ham classification, sentiment analysis, and Transformers in NLP.

Topics in this chapter

  • Tokenization, Lower Casing, Stop Word Removal, Stemming and Lemmatization
  • One Hot Encoding, Count Vectorization, TF-IDF Vectorization
  • Word Embeddings, Generating N-Grams, Hash Vectorization
  • Finding Text Similarity
  • POS Tagging, Entity Extraction, Topic Extraction
  • Spam-Ham Classification
  • Sentiment Analysis
  • Transformer in NLP
  • Programs using NLTK

Text Preprocessing and Feature Representation

The Necessity of Numerical Representations

Raw text is unstructured, variable in length, and rich with linguistic nuance. For any predictive model — be it a classifier, a clustering algorithm, or a neural network — the first and most crucial step is to transform this amorphous sequence of characters into a fixed-size numerical vector that preserves as much relevant information as possible. The quality of this transformation often overshadows the choice of the downstream algorithm: a well-engineered feature representation can make a linear model competitive, while a poor one will cripple even the most sophisticated architecture.

Text Preprocessing: Cleaning and Normalizing

Before any numerical encoding, the raw string must be broken into atomic units — typically words — and stripped of noise that would inflate the feature space or obscure meaningful patterns. Using libraries such as NLTK (Natural Language Toolkit), we execute the following sequential operations:

Tokenization splits a document into a sequence of tokens, usually individual words or punctuation. NLTK's word_tokenize uses a rule-based approach that handles contractions, possessives, and sentence boundaries. For example:

"The students' projects, which aren't trivial, cost $50 each."

becomes ['The', 'students', "'", 'projects', ',', 'which', 'are', "n't", 'trivial', ',', 'cost', '$', '50', 'each', '.'].

While regular-expression tokenizers (RegexpTokenizer) offer finer control, word_tokenize is a reliable default.

from nltk.tokenize import word_tokenize
text = "The students' projects, which aren't trivial, cost $50 each."
tokens = word_tokenize(text)
print(tokens)

Normalization typically includes lowercasing all tokens. This not only reduces the vocabulary size by collapsing case variations ("Book" vs. "book") but also aligns with the fact that most downstream tasks are case-insensitive. Exceptions exist for named-entity recognition, where capitalization carries signal.

Stopword removal eliminates high-frequency function words (e.g., "the", "is", "and") that contribute little semantic content. NLTK provides a list of stopwords for many languages. Removing them compacts the representation and often improves signal-to-noise ratio, although modern contextual embeddings may retain them to preserve syntactic structure.

from nltk.corpus import stopwords
stop_words = set(stopwords.words('english'))
filtered_tokens = [w for w in tokens if w.lower() not in stop_words]

Stemming and lemmatization reduce inflectional forms to a common base. Stemming (e.g., the Porter stemmer in NLTK) applies crude heuristic rules ("running" → "run", "berries" → "berri") and is fast, but it can produce non-words. Lemmatization, using a vocabulary and morphological analysis ("am", "are", "is" → "be"), yields valid dictionary forms. For many applications, a lemmatizer like NLTK's WordNetLemmatizer is preferred because it preserves semantic consistency.

from nltk.stem import PorterStemmer, WordNetLemmatizer
stemmer = PorterStemmer()
lemmatizer = WordNetLemmatizer()

print(stemmer.stem('running'))    # 'run'
print(lemmatizer.lemmatize('running', pos='v'))  # 'run'
print(lemmatizer.lemmatize('better', pos='a'))   # 'good'

Additional cleaning steps — removing punctuation, numeric digits, HTML tags, and expanding contractions — are tailored to the corpus. After preprocessing, each document is reduced to a sequence of normalized tokens ready for vectorization.

One-Hot Encoding and Count Vectorization

The simplest representation is the bag-of-words (BoW) model, which treats a document as an unordered multiset of its tokens. Given a vocabulary V={w1,w2,,wV}V = \{w_1, w_2, \dots, w_{|V|}\} constructed from the entire corpus, each document dd is mapped to a vector x(d)RV\mathbf{x}^{(d)} \in \mathbb{R}^{|V|}.

In one-hot encoding (strictly, a binary BoW), the jj-th component is 11 if wjw_j appears in dd and 00 otherwise:

xj(d)=I[wjd].x_j^{(d)} = \mathbb{I}[\, w_j \in d\,].

While extremely sparse, one-hot vectors are suitable as input to naive Bayes classifiers that model the presence or absence of words.

Count vectorization extends the idea by recording the raw frequency fwj,df_{w_j,d} of each term:

xj(d)=fwj,d=count of wj in document d.x_j^{(d)} = f_{w_j,d} = \text{count of } w_j \text{ in document } d.

This representation, often implemented via scikit-learn's CountVectorizer, captures term intensity: a document that mentions "data" ten times likely values the concept more than one that mentions it once. However, raw counts inflate the influence of common words that appear frequently in every document, obscuring truly discriminative terms.

from sklearn.feature_extraction.text import CountVectorizer
corpus = ["The cat sat on the mat", "The dog sat on the log"]
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(corpus)
print(vectorizer.get_feature_names_out())
print(X.toarray())

TF-IDF Vectorization: Weighting Term Importance

Term Frequency–Inverse Document Frequency (TF-IDF) adjusts raw counts by penalizing words that appear in many documents and boosting rare, informative ones. It combines two statistics:

  • Term Frequency TF(t,d)TF(t, d): how often term tt occurs in document dd. A common formulation (sublinear scaling) is

{1+logft,dif ft,d>0,0otherwise.\begin{cases} 1 + \log f_{t,d} & \text{if } f_{t,d} > 0,\\ 0 & \text{otherwise}. \end{cases}

The logarithm dampens the effect of very high counts, acknowledging that the 100th occurrence adds less information than the first. - **Inverse Document Frequency** $IDF(t, D)$: $$IDF(t,D) = \log\frac{N}{1 + n_t},$$ where $N$ is the total number of documents and $n_t = |\{d \in D : t \in d\}|$ is the document frequency of $t$. The "$+1$" in the denominator prevents division by zero and is analogous to Laplace smoothing. A term that appears in every document yields $IDF \approx \log 1 = 0$, effectively nullifying its weight. The **TF-IDF score** for term $t$ in document $d$ is the product

TFIDF(t,d,D) = TF(t,d) \times IDF(t,D).

The resulting document vectors are often normalized to unit Euclidean length ($L_2$ normalization):

x_j^{(d)} = \frac{TFIDF(w_j,d,D)}{\sqrt{\sum_{k} TFIDF(w_k,d,D)^2}}.

*Economic and Analytical Intuition:* We can view TF-IDF through the lens of information economics. The TF component represents the *local supply* or *local utility* of a term within a specific document. The IDF component acts as a *scarcity premium* or *information rent*. Ubiquitous terms have high supply across the corpus, driving their marginal information value (IDF) to zero. Conversely, domain-specific or rare terms (e.g., "quantitative easing" or "EBITDA") command a high scarcity premium. Thus, TF-IDF effectively prices words based on their marginal contribution to distinguishing one document from another. ```python from sklearn.feature_extraction.text import TfidfVectorizer tfidf = TfidfVectorizer(sublinear_tf=True, stop_words='english') X_tfidf = tfidf.fit_transform(corpus) ``` ### N-Gram Expansion and Hash Vectorization The bag-of-words assumption discards word order, losing phrases like "New York" or negations like "not good". **N-gram features** capture contiguous sequences of $n$ tokens. For instance, adding bigrams ($n=2$) to a unigram vocabulary allows the model to distinguish "not good" from "good" alone. Scikit-learn's `CountVectorizer` accepts `ngram_range=(1,2)` to include both unigrams and bigrams. The vocabulary size grows quickly with $n$; careful pruning by minimum document frequency or feature selection is required. When the vocabulary becomes prohibitively large — for example, with character n-grams or streaming data — **hash vectorization** (the "hashing trick") maps each feature directly to a fixed-size vector of dimension $M$ using a hash function $h: \text{string} \to \{1,\dots,M\}$. Each occurrence of a token $t$ increments the component at index $h(t)$, possibly with a second hash function $h_2$ that determines the sign (±1) to mitigate collision bias:

x_{k}^{(d)} = \sum_{t \in d} \sum_{\text{occurrences}} \operatorname{sign}(h_2(t)) \cdot \mathbb{I}[h(t) = k].

Hash vectorization never needs a vocabulary dictionary, making it memory-efficient and suitable for online learning. The cost is the loss of interpretability (indices no longer correspond to known words) and occasional collisions, which become negligible when $M$ is large enough (typically $2^{18}$ to $2^{24}$). ```python from sklearn.feature_extraction.text import HashingVectorizer hasher = HashingVectorizer(n_features=2**18, alternate_sign=True) X_hashed = hasher.fit_transform(corpus) ``` ### Dense Word Embeddings: Capturing Semantic Relationships Count-based representations treat each word as an atomic, independent symbol — the vector for "dog" is as close to "cat" as it is to "car". **Word embeddings** overcome this by mapping every word to a low-dimensional dense vector $\mathbf{v}_w \in \mathbb{R}^d$ (e.g., $d=300$) such that semantically similar words lie close to one another in the vector space. These embeddings are learned from large corpora by exploiting the **distributional hypothesis** (you shall know a word by the company it keeps). The most influential framework, **Word2Vec**, offers two architectures: - **Skip-gram**: Given a center word $w_t$, predict the surrounding context words $w_{t+j}$ for $-c \le j \le c, j \neq 0$. The objective function maximizes: $$\max \sum_{t=1}^{T} \sum_{-c \le j \le c, j \neq 0} \log p(w_{t+j} | w_t).$$ - **Continuous Bag-of-Words (CBOW)**: Given context words, predict the center word. The Skip-gram model with negative sampling maximizes the probability of observing a context word $w_O$ given a target word $w_I$, while pushing apart the representations of randomly sampled negative words:

\mathcal{L} = \log \sigma(v_{w_O}^{\top} v_{w_I}) + \sum_{k=1}^{K} \mathbb{E}{w_k \sim P_n(w)} \left[ \log \sigma(-v{w_k}^{\top} v_{w_I}) \right],

where $\sigma$ is the logistic function and $P_n$ is a noise distribution (often the unigram distribution raised to the $3/4$ power). After training, each word is represented by a vector $v_w$. **GloVe** (Global Vectors for Word Representation) combines global matrix factorization with local context window methods. It factorizes the log-count matrix of word co-occurrences, producing embeddings where vector differences capture semantic relationships (e.g., $\vec{\text{king}} - \vec{\text{man}} + \vec{\text{woman}} \approx \vec{\text{queen}}$). ```python from gensim.models import Word2Vec sentences = [["the", "cat", "sat"], ["the", "dog", "barked"]] model = Word2Vec(sentences, vector_size=100, window=5, min_count=1, workers=4) vector = model.wv['cat'] similar = model.wv.most_similar('cat', topn=5) ``` *Analytical Intuition:* Word embeddings operationalize the distributional hypothesis — the linguistic premise that words occurring in similar contexts tend to have similar meanings. In a business analytics context, this allows models to understand that "revenue" and "sales" occupy similar regions in the vector space. This geometric representation enables robust sentiment analysis, topic extraction, and text classification, allowing algorithms to generalize across synonymous terminology even when exact keyword matches fail.

Core Text Analysis: Similarity, Tagging, and Topic Extraction

Document Similarity and the Vector Space Model

Quantifying the semantic relatedness of documents is a foundational task in text analytics, underpinning clustering, information retrieval, and recommendation. The most established approach builds on the Vector Space Model (VSM), which represents each document as a weighted vector of the terms in a corpus.

Let V\mathcal{V} be the vocabulary of size VV. A document dd is mapped to a vector vdRV\mathbf{v}_d \in \mathbb{R}^V. The most robust weighting scheme is TF-IDF. For a term tt in document dd within corpus DD, the weight wt,dw_{t,d} is defined as:

wt,d=tf(t,d)×idf(t,D)w_{t,d} = tf(t,d) \times idf(t,D)

where tf(t,d)=ft,dtdft,dtf(t,d) = \frac{f_{t,d}}{\sum_{t' \in d} f_{t',d}} and idf(t,D)=logN{dD:td}idf(t,D) = \log \frac{N}{|\{d \in D : t \in d\}|}.

Measuring similarity between two documents d1d_1 and d2d_2 then reduces to comparing their TF-IDF vectors. The most common choice is the cosine similarity, which normalizes for document length:

sim(d1,d2)=vd1vd2vd1vd2=i=1Vv1,iv2,ii=1Vv1,i2i=1Vv2,i2.\text{sim}(d_1, d_2) = \frac{\mathbf{v}_{d_1} \cdot \mathbf{v}_{d_2}}{\|\mathbf{v}_{d_1}\| \, \|\mathbf{v}_{d_2}\|} = \frac{\sum_{i=1}^{V} v_{1,i} v_{2,i}}{\sqrt{\sum_{i=1}^{V} v_{1,i}^2} \sqrt{\sum_{i=1}^{V} v_{2,i}^2}}.

Geometrically, cosine similarity is the cosine of the angle between the two vectors, ranging from 00 (orthogonal, no shared terms) to 11 (identical direction). In practice, documents with high cosine similarity often share thematic content, but the measure is purely lexical and fails to capture synonymy or polysemy.

To overcome this lexical gap, document-level representations can be obtained by simple averaging of word vectors (weighted by TF-IDF) or by more sophisticated models such as Doc2Vec. Cosine similarity computed on these dense vectors yields semantically richer document similarities.

from sklearn.metrics.pairwise import cosine_similarity
sim_matrix = cosine_similarity(X_tfidf)

Economic Intuition: In spatial models of product differentiation and industrial organization, textual similarity maps directly to product substitutability. If we treat product descriptions or patent abstracts as vectors in the VSM, a high cosine similarity between two firms' product portfolios implies a high Cross-Price Elasticity of Demand. Analysts use these similarity matrices to map the Product Space, identifying direct competitors and visualizing strategic clustering within an industry.

Part-of-Speech Tagging and Named Entity Recognition

Part-of-speech (POS) tagging and named entity recognition (NER) are sequence labeling problems: given a sequence of words x=(x1,,xT)\mathbf{x} = (x_1, \dots, x_T), the goal is to predict the most probable sequence of tags y=(y1,,yT)\mathbf{y} = (y_1, \dots, y_T). For POS tagging, the tags are grammatical categories (noun, verb, adjective, etc.); for NER, tags indicate entity types (person, organization, location, or null) and are often encoded with a Beginning-Inside-Outside (BIO) scheme.

A classical generative model for POS tagging is the Hidden Markov Model (HMM). It assumes that the probability of observing a sentence and its tag sequence factorizes as:

p(x,y)=t=1Tp(ytyt1)p(xtyt),p(\mathbf{x}, \mathbf{y}) = \prod_{t=1}^{T} p(y_t \mid y_{t-1})\, p(x_t \mid y_t),

with special start and end states. The transition probabilities p(ytyt1)p(y_t \mid y_{t-1}) capture grammatical ordering constraints, while the emission probabilities p(xtyt)p(x_t \mid y_t) model the likelihood of a word being emitted by a given tag. Inference — finding argmaxyp(yx)\arg\max_{\mathbf{y}} p(\mathbf{y} \mid \mathbf{x}) — is performed by the Viterbi algorithm, which uses dynamic programming with the recursion:

δt(j)=maxiYδt1(i)p(yt=jyt1=i)p(xtyt=j).\delta_t(j) = \max_{i \in \mathcal{Y}} \delta_{t-1}(i)\, p(y_t=j \mid y_{t-1}=i)\, p(x_t \mid y_t=j).

HMMs are simple and efficient, but they suffer from label bias. To address this, Conditional Random Fields (CRFs) model the conditional probability p(yx)p(\mathbf{y} \mid \mathbf{x}) directly with a log-linear model:

p(yx)=1Z(x)exp(t,kλkfk(yt,yt1,x,t)),p(\mathbf{y} \mid \mathbf{x}) = \frac{1}{Z(\mathbf{x})} \exp\left( \sum_{t,k} \lambda_k f_k(y_t, y_{t-1}, \mathbf{x}, t) \right),

where Z(x)Z(\mathbf{x}) is a global partition function. The feature functions fkf_k can capture rich dependencies on the entire input sequence.

import nltk
nltk.download('averaged_perceptron_tagger')
nltk.download('maxent_ne_chunker')
nltk.download('words')

pos_tags = nltk.pos_tag(tokens)
named_entities = nltk.ne_chunk(pos_tags)

Economic Intuition: NER is the foundational step for constructing Economic Networks from textual sources. By extracting firm names, geographic locations, and commodity types from earnings call transcripts or global news feeds, analysts can build bipartite graphs linking supply chain nodes. Tracking the co-occurrence of specific entities allows researchers to measure Supply Chain Contagion or gauge market exposure to localized geopolitical shocks.

Topic Extraction: Latent Dirichlet Allocation (LDA)

When a corpus consists of thousands of unstructured documents, manually labeling their themes is infeasible. Topic modeling automates the discovery of latent thematic structures. The most influential technique is Latent Dirichlet Allocation (LDA).

LDA is a generative probabilistic model that posits the following process for creating a corpus of DD documents:

  1. For each topic k=1,,Kk = 1,\dots,K, draw a word distribution βkDirichlet(η)\beta_k \sim \text{Dirichlet}(\eta).
  2. For each document d=1,,Dd = 1,\dots,D: a. Choose topic proportions θdDirichlet(α)\theta_d \sim \text{Dirichlet}(\alpha). b. For each word position n=1,,Ndn = 1,\dots,N_d: i. Choose a topic assignment zdnMultinomial(θd)z_{dn} \sim \text{Multinomial}(\theta_d). ii. Choose a word wdnMultinomial(βzdn)w_{dn} \sim \text{Multinomial}(\beta_{z_{dn}}).

The symmetric Dirichlet hyperparameters α\alpha and η\eta control the sparsity of topic mixtures and topic-word distributions, respectively. The posterior distribution over the latent variables (θ,β,z)(\theta, \beta, z) given the observed words is intractable, so approximate inference is carried out via collapsed Gibbs sampling that integrates out θ\theta and β\beta. For a given word token wiw_i in document dd, the conditional probability of its topic assignment zi=kz_i = k given all other assignments is:

p(zi=kzi,w)(nd,ki+α)nk,wii+ηnki+Vη,p(z_i = k \mid \mathbf{z}_{-i}, \mathbf{w}) \propto \bigl(n_{d,k}^{-i} + \alpha\bigr) \, \frac{n_{k,w_i}^{-i} + \eta}{n_k^{-i} + V\eta},

where nd,kin_{d,k}^{-i} is the number of words in document dd assigned to topic kk excluding the current token, nk,wiin_{k,w_i}^{-i} is the number of times word wiw_i is assigned to topic kk across all documents, and nkin_k^{-i} is the total number of words assigned to topic kk excluding the current token.

Economic Intuition: In information economics and behavioral finance, LDA topics represent Latent Consumer Preferences or Macroeconomic Regimes. For example, applying LDA to decades of central bank communications (e.g., FOMC minutes) allows economists to quantify shifts in monetary policy stance. A rising probability weight θd,k\theta_{d,k} on a topic characterized by words like "inflation", "supply", and "commodity" signals a hawkish regime shift. Visualizing the temporal evolution of θd\theta_d via Streamgraphs or Ridgeline Plots provides a powerful visual narrative of thematic momentum.

from sklearn.decomposition import LatentDirichletAllocation
from sklearn.feature_extraction.text import CountVectorizer

vectorizer = CountVectorizer(max_df=0.95, min_df=2, stop_words='english')
dtm = vectorizer.fit_transform(docs)

lda = LatentDirichletAllocation(n_components=5, random_state=42)
lda.fit(dtm)

# Display top words per topic
for i, topic in enumerate(lda.components_):
    top_words = [vectorizer.get_feature_names_out()[idx] for idx in topic.argsort()[-10:]]
    print(f"Topic {i}: {', '.join(top_words)}")

Text Classification, Sentiment Analysis, and Transformers in NLP

Spam-Ham Classification with Naive Bayes

For binary classification tasks like spam–ham filtering, the Multinomial Naive Bayes classifier is a generative probabilistic model that assumes features (word counts) are conditionally independent given the class. Let CC be the class variable with two outcomes: spam (SS) and ham (HH). By Bayes' theorem, the posterior probability for a document represented by word count vector x=(x1,,xV)\mathbf{x} = (x_1,\dots,x_V) is

P(C=cx)=P(xC=c)P(C=c)cP(xC=c)P(C=c).P(C = c \mid \mathbf{x}) = \frac{P(\mathbf{x} \mid C = c)\, P(C = c)}{\sum_{c'} P(\mathbf{x} \mid C = c')\, P(C = c')}.

The likelihood under the multinomial model is

P(xC=c)=(j=1Vxj)!j=1Vxj!j=1Vθc,jxj,P(\mathbf{x} \mid C = c) = \frac{(\sum_{j=1}^V x_j)!}{\prod_{j=1}^V x_j!} \prod_{j=1}^V \theta_{c,j}^{x_j},

where θc,j\theta_{c,j} is the probability of generating word jj in class cc, with j=1Vθc,j=1\sum_{j=1}^V \theta_{c,j} = 1. The parameters are estimated via maximum likelihood with Laplace smoothing (additive smoothing) to avoid zero probabilities:

θ^c,j=Nc,j+αNc+αV,\hat{\theta}_{c,j} = \frac{N_{c,j} + \alpha}{N_c + \alpha V},

where Nc,jN_{c,j} is the count of word jj in class cc, NcN_c is the total number of words in class cc, and α\alpha is a smoothing hyperparameter (typically α=1\alpha=1). The prior P^(C=c)\hat{P}(C=c) is the fraction of documents in class cc.

The decision rule assigns the class with the highest posterior. Working in log space avoids underflow:

logP(C=cx)logP(C=c)+j=1Vxjlogθ^c,j.\log P(C=c\mid\mathbf{x}) \propto \log P(C=c) + \sum_{j=1}^V x_j \log \hat{\theta}_{c,j}.

Economic Intuition and Utility Maximization: In practical analytics, classification is rarely a purely statistical exercise; it is a decision-making process under uncertainty. In spam-ham filtering, the cost matrix is highly asymmetric. A false positive (classifying ham as spam) incurs a severe utility loss, such as a missed critical business contract. Conversely, a false negative (allowing spam into the inbox) incurs a lower, albeit cumulative, cognitive cost. Therefore, an optimal analytics system does not merely predict the class with the highest probability; it selects a decision threshold τ\tau that maximizes the expected economic utility E[U]E[U]. If U(c,c^)U(c, \hat{c}) represents the utility of predicting c^\hat{c} when the true class is cc, the optimal decision rule is c^=argmaxc^cP(cd)U(c,c^)\hat{c} = \arg\max_{\hat{c}} \sum_{c} P(c|d) U(c, \hat{c}).

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import make_pipeline

corpus = [
    "Win a free iPhone now! Click the link to claim your prize.",
    "Hey, are we still on for the budget meeting at 3 PM?",
    "URGENT: Your bank account has been compromised. Verify immediately.",
    "Please review the attached Q3 financial projections before tomorrow."
]
labels = ['spam', 'ham', 'spam', 'ham']

model = make_pipeline(
    TfidfVectorizer(sublinear_tf=True, stop_words='english'), 
    MultinomialNB(alpha=1.0)
)
model.fit(corpus, labels)

new_messages = ["Meeting moved to 4 PM.", "Congratulations! You won a $1000 gift card."]
predictions = model.predict(new_messages)
probabilities = model.predict_proba(new_messages)

for msg, pred, prob in zip(new_messages, predictions, probabilities):
    print(f"Message: '{msg}'\nPredicted: {pred} | P(spam): {prob[1]:.4f}\n")

Discriminative Models: Logistic Regression and SVMs

A more flexible approach models the conditional probability P(Cx)P(C \mid \mathbf{x}) directly. Logistic regression models the probability of the positive class as

P(C=1x;w,b)=σ(wx+b)=11+e(wx+b).P(C=1 \mid \mathbf{x}; \mathbf{w}, b) = \sigma(\mathbf{w}^\top \mathbf{x} + b) = \frac{1}{1 + e^{-(\mathbf{w}^\top \mathbf{x} + b)}}.

The parameters w\mathbf{w} and bb are learned by minimizing the negative log-likelihood (cross-entropy loss) with L2 regularization:

L(w,b)=i=1n[yilogp^i+(1yi)log(1p^i)]+λw22.\mathcal{L}(\mathbf{w}, b) = -\sum_{i=1}^n \left[ y_i \log \hat{p}_i + (1-y_i) \log (1-\hat{p}_i) \right] + \lambda \|\mathbf{w}\|_2^2.

The resulting model yields interpretable coefficients: a high positive wjw_j means that the presence of word jj increases the log-odds of spam. Support Vector Machines (SVMs) find a maximum-margin hyperplane in a high-dimensional feature space. For text data, linear SVMs with TF-IDF weighted BoW vectors are highly effective due to the high dimensionality and sparsity of the feature space.

Sentiment Analysis

Sentiment analysis (or opinion mining) is the text classification task of determining the affective stance (positive, negative, neutral) expressed in a piece of text. It extends binary classification to multi-class or ordinal regression.

Lexicon-Based Approaches use a predefined sentiment lexicon — a dictionary mapping words to sentiment scores. For instance, the AFINN lexicon assigns integer scores from –5 (negative) to +5 (positive), and VADER (Valence Aware Dictionary and sEntiment Reasoner) handles negations, intensifiers, and emojis. The sentiment of a document is computed as the sum of its word scores:

Sentiment(d)=wds(w).\text{Sentiment}(d) = \sum_{w \in d} s(w).

Lexicon methods are transparent and require no training data, but they ignore context, sarcasm, and domain-specific jargon.

Supervised Learning for Sentiment learns from labeled documents. The BoW (or TF-IDF) representation is fed into any classifier — Naive Bayes, logistic regression, or SVMs. Because sentiment is often expressed through word combinations, n-gram features (e.g., bigrams like "not good") are essential. Feature engineering includes handling negation by appending a "NOT_" prefix to words following a negation term within a window.

Economic Intuition: From a macroeconomic and strategic perspective, consumer sentiment acts as a leading indicator for market dynamics. The marginal value of information VI\frac{\partial V}{\partial I} derived from real-time sentiment analysis allows firms to dynamically adjust pricing strategies, optimize inventory allocation, and hedge against reputational shocks. An unexpected spike in negative sentiment regarding a product's battery life on social media provides an early signal that allows a firm to reallocate supply chain resources before the issue manifests in lagging financial metrics.

Transformers in NLP

The limitations of recurrent neural networks (RNNs) — namely, their inability to parallelize training and their struggle with long-range dependencies due to vanishing gradients — were fundamentally resolved by the introduction of Transformer Models. Transformers discard recurrence and convolution entirely, relying instead on the Self-Attention Mechanism to model global dependencies.

The core mathematical operation is the Scaled Dot-Product Attention:

Attention(Q,K,V)=softmax(QKTdk)V.\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V.

The scaling factor 1dk\frac{1}{\sqrt{d_k}} prevents the softmax from saturating. Multi-Head Attention runs multiple attention operations in parallel:

MultiHead(Q,K,V)=Concat(head1,,headh)WO.\text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \dots, \text{head}_h)W^O.

This architectural paradigm shift has had a transformative impact on NLP, enabling the pre-training of massive Large Language Models (LLMs) on vast corpora, which can then be fine-tuned for downstream tasks with minimal task-specific data. Pre-trained models like BERT (Bidirectional Encoder Representations from Transformers) and DistilBERT use the Transformer encoder to produce deeply contextualized word representations. The pipeline API from Hugging Face's transformers library enables zero-shot classification and sentiment analysis:

from transformers import pipeline

# Sentiment analysis with a pre-trained Transformer
sentiment_analyzer = pipeline(
    "sentiment-analysis", 
    model="distilbert-base-uncased-finetuned-sst-2-english"
)

reviews = [
    "The company's earnings call was incredibly disappointing, missing all revenue targets.",
    "This product exceeded all my expectations. Highly recommended!"
]

for review in reviews:
    result = sentiment_analyzer(review)[0]
    print(f"Review: '{review}'\nSentiment: {result['label']} (score: {result['score']:.4f})\n")
# Zero-shot text classification
classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli")
text = "The new fiscal policy aims to reduce inflation while maintaining employment levels."
candidate_labels = ["economics", "politics", "sports", "technology"]
result = classifier(text, candidate_labels)
for label, score in zip(result['labels'], result['scores']):
    print(f"{label}: {score:.4f}")

The Transformer architecture revolutionizes text analytics by enabling transfer learning: models pre-trained on massive corpora capture general linguistic knowledge, which is then fine-tuned on domain-specific tasks with relatively small labeled datasets. This paradigm has set new state-of-the-art benchmarks across virtually all NLP tasks, from classification and sentiment analysis to question answering and text generation.