The best starting point is to consider the queries that you need to run. Use these to guide the way you represent the domain, rather than treating it as an abstract modelling problem.
For example you may decide that the most important query is: "given the name of a teacher, how many pupils do they have?"
MATCH ({name: "Bob"})-[:TEACHES]->(p) RETURN count(p)
This is the simplest way to express this query, so there is no need to complicate the model with node labels.
But then you may find that you need to get a list of all the teachers, at which point you could introduce a label.
MATCH (t:Teacher) RETURN t.name
Or you may even find that for performance reasons you need a label for the first query.
MATCH (:Teacher {name: {"Bob"})-[:TEACHES]->(p) RETURN count(p)
But it's really worth trying to stop yourself from doing too much modelling up front and including things in the model that may not be necessary. You will find that this results in a much simpler and more flexible model.
This raises the question of how to modify the data as the model changes. You will usually find that there is a series of safe, simple refactorings that you can do as you go along, just as though you were refactoring code. In this case you need to add labels to all nodes where a :TEACHES
relationship starts.
MATCH (t)-[:TEACHES]->() SET t:Teacher