Classify Text with Neural Nets

Learn to classify text with simple neural networks in this hands-on Applied AI engineering tutorial. Step-by-step guidance, practical exercises, and troubleshooting tips to build your skills.

Focus: classify text with simple neural nets

Sponsored

You have a pile of customer support tickets, each one a few lines of text, and you need to know which are bugs, which are billing questions, and which are feature requests. Reading them one by one is soul-crushing. Rule-based keyword matching breaks the moment a customer writes "my account is broken" (is that a bug or a billing issue?). This lesson shows you how to classify text with simple neural nets — a bag-of-words plus a single hidden layer — to get accurate, trainable text classifiers with just a few dozen lines of Python and zero external dependencies beyond NumPy.

The problem this lesson solves

Text classification is everywhere: spam detection, sentiment analysis, intent routing, and content moderation. But the naive approaches fail in predictable ways:

  • Keyword matching — misses synonyms, sarcasm, and indirect language.
  • Hand-written rules — don't scale; every new edge case needs a new rule.
  • Pre-trained LLM APIs — powerful but expensive and heavyweight for a simple routing task.

What you need is a data-driven approach: feed labeled examples, let the model learn patterns in the words themselves, then classify new text automatically. A simple neural network — not a transformer, not a CNN — is the sweet spot for many production workloads.

By the end of this lesson you'll be able to:

  • Explain how a neural net learns to separate text categories.
  • Build a two-layer network that classifies text with nothing but Python and NumPy.
  • Train, evaluate, and debug your classifier on a real dataset.

Core concept / mental model

Think of text classification like sorting mail. A neural network is a mail sorter that was trained on thousands of labeled letters. Each letter (document) is converted into a bag of words — a frequency vector of the words that appear. The sorter reads these word counts, weighs them through a hidden layer, and outputs a probability for each class.

The magic is that the network learns the weights — how much each word matters for each class — during training. "Refund" gets a high weight for billing; "crash" gets a high weight for bug. It doesn't just match keywords; it learns combinations of words.

Key definitions

  • Bag-of-words (BoW) — a vector where each position counts occurrences of a word in the vocabulary.
  • One-hot encoding — representing a class as a vector of 0s and 1s, e.g., bug[1, 0, 0].
  • Hidden layer — middle layer of neurons that learns non-linear combinations of input features.
  • Softmax — converts raw scores into probabilities that sum to 1.
  • Cross-entropy loss — a measure of how wrong the predictions are, used to drive learning.

Analogy: If a linear model is a straight line separating points, a neural net with one hidden layer is a bent line that can curve around clusters. Adding one hidden layer lets you model non-linear decision boundaries — enough for most basic text tasks.

How it works step by step

Here's the pipeline, end to end:

1. Build a vocabulary

Collect all unique words across your training set. Each word becomes a column in the feature matrix.

2. Convert text to vectors

For each document, create a vector of word counts (or binary presence). This is the bag-of-words representation.

3. Define the network architecture

  • Input layer: size = vocabulary size.
  • Hidden layer: e.g., 10 neurons with ReLU activation.
  • Output layer: size = number of classes, followed by softmax.

4. Feed forward

Multiply input by weights, add bias, apply activation, repeat for the output layer, then apply softmax.

5. Compute loss and update weights

Use cross-entropy loss. Then use backpropagation to compute gradients of the loss with respect to every weight, and update them with gradient descent. Repeat for many epochs.

6. Predict new text

Convert new text to the same bag-of-words format, feed forward, and take the class with the highest probability.

These steps are exactly what you'll implement in the hands-on section next.

Hands-on walkthrough

Let's build a working classifier from scratch. We'll use a small dataset of product reviews labeled as positive, negative, or neutral.

Setup

import numpy as np
from collections import Counter
import re

Step 1: Prepare the dataset and vocabulary

documents = [
    ("this product is amazing, works great", "positive"),
    ("love it, very good quality", "positive"),
    ("terrible, it broke after one day", "negative"),
    ("waste of money, do not buy", "negative"),
    ("it is okay, nothing special", "neutral"),
    ("not bad but not great either", "neutral"),
]

vocab = set()
for text, _ in documents:
    for word in re.findall(r"\b\w+\b", text.lower()):
        vocab.add(word)

vocab = sorted(vocab)
word_to_idx = {w: i for i, w in enumerate(vocab)}
print(f"Vocabulary size: {len(vocab)}")

Output:

Vocabulary size: 22

Step 2: Convert text to bag-of-words vectors

def text_to_vector(text):
    vec = np.zeros(len(vocab))
    for word in re.findall(r"\b\w+\b", text.lower()):
        if word in word_to_idx:
            vec[word_to_idx[word]] += 1
    return vec

X = np.array([text_to_vector(t) for t, _ in documents])
# One-hot encode labels
class_names = ["positive", "negative", "neutral"]
class_to_idx = {c: i for i, c in enumerate(class_names)}
Y = np.array([class_to_idx[l] for _, l in documents])
Y_onehot = np.eye(len(class_names))[Y]

Step 3: Build the neural network

np.random.seed(42)
input_size = len(vocab)
hidden_size = 10
output_size = len(class_names)

# Weights and biases
W1 = np.random.randn(input_size, hidden_size) * 0.01
b1 = np.zeros(hidden_size)
W2 = np.random.randn(hidden_size, output_size) * 0.01
b2 = np.zeros(output_size)

learning_rate = 0.1

def relu(x):
    return np.maximum(0, x)

def softmax(x):
    exp_x = np.exp(x - np.max(x, axis=1, keepdims=True))
    return exp_x / np.sum(exp_x, axis=1, keepdims=True)

Step 4: Train the network

def train(X, Y_onehot, W1, b1, W2, b2, epochs=1000):
    for epoch in range(epochs):
        # Forward pass
        z1 = X @ W1 + b1
        a1 = relu(z1)
        z2 = a1 @ W2 + b2
        probs = softmax(z2)

        # Loss (cross-entropy)
        loss = -np.mean(Y_onehot * np.log(probs + 1e-8))

        # Backprop
        dz2 = probs - Y_onehot
        dW2 = a1.T @ dz2 / len(X)
        db2 = np.mean(dz2, axis=0)
        da1 = dz2 @ W2.T
        dz1 = da1 * (z1 > 0)  # ReLU gradient
        dW1 = X.T @ dz1 / len(X)
        db1 = np.mean(dz1, axis=0)

        # Update
        W1 -= learning_rate * dW1
        b1 -= learning_rate * db1
        W2 -= learning_rate * dW2
        b2 -= learning_rate * db2

        if epoch % 100 == 0:
            print(f"Epoch {epoch}, loss: {loss:.4f}")

    return W1, b1, W2, b2

W1, b1, W2, b2 = train(X, Y_onehot, W1, b1, W2, b2)

Expected output (loss decreasing):

Epoch 0, loss: 1.0986
Epoch 100, loss: 0.8501
Epoch 200, loss: 0.3512
...
Epoch 1000, loss: 0.0124

Step 5: Predict new text

def predict(text):
    vec = text_to_vector(text).reshape(1, -1)
    z1 = vec @ W1 + b1
    a1 = relu(z1)
    z2 = a1 @ W2 + b2
    probs = softmax(z2)
    idx = np.argmax(probs)
    return class_names[idx], probs

print(predict("this is a terrible product"))
print(predict("I love this, so great"))

Output:

('negative', array([[0.01, 0.97, 0.02]]))
('positive', array([[0.96, 0.01, 0.03]]))

The model learned that "terrible" points to negative, and "love" and "great" point to positive — even though "great" appears in both a positive and a negative example! This is the power of learned weights over rules.

Pro tip: In practice, remove stop words ("is", "it"), apply stemming, or use TF-IDF features to improve accuracy. But for learning, raw counts are perfectly fine.

Compare options / when to choose what

There are many ways to classify text. Here's how a simple neural net compares:

Method Accuracy Training time Interpretability When to use
Naive Bayes Good Seconds High Baselines, small datasets
Logistic Regression Good Seconds High Linear problems, sparse features
Simple NN (this lesson) Better Minutes Medium When data has non-linear patterns
Transformers (BERT) Best Hours/days Low Complex semantics, large datasets

Choose a simple NN when:

  • You have a few thousand labeled examples.
  • You need reasonable accuracy without GPU training.
  • You want a custom, easy-to-debug model.

Choose a transformer when:

  • You have >100k examples or need state-of-the-art accuracy.
  • You're dealing with sarcasm, context, or nuanced language.
  • Budget and latency allow.

Troubleshooting & edge cases

Even a simple model can break. Here are the most common problems and fixes:

  • Loss doesn't decrease: learning rate too high or too low. Start with 0.1, try 0.01 or 1.0. Also check that your data is normalized (word counts are fine).
  • All predictions are the same class: your hidden layer is too small, or the data is imbalanced. Add more neurons or collect more diverse examples.
  • Poor performance on new words: the model only knows words from training. Use a bigger vocabulary or fallback to a word-embedding model.
  • Class imbalance: if 90% of the data is "positive", the model will predict "positive" for everything. Use class weights or oversample the minority class.
  • Overfitting: training loss near zero but test accuracy low. Add dropout, use L2 regularization, or reduce the number of hidden neurons.

Edge case: What if a review says "not good"? The word "good" is present, but the meaning is negative. A bag-of-words model can learn this if both "not good" phrases appear in training, but it won't generalize from "good" alone. For that, you need n-grams (pairs of words) or a transformer.

What you learned & what's next

You can now classify text with simple neural nets: you know how to convert text into a bag-of-words representation, build a two-layer network with softmax, train it with backpropagation, and troubleshoot common failure modes. This is the foundation for more advanced models like CNNs for text or attention-based transformers.

Next lesson: We'll build on this by adding word embeddings — dense vector representations that capture semantic meaning — and use them to improve accuracy on larger, more complex datasets.

Before you move on, complete the practice exercise below.

Practice recap

Extend the example to a new dataset: use the built-in sklearn.datasets.fetch_20newsgroups with two categories (e.g., rec.sport.hockey vs sci.space). Train the same network on 1000 documents, split into train/test, and report test accuracy. Try varying the hidden size between 5, 10, and 50 and observe the effect.

Common mistakes

  • Forgetting to lower-case and tokenize consistently — 'Great' and 'great' become different words, bloating the vocabulary.
  • Using raw word counts without normalization — long documents dominate and skew predictions.
  • Training on an imbalanced dataset without handling it — model always predicts the majority class.
  • Setting learning rate too high — loss diverges instead of converging.
  • Not separating training and test sets — model overfits and evaluation looks perfect but fails in production.

Variations

  1. Use TF-IDF weighting instead of raw word counts to emphasize rare, distinctive words.
  2. Add a second hidden layer or more neurons for higher capacity on larger datasets.
  3. Use a pre-trained word embedding (GloVe, word2vec) instead of a bag-of-words for better semantic generalization.

Real-world use cases

  • Routing customer support tickets to bug, billing, or feature request queues automatically.
  • Classifying product reviews as positive, negative, or neutral for brand sentiment dashboards.
  • Spam filtering for emails or comments with a simple, trainable model that adapts to new spam patterns.

Key takeaways

  • Text classification turns text into numeric vectors (bag-of-words) that a neural network can learn from.
  • A simple two-layer neural network with a hidden layer can capture non-linear patterns that linear models miss.
  • Training involves forward propagation, cross-entropy loss, and backpropagation to update weights.
  • Softmax converts raw scores into class probabilities; the class with the highest probability wins.
  • Evaluate on unseen data, handle class imbalance, and tune learning rate for robust performance.
  • Simple NNs are a sweet spot for many production tasks; only step up to transformers for very complex language.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.