PostgreSQL 10 Optimizing slow query performance with aggregate function
You really need an index on the array:

create index on person using gin (tags);


It is not obvious to me that the subselect is worse than any other way of accomplishing the same thing.

Your query plan doesn't match your query text, "is_deleted" occurs in the plan but not the text.  If you want to optimize for that you could do: 

create index on person using gin (tags) where not is_deleted;


How important that is depends on what fraction of your table is_deleted.



An alternative method not needing the index would be 

select * from 
    (select unnest(tags) as id, count(id) from person group by 1) agg 
join 
    tag 
using (id);


But this would not return any tags which had a count of zero.  You could make the join a right join, but then reported count would be null rather than zero.