🌐 EN | 🇯🇵 JP | Last sync: 2026-01

Chapter 1: NLP Basics

Fundamental Technologies in Natural Language Processing - From Text Preprocessing to Word Embeddings

📖 Reading Time: 30-35 minutes 📊 Difficulty: Beginner to Intermediate 💻 Code Examples: 10 📝 Exercises: 5

Learning Objectives

By reading this chapter, you will be able to:


1.1 Text Preprocessing

What Is Text Preprocessing?

Text Preprocessing is the process of converting raw text data into a format that machine learning models can process.

It is often said that "80% of NLP is preprocessing" — the quality of preprocessing has a major impact on the final performance of the model.

Overview of Text Preprocessing

graph TD A[Raw Text] --> B[Cleaning] B --> C[Tokenization] C --> D[Normalization] D --> E[Stopword Removal] E --> F[Stemming / Lemmatization] F --> G[Vectorization] G --> H[Model Input] style A fill:#ffebee style B fill:#fff3e0 style C fill:#f3e5f5 style D fill:#e3f2fd style E fill:#e8f5e9 style F fill:#fce4ec style G fill:#e1f5fe style H fill:#c8e6c9

1.1.1 Tokenization

Tokenization is the process of splitting text into meaningful units called tokens.

Word-Level Tokenization

import nltk
from nltk.tokenize import word_tokenize, sent_tokenize

# Download only on the first run
# nltk.download('punkt')

text = """Natural Language Processing (NLP) is a field of AI.
It helps computers understand human language."""

# Sentence splitting
sentences = sent_tokenize(text)
print("=== Sentence Splitting ===")
for i, sent in enumerate(sentences, 1):
    print(f"{i}. {sent}")

# Word splitting
words = word_tokenize(text)
print("\n=== Word Tokenization ===")
print(f"Token count: {len(words)}")
print(f"First 10 tokens: {words[:10]}")

Output:

=== Sentence Splitting ===
1. Natural Language Processing (NLP) is a field of AI.
2. It helps computers understand human language.

=== Word Tokenization ===
Token count: 20
First 10 tokens: ['Natural', 'Language', 'Processing', '(', 'NLP', ')', 'is', 'a', 'field', 'of']

Subword Tokenization

from transformers import BertTokenizer

# BERT's tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

text = "Tokenization is fundamental for NLP preprocessing."

# Tokenize
tokens = tokenizer.tokenize(text)
print("=== Subword Tokenization (BERT) ===")
print(f"Tokens: {tokens}")

# Convert to IDs
token_ids = tokenizer.encode(text, add_special_tokens=True)
print(f"\nToken IDs: {token_ids}")

# Decode
decoded = tokenizer.decode(token_ids)
print(f"Decoded: {decoded}")

Output:

=== Subword Tokenization (BERT) ===
Tokens: ['token', '##ization', 'is', 'fundamental', 'for', 'nl', '##p', 'pre', '##processing', '.']

Token IDs: [101, 19204, 3989, 2003, 8148, 2005, 17953, 2243, 3653, 6693, 1012, 102]
Decoded: [CLS] tokenization is fundamental for nlp preprocessing. [SEP]

Character-Level Tokenization

text = "Hello, NLP!"

# Character-level tokenization
char_tokens = list(text)
print("=== Character-Level Tokenization ===")
print(f"Tokens: {char_tokens}")
print(f"Token count: {len(char_tokens)}")

# Unique characters
unique_chars = sorted(set(char_tokens))
print(f"Unique character count: {len(unique_chars)}")
print(f"Vocabulary: {unique_chars}")

Comparison of Tokenization Methods

Method Granularity Advantages Disadvantages Use Case
Word-level Word Easy to interpret Large vocabulary size, OOV problem Traditional NLP
Subword Part of a word Handles OOV, compresses vocabulary Somewhat complex Modern Transformers
Character-level Character Minimal vocabulary, no OOV Longer sequences Language modeling

1.1.2 Normalization and Standardization

import re
import string

def normalize_text(text):
    """Normalize text"""
    # Lowercase
    text = text.lower()

    # Remove URLs
    text = re.sub(r'http\S+|www\S+', '', text)

    # Remove mentions
    text = re.sub(r'@\w+', '', text)

    # Remove hashtags
    text = re.sub(r'#\w+', '', text)

    # Remove digits
    text = re.sub(r'\d+', '', text)

    # Remove punctuation
    text = text.translate(str.maketrans('', '', string.punctuation))

    # Collapse multiple spaces into one
    text = re.sub(r'\s+', ' ', text).strip()

    return text

# Test
raw_text = """Check out https://example.com! @user tweeted #NLP is AMAZING!!
Contact us at 123-456-7890."""

normalized = normalize_text(raw_text)

print("=== Text Normalization ===")
print(f"Original text:\n{raw_text}\n")
print(f"After normalization:\n{normalized}")

Output:

=== Text Normalization ===
Original text:
Check out https://example.com! @user tweeted #NLP is AMAZING!!
Contact us at 123-456-7890.

After normalization:
check out tweeted is amazing contact us at

1.1.3 Stopword Removal

Stopwords are words that occur frequently but carry little semantic importance — for example function words such as Japanese "は" (wa) and "の" (no), or English articles like "a" and "the".

import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize

# nltk.download('stopwords')

text = "Natural language processing is a subfield of artificial intelligence that focuses on the interaction between computers and humans."

# Tokenize
tokens = word_tokenize(text.lower())

# English stopwords
stop_words = set(stopwords.words('english'))

# Remove stopwords
filtered_tokens = [word for word in tokens if word.isalpha() and word not in stop_words]

print("=== Stopword Removal ===")
print(f"Original token count: {len(tokens)}")
print(f"Original tokens: {tokens[:15]}")
print(f"\nToken count after removal: {len(filtered_tokens)}")
print(f"Tokens after removal: {filtered_tokens}")

Output:

=== Stopword Removal ===
Original token count: 21
Original tokens: ['natural', 'language', 'processing', 'is', 'a', 'subfield', 'of', 'artificial', 'intelligence', 'that', 'focuses', 'on', 'the', 'interaction', 'between']

Token count after removal: 10
Tokens after removal: ['natural', 'language', 'processing', 'subfield', 'artificial', 'intelligence', 'focuses', 'interaction', 'computers', 'humans']

1.1.4 Stemming and Lemmatization

Stemming: Converts a word to its stem using rule-based transformations
Lemmatization: Converts a word to its dictionary form using a lexicon

from nltk.stem import PorterStemmer, WordNetLemmatizer
from nltk.tokenize import word_tokenize
import nltk

# nltk.download('wordnet')
# nltk.download('omw-1.4')

# Stemmer
stemmer = PorterStemmer()

# Lemmatizer
lemmatizer = WordNetLemmatizer()

words = ["running", "runs", "ran", "easily", "fairly", "better", "worse"]

print("=== Stemming vs. Lemmatization ===")
print(f"{'Word':<15} {'Stemmed':<15} {'Lemmatized':<15}")
print("-" * 45)

for word in words:
    stemmed = stemmer.stem(word)
    lemmatized = lemmatizer.lemmatize(word, pos='v')  # Treat as a verb
    print(f"{word:<15} {stemmed:<15} {lemmatized:<15}")

Output:

=== Stemming vs. Lemmatization ===
Word            Stemmed         Lemmatized
---------------------------------------------
running         run             run
runs            run             run
ran             ran             run
easily          easili          easily
fairly          fairli          fairly
better          better          better
worse           wors            worse

Selection guideline: Stemming is fast but coarse. Lemmatization is accurate but slower. Choose based on the requirements of the task.


1.2 Word Representations

1.2.1 One-Hot Encoding

One-Hot Encoding represents each word as a vector whose dimension equals the vocabulary size, where only the corresponding position is 1 and all others are 0.

import numpy as np
from sklearn.preprocessing import LabelEncoder, OneHotEncoder

sentences = ["I love NLP", "NLP is amazing", "I love AI"]

# Collect all words
words = ' '.join(sentences).lower().split()
unique_words = sorted(set(words))

print("=== One-Hot Encoding ===")
print(f"Vocabulary: {unique_words}")
print(f"Vocabulary size: {len(unique_words)}")

# Build the One-Hot encoding
word_to_idx = {word: idx for idx, word in enumerate(unique_words)}
idx_to_word = {idx: word for word, idx in word_to_idx.items()}

def one_hot_encode(word, vocab_size):
    """Convert a word to a One-Hot vector"""
    vector = np.zeros(vocab_size)
    if word in word_to_idx:
        vector[word_to_idx[word]] = 1
    return vector

# One-Hot representation for each word
print("\nOne-Hot representation of words:")
for word in ["nlp", "love", "ai"]:
    vector = one_hot_encode(word, len(unique_words))
    print(f"{word}: {vector}")

Output:

=== One-Hot Encoding ===
Vocabulary: ['ai', 'amazing', 'i', 'is', 'love', 'nlp']
Vocabulary size: 6

One-Hot representation of words:
nlp: [0. 0. 0. 0. 0. 1.]
love: [0. 0. 0. 0. 1. 0.]
ai: [1. 0. 0. 0. 0. 0.]

Problem: As vocabulary grows, the vectors become enormous (the curse of dimensionality), and semantic relationships between words cannot be represented.

1.2.2 Bag of Words (BoW)

Bag of Words represents a document as a vector of word occurrence frequencies.

from sklearn.feature_extraction.text import CountVectorizer

corpus = [
    "I love machine learning",
    "I love deep learning",
    "Deep learning is powerful",
    "Machine learning is interesting"
]

# BoW vectorizer
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(corpus)

# Vocabulary
vocab = vectorizer.get_feature_names_out()

print("=== Bag of Words ===")
print(f"Vocabulary: {vocab}")
print(f"Vocabulary size: {len(vocab)}")
print(f"\nDocument-term matrix:")
print(X.toarray())

# Representation per document
import pandas as pd
df = pd.DataFrame(X.toarray(), columns=vocab)
print("\nDocument-term matrix (DataFrame):")
print(df)

Output:

=== Bag of Words ===
Vocabulary: ['deep' 'interesting' 'is' 'learning' 'love' 'machine' 'powerful']
Vocabulary size: 7

Document-term matrix:
[[0 0 0 1 1 1 0]
 [1 0 0 1 1 0 0]
 [1 0 1 1 0 0 1]
 [0 1 1 1 0 1 0]]

Document-term matrix (DataFrame):
   deep  interesting  is  learning  love  machine  powerful
0     0            0   0         1     1        1         0
1     1            0   0         1     1        0         0
2     1            0   1         1     0        0         1
3     0            1   1         1     0        1         0

1.2.3 TF-IDF

TF-IDF (Term Frequency-Inverse Document Frequency) is a method for evaluating the importance of a word.

$$ \text{TF-IDF}(t, d) = \text{TF}(t, d) \times \text{IDF}(t) $$

$$ \text{IDF}(t) = \log\left(\frac{N}{df(t)}\right) $$

from sklearn.feature_extraction.text import TfidfVectorizer

corpus = [
    "The cat sat on the mat",
    "The dog sat on the log",
    "Cats and dogs are animals",
    "The mat is on the floor"
]

# TF-IDF vectorizer
tfidf_vectorizer = TfidfVectorizer()
X_tfidf = tfidf_vectorizer.fit_transform(corpus)

# Vocabulary
vocab = tfidf_vectorizer.get_feature_names_out()

print("=== TF-IDF ===")
print(f"Vocabulary: {vocab}")
print(f"\nTF-IDF matrix:")
df_tfidf = pd.DataFrame(X_tfidf.toarray(), columns=vocab)
print(df_tfidf.round(3))

# Most important words (document 0)
doc_idx = 0
feature_scores = list(zip(vocab, X_tfidf.toarray()[doc_idx]))
sorted_scores = sorted(feature_scores, key=lambda x: x[1], reverse=True)

print(f"\nTop 3 important words for document {doc_idx}:")
for word, score in sorted_scores[:3]:
    print(f"  {word}: {score:.3f}")

Output:

=== TF-IDF ===
Vocabulary: ['and' 'animals' 'are' 'cat' 'cats' 'dog' 'dogs' 'floor' 'is' 'log' 'mat' 'on' 'sat' 'the']

TF-IDF matrix:
    and  animals   are   cat  cats   dog  dogs  floor    is   log   mat    on   sat   the
0  0.00    0.000  0.00  0.48  0.00  0.00  0.00   0.00  0.00  0.00  0.48  0.35  0.35  0.58
1  0.00    0.000  0.00  0.00  0.00  0.50  0.00   0.00  0.00  0.50  0.00  0.36  0.36  0.60
2  0.41    0.410  0.41  0.00  0.31  0.00  0.31   0.00  0.00  0.00  0.00  0.00  0.00  0.52
3  0.00    0.000  0.00  0.00  0.00  0.00  0.00   0.50  0.50  0.00  0.38  0.28  0.00  0.55

Top 3 important words for document 0:
  the: 0.576
  cat: 0.478
  mat: 0.478

1.2.4 N-gram Models

N-grams are combinations of N consecutive words.

from sklearn.feature_extraction.text import CountVectorizer

text = ["Natural language processing is fun"]

# Unigram (1-gram)
unigram_vec = CountVectorizer(ngram_range=(1, 1))
unigrams = unigram_vec.fit_transform(text)
print("=== Unigram (1-gram) ===")
print(unigram_vec.get_feature_names_out())

# Bigram (2-gram)
bigram_vec = CountVectorizer(ngram_range=(2, 2))
bigrams = bigram_vec.fit_transform(text)
print("\n=== Bigram (2-gram) ===")
print(bigram_vec.get_feature_names_out())

# Trigram (3-gram)
trigram_vec = CountVectorizer(ngram_range=(3, 3))
trigrams = trigram_vec.fit_transform(text)
print("\n=== Trigram (3-gram) ===")
print(trigram_vec.get_feature_names_out())

# From 1-gram through 3-gram
combined_vec = CountVectorizer(ngram_range=(1, 3))
combined = combined_vec.fit_transform(text)
print(f"\n=== Combined (1-3 gram) ===")
print(f"Total feature count: {len(combined_vec.get_feature_names_out())}")

Output:

=== Unigram (1-gram) ===
['fun' 'is' 'language' 'natural' 'processing']

=== Bigram (2-gram) ===
['is fun' 'language processing' 'natural language' 'processing is']

=== Trigram (3-gram) ===
['language processing is' 'natural language processing' 'processing is fun']

=== Combined (1-3 gram) ===
Total feature count: 12

1.3 Word Embeddings

What Are Word Embeddings?

Word Embeddings represent words as low-dimensional, dense vectors. Semantically similar words are placed close together in this vector space.

graph LR A[One-Hot
Sparse, high-dimensional] --> B[Word2Vec
Dense, low-dimensional] A --> C[GloVe
Dense, low-dimensional] A --> D[FastText
Dense, low-dimensional] style A fill:#ffebee style B fill:#e8f5e9 style C fill:#e3f2fd style D fill:#fff3e0

1.3.1 Word2Vec

Word2Vec learns distributed representations of words from a large corpus. It has two architectures:

from gensim.models import Word2Vec
from nltk.tokenize import word_tokenize
import numpy as np

# Sample corpus
corpus = [
    "Natural language processing with deep learning",
    "Machine learning is a subset of artificial intelligence",
    "Deep learning uses neural networks",
    "Neural networks are inspired by biological neurons",
    "Natural language understanding requires context",
    "Context is important in language processing"
]

# Tokenize
tokenized_corpus = [word_tokenize(sent.lower()) for sent in corpus]

# Train the Word2Vec model
# Skip-gram model (sg=1), CBOW (sg=0)
model = Word2Vec(
    sentences=tokenized_corpus,
    vector_size=100,  # Embedding dimension
    window=5,         # Context window
    min_count=1,      # Minimum word count
    sg=1,             # Skip-gram
    workers=4
)

print("=== Word2Vec ===")
print(f"Vocabulary size: {len(model.wv)}")
print(f"Embedding dimension: {model.wv.vector_size}")

# Get a word vector
word = "learning"
vector = model.wv[word]
print(f"\nVector for '{word}' (first 10 dimensions):")
print(vector[:10])

# Search for similar words
similar_words = model.wv.most_similar("learning", topn=5)
print(f"\nWords similar to '{word}':")
for word, similarity in similar_words:
    print(f"  {word}: {similarity:.3f}")

# Word similarity
similarity = model.wv.similarity("neural", "networks")
print(f"\nSimilarity between 'neural' and 'networks': {similarity:.3f}")

# Word arithmetic (example: King - Man + Woman ≈ Queen)
# Simple example: deep - neural + machine
try:
    result = model.wv.most_similar(
        positive=['deep', 'machine'],
        negative=['neural'],
        topn=3
    )
    print("\nWord arithmetic (deep - neural + machine):")
    for word, score in result:
        print(f"  {word}: {score:.3f}")
except:
    print("\nWord arithmetic: not enough data")

Example output:

=== Word2Vec ===
Vocabulary size: 27
Embedding dimension: 100

Vector for 'learning' (first 10 dimensions):
[-0.00234  0.00891 -0.00156  0.00423 -0.00678  0.00234  0.00567 -0.00123  0.00789 -0.00345]

Words similar to 'learning':
  deep: 0.876
  neural: 0.823
  processing: 0.791
  networks: 0.765
  natural: 0.734

Similarity between 'neural' and 'networks': 0.892

1.3.2 GloVe (Global Vectors)

GloVe learns embeddings using word co-occurrence statistics. Unlike Word2Vec, it leverages global co-occurrence information.

import gensim.downloader as api
import numpy as np

# Load a pretrained GloVe model (downloads on first run)
print("Downloading GloVe model...")
glove_model = api.load("glove-wiki-gigaword-100")

print("\n=== GloVe (Pretrained) ===")
print(f"Vocabulary size: {len(glove_model)}")
print(f"Embedding dimension: {glove_model.vector_size}")

# Word vector
word = "computer"
vector = glove_model[word]
print(f"\nVector for '{word}' (first 10 dimensions):")
print(vector[:10])

# Similar words
similar_words = glove_model.most_similar(word, topn=5)
print(f"\nWords similar to '{word}':")
for w, sim in similar_words:
    print(f"  {w}: {sim:.3f}")

# The famous word arithmetic: King - Man + Woman ≈ Queen
result = glove_model.most_similar(
    positive=['king', 'woman'],
    negative=['man'],
    topn=5
)
print("\nWord arithmetic (King - Man + Woman):")
for w, sim in result:
    print(f"  {w}: {sim:.3f}")

# Similarity calculation
pairs = [
    ("good", "bad"),
    ("good", "excellent"),
    ("cat", "dog"),
    ("cat", "car")
]
print("\nSimilarity of word pairs:")
for w1, w2 in pairs:
    sim = glove_model.similarity(w1, w2)
    print(f"  {w1} - {w2}: {sim:.3f}")

Example output:

=== GloVe (Pretrained) ===
Vocabulary size: 400000
Embedding dimension: 100

Vector for 'computer' (first 10 dimensions):
[ 0.45893  0.19521 -0.23456  0.67234 -0.34521  0.12345  0.89012 -0.45678  0.23456 -0.78901]

Words similar to 'computer':
  computers: 0.887
  software: 0.756
  hardware: 0.734
  pc: 0.712
  system: 0.689

Word arithmetic (King - Man + Woman):
  queen: 0.768
  monarch: 0.654
  princess: 0.621
  crown: 0.598
  prince: 0.587

Similarity of word pairs:
  good - bad: 0.523
  good - excellent: 0.791
  cat - dog: 0.821
  cat - car: 0.234

1.3.3 FastText

FastText is a word embedding method that can handle out-of-vocabulary (OOV) words by using subword information.

from gensim.models import FastText

# Sample corpus
sentences = [
    ["machine", "learning", "is", "awesome"],
    ["deep", "learning", "with", "neural", "networks"],
    ["natural", "language", "processing"],
    ["fasttext", "handles", "unknown", "words"]
]

# Train the FastText model
ft_model = FastText(
    sentences=sentences,
    vector_size=100,
    window=3,
    min_count=1,
    sg=1  # Skip-gram
)

print("=== FastText ===")
print(f"Vocabulary size: {len(ft_model.wv)}")

# A word included in the training data
word = "learning"
vector = ft_model.wv[word]
print(f"\nVector for '{word}' (first 5 dimensions):")
print(vector[:5])

# Vectors can be obtained even for unknown (OOV) words
unknown_word = "machinelearning"  # not in the training data
try:
    unknown_vector = ft_model.wv[unknown_word]
    print(f"\nVector for unknown word '{unknown_word}' (first 5 dimensions):")
    print(unknown_vector[:5])
    print("✓ FastText can handle unknown words")
except:
    print(f"\nUnknown word '{unknown_word}' could not be vectorized")

# Similar words
similar = ft_model.wv.most_similar("learning", topn=3)
print(f"\nWords similar to '{word}':")
for w, sim in similar:
    print(f"  {w}: {sim:.3f}")

Word2Vec vs. GloVe vs. FastText

Method Training Approach Advantages Disadvantages OOV Support
Word2Vec Local co-occurrence (window) Fast, efficient Ignores global statistics No
GloVe Global co-occurrence matrix Leverages global statistics Somewhat slower No
FastText Subword information Handles OOV, uses morphological info Somewhat complex Yes

1.4 Japanese NLP

Characteristics and Challenges of Japanese

Unlike English, Japanese has the following characteristics:

1.4.1 Morphological Analysis with MeCab

import MeCab

# Initialize MeCab
mecab = MeCab.Tagger()

text = "自然言語処理は人工知能の一分野です。"

# Morphological analysis
print("=== MeCab Morphological Analysis ===")
print(mecab.parse(text))

# Wakachigaki (word segmentation)
mecab_wakati = MeCab.Tagger("-Owakati")
wakati_text = mecab_wakati.parse(text).strip()
print(f"Segmented: {wakati_text}")

# Extract part-of-speech information
node = mecab.parseToNode(text)
words = []
pos_tags = []

while node:
    features = node.feature.split(',')
    if node.surface:
        words.append(node.surface)
        pos_tags.append(features[0])
    node = node.next

print("\nWords and parts of speech:")
for word, pos in zip(words, pos_tags):
    print(f"  {word}: {pos}")

Output:

=== MeCab Morphological Analysis ===
自然	名詞,一般,*,*,*,*,自然,シゼン,シゼン
言語	名詞,一般,*,*,*,*,言語,ゲンゴ,ゲンゴ
処理	名詞,サ変接続,*,*,*,*,処理,ショリ,ショリ
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
人工	名詞,一般,*,*,*,*,人工,ジンコウ,ジンコー
知能	名詞,一般,*,*,*,*,知能,チノウ,チノー
の	助詞,連体化,*,*,*,*,の,ノ,ノ
一	名詞,数,*,*,*,*,一,イチ,イチ
分野	名詞,一般,*,*,*,*,分野,ブンヤ,ブンヤ
です	助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。	記号,句点,*,*,*,*,。,。,。
EOS

Segmented: 自然 言語 処理 は 人工 知能 の 一 分野 です 。

Words and parts of speech:
  自然: 名詞
  言語: 名詞
  処理: 名詞
  は: 助詞
  人工: 名詞
  知能: 名詞
  の: 助詞
  一: 名詞
  分野: 名詞
  です: 助動詞
  。: 記号

1.4.2 Morphological Analysis with SudachiPy

SudachiPy provides multiple segmentation modes (A: short unit, B: medium unit, C: long unit).

from sudachipy import tokenizer
from sudachipy import dictionary

# Initialize Sudachi
tokenizer_obj = dictionary.Dictionary().create()

text = "東京都渋谷区に行きました。"

print("=== SudachiPy Segmentation Mode Comparison ===\n")

# Mode A (short unit)
mode_a = tokenizer_obj.tokenize(text, tokenizer.Tokenizer.SplitMode.A)
print("Mode A (short unit):")
print([m.surface() for m in mode_a])

# Mode B (medium unit)
mode_b = tokenizer_obj.tokenize(text, tokenizer.Tokenizer.SplitMode.B)
print("\nMode B (medium unit):")
print([m.surface() for m in mode_b])

# Mode C (long unit)
mode_c = tokenizer_obj.tokenize(text, tokenizer.Tokenizer.SplitMode.C)
print("\nMode C (long unit):")
print([m.surface() for m in mode_c])

# Detailed information
print("\nDetailed information (Mode B):")
for token in mode_b:
    print(f"  Surface form: {token.surface()}")
    print(f"  Base form: {token.dictionary_form()}")
    print(f"  Part of speech: {token.part_of_speech()[0]}")
    print(f"  Reading: {token.reading_form()}")
    print()

Output:

=== SudachiPy Segmentation Mode Comparison ===

Mode A (short unit):
['東京', '都', '渋谷', '区', 'に', '行き', 'まし', 'た', '。']

Mode B (medium unit):
['東京都', '渋谷区', 'に', '行く', 'た', '。']

Mode C (long unit):
['東京都渋谷区', 'に', '行く', 'た', '。']

Detailed information (Mode B):
  Surface form: 東京都
  Base form: 東京都
  Part of speech: 名詞
  Reading: トウキョウト
  ...

1.4.3 Japanese Text Normalization

import unicodedata

def normalize_japanese(text):
    """Normalize Japanese text"""
    # Unicode normalization (NFKC: convert compatibility characters to their standard form)
    text = unicodedata.normalize('NFKC', text)

    # Convert full-width alphanumerics to half-width
    text = text.translate(str.maketrans(
        '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
        '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
    ))

    # Normalize long-vowel marks
    text = text.replace('ー', '')

    return text

# Test
texts = [
    "コンピュータ",
    "コンピューター",
    "AI技術",
    "AI技術",
    "12345"
]

print("=== Japanese Normalization ===")
for original in texts:
    normalized = normalize_japanese(original)
    print(f"{original} → {normalized}")

Output:

=== Japanese Normalization ===
コンピュータ → コンピュータ
コンピューター → コンピュータ
AI技術 → AI技術
AI技術 → AI技術
12345 → 12345

1.5 Basic NLP Tasks

1.5.1 Document Classification

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, accuracy_score

# Sample data
texts = [
    "Machine learning is a subset of AI",
    "Deep learning uses neural networks",
    "Natural language processing analyzes text",
    "Computer vision recognizes images",
    "Reinforcement learning learns from rewards",
    "Supervised learning uses labeled data",
    "Unsupervised learning finds patterns",
    "NLP understands human language",
    "CNN is used for image classification",
    "RNN is good for sequence data"
]

labels = [
    "ML", "DL", "NLP", "CV", "RL",
    "ML", "ML", "NLP", "CV", "DL"
]

# Split into train and test data
X_train, X_test, y_train, y_test = train_test_split(
    texts, labels, test_size=0.3, random_state=42
)

# TF-IDF vectorization
vectorizer = TfidfVectorizer()
X_train_tfidf = vectorizer.fit_transform(X_train)
X_test_tfidf = vectorizer.transform(X_test)

# Naive Bayes classifier
classifier = MultinomialNB()
classifier.fit(X_train_tfidf, y_train)

# Predict
y_pred = classifier.predict(X_test_tfidf)

# Evaluate
print("=== Document Classification ===")
print(f"Accuracy: {accuracy_score(y_test, y_pred):.3f}")
print(f"\nClassification report:")
print(classification_report(y_test, y_pred))

# Classify new documents
new_texts = [
    "Neural networks are powerful",
    "Text mining extracts information"
]
new_tfidf = vectorizer.transform(new_texts)
predictions = classifier.predict(new_tfidf)

print("\nClassification of new documents:")
for text, pred in zip(new_texts, predictions):
    print(f"  '{text}' → {pred}")

1.5.2 Similarity Calculation

from sklearn.metrics.pairwise import cosine_similarity
from sklearn.feature_extraction.text import TfidfVectorizer

documents = [
    "Machine learning is fun",
    "Deep learning is exciting",
    "Natural language processing is interesting",
    "I love pizza and pasta",
    "Python is a great programming language"
]

# TF-IDF vectorization
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(documents)

# Compute cosine similarity
similarity_matrix = cosine_similarity(tfidf_matrix)

print("=== Cosine Similarity Between Documents ===\n")
print("Similarity matrix:")
import pandas as pd
df_sim = pd.DataFrame(
    similarity_matrix,
    index=[f"Doc{i}" for i in range(len(documents))],
    columns=[f"Doc{i}" for i in range(len(documents))]
)
print(df_sim.round(3))

# Most similar document pair
print("\nMost similar document for each document:")
for i, doc in enumerate(documents):
    # Exclude the document itself
    similarities = similarity_matrix[i].copy()
    similarities[i] = -1
    most_similar_idx = similarities.argmax()
    print(f"Doc{i}: '{doc[:30]}...'")
    print(f"  → Doc{most_similar_idx}: '{documents[most_similar_idx][:30]}...' (similarity: {similarities[most_similar_idx]:.3f})\n")

1.5.3 Text Clustering

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans
import numpy as np

documents = [
    "Machine learning algorithms are powerful",
    "Deep learning uses neural networks",
    "Supervised learning needs labeled data",
    "Pizza is delicious food",
    "I love eating pasta",
    "Italian cuisine is amazing",
    "Python is a programming language",
    "JavaScript is used for web development",
    "Java is object-oriented"
]

# TF-IDF vectorization
vectorizer = TfidfVectorizer(max_features=20)
X = vectorizer.fit_transform(documents)

# K-Means clustering
n_clusters = 3
kmeans = KMeans(n_clusters=n_clusters, random_state=42)
clusters = kmeans.fit_predict(X)

print("=== Text Clustering ===\n")
print(f"Number of clusters: {n_clusters}\n")

# Display documents per cluster
for i in range(n_clusters):
    print(f"Cluster {i}:")
    cluster_docs = [doc for doc, cluster in zip(documents, clusters) if cluster == i]
    for doc in cluster_docs:
        print(f"  - {doc}")
    print()

# Words close to the cluster centers
feature_names = vectorizer.get_feature_names_out()
print("Characteristic words for each cluster (top 5):")
for i in range(n_clusters):
    center = kmeans.cluster_centers_[i]
    top_indices = center.argsort()[-5:][::-1]
    top_words = [feature_names[idx] for idx in top_indices]
    print(f"Cluster {i}: {', '.join(top_words)}")

Example output:

=== Text Clustering ===

Number of clusters: 3

Cluster 0:
  - Machine learning algorithms are powerful
  - Deep learning uses neural networks
  - Supervised learning needs labeled data

Cluster 1:
  - Pizza is delicious food
  - I love eating pasta
  - Italian cuisine is amazing

Cluster 2:
  - Python is a programming language
  - JavaScript is used for web development
  - Java is object-oriented

Characteristic words for each cluster (top 5):
Cluster 0: learning, neural, deep, machine, supervised
Cluster 1: italian, food, pizza, pasta, cuisine
Cluster 2: programming, language, python, java, javascript

1.6 Chapter Summary

What You Learned

  1. Text Preprocessing

    • Tokenization (word, subword, character level)
    • Normalization and standardization
    • Stopword removal
    • Stemming and lemmatization
  2. Word Representations

    • One-Hot Encoding: simple but high-dimensional
    • Bag of Words: based on occurrence frequency
    • TF-IDF: accounts for word importance
    • N-gram: word combinations
  3. Word Embeddings

    • Word2Vec: learns from local co-occurrence
    • GloVe: leverages global co-occurrence statistics
    • FastText: handles OOV using subword information
  4. Japanese NLP

    • MeCab: fast morphological analysis
    • SudachiPy: flexible segmentation modes
    • Unicode normalization and handling spelling variation
  5. Basic NLP Tasks

    • Document classification
    • Similarity calculation
    • Text clustering

Guidelines for Choosing a Method

Task Recommended Method Reason
Document classification (small-scale) TF-IDF + linear model Simple, fast
Semantic similarity Word Embeddings Captures meaning
Handling unknown words FastText, subwords Leverages morphological information
Large-scale data Pretrained models Transfer learning
Japanese text processing MeCab/SudachiPy + normalization Addresses language-specific characteristics

Coming Up Next

In Chapter 2, you will learn about sequence models and RNNs:


Exercises

Problem 1 (Difficulty: Easy)

Explain the difference between stemming and lemmatization, and describe the advantages and disadvantages of each.

Sample Answer

Answer:

Stemming:

Lemmatization:

When to use each:

Problem 2 (Difficulty: Medium)

Calculate TF-IDF for the following texts and identify the most important word in each document.

documents = [
    "The cat sat on the mat",
    "The dog sat on the log",
    "Cats and dogs are pets"
]
Sample Answer
from sklearn.feature_extraction.text import TfidfVectorizer
import pandas as pd
import numpy as np

documents = [
    "The cat sat on the mat",
    "The dog sat on the log",
    "Cats and dogs are pets"
]

# TF-IDF vectorizer
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(documents)

# Feature names
feature_names = vectorizer.get_feature_names_out()

# Display as a DataFrame
df = pd.DataFrame(
    tfidf_matrix.toarray(),
    columns=feature_names
)

print("=== TF-IDF Matrix ===")
print(df.round(3))

# Most important word for each document
print("\nMost important word for each document:")
for i, doc in enumerate(documents):
    scores = tfidf_matrix[i].toarray()[0]
    top_idx = scores.argmax()
    top_word = feature_names[top_idx]
    top_score = scores[top_idx]
    print(f"Document {i}: '{doc}'")
    print(f"  Top word: '{top_word}' (score: {top_score:.3f})")

    # Top 3
    top_3_indices = scores.argsort()[-3:][::-1]
    print("  Top 3:")
    for idx in top_3_indices:
        if scores[idx] > 0:
            print(f"    {feature_names[idx]}: {scores[idx]:.3f}")
    print()

Output:

=== TF-IDF Matrix ===
    and   are   cat  cats   dog  dogs   log   mat    on  pets   sat   the
0  0.00  0.00  0.48  0.00  0.00  0.00  0.00  0.48  0.35  0.00  0.35  0.58
1  0.00  0.00  0.00  0.00  0.50  0.00  0.50  0.00  0.36  0.00  0.36  0.60
2  0.41  0.41  0.00  0.31  0.00  0.31  0.00  0.00  0.00  0.41  0.00  0.52

Most important word for each document:
Document 0: 'The cat sat on the mat'
  Top word: 'the' (score: 0.576)
  Top 3:
    the: 0.576
    cat: 0.478
    mat: 0.478

Document 1: 'The dog sat on the log'
  Top word: 'the' (score: 0.596)
  Top 3:
    the: 0.596
    log: 0.496
    dog: 0.496

Document 2: 'Cats and dogs are pets'
  Top word: 'the' (score: 0.524)
  Top 3:
    the: 0.524
    and: 0.412
    pets: 0.412

Problem 3 (Difficulty: Medium)

Explain the difference between Word2Vec's two architectures (CBOW and Skip-gram), and describe the situations in which each is most effective.

Sample Answer

Answer:

CBOW (Continuous Bag of Words):

Skip-gram:

When to use each:

Situation Recommendation
Small corpus CBOW
Large corpus Skip-gram
Speed-focused CBOW
Quality-focused Skip-gram
Frequent words matter most CBOW
Rare words matter too Skip-gram

Problem 4 (Difficulty: Hard)

Perform morphological analysis with MeCab on the Japanese text "東京都渋谷区でAI開発を行っています。" ("I am developing AI in Shibuya, Tokyo.") and extract only the nouns. Then write code that vectorizes the result with TF-IDF and performs document classification.

Sample Answer
import MeCab
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB

# Initialize MeCab
mecab = MeCab.Tagger()

def extract_nouns(text):
    """Extract only nouns"""
    node = mecab.parseToNode(text)
    nouns = []
    while node:
        features = node.feature.split(',')
        # If the part of speech is a noun
        if features[0] == '名詞' and node.surface:
            nouns.append(node.surface)
        node = node.next
    return ' '.join(nouns)

# Sample data
texts = [
    "東京都渋谷区でAI開発を行っています。",
    "大阪府でロボット研究をしています。",
    "機械学習の勉強を東京でしています。",
    "人工知能の開発は大阪で進めています。"
]

labels = ["tech", "robot", "ml", "ai"]

print("=== Japanese Text Preprocessing and Classification ===\n")

# Extract nouns
processed_texts = []
for text in texts:
    nouns = extract_nouns(text)
    print(f"Original: {text}")
    print(f"Nouns: {nouns}\n")
    processed_texts.append(nouns)

# TF-IDF vectorization
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(processed_texts)

print("Vocabulary:")
print(vectorizer.get_feature_names_out())

print("\nTF-IDF matrix:")
import pandas as pd
df = pd.DataFrame(X.toarray(), columns=vectorizer.get_feature_names_out())
print(df.round(3))

# Train the classifier
classifier = MultinomialNB()
classifier.fit(X, labels)

# Classify new text
new_text = "東京でAI技術の研究をしています。"
new_nouns = extract_nouns(new_text)
new_vector = vectorizer.transform([new_nouns])
prediction = classifier.predict(new_vector)

print(f"\nNew text: {new_text}")
print(f"Extracted nouns: {new_nouns}")
print(f"Classification result: {prediction[0]}")

Example output:

=== Japanese Text Preprocessing and Classification ===

Original: 東京都渋谷区でAI開発を行っています。
Nouns: 東京 都 渋谷 区 AI 開発

Original: 大阪府でロボット研究をしています。
Nouns: 大阪 府 ロボット 研究

Original: 機械学習の勉強を東京でしています。
Nouns: 機械 学習 勉強 東京

Original: 人工知能の開発は大阪で進めています。
Nouns: 人工 知能 開発 大阪

Vocabulary:
['ai' 'ロボット' '人工' '勉強' '大阪' '学習' '府' 'East京' '機械' '渋谷' '知能' '研究' '開発']

TF-IDF matrix:
    ai  ロボット  人工  勉強  大阪  学習   府  東京  機械  渋谷  知能  研究  開発
0  0.45   0.0  0.0  0.0  0.0  0.0  0.0  0.36  0.0  0.45  0.0  0.0  0.36
1  0.00   0.52  0.0  0.0  0.40  0.0  0.52  0.00  0.0  0.00  0.0  0.52  0.00
2  0.00   0.00  0.0  0.48  0.00  0.48  0.00  0.37  0.48  0.00  0.0  0.00  0.00
3  0.00   0.00  0.48  0.0  0.37  0.00  0.00  0.00  0.0  0.00  0.48  0.00  0.37

New text: 東京でAI技術の研究をしています。
Extracted nouns: 東京 AI 技術 研究
Classification result: tech

Problem 5 (Difficulty: Hard)

Calculate the cosine similarity between two sentences using (1) Bag of Words and (2) the average of their Word2Vec word-vector embeddings, then compare the results.

Sample Answer
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from gensim.models import Word2Vec
from nltk.tokenize import word_tokenize
import numpy as np

# Two sentences
sentence1 = "I love machine learning"
sentence2 = "I enjoy deep learning"

print("=== Comparing Cosine Similarity ===\n")
print(f"Sentence 1: {sentence1}")
print(f"Sentence 2: {sentence2}\n")

# ========================================
# (1) Similarity via Bag of Words
# ========================================
vectorizer = CountVectorizer()
bow_vectors = vectorizer.fit_transform([sentence1, sentence2])
bow_similarity = cosine_similarity(bow_vectors[0], bow_vectors[1])[0][0]

print("=== (1) Bag of Words ===")
print(f"Vocabulary: {vectorizer.get_feature_names_out()}")
print(f"Sentence 1 BoW: {bow_vectors[0].toarray()}")
print(f"Sentence 2 BoW: {bow_vectors[1].toarray()}")
print(f"Cosine similarity: {bow_similarity:.3f}\n")

# ========================================
# (2) Average of Word2Vec embeddings
# ========================================
# Prepare a corpus (a larger corpus would be needed in practice)
corpus = [
    "I love machine learning",
    "I enjoy deep learning",
    "Machine learning is fun",
    "Deep learning uses neural networks",
    "I love deep neural networks"
]
tokenized_corpus = [word_tokenize(sent.lower()) for sent in corpus]

# Train the Word2Vec model
w2v_model = Word2Vec(
    sentences=tokenized_corpus,
    vector_size=50,
    window=3,
    min_count=1,
    sg=1
)

def sentence_vector(sentence, model):
    """Sentence vector representation (average of word vectors)"""
    words = word_tokenize(sentence.lower())
    word_vectors = [model.wv[word] for word in words if word in model.wv]
    if len(word_vectors) == 0:
        return np.zeros(model.vector_size)
    return np.mean(word_vectors, axis=0)

# Compute sentence vectors
vec1 = sentence_vector(sentence1, w2v_model)
vec2 = sentence_vector(sentence2, w2v_model)

# Cosine similarity
w2v_similarity = cosine_similarity([vec1], [vec2])[0][0]

print("=== (2) Word2Vec ===")
print(f"Sentence 1 vector (first 5 dimensions): {vec1[:5]}")
print(f"Sentence 2 vector (first 5 dimensions): {vec2[:5]}")
print(f"Cosine similarity: {w2v_similarity:.3f}\n")

# ========================================
# Comparison
# ========================================
print("=== Comparison ===")
print(f"BoW similarity: {bow_similarity:.3f}")
print(f"Word2Vec similarity: {w2v_similarity:.3f}")
print(f"\nDifference: {abs(w2v_similarity - bow_similarity):.3f}")

print("\nDiscussion:")
print("- BoW only considers shared words ('I', 'learning')")
print("- Word2Vec captures semantic similarity ('love' ≈ 'enjoy', 'machine' ≈ 'deep')")
print("- Word2Vec generally returns more semantically accurate similarity scores")

Example output:

=== Comparing Cosine Similarity ===

Sentence 1: I love machine learning
Sentence 2: I enjoy deep learning

=== (1) Bag of Words ===
Vocabulary: ['deep' 'enjoy' 'learning' 'love' 'machine']
Sentence 1 BoW: [[0 0 1 1 1]]
Sentence 2 BoW: [[1 1 1 0 0]]
Cosine similarity: 0.333

=== (2) Word2Vec ===
Sentence 1 vector (first 5 dimensions): [-0.00123  0.00456 -0.00789  0.00234  0.00567]
Sentence 2 vector (first 5 dimensions): [-0.00098  0.00423 -0.00712  0.00198  0.00534]
Cosine similarity: 0.876

=== Comparison ===
BoW similarity: 0.333
Word2Vec similarity: 0.876

Difference: 0.543

Discussion:
- BoW only considers shared words ('I', 'learning')
- Word2Vec captures semantic similarity ('love' ≈ 'enjoy', 'machine' ≈ 'deep')
- Word2Vec generally returns more semantically accurate similarity scores

References

  1. Jurafsky, D., & Martin, J. H. (2023). Speech and Language Processing (3rd ed.). Stanford University.
  2. Eisenstein, J. (2019). Introduction to Natural Language Processing. MIT Press.
  3. Mikolov, T., et al. (2013). "Efficient Estimation of Word Representations in Vector Space." arXiv:1301.3781.
  4. Pennington, J., Socher, R., & Manning, C. D. (2014). "GloVe: Global Vectors for Word Representation." EMNLP.
  5. Bojanowski, P., et al. (2017). "Enriching Word Vectors with Subword Information." TACL.
  6. Kudo, T., & Shindo, H. (2018). Theory and Implementation of Morphological Analysis [形態素解析の理論と実装]. Kindai Kagaku Sha.

Disclaimer