How do I differentiate between user-defined public synonyms and built-in public synonyms?
Well, you could go by object creation date:

SELECT AO.*
  FROM ALL_OBJECTS AO
  INNER JOIN ALL_SYNONYMS ASY ON AO.OWNER = ASY.OWNER and AO.OBJECT_NAME = ASY.SYNONYM_NAME
  AND ASY.OWNER = 'PUBLIC'
  ORDER by AO.CREATED DESC


The system created synonyms should all have the same created date, equal to the creation time of the database and the earliest of this list.

You can also filter out the system owned objects being referenced by the synonym using this :

SELECT AO.*
  FROM ALL_OBJECTS AO
  INNER JOIN ALL_SYNONYMS ASY ON AO.OWNER = ASY.OWNER and AO.OBJECT_NAME = ASY.SYNONYM_NAME
  AND ASY.OWNER = 'PUBLIC'
  AND ASY.TABLE_OWNER NOT IN ('CTXSYS','EXFSYS','APPQOSSYS','XDB','SYS', 'DVSYS', 'FLOWS_FILES', 'LBACSYS', 'MDSYS', 'OLAPSYS', 'ORDSYS', 'ORDDATA','WMSYS','SYSTEM', 'DBMS_PRIVILEGE_CAPTURE') 
  and ASY.TABLE_OWNER not like 'APEX_%'
  ORDER by AO.CREATED DESC