The Language of the Cat
Cats hide their mood in their face — but they give it away in sound. You rebuild the two-stage chain from the book: first the kind of sound, then — trained on real data — the feeling in the meow.
In a nutshell
What: You turn cat sounds — as with voice and plant signal before — into features of their sound and build two small models in sequence. The first sorts the kind of sound (hiss, purr, chirp, meow). The second asks, only for the meow, about the mood — and learns it from a real dataset of hundreds of meows.
The core idea: It is the same two-stage idea as "Beti" and "Kisha" in the book. The easy part — the kind — you train on a few of your own recordings. The hard part — the feeling in the meow — cannot be learned from a handful of examples; for that you fetch real data from Kaggle.
You need: the CatMeows dataset from Kaggle, Python with librosa for the
sound analysis and scikit-learn for the two models. Optionally a few of your own cat
recordings to test with.
What it's about
Dog and horse wear their mood outwardly — in posture and face. The cat keeps it hidden. Instead it talks to us: adult cats use the meow almost only towards humans, hardly among themselves. So if we want to read a cat, we do not listen to the face but to the sound.
And the sound becomes readable like everything in this book: we turn it into an image. A spectrogram shows the frequencies of a sound over time — and then the same image recognition that otherwise tells cat from dog takes over. A hiss, a purr, a meow become numbers, and from the numbers a careful guess.
Before you start
For the second stage you need real data. The freely available CatMeows dataset contains 440 meows from 21 cats, recorded in three situations that serve as feeling labels: while being brushed (content), alone in an unfamiliar place (unsettled) and waiting for food (demanding). Exactly these situations your model will later learn to tell apart.
A little background
Two ears that listen one after another. The book holds two models in sequence. The first, "Beti", first sorts a sound roughly by its kind and is right in about 85 out of 100 cases. That already gives something away: a hiss sounds like stress, a purr like contentment. With the meow, though, the feeling stays open — and here the second model, "Kisha", takes over. Specialised on meows alone, it tells happy, angry and sad apart with nearly 92 per cent. From this chain came the little app CatMotion.
Why real data for the second stage? "Is it a meow at all?" is easy by sound — a few examples suffice. "How does this meow feel?" is far finer: the differences are subtle, and a model recognises them only once it has seen many meows from many cats. That is why Kisha learns from hundreds of examples instead of your handful — otherwise it would only imagine certainty.
Getting the data
- Install the packages.
pip install librosa scikit-learn numpy soundfile. - Download CatMeows. From Kaggle:
kaggle.com/datasets/andrewmvd/cat-meow-classification(download and unzip). The meows are WAV files in thedataset/folder. Alternatively the same data on Zenodo (record 4008297). - Understand the labels. Each filename begins with the situation letter: B (Brushing), I (Isolation), F (Food). The exact naming rule is on the dataset page — take a look before you go on.
- Optional: your own test sounds. Record a few meows of your own cat
(
test_meow.wav) to check the finished model on fresh material.
Stage 1 — the kind of sound ("Beti")
The rough sorting is easy by sound — a few of your own recordings per kind are enough. From each sound we
pull a handy row of numbers: the MFCCs, a compact fingerprint of the sound that librosa
gives directly. The full code is on
GitHub.
import librosa
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
def features(file):
y, sr = librosa.load(file) # load the sound
mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)
# mean and spread over time -> a fingerprint of the sound
return np.concatenate([mfcc.mean(axis=1), mfcc.std(axis=1)])
# a few of your own recordings per kind (kind is easy to tell apart by sound)
training = [
("hiss1.wav", "Hiss"), ("hiss2.wav", "Hiss"),
("purr1.wav", "Purr"), ("purr2.wav", "Purr"),
("chirp1.wav", "Chirp"), ("chirp2.wav", "Chirp"),
("meow1.wav", "Meow"), ("meow2.wav", "Meow"),
]
X = np.array([features(f) for f, _ in training])
y = [kind for _, kind in training]
beti = KNeighborsClassifier(n_neighbors=1).fit(X, y) # few data: nearest neighbour
print("Beti says:", beti.predict([features("test_meow.wav")])[0])
Stage 2 — the mood in the meow ("Kisha")
Now the hard part. Kisha learns not from your few recordings but from the hundreds of meows in CatMeows. We read the folder, take the situation from each filename and train — and, importantly, we check honestly on held-back data how good it really is.
import glob, os
from sklearn.model_selection import train_test_split
FOLDER = "cat-meow-classification/dataset" # unzipped Kaggle dataset
situation = {"B": "content", "I": "unsettled", "F": "demanding"}
X, y = [], []
for file in glob.glob(os.path.join(FOLDER, "*.wav")):
tag = os.path.basename(file)[0].upper() # first letter = situation
if tag in situation:
X.append(features(file))
y.append(situation[tag])
X = np.array(X)
print("read:", len(X), "meows")
# check honestly: hold back a quarter
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.25,
random_state=0, stratify=y)
kisha = KNeighborsClassifier(n_neighbors=5).fit(Xtr, ytr)
print("hits on unseen meows:", round(kisha.score(Xte, yte), 2))
# the full chain: first kind (Beti), then — only for a meow — mood (Kisha)
def interpret(file):
kind = beti.predict([features(file)])[0]
if kind == "Meow":
return f"Meow — sounds {kisha.predict([features(file)])[0]}"
return kind
print(interpret("test_meow.wav"))
What you should see
Kisha does clearly better than chance on the unseen meows (chance would be one third), but well below the 92 per cent from the book — a plain KNN on MFCCs is no finely tuned special network. That is exactly the honest lesson: with real data the mood becomes learnable at all; with a simple model it stays a good but uncertain guess. And your own test meow? Sometimes Kisha hits, sometimes not — that too is a result.
Worksheet
From sound to guess
- What is Kisha's hit rate on the held-back meows? How much better than pure guessing (one third) is that?
- Which two situations does Kisha confuse most often? Look at a few spectrograms — can you sense why they resemble each other in sound?
- Why do we train Kisha on CatMeows and not on your own recordings — yet Beti on your few recordings? What is the difference between the two tasks?
- The labels are situations (brushing, isolation, food), not pure feelings. Why is that a more honest name — and where does this equation reach its limit?
- The meow is directed almost only at humans. What does that mean for the question of whether we read "the cat" here — or something the cat does especially for us?
Show solution
1. Individual (typically noticeably above one third, but well below 90%). What matters is measuring against the chance line: "better than guessing" is the proof that something about the situation really sits in the sound.
2. Often "unsettled" and "demanding" blur, because both can sound urgent and tense, while "content" is calmer. In the spectrogram this shows in pitch, length and roughness.
3. "Is it a meow?" is easy by sound — hiss, purr and meow are very different, a few examples suffice. "How does the meow feel?" is fine and variable; a model learns that only from many meows of many cats. A few of your own recordings would only feign certainty here.
4. We know for sure which situation was recorded, not for sure what the cat feels. "Situation" is therefore more honest than "feeling". The limit: brushing usually, but not always, means contentment — situation is a good but imperfect stand-in for the feeling.
5. It shifts the question: we read less an inner state than a communication signal directed at humans. That does not make it fake — but it is a language developed for us, not a window into the cat's soul.
When it sticks
| Problem | Likely cause & fix |
|---|---|
glob finds no files | Wrong folder path. Check where you unzipped and adjust FOLDER — the WAVs are in the sub-folder dataset/. |
read: 0 meows | The filenames start differently than expected. Look at one name and adjust the mapping in situation to the actual first letters. |
| Kisha barely beats chance | Too few features. Raise n_mfcc, try n_neighbors between 3 and 9, or add pitch and loudness (as in 10.1). |
| Beti confuses chirp and meow | Both are high and short. Record clearer, longer examples or give more per kind. |
| Very slow | Many files. Compute the features once and keep them in an array; optionally load with librosa.load(file, sr=16000). |
Food for thought
- Always it becomes an image: face, voice, plant signal — and now the cat sound. Once a signal is a spectrogram, the same machinery reads it. One Babel fish, many species.
- The two-stage chain is a fine pattern: split a hard task into an easy and a fine one — and for the fine one fetch real data instead of fooling yourself with a few examples.
- Keep the boundary: the read sound is an expression in a situation, not proof of a feeling. Used well — to notice stress or pain early — such a reader is care; interpreted carelessly it becomes projection.
Extension
- Make spectrograms visible. Draw with
librosa.display.specshow(librosa.amplitude_to_db(np.abs(librosa.stft(y))))the image of single meows from the three situations. Do you see a difference yourself? - Confusion matrix. Have
sklearn.metrics.confusion_matrix(yte, kisha.predict(Xte))show which situations are confused most often. Does it match your listening impression? - A real network. Instead of KNN on MFCCs: turn each sound into a spectrogram image and train a small image network on it — the step from the number fingerprint to real image recognition from Chapter 3.