You can do this with find, but to do it robustly you will need to embed a shell one-liner as well. The proper way to do this is one of the following:
Stuff the looping into the spawned shell:
find . -type f -name '._*' -exec sh -c 'for a in "$@"; do f="${a%/*}/${a##*/._}"; [ -e "$f" ] && printf %s\\n "$f"; done' find-sh {} +
Or, spawn a separate shell for each file to be tested (less efficient, potentially more readable):
find . -type f -name '._*' -exec sh -c 'f="${1%/*}/${1##*/._}"; [ -e "$f" ] && printf %s\\n "$f"' find-sh {} \;
To directly remove the backup files, change this to the following for a dry run:
find . -type f -name '._*' -exec sh -c 'for a; do f="${a%/*}/${a##*/._}"; [ -e "$f" ] && printf "rm -- %s\n" "$a"; done' find-sh {} +
Then once you're satisfied with the list of commands that gets printed, use:
find . -type f -name '._*' -exec sh -c 'for a; do f="${a%/*}/${a##*/._}"; [ -e "$f" ] && rm -- "$a"; done' find-sh {} +
Notes:
In all of these, the find-sh argument is an arbitrary string; you could put anything there. It gets set as $0 within the spawned shell and is used for error reporting.
for a in "$@"; do is exactly equivalent to for a; do.