The commands are connected with a pipe (I'm talking the system primitive here—obviously, they're connected with a |). When the read end of the pipe (the stdin of head) becomes closed (== when head either explicitly (close) or implicitly (exit) closes it), attempts to write to the write end (stdout of yes) will fail.
By default, this is not just a regular errno failure, but a failure that results in the writing process receiving the SIGPIPE signal. The default handler action of the SIGPIPE signal is to terminate.
In short—if you write to a broken pipe, the system will send you a SIGPIPE and by default, a SIGPIPE will kill you. That is why yes terminates when head has ended (and thereby broken the pipe).
If you ignore SIGPIPE in the parent shell, then the commands will inherit this disposition and a write to a broken pipe will simply cause an errno error of EPIPE. As it appears, yes stringifies this error an prints it:
$ (trap "" SIGPIPE; yes | head -n 5)
y
y
y
y
yes: standard output: Broken pipe
yes: write error