AI usage disclosure: The project was completed for academic purposes. Although some AI usage was allowed, I wanted this project to be a deeply educational experience and an accurate representation of my own scientific thinking and technical skills. I very rarely used AI to generate code and never to generate larger blocks of code. An LLM was used to help translate the original Final Report available on GitHub into a web-based format.Czech readers often describe Božena Němcová's Babička (The Grandmother, 1855) as gentle, and they usually point to the way her sentences end. It sounds like a claim you can't test, which is why I wanted to try: does Němcová put softer-sounding words at the ends of her sentences?I needed three things I didn't have: a definition of "soft" that went beyond my own taste, a way to turn Czech spelling into the sounds a reader actually hears, and a corpus large enough for the comparison to mean something. The result was a Dask pipeline over 11,187,843 sentences, covering the whole Czech Wikipedia plus 51 books, with Babička as the book under study.
Measuring softness without my own ear
I could have just declared that ň and l are soft and k is hard. But I'm a native speaker, I like this book, and I had an answer I was hoping for. My intuition was the wrong tool for measuring this.Instead, I let other people set the scale. I recorded each Czech consonant phoneme, put all 28 recordings into a Qualtrics survey in random order, and asked 18 participants one question per sound: how soft does it sound, from 0 to 100?
The ratings were more consistent than I expected. Sonorants scored highest (ň 73.6, l 70.2, j 65.6) and obstruents lowest (k 25.9, z 31.3, d 31.6). I had to drop one question: the recording for A14 turned out to be the two-phoneme cluster ks rather than a single sound, which left 27 usable phonemes.Those mean ratings became the scale for the rest of the analysis. A word's softness is the mean softness of its phonemes, with vowels counted as 1.0 on a 0–1 scale. In code, that is a dot product of a phoneme-count vector with the survey means.
Spelling is not sound
Czech is mostly pronounced the way it's written, except at word and consonant boundaries, which is exactly where this project looks. Voicing assimilates backward through a consonant cluster, so the last consonant determines how the ones before it are pronounced. Consonants at the end of a word lose their voicing. v can be voiced by its neighbors, but it only voices other consonants at the start of a word. ř becomes the voiceless ř̥ whenever it sits next to a voiceless consonant.Measuring letters instead of sounds would have meant measuring 19th-century spelling conventions, not what a reader hears. So the pipeline includes a small phonology engine that applies the assimilation rules from the Czech Academy's Internetová jazyková příručka, along with the two exceptions above. A test suite covers each rule and the tricky cases where several of them interact.It isn't a complete model of Czech phonology. I left the rarer exceptions out of scope. But it is close enough that the analysis measures pronunciation rather than spelling.
Where the time actually went
My first full run stalled. After ten minutes, the progress bar was at four percent.Fixing that took up most of the project. I opened the Dask dashboard and looked at the task graph:
The graph showed over 1300 partitions of roughly 1 MB each, all feeding into one aggregation. Dask was spending more time scheduling tasks than running them. The documentation recommends partitions of around 100 MB, and my ~1 GB Wikipedia bag had been split more than a thousand ways. After repartitioning, a full preprocessing pass took about ten minutes.The second bottleneck took longer to find. By testing the pipeline one stage at a time, I found that read_text was producing millions of separate bag items, one per sentence. Each item carries graph overhead far larger than the tiny amount of work done on it, which explains why the progress bar would jump to 97% and then sit there for eight minutes. Since I needed a representative sample of Wikipedia rather than every sentence in it, I switched to seeded random sampling of the 1 MB chunks.The third problem surprised me. I had been using Dask arrays for the softness dot product, assuming more Dask would mean more speed. After twelve minutes it still hadn't finished. I replaced it with a plain NumPy array (the matrices are only 27 elements wide), and the same computation took a few seconds.Parallelism adds coordination overhead in exchange for throughput. On small data, you pay the overhead and gain nothing.
What the corpus shows
Once the pipeline ran end to end, each sentence came down to three softness measurements: its last word, its second-to-last word, and the rest of the sentence.
Source
Sentences
Last word
2nd-to-last
Rest of sentence
Babička
4,283
0.7044
0.7063
0.7058
50 other Czech books
169,131
0.6962
0.7044
0.6997
Czech Wikipedia
11,014,429
0.6880
0.6923
0.6953
The data changed the question. In the bottom two rows, the last word of a sentence is consistently less soft than both the second-to-last word and the rest of the sentence. That made the real question whether this drop is smaller in Babička than in the other books and Wikipedia.
Text
last word vs. second-to-last word
t p df
Babička -0.79 4.31e-01 4,282 not significant
other books -21.85 1.08e-105 169,130 p < .001
Wikipedia -86.87 0.0 11,014,428 p < .001
In Babička, the drop at the end of the sentence doesn't appear (p = .43, and p = .24 for last word vs. rest). When I compared the size of that drop across corpora, Němcová differed significantly from the other books (t = 3.76, p < .001) and from Wikipedia on last word vs. rest (t = 5.54, p < .001).So Němcová's endings aren't especially soft. Rather, they lack the hardening that shows up at the end of sentences in the rest of written Czech.There are two caveats. First, Babička has only 4,283 sentences, so the test within the book has limited power to detect a small effect, and a non-significant result there is weak evidence. Second, with a 20 MB Wikipedia sample, the Babička-vs-Wikipedia comparison came out significant, while the full corpus says it isn't. At least one of those results is likely being skewed by outliers, and I'd want to look into them.
A follow-up on bigrams
To see which sounds drive the difference, I split the last and second-to-last words into character bigrams and compared their normalized frequencies.The bigrams overrepresented in final words are built on the consonants the survey rated hardest: ku, ki, ka, ce, which contain k (25.9) and c (36.1) from the bottom of the scale. Several bigrams overrepresented one word earlier contain sounds from the top: ňí, ho, íx, with ň (73.6), ch (60.7) and h (59.3). This reproduces the main effect without using the softness scale at all.The second-to-last list isn't uniformly soft, though. ké, sk, ck and kí all contain k, the hardest sound I measured. Along with éh, ím and ov, they are pieces of Czech adjective endings and adjective-forming suffixes (-ké, -ého, -ím, -ský, -ový). Czech adjectives come before the nouns they modify, so the second-to-last position is often an adjective.That suggests the position effect could partly be a part-of-speech effect. Instead of Czech ending sentences on hard sounds, it may be that adjectives tend to sit one word from the end, and their endings sound a certain way. Controlling for word class would be the next step if I continued this work.
Does it scale?
On a single machine, runtime grows close to linearly with problem size: 189 s at 20 MB and 2,768 s at 300 MB.
To test weak scaling, I ran the same job on a Google Cloud Dataproc node (a single 8-vCPU n2-highmem-8), doubling the data and the number of Dask worker processes together, from 20 MB on 1 worker up to 160 MB on 8. With ideal weak scaling the line would be flat, since eight times the work on eight times the workers should take the same time as one on one.
My results weren't flat: runtime went from 95 s to 297 s, a 3.1× increase for a job 8× larger. However, the curve flattens as the problem grows instead of getting steeper, which is the opposite of what you'd expect if coordination costs were the problem. I think the cause is the same one behind the original stall. At 20 MB, fixed scheduling overhead dominates the run, and as the data grows, Dask gets more partitions of a better size and spreads that overhead across them. Dask also lost workers at the largest size, most likely because of limited RAM per CPU, and recovered on its own.
What I learned
I learned several NLP techniques specific to Czech, got better at research methodology and analysis, and gained a much deeper understanding of parallel computing. Parallelism has real limits and only suits certain problems, and improving the algorithm is often the simpler and more effective fix. Still, it was satisfying to see the whole Czech Wikipedia processed, converted into phonemes through a long list of rules, and measured for softness in under four hours on my own laptop.Code, data and the full report: github.com/vk4444/DATA301_Project
Related projects
Web for Change: websites on a pay-what-you-can basis
I design, build, host and maintain websites on a pay-what-you-can basis for projects that support sustainability, social change, animal rights or art, and for students who need a portfolio. I started Web for Change so that money wouldn't keep these projects from having a professional website.
Schedule Squared: group scheduling without the friction
My classmates kept using when2meet because it is simple, fast and needs no sign-in, but it was hard to use on a phone and couldn't help with harder scheduling problems. I built Schedule Squared to keep that simplicity and add features for the problems I kept seeing, including filling in availability from a calendar screenshot with OCR and finding optimal pairings with an integer program.