NATURAL LANGUAGE PROCESSING (NLP) LAB
Experiments 1 to 10 - Programs and Outcomes
This document contains concise, runnable Python programs for common NLP lab experiments. Each experiment
includes Aim, Program, and Outcome (expected output/behavior). Recommended environment: Google Colab or a
Python 3.10+ setup.
Section Topic
1 Preprocessing + Edit Distance
2 N-gram Language Models + Smoothing + Perplexity
3 HMM POS Tagger + Viterbi
4 RNN Next-Word Prediction (Embeddings)
5 CFG + Top-down & Bottom-up Parsing
6 PCFG + Dependency Parsing
7 WSD (Decision Tree & Lesk)
8 Information Extraction (NER + Relations)
9 ASR + TTS (Deep Learning Pipelines)
10 Neural Machine Translation (Seq2Seq + Attention)
Experiment 1: Text Preprocessing and Edit Distance
Aim: Implement tokenization, case folding, stemming, lemmatization, and compute edit distance between two
strings.
Setup / Libraries: Python, NLTK (punkt, wordnet).
Program:
import re
import nltk
[Link]("punkt")
[Link]("wordnet")
[Link]("omw-1.4")
from [Link] import word_tokenize
from [Link] import PorterStemmer, WordNetLemmatizer
def preprocess(text):
text = [Link]() # case folding
tokens = word_tokenize(text) # tokenization
tokens = [t for t in tokens if [Link](r"^[a-z]+$", t)] # keep only words
return tokens
def stem(tokens):
ps = PorterStemmer()
return [[Link](t) for t in tokens]
def lemmatize(tokens):
lem = WordNetLemmatizer()
return [[Link](t) for t in tokens]
def edit_distance(a, b):
# Levenshtein distance (DP)
m, n = len(a), len(b)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(m+1): dp[i][0] = i
for j in range(n+1): dp[0][j] = j
for i in range(1, m+1):
for j in range(1, n+1):
cost = 0 if a[i-1] == b[j-1] else 1
dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + cost)
return dp[m][n]
text = "NLP is Amazing! Tokenization, stemming & lemmatization are useful."
tokens = preprocess(text)
print("Tokens:", tokens)
print("Stemmed:", stem(tokens))
print("Lemmatized:", lemmatize(tokens))
print("Edit distance (kitten, sitting):", edit_distance("kitten", "sitting"))
Outcome: Shows tokens, stems, lemmas for the input text; edit distance for 'kitten' and 'sitting' is 3.
Experiment 2: Unigram, Bigram, Trigram Language Models
with Smoothing and Perplexity
Aim: Build unigram/bigram/trigram LMs, apply Laplace (add-1) smoothing, and compute perplexity for a test
sentence.
Setup / Libraries: Python standard library only.
Program:
import math
from collections import Counter
def ngrams(tokens, n):
return [tuple(tokens[i:i+n]) for i in range(len(tokens)-n+1)]
def train_ngram(tokens, n):
ng = Counter(ngrams(tokens, n))
ctx = Counter(ngrams(tokens, n-1)) if n > 1 else None
vocab = sorted(set(tokens))
return ng, ctx, vocab
def prob_add1(ngram, ng_counts, ctx_counts, vocab):
V = len(vocab)
if len(ngram) == 1:
return (ng_counts[ngram] + 1) / (sum(ng_counts.values()) + V)
context = ngram[:-1]
return (ng_counts[ngram] + 1) / (ctx_counts[context] + V)
def perplexity(test_tokens, n, ng_counts, ctx_counts, vocab):
test_ng = ngrams(test_tokens, n)
logp = 0.0
for g in test_ng:
p = prob_add1(g, ng_counts, ctx_counts, vocab)
logp += [Link](p)
return [Link](-logp / len(test_ng))
train_text = "i love nlp and i love machine learning"
train_tokens = train_text.split()
test_text = "i love learning"
test_tokens = test_text.split()
for n in [1, 2, 3]:
ng, ctx, vocab = train_ngram(train_tokens, n)
ppl = perplexity(test_tokens, n, ng, ctx, vocab)
print(f"{n}-gram perplexity for '{test_text}': {ppl:.4f}")
Outcome: Prints perplexity values for 1-gram, 2-gram, 3-gram models. Lower perplexity means better prediction of
the test sentence.
Experiment 3: POS Tagger using HMM and Viterbi Decoding
Aim: Construct a POS tagger using an HMM and decode the most probable tag sequence using the Viterbi
algorithm.
Setup / Libraries: Python standard library only.
Program:
import math
from collections import Counter
train_sents = [
[("time","NN"), ("flies","VBZ"), ("like","IN"), ("an","DT"), ("arrow","NN")],
[("fruit","NN"), ("flies","NNS"), ("like","IN"), ("a","DT"), ("banana","NN")],
]
def train_hmm(tagged_sents):
tag_counts = Counter()
emit = Counter()
trans = Counter()
start = Counter()
for sent in tagged_sents:
start[sent[0][1]] += 1
prev_tag = None
for word, tag in sent:
tag_counts[tag] += 1
emit[(tag, word)] += 1
if prev_tag is not None:
trans[(prev_tag, tag)] += 1
prev_tag = tag
tags = list(tag_counts.keys())
vocab = set(w for sent in tagged_sents for (w, _) in sent)
return tags, vocab, tag_counts, emit, trans, start
def viterbi(words, tags, vocab, tag_counts, emit, trans, start):
V = len(vocab) + 1
T = len(tags)
def log(x): return -1e9 if x == 0 else [Link](x)
dp = [{} for _ in range(len(words))]
back = [{} for _ in range(len(words))]
for tag in tags:
p_start = (start[tag] + 1) / (sum([Link]()) + T)
p_emit = (emit[(tag, words[0])] + 1) / (tag_counts[tag] + V)
dp[0][tag] = log(p_start) + log(p_emit)
back[0][tag] = None
for i in range(1, len(words)):
for tag in tags:
p_emit = (emit[(tag, words[i])] + 1) / (tag_counts[tag] + V)
best_prev, best_score = None, -1e18
for prev in tags:
p_trans = (trans[(prev, tag)] + 1) / (tag_counts[prev] + T)
score = dp[i-1][prev] + log(p_trans) + log(p_emit)
if score > best_score:
best_score, best_prev = score, prev
dp[i][tag] = best_score
back[i][tag] = best_prev
last = max(dp[-1], key=dp[-1].get)
seq, cur = [], last
for i in reversed(range(len(words))):
[Link](cur)
cur = back[i][cur]
return list(reversed(seq))
tags, vocab, tag_counts, emit, trans, start = train_hmm(train_sents)
sent = "time flies like a banana".split()
pred = viterbi(sent, tags, vocab, tag_counts, emit, trans, start)
print(list(zip(sent, pred)))
Outcome: Outputs word-tag pairs for the input sentence (the tag for 'flies' may be VBZ or NNS depending on
learned probabilities).
Experiment 4: RNN for Next-Word Prediction (with
Embeddings)
Aim: Implement an RNN architecture for next-word prediction using an embedding layer (can be initialized with
Word2Vec/GloVe).
Setup / Libraries: PyTorch (torch).
Program:
# Next-word prediction with a simple RNN language model (PyTorch)
# Colab setup: !pip -q install torch
import torch
import [Link] as nn
sentences = [
"i love nlp",
"i love deep learning",
"nlp is fun",
"deep learning is powerful"
]
tokens = [w for s in sentences for w in [Link]()]
vocab = sorted(set(tokens))
stoi = {w:i for i,w in enumerate(vocab)}
itos = {i:w for w,i in [Link]()}
pairs = []
for s in sentences:
w = [Link]()
for i in range(len(w)-1):
[Link]((w[i], w[i+1]))
X = [Link]([stoi[a] for a,_ in pairs])
Y = [Link]([stoi[b] for _,b in pairs])
class RNNLM([Link]):
def __init__(self, vocab_size, emb_dim=32, hidden=64):
super().__init__()
[Link] = [Link](vocab_size, emb_dim)
[Link] = [Link](emb_dim, hidden, batch_first=True)
[Link] = [Link](hidden, vocab_size)
def forward(self, x):
e = [Link](x).unsqueeze(1) # (B,1,emb)
out, _ = [Link](e) # (B,1,hid)
return [Link]([Link](1)) # (B,vocab)
model = RNNLM(len(vocab))
opt = [Link]([Link](), lr=0.01)
loss_fn = [Link]()
for epoch in range(200):
opt.zero_grad()
logits = model(X)
loss = loss_fn(logits, Y)
[Link]()
[Link]()
def predict_next(word, k=3):
with torch.no_grad():
x = [Link]([stoi[word]])
probs = [Link](model(x), dim=-1).squeeze()
top = [Link](probs, k)
return [(itos[int(i)], float(p)) for p,i in zip([Link], [Link])]
print("Next words for 'love':", predict_next("love"))
print("Next words for 'deep':", predict_next("deep"))
Outcome: Prints top predicted next words for input tokens (e.g., love -> nlp/deep/learning).
Notes: To use GloVe/Word2Vec: load vectors and copy them into [Link].
Experiment 5: Context-Free Grammar (CFG) and Parsing
(Top-down and Bottom-up)
Aim: Construct a CFG and apply top-down and bottom-up parsing algorithms for syntactic analysis.
Setup / Libraries: NLTK (CFG, parsers).
Program:
import nltk
from nltk import CFG
from [Link] import RecursiveDescentParser, ChartParser
grammar = [Link]('''
S -> NP VP
NP -> Det N | Det Adj N
VP -> V NP | V
Det -> 'a' | 'the'
Adj -> 'big' | 'small'
N -> 'cat' | 'dog'
V -> 'sees' | 'runs'
''')
sentence = "the big dog sees a cat".split()
topdown = RecursiveDescentParser(grammar) # Top-down
bottomup = ChartParser(grammar) # Bottom-up
print("Top-down parse trees:")
for tree in [Link](sentence):
print(tree)
print("\nBottom-up parse trees:")
for tree in [Link](sentence):
print(tree)
Outcome: Prints parse tree(s) for the given sentence using both parsing strategies.
Experiment 6: PCFG Statistical Parsing and Dependency
Parsing
Aim: Implement PCFG for statistical parsing and explore dependency parsing methods for syntactic analysis.
Setup / Libraries: NLTK (PCFG, ViterbiParser). spaCy optional for dependency parsing.
Program:
# PCFG (statistical parsing) + Dependency parsing example
# ----- PCFG with Viterbi parsing (NLTK) -----
from nltk import PCFG
from [Link] import ViterbiParser
pcfg = [Link]('''
S -> NP VP [1.0]
NP -> Det N [0.6] | Det Adj N [0.4]
VP -> V NP [0.7] | V [0.3]
Det -> 'the' [0.6] | 'a' [0.4]
Adj -> 'big' [0.5] | 'small' [0.5]
N -> 'dog' [0.5] | 'cat' [0.5]
V -> 'sees' [0.6] | 'runs' [0.4]
''')
parser = ViterbiParser(pcfg)
sent = "the dog sees a cat".split()
for tree in [Link](sent):
print(tree)
print("Tree probability:", [Link]())
# ----- Dependency parsing (spaCy) -----
# Colab setup:
# !pip -q install spacy
# !python -m spacy download en_core_web_sm
# import spacy
# nlp = [Link]("en_core_web_sm")
# doc = nlp("The dog sees a cat.")
# for token in doc:
# print([Link], token.dep_, "->", [Link])
Outcome: PCFG: prints most probable parse tree with probability. Dependency parsing: prints dependency arcs if
spaCy is installed.
Experiment 7: Word Sense Disambiguation (WSD):
Supervised and Unsupervised
Aim: Implement and compare Decision Tree classifier (supervised) and Lesk algorithm (unsupervised) for WSD.
Setup / Libraries: scikit-learn, NLTK (wordnet, punkt).
Program:
# Word Sense Disambiguation (WSD): Decision Tree (supervised) and Lesk (unsupervised)
from [Link] import DecisionTreeClassifier
from sklearn.feature_extraction.text import CountVectorizer
X_train = [
"I deposited money in the bank",
"The bank approved my loan",
"He sat on the river bank",
"Trees grew along the bank"
]
y_train = ["FIN", "FIN", "RIVER", "RIVER"]
vec = CountVectorizer()
Xv = vec.fit_transform(X_train)
clf = DecisionTreeClassifier()
[Link](Xv, y_train)
tests = [
"She went to the bank to withdraw cash",
"We walked along the bank of the river"
]
Xt = [Link](tests)
print("Decision Tree WSD:", list(zip(tests, [Link](Xt))))
import nltk
[Link]("wordnet")
[Link]("punkt")
from [Link] import lesk
s1 = "I went to the bank to deposit money".split()
s2 = "The fisherman sat on the bank".split()
sense1 = lesk(s1, "bank")
sense2 = lesk(s2, "bank")
print("Lesk sense (money):", sense1, "-", [Link]() if sense1 else None)
print("Lesk sense (river):", sense2, "-", [Link]() if sense2 else None)
Outcome: Decision Tree predicts FIN vs RIVER senses; Lesk outputs WordNet synset and definition for each
context.
Experiment 8: Information Extraction using NER and
Relation Extraction
Aim: Implement Information Extraction using Named Entity Recognition and Relation Extraction.
Setup / Libraries: spaCy (en_core_web_sm).
Program:
# Information Extraction: Named Entity Recognition (NER) + simple Relation Extraction
# Setup: !pip -q install spacy ; !python -m spacy download en_core_web_sm
import spacy
nlp = [Link]("en_core_web_sm")
text = "Sundar Pichai is the CEO of Google and he lives in the United States."
doc = nlp(text)
print("Named Entities:")
for ent in [Link]:
print([Link], ent.label_)
# Simple relation extraction: PERSON CEO_OF ORG
person = next(([Link] for e in [Link] if e.label_ == "PERSON"), None)
org = next(([Link] for e in [Link] if e.label_ == "ORG"), None)
if person and org and "CEO" in text:
print("Relation:", (person, "CEO_OF", org))
Outcome: Prints entities (PERSON, ORG, GPE, etc.) and a relation tuple such as (Sundar Pichai, CEO_OF,
Google).
Experiment 9: ASR and TTS Systems using Deep Learning
Aim: Construct ASR and TTS systems using deep learning techniques.
Setup / Libraries: transformers (Whisper), gTTS (quick TTS).
Program:
# ASR and TTS using deep learning pipelines (HuggingFace + gTTS)
# Recommended: Google Colab
# Setup:
# !pip -q install transformers torchaudio soundfile gtts
from transformers import pipeline
# ASR (Whisper)
asr = pipeline("automatic-speech-recognition", model="openai/whisper-small")
print("ASR pipeline loaded. Use: asr('[Link]')")
# Example:
# result = asr("[Link]")
# print("Transcription:", result["text"])
# TTS (quick demo)
from gtts import gTTS
text = "Hello, this is a text to speech demo."
tts = gTTS(text=text, lang="en")
[Link]("tts_demo.mp3")
print("Saved tts_demo.mp3")
Outcome: ASR: returns transcribed text from an audio file. TTS: generates an mp3 file with synthesized speech.
Experiment 10: Neural Machine Translation (Seq2Seq with
Attention)
Aim: Build a Seq2Seq NMT model with attention and translate simple sentences.
Setup / Libraries: PyTorch (torch).
Program:
# Neural Machine Translation (NMT): Seq2Seq with Attention (toy example in PyTorch)
import torch
import [Link] as nn
import [Link] as optim
pairs = [
("i am happy", "mai khush hu"),
("i am sad", "mai udaas hu"),
("he is happy", "vah khush hai"),
("he is sad", "vah udaas hai"),
]
def tok(s): return [Link]()
src_vocab = sorted(set(w for a,_ in pairs for w in tok(a)))
tgt_vocab = sorted(set(w for _,b in pairs for w in tok(b)))
src_vocab = ["<pad>","<sos>","<eos>"] + src_vocab
tgt_vocab = ["<pad>","<sos>","<eos>"] + tgt_vocab
src_stoi = {w:i for i,w in enumerate(src_vocab)}
tgt_stoi = {w:i for i,w in enumerate(tgt_vocab)}
tgt_itos = {i:w for w,i in tgt_stoi.items()}
def encode(sent, stoi):
ids = [stoi["<sos>"]] + [stoi[w] for w in tok(sent)] + [stoi["<eos>"]]
return [Link](ids, dtype=[Link])
data = [(encode(a,src_stoi), encode(b,tgt_stoi)) for a,b in pairs]
class Encoder([Link]):
def __init__(self, V, emb=32, hid=64):
super().__init__()
[Link] = [Link](V, emb)
[Link] = [Link](emb, hid, batch_first=True)
def forward(self, x):
e = [Link]([Link](0))
out, h = [Link](e)
return out, h
class Attention([Link]):
def __init__(self, hid=64):
super().__init__()
self.W = [Link](hid*2, hid)
self.v = [Link](hid, 1, bias=False)
def forward(self, enc_out, dec_h):
src_len = enc_out.size(1)
dec_rep = dec_h.repeat(1, src_len, 1)
energy = [Link](self.W([Link]([enc_out, dec_rep], dim=-1)))
scores = self.v(energy).squeeze(-1)
attn = [Link](scores, dim=-1)
ctx = [Link]([Link](1), enc_out)
return ctx, attn
class Decoder([Link]):
def __init__(self, V, emb=32, hid=64):
super().__init__()
[Link] = [Link](V, emb)
[Link] = Attention(hid)
[Link] = [Link](emb + hid, hid, batch_first=True)
[Link] = [Link](hid, V)
def forward(self, y_prev, h, enc_out):
yemb = [Link](y_prev).unsqueeze(0).unsqueeze(1)
ctx, _ = [Link](enc_out, [Link](1,0,2))
rnn_in = [Link]([yemb, ctx], dim=-1)
out, h_new = [Link](rnn_in, h)
logits = [Link]([Link](1))
return logits, h_new
enc_model = Encoder(len(src_vocab))
dec_model = Decoder(len(tgt_vocab))
opt = [Link](list(enc_model.parameters()) + list(dec_model.parameters()), lr=0.01)
loss_fn = [Link]()
# train (toy)
for epoch in range(300):
for x, y in data:
opt.zero_grad()
enc_out, h = enc_model(x)
loss = 0.0
for t in range(1, len(y)):
logits, h = dec_model(y[t-1], h, enc_out)
loss += loss_fn(logits, y[t].unsqueeze(0))
[Link]()
[Link]()
def translate(src_sentence, max_len=10):
x = encode(src_sentence, src_stoi)
enc_out, h = enc_model(x)
y_prev = [Link](tgt_stoi["<sos>"])
out_words = []
for _ in range(max_len):
logits, h = dec_model(y_prev, h, enc_out)
y_prev = [Link](logits, dim=-1).squeeze(0)
w = tgt_itos[int(y_prev)]
if w == "<eos>":
break
out_words.append(w)
return " ".join(out_words)
print("i am happy ->", translate("i am happy"))
print("he is sad ->", translate("he is sad"))
Outcome: After toy training, prints translations like 'i am happy -> mai khush hu'.
Notes: For real tasks: train on parallel corpora and evaluate using BLEU.