Skip to content

Concept

Concept is a technique that leverages CLIP and BERTopic-based techniques to perform Concept Modeling on images.

Since topics are part of conversations and text, they do not represent the context of images well. Therefore, these clusters of images are referred to as 'Concepts' instead of the traditional 'Topics'.

Thus, Concept Modeling takes inspiration from topic modeling techniques to cluster images, find common concepts and model them both visually using images and textually using topic representations.

Usage:

from concept import ConceptModel

concept_model = ConceptModel()
concept_clusters = concept_model.fit_transform(images)

__init__(self, min_concept_size=30, diversity=0.3, embedding_model='clip-ViT-B-32', vectorizer_model=None, umap_model=None, hdbscan_model=None, ctfidf=False) special

Concept Model Initialization

Parameters:

Name Type Description Default
min_concept_size int

The minimum size of concepts. Increasing this value will lead to a lower number of concept clusters.

30
diversity float

How diverse the images within a concept are. Values between 0 and 1 with 0 being not diverse at all and 1 being most diverse.

0.3
embedding_model str

The CLIP model to use. Current options include: * clip-ViT-B-32 * clip-ViT-B-32-multilingual-v1

'clip-ViT-B-32'
vectorizer_model CountVectorizer

Pass in a CountVectorizer instead of the default

None
umap_model UMAP

Pass in a UMAP model to be used instead of the default

None
hdbscan_model HDBSCAN

Pass in a hdbscan.HDBSCAN model to be used instead of the default

None
ctfidf bool

Whether to use c-TF-IDF to create the textual concept representation

False
Source code in concept\_model.py
def __init__(self,
             min_concept_size: int = 30,
             diversity: float = 0.3,
             embedding_model: str = "clip-ViT-B-32",
             vectorizer_model: CountVectorizer = None,
             umap_model: UMAP = None,
             hdbscan_model: hdbscan.HDBSCAN = None,
             ctfidf: bool = False):
    """ Concept Model Initialization

    Arguments:
        min_concept_size: The minimum size of concepts. Increasing this value will lead
                          to a lower number of concept clusters.
        diversity: How diverse the images within a concept are.
                   Values between 0 and 1 with 0 being not diverse at all
                   and 1 being most diverse.
        embedding_model: The CLIP model to use. Current options include:
                * clip-ViT-B-32
                * clip-ViT-B-32-multilingual-v1
        vectorizer_model: Pass in a CountVectorizer instead of the default
        umap_model: Pass in a UMAP model to be used instead of the default
        hdbscan_model: Pass in a hdbscan.HDBSCAN model to be used instead of the default
        ctfidf: Whether to use c-TF-IDF to create the textual concept representation
    """
    self.diversity = diversity
    self.min_concept_size = min_concept_size

    # Embedding model
    self.embedding_model = SentenceTransformer(embedding_model)

    # Vectorizer
    self.vectorizer_model = vectorizer_model or CountVectorizer()

    # UMAP
    self.umap_model = umap_model or UMAP(n_neighbors=15,
                                         n_components=5,
                                         min_dist=0.0,
                                         metric='cosine')

    # HDBSCAN
    self.hdbscan_model = hdbscan_model or hdbscan.HDBSCAN(min_cluster_size=self.min_concept_size,
                                                          metric='euclidean',
                                                          cluster_selection_method='eom',
                                                          prediction_data=True)

    self.frequency = None
    self.topics = None
    self.cluster_embeddings = None
    self.ctfidf = ctfidf

find_concepts(self, search_term)

Based on a search term, find the top 5 related concepts

Parameters:

Name Type Description Default
search_term str

The search term to search for

required

Returns:

Type Description
results

The top 5 related concepts with their similarity scores

Usage:

results = concept_model.find_concepts(search_term="dog")
Source code in concept\_model.py
def find_concepts(self, search_term: str) -> List[Tuple[int, float]]:
    """ Based on a search term, find the top 5 related concepts

    Arguments:
        search_term: The search term to search for

    Returns:
        results: The top 5 related concepts with their similarity scores

    Usage:

    ```python
    results = concept_model.find_concepts(search_term="dog")
    ```
    """
    embedding = self.embedding_model.encode(search_term)
    sim_matrix = cosine_similarity(embedding.reshape(1, -1), np.array(self.cluster_embeddings)[:, 0, :])
    related_concepts = np.argsort(sim_matrix)[0][::-1][:5]
    vals = list(np.sort(sim_matrix)[0][::-1][:5])

    results = [(concept, val) for concept, val in zip(related_concepts, vals)]
    return results

fit(self, images, image_names=None, image_embeddings=None)

Fit the model on a collection of images and return concepts

Parameters:

Name Type Description Default
images List[str]

A list of paths to each image

required
image_names List[str]

The names of the images for easier reading of concept clusters

None
image_embeddings ndarray

Pre-trained image embeddings to use instead of generating them in Concept

None

Usage:

from concept import ConceptModel
concept_model = ConceptModel()
concepts = concept_model.fit(images)
Source code in concept\_model.py
def fit(self,
        images: List[str],
        image_names: List[str] = None,
        image_embeddings: np.ndarray = None):
    """ Fit the model on a collection of images and return concepts

    Arguments:
        images: A list of paths to each image
        image_names: The names of the images for easier
                     reading of concept clusters
        image_embeddings: Pre-trained image embeddings to use
                          instead of generating them in Concept

    Usage:

    ```python
    from concept import ConceptModel
    concept_model = ConceptModel()
    concepts = concept_model.fit(images)
    ```
    """
    self.fit_transform(images, image_names=image_names, image_embeddings=image_embeddings)
    return self

fit_transform(self, images, docs=None, image_names=None, image_embeddings=None)

Fit the model on a collection of images and return concepts

Parameters:

Name Type Description Default
images List[str]

A list of paths to each image

required
docs List[str]

The documents from which to extract textual concept representation

None
image_names List[str]

The names of the images for easier reading of concept clusters

None
image_embeddings ndarray

Pre-trained image embeddings to use instead of generating them in Concept

None

Returns:

Type Description
predictions

Concept prediction for each image

Usage:

from concept import ConceptModel
concept_model = ConceptModel()
concepts = concept_model.fit_transform(images)
Source code in concept\_model.py
def fit_transform(self,
                  images: List[str],
                  docs: List[str] = None,
                  image_names: List[str] = None,
                  image_embeddings: np.ndarray = None) -> List[int]:
    """ Fit the model on a collection of images and return concepts

    Arguments:
        images: A list of paths to each image
        docs: The documents from which to extract textual concept representation
        image_names: The names of the images for easier
                     reading of concept clusters
        image_embeddings: Pre-trained image embeddings to use
                          instead of generating them in Concept

    Returns:
        predictions: Concept prediction for each image

    Usage:

    ```python
    from concept import ConceptModel
    concept_model = ConceptModel()
    concepts = concept_model.fit_transform(images)
    ```
    """

    # Calculate image embeddings if not already generated
    if image_embeddings is None:
        image_embeddings = self._embed_images(images)

    # Reduce dimensionality and cluster images into concepts
    reduced_embeddings = self._reduce_dimensionality(image_embeddings)
    predictions = self._cluster_embeddings(reduced_embeddings)

    # Extract representative images through exemplars
    representative_images = self._extract_exemplars(image_names)
    exemplar_embeddings = self._extract_cluster_embeddings(image_embeddings,
                                                           representative_images)
    selected_exemplars = self._extract_exemplar_subset(exemplar_embeddings,
                                                       representative_images)

    # Create collective representation of images
    self._cluster_representation(images, selected_exemplars)

    # Find the best words for each concept cluster
    if docs is not None:
        if self.ctfidf:
            self._extract_ctfidf_representation(docs, image_embeddings)
        else:
            self._extract_textual_representation(docs)

    return predictions

load(path) classmethod

Loads the model from the specified path

Parameters:

Name Type Description Default
path str

the location and name of the ConceptModel file you want to load

required

Usage:

ConceptModel.load("my_model")

Source code in concept\_model.py
@classmethod
def load(cls,
         path: str):
    """ Loads the model from the specified path

    Arguments:
        path: the location and name of the ConceptModel file you want to load

    Usage:
    ```python
    ConceptModel.load("my_model")
    ```
    """
    with open(path, 'rb') as file:
        concept_model = joblib.load(file)
        return concept_model

save(self, path)

Saves the model to the specified path

Parameters:

Name Type Description Default
path str

the location and name of the file you want to save

required

Usage:

concept_model.save("my_model")

Source code in concept\_model.py
def save(self,
         path: str) -> None:
    """ Saves the model to the specified path

    Arguments:
        path: the location and name of the file you want to save

    Usage:
    ```python
    concept_model.save("my_model")
    ```
    """
    with open(path, 'wb') as file:
        joblib.dump(self, file)

transform(self, images, image_embeddings=None)

After having fit a model, use transform to predict new instances

Parameters:

Name Type Description Default
images Union[List[str], str]

A single images or a list of images to predict

required
image_embeddings ndarray

Pre-trained image embeddings. These can be used instead of the sentence-transformer model.

None

Returns:

Type Description
predictions

Concept predictions for each image

Usage:

concept_model = ConceptModel()
concepts = concept_model.fit(images)
new_concepts = concept_model.transform(new_images)

Source code in concept\_model.py
def transform(self, 
              images: Union[List[str], str], 
              image_embeddings: np.ndarray = None):
    """ After having fit a model, use transform to predict new instances

    Arguments:
        images: A single images or a list of images to predict
        image_embeddings: Pre-trained image embeddings. These can be used
                          instead of the sentence-transformer model.
    Returns:
        predictions: Concept predictions for each image

    Usage:
    ```python
    concept_model = ConceptModel()
    concepts = concept_model.fit(images)
    new_concepts = concept_model.transform(new_images)
    ```
    """
    if image_embeddings is None:
        if isinstance(images, str):
            images = [images]
        image_embeddings = self._embed_images(images)

    umap_embeddings = self.umap_model.transform(image_embeddings)
    predictions, _ = hdbscan.approximate_predict(self.hdbscan_model, umap_embeddings)
    return predictions

visualize_concepts(self, top_n=9, concepts=None, figsize=(20, 15))

Visualize concepts using merged exemplars

Parameters:

Name Type Description Default
top_n int

The top_n concepts to visualize

9
concepts List[int]

The concept clusters to visualize

None
figsize Tuple[int, int]

The size of the figure

(20, 15)
Source code in concept\_model.py
def visualize_concepts(self,
                       top_n: int = 9,
                       concepts: List[int] = None,
                       figsize: Tuple[int, int] = (20, 15)):
    """ Visualize concepts using merged exemplars

    Arguments:
        top_n: The top_n concepts to visualize
        concepts: The concept clusters to visualize
        figsize: The size of the figure
    """
    if not concepts:
        concepts = [self.frequency.index[index] for index in range(top_n)]
        images = [self.cluster_images[index] for index in concepts]
    else:
        images = [self.cluster_images[index] for index in concepts]

    nr_columns = 3 if len(images) >= 3 else len(images)
    nr_rows = int(np.ceil(len(concepts) / nr_columns))

    fig, axs = plt.subplots(nr_rows, nr_columns, figsize=figsize)

    # visualize multiple concepts
    if len(images) > 1:
        axs = axs.flatten()
        for index, ax in enumerate(axs):
            if index < len(images):
                ax.imshow(images[index])
                if self.topics:
                    title = f"Concept {concepts[index]}: \n{self.topics[concepts[index]]}"
                else:
                    title = f"Concept {concepts[index]}"
                ax.set_title(title)
            ax.axis('off')

    # visualize a single concept
    else:
        axs.imshow(images[0])
        if self.topics:
            title = f"Concept {concepts[0]}: \n{self.topics[concepts[0]]}"
        else:
            title = f"Concept {concepts[0]}"
        axs.set_title(title)
        axs.axis('off')
    return fig