If you measure similarity by occurrence of common words, you don't even need spacy: just vectorize your texts using word count and feed to any clustering algotithsm. AgglomerativeClustering is one of them - it is not very time efficient for large datasets, but it is highly controllable. The only parameter you need to tune for your dataset is distance_threshold: the smaller it is, the more clusters will there be.
After clustering the texts, you can just concatenate all the unique words in each cluster (or do something smarter, depending on the ultimate problem you are trying to solve). The whole code could look like:
texts = '''yellow color
yellow color looks like
yellow color bright
red color okay
red color blood'''.split('\n')
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.preprocessing import Normalizer, FunctionTransformer
from sklearn.cluster import AgglomerativeClustering
from sklearn.pipeline import make_pipeline
model = make_pipeline(
CountVectorizer(),
Normalizer(),
FunctionTransformer(lambda x: x.todense(), accept_sparse=True),
AgglomerativeClustering(distance_threshold=1.0, n_clusters=None),
)
clusters = model.fit_predict(texts)
print(clusters) # [0 0 0 1 1]
from collections import defaultdict
cluster2words = defaultdict(list)
for text, cluster in zip(texts, clusters):
for word in text.split():
if word not in cluster2words[cluster]:
cluster2words[cluster].append(word)
result = [' '.join(wordlist) for wordlist in cluster2words.values()]
print(result) # ['yellow color looks like bright', 'red color okay blood']
You need Spacy or any other framework with pre-trained models only if common words are not enough, and you want to capture semantic similarity. The whole pipeline would change only a little.
# !python -m spacy download en_core_web_lg
import spacy
import numpy as np
nlp = spacy.load("en_core_web_lg")
model = make_pipeline(
FunctionTransformer(lambda x: np.stack([nlp(t).vector for t in x])),
Normalizer(),
AgglomerativeClustering(distance_threshold=0.5, n_clusters=None),
)
clusters = model.fit_predict(texts)
print(clusters) # [2 0 2 0 1]
You see that the clustering is clearly incorrect here, so it seems that Spacy word vectors are not appropriate for this particular problem.
If you want to use a pretrained model to capture semantic similarity between texts, I would suggest you use Laser instead. It is based explicitly on sentence embeddings, and it is highly multilingual:
# !pip install laserembeddings
# !python -m laserembeddings download-models
from laserembeddings import Laser
laser = Laser()
model = make_pipeline(
FunctionTransformer(lambda x: laser.embed_sentences(x, lang='en')),
Normalizer(),
AgglomerativeClustering(distance_threshold=0.8, n_clusters=None),
)
clusters = model.fit_predict(texts)
print(clusters) # [1 1 1 0 0]