CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-setfit

Efficient few-shot learning with Sentence Transformers

Pending
Overview
Eval results
Files

absa.mddocs/

Aspect-Based Sentiment Analysis (ABSA)

Specialized models and trainers for aspect-based sentiment analysis tasks with span-level predictions. ABSA enables fine-grained sentiment analysis by identifying specific aspects mentioned in text and determining the sentiment toward each aspect.

Capabilities

ABSA Model

Combined model that performs both aspect extraction and polarity classification for aspect-based sentiment analysis.

class AbsaModel:
    def __init__(
        self,
        aspect_model: Optional["AspectModel"] = None,
        polarity_model: Optional["PolarityModel"] = None
    ):
        """
        Initialize an ABSA model with aspect extraction and polarity classification.

        Parameters:
        - aspect_model: Model for extracting aspects from text
        - polarity_model: Model for classifying sentiment polarity of aspects
        """

    def predict(self, inputs: Union[str, List[str]]) -> List[Dict]:
        """
        Perform aspect-based sentiment analysis on input texts.

        Parameters:
        - inputs: Input text(s) to analyze

        Returns:
        List of dictionaries containing aspect-polarity pairs for each input
        """

    def __call__(self, inputs: Union[str, List[str]]) -> List[Dict]:
        """
        Callable interface for prediction.

        Parameters:
        - inputs: Input text(s) to analyze

        Returns:
        ABSA predictions with aspects and their sentiment polarities
        """

Aspect Model

Model specialized for extracting aspect terms from text using span-based classification.

class AspectModel:
    def __init__(
        self,
        model_body: Optional[SentenceTransformer] = None,
        model_head: Optional[Union[SetFitHead, LogisticRegression]] = None,
        spacy_model: str = "en_core_web_sm",
        span_context: int = 0,
        normalize_embeddings: bool = True,
        labels: Optional[List[str]] = None,
        model_card_data: Optional[SetFitModelCardData] = None
    ):
        """
        Initialize an aspect extraction model.

        Parameters:
        - model_body: Sentence transformer for generating embeddings
        - model_head: Classification head for aspect/non-aspect prediction
        - spacy_model: SpaCy model name for tokenization and NER
        - span_context: Number of context tokens around spans
        - normalize_embeddings: Whether to normalize embeddings
        - labels: Label names for aspect categories
        - model_card_data: Metadata for model card generation
        """

    def predict(self, inputs: Union[str, List[str]]) -> List[List[str]]:
        """
        Extract aspect terms from input texts.

        Parameters:
        - inputs: Input text(s) to extract aspects from

        Returns:
        List of aspect terms for each input text
        """

    def prepend_aspects(
        self, 
        sentences: List[str], 
        aspects: List[List[str]]
    ) -> List[str]:
        """
        Prepend aspect terms to sentences for polarity classification.

        Parameters:
        - sentences: Original sentences
        - aspects: Extracted aspects for each sentence

        Returns:
        Modified sentences with prepended aspects
        """

Polarity Model

Model specialized for classifying sentiment polarity of extracted aspects.

class PolarityModel:
    def __init__(
        self,
        model_body: Optional[SentenceTransformer] = None,
        model_head: Optional[Union[SetFitHead, LogisticRegression]] = None,
        spacy_model: str = "en_core_web_sm", 
        span_context: int = 0,
        normalize_embeddings: bool = True,
        labels: Optional[List[str]] = None,
        model_card_data: Optional[SetFitModelCardData] = None
    ):
        """
        Initialize a polarity classification model for aspects.

        Parameters:
        - model_body: Sentence transformer for generating embeddings
        - model_head: Classification head for polarity prediction
        - spacy_model: SpaCy model name for tokenization
        - span_context: Number of context tokens around aspects
        - normalize_embeddings: Whether to normalize embeddings
        - labels: Label names for polarity classes (e.g., ["negative", "neutral", "positive"])
        - model_card_data: Metadata for model card generation
        """

    def predict(self, inputs: Union[str, List[str]]) -> List[int]:
        """
        Classify sentiment polarity of aspects in input texts.

        Parameters:
        - inputs: Input text(s) with aspects to classify

        Returns:
        Polarity class predictions for each input
        """

Span SetFit Model

Base class for span-based SetFit models that work with text spans and context.

class SpanSetFitModel:
    def __init__(
        self,
        model_body: Optional[SentenceTransformer] = None,
        model_head: Optional[Union[SetFitHead, LogisticRegression]] = None,
        spacy_model: str = "en_core_web_sm",
        span_context: int = 0,
        multi_target_strategy: Optional[str] = None,
        normalize_embeddings: bool = True,
        labels: Optional[List[str]] = None,
        model_card_data: Optional[SetFitModelCardData] = None
    ):
        """
        Initialize a span-based SetFit model.

        Parameters:
        - model_body: Sentence transformer for embeddings
        - model_head: Classification head
        - spacy_model: SpaCy model for text processing
        - span_context: Number of context tokens around spans
        - multi_target_strategy: Strategy for multi-target classification
        - normalize_embeddings: Whether to normalize embeddings
        - labels: Label names
        - model_card_data: Model metadata
        """

    def prepend_aspects(
        self,
        sentences: List[str],
        aspects: List[List[str]]
    ) -> List[str]:
        """
        Prepend aspect information to sentences.

        Parameters:
        - sentences: Input sentences
        - aspects: Aspect terms for each sentence

        Returns:
        Sentences with prepended aspect information
        """

    def __call__(self, inputs: Union[str, List[str]]) -> List:
        """
        Process inputs through the span-based model.

        Parameters:
        - inputs: Input texts or spans

        Returns:
        Model predictions for the spans
        """

ABSA Trainer

Specialized trainer for training ABSA models with span-level supervision.

class AbsaTrainer:
    def __init__(
        self,
        model: Optional[Union[AspectModel, PolarityModel]] = None,
        args: Optional[TrainingArguments] = None,
        train_dataset: Optional[Dataset] = None,
        eval_dataset: Optional[Dataset] = None,
        compute_metrics: Optional[Callable] = None,
        callbacks: Optional[List] = None,
        column_mapping: Optional[Dict[str, str]] = None
    ):
        """
        Initialize an ABSA trainer.

        Parameters:
        - model: ABSA model to train (aspect or polarity model)
        - args: Training arguments
        - train_dataset: Training dataset with span annotations
        - eval_dataset: Evaluation dataset
        - compute_metrics: Function to compute evaluation metrics
        - callbacks: Training callbacks
        - column_mapping: Mapping of dataset columns
        """

    def train(self) -> None:
        """
        Train the ABSA model on span-level data.
        """

    def evaluate(self, eval_dataset: Optional[Dataset] = None) -> Dict[str, float]:
        """
        Evaluate the ABSA model.

        Parameters:
        - eval_dataset: Evaluation dataset

        Returns:
        Evaluation metrics
        """

Aspect Extractor

Utility class for extracting aspect terms from text using various strategies.

class AspectExtractor:
    def __init__(
        self,
        spacy_model: str = "en_core_web_sm",
        aspect_model: Optional[AspectModel] = None
    ):
        """
        Initialize aspect extractor.

        Parameters:
        - spacy_model: SpaCy model for NLP processing
        - aspect_model: Trained aspect model for ML-based extraction
        """

    def extract_aspects(
        self, 
        text: str,
        method: str = "model"
    ) -> List[str]:
        """
        Extract aspect terms from text.

        Parameters:
        - text: Input text to extract aspects from
        - method: Extraction method ("model", "pos", "ner", "hybrid")

        Returns:
        List of extracted aspect terms
        """

    def extract_aspects_batch(
        self,
        texts: List[str],
        method: str = "model"
    ) -> List[List[str]]:
        """
        Extract aspects from multiple texts.

        Parameters:
        - texts: List of input texts
        - method: Extraction method

        Returns:
        List of aspect lists for each text
        """

Usage Examples

Basic ABSA Pipeline

from setfit import AbsaModel, AspectModel, PolarityModel, SetFitModel
from datasets import Dataset

# Prepare ABSA training data
absa_data = {
    "text": [
        "The food was great but the service was slow.",
        "Amazing pizza, terrible atmosphere.",
        "Good value for money, poor location."
    ],
    "aspects": [
        ["food", "service"],
        ["pizza", "atmosphere"], 
        ["value", "location"]
    ],
    "polarities": [
        [1, 0],  # food: positive, service: negative
        [1, 0],  # pizza: positive, atmosphere: negative
        [1, 0]   # value: positive, location: negative
    ]
}

# Create datasets for aspect and polarity training
aspect_train_data = []
polarity_train_data = []

for text, aspects, polarities in zip(absa_data["text"], absa_data["aspects"], absa_data["polarities"]):
    # Create aspect extraction examples (binary classification per span)
    # This would need proper span annotation in practice
    
    # Create polarity classification examples
    for aspect, polarity in zip(aspects, polarities):
        aspect_text = f"{aspect}: {text}"
        polarity_train_data.append({
            "text": aspect_text,
            "label": polarity
        })

polarity_dataset = Dataset.from_list(polarity_train_data)

# Train aspect model (simplified example)
aspect_model = AspectModel.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")

# Train polarity model
polarity_model = PolarityModel.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")

from setfit import AbsaTrainer, TrainingArguments

polarity_trainer = AbsaTrainer(
    model=polarity_model,
    train_dataset=polarity_dataset,
    args=TrainingArguments(
        batch_size=16,
        num_epochs=4,
        learning_rate=2e-5
    ),
    column_mapping={"text": "text", "label": "label"}
)

polarity_trainer.train()

# Create combined ABSA model
absa_model = AbsaModel(
    aspect_model=aspect_model,
    polarity_model=polarity_model
)

# Perform ABSA
test_texts = [
    "The pasta was delicious but the service was really poor.",
    "Great ambiance, overpriced drinks."
]

results = absa_model.predict(test_texts)
for i, result in enumerate(results):
    print(f"Text {i+1}: {test_texts[i]}")
    print(f"ABSA Results: {result}")
    print()

Training Aspect Extraction Model

from setfit import AspectModel, AbsaTrainer, TrainingArguments
from datasets import Dataset
import spacy

# Load SpaCy model for tokenization
nlp = spacy.load("en_core_web_sm")

def create_aspect_training_data(texts, aspect_annotations):
    """
    Create training data for aspect extraction from span annotations.
    
    Parameters:
    - texts: List of input texts
    - aspect_annotations: List of (start, end, label) tuples for each text
    
    Returns:
    Dataset with span-level labels
    """
    training_examples = []
    
    for text, annotations in zip(texts, aspect_annotations):
        doc = nlp(text)
        
        # Create binary labels for each token/span
        for token in doc:
            is_aspect = False
            aspect_label = "O"  # Outside
            
            # Check if token is part of an aspect span
            for start, end, label in annotations:
                if start <= token.idx < end:
                    is_aspect = True
                    aspect_label = label
                    break
            
            training_examples.append({
                "text": token.text,
                "context": text,
                "label": 1 if is_aspect else 0,
                "aspect_type": aspect_label
            })
    
    return Dataset.from_list(training_examples)

# Example aspect annotations (start_char, end_char, aspect_type)
texts = [
    "The pizza was amazing but the service was slow.",
    "Great atmosphere, terrible food quality."
]

annotations = [
    [(4, 9, "food"), (33, 40, "service")],  # "pizza", "service"
    [(6, 16, "ambiance"), (27, 39, "food")]  # "atmosphere", "food quality"
]

# Create training dataset
aspect_dataset = create_aspect_training_data(texts, annotations)

# Initialize and train aspect model
aspect_model = AspectModel(
    spacy_model="en_core_web_sm",
    span_context=2  # Include 2 tokens of context around spans
)

aspect_trainer = AbsaTrainer(
    model=aspect_model,
    train_dataset=aspect_dataset,
    args=TrainingArguments(
        batch_size=32,
        num_epochs=5,
        learning_rate=2e-5,
        eval_strategy="epoch"
    )
)

aspect_trainer.train()

# Test aspect extraction
test_sentences = [
    "The burger was tasty but the fries were cold.",
    "Excellent wine selection, poor table service."
]

extracted_aspects = aspect_model.predict(test_sentences)
for sentence, aspects in zip(test_sentences, extracted_aspects):
    print(f"Sentence: {sentence}")
    print(f"Extracted aspects: {aspects}")

Fine-grained ABSA with Multiple Aspects

from setfit import AbsaModel, AspectModel, PolarityModel
import json

# Define aspect categories for restaurant reviews
ASPECT_CATEGORIES = {
    "food": ["taste", "quality", "freshness", "variety"],
    "service": ["speed", "friendliness", "attentiveness", "professionalism"],  
    "ambiance": ["atmosphere", "noise", "lighting", "decor"],
    "value": ["price", "portion", "worth", "deals"]
}

class DetailedAbsaModel:
    def __init__(self, aspect_model, polarity_model, aspect_categories):
        self.aspect_model = aspect_model
        self.polarity_model = polarity_model
        self.aspect_categories = aspect_categories
    
    def predict_detailed(self, text):
        """Perform detailed ABSA with aspect categorization."""
        # Extract aspects
        aspects = self.aspect_model.predict([text])[0]
        
        # Categorize aspects
        categorized_aspects = {}
        for aspect in aspects:
            for category, subcategories in self.aspect_categories.items():
                if any(sub in aspect.lower() for sub in subcategories):
                    if category not in categorized_aspects:
                        categorized_aspects[category] = []
                    categorized_aspects[category].append(aspect)
                    break
            else:
                # Uncategorized aspect
                if "other" not in categorized_aspects:
                    categorized_aspects["other"] = []
                categorized_aspects["other"].append(aspect)
        
        # Get sentiment for each aspect
        results = {}
        for category, category_aspects in categorized_aspects.items():
            category_results = []
            for aspect in category_aspects:
                aspect_text = f"{aspect}: {text}"
                polarity_probs = self.polarity_model.predict_proba([aspect_text])[0]
                sentiment_score = polarity_probs[1] - polarity_probs[0]  # positive - negative
                
                category_results.append({
                    "aspect": aspect,
                    "sentiment_score": float(sentiment_score),
                    "sentiment": "positive" if sentiment_score > 0.1 else "negative" if sentiment_score < -0.1 else "neutral",
                    "confidence": float(max(polarity_probs))
                })
            
            results[category] = category_results
        
        return results

# Initialize detailed ABSA model
detailed_model = DetailedAbsaModel(
    aspect_model=aspect_model,
    polarity_model=polarity_model,
    aspect_categories=ASPECT_CATEGORIES
)

# Analyze restaurant review
review = """
The seafood pasta was absolutely delicious and the portion size was generous. 
However, our waiter was quite rude and it took forever to get our drinks. 
The restaurant is beautifully decorated but it was way too noisy to have a conversation.
For the price we paid, I expected much better service.
"""

detailed_results = detailed_model.predict_detailed(review)

print("Detailed ABSA Results:")
print(json.dumps(detailed_results, indent=2))

ABSA Model Evaluation

from setfit import AbsaModel
from sklearn.metrics import classification_report, accuracy_score
import numpy as np

def evaluate_absa_model(absa_model, test_dataset):
    """
    Comprehensive evaluation of ABSA model performance.
    
    Parameters:
    - absa_model: Trained ABSA model
    - test_dataset: Test dataset with ground truth aspects and sentiments
    
    Returns:
    Dictionary with evaluation metrics
    """
    
    # Prepare test data
    test_texts = test_dataset["text"]
    true_aspects = test_dataset["aspects"]  # List of lists
    true_sentiments = test_dataset["sentiments"]  # List of lists
    
    # Get predictions
    predictions = absa_model.predict(test_texts)
    
    # Extract predicted aspects and sentiments
    pred_aspects = []
    pred_sentiments = []
    
    for pred in predictions:
        aspects = list(pred.keys())
        sentiments = [pred[aspect]["sentiment"] for aspect in aspects]
        pred_aspects.append(aspects)
        pred_sentiments.append(sentiments)
    
    # Aspect extraction evaluation
    aspect_precision_scores = []
    aspect_recall_scores = []
    aspect_f1_scores = []
    
    for true_asp, pred_asp in zip(true_aspects, pred_aspects):
        true_set = set(true_asp)
        pred_set = set(pred_asp)
        
        if len(pred_set) > 0:
            precision = len(true_set & pred_set) / len(pred_set)
        else:
            precision = 0.0
            
        if len(true_set) > 0:
            recall = len(true_set & pred_set) / len(true_set)
        else:
            recall = 1.0 if len(pred_set) == 0 else 0.0
            
        if precision + recall > 0:
            f1 = 2 * precision * recall / (precision + recall)
        else:
            f1 = 0.0
            
        aspect_precision_scores.append(precision)
        aspect_recall_scores.append(recall)
        aspect_f1_scores.append(f1)
    
    # Sentiment classification evaluation (for correctly identified aspects)
    sentiment_true = []
    sentiment_pred = []
    
    for i, (true_asp, pred_asp, true_sent, pred_sent) in enumerate(
        zip(true_aspects, pred_aspects, true_sentiments, pred_sentiments)
    ):
        # Only evaluate sentiment for aspects that were correctly identified
        for j, aspect in enumerate(true_asp):
            if aspect in pred_asp:
                aspect_idx = pred_asp.index(aspect)
                sentiment_true.append(true_sent[j])
                sentiment_pred.append(pred_sent[aspect_idx])
    
    # Calculate metrics
    results = {
        "aspect_extraction": {
            "precision": np.mean(aspect_precision_scores),
            "recall": np.mean(aspect_recall_scores),
            "f1": np.mean(aspect_f1_scores)
        },
        "sentiment_classification": {
            "accuracy": accuracy_score(sentiment_true, sentiment_pred) if sentiment_true else 0.0,
            "classification_report": classification_report(sentiment_true, sentiment_pred, output_dict=True) if sentiment_true else {}
        },
        "overall_scores": {
            "num_samples": len(test_texts),
            "avg_aspects_per_sample": np.mean([len(aspects) for aspects in true_aspects]),
            "aspect_coverage": len(sentiment_true) / sum(len(aspects) for aspects in true_aspects) if true_aspects else 0.0
        }
    }
    
    return results

# Example evaluation
test_data = {
    "text": [
        "Great food, terrible service.",
        "The ambiance was nice but the food was cold."
    ],
    "aspects": [
        ["food", "service"],
        ["ambiance", "food"]
    ],
    "sentiments": [
        ["positive", "negative"],
        ["positive", "negative"]
    ]
}

test_dataset = Dataset.from_dict(test_data)
evaluation_results = evaluate_absa_model(absa_model, test_dataset)

print("ABSA Model Evaluation Results:")
print(f"Aspect Extraction F1: {evaluation_results['aspect_extraction']['f1']:.3f}")
print(f"Sentiment Classification Accuracy: {evaluation_results['sentiment_classification']['accuracy']:.3f}")
print(f"Aspect Coverage: {evaluation_results['overall_scores']['aspect_coverage']:.3f}")

Install with Tessl CLI

npx tessl i tessl/pypi-setfit

docs

absa.md

core-model-training.md

data-utilities.md

index.md

knowledge-distillation.md

model-cards.md

model-export.md

tile.json