Я думаю, вы хотите, прежде всего, не strtoupper. Это будет использовать только первую букву каждого совпадения, а не все совпадение, как в strtoupper. Я также думаю, что вам нужно переключиться на preg_replace_callback, потому что ваш текущий синтаксис говорит php запустить strtoupper для строки '$ 1' (которая ничего не делает), а затем передать ее в качестве строки замены для использования для ВСЕХ совпадений сделал. Что даст вам точно такой же вывод, как ввод.
Попробуйте вместо этого:
<?php
preg_replace_callback(
'/(_([a-z]{1}))/',
create_function(
// single quotes are essential here,
// or alternative escape all $ as \$
'$matches',
'return ucfirst($matches[0]);'
),
$subject
);
?>