or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

absa.mdcore-model-training.mddata-utilities.mdindex.mdknowledge-distillation.mdmodel-cards.mdmodel-export.md

absa.mddocs/

0

# Aspect-Based Sentiment Analysis (ABSA)

1

2

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.

3

4

## Capabilities

5

6

### ABSA Model

7

8

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

9

10

```python { .api }

11

class AbsaModel:

12

def __init__(

13

self,

14

aspect_model: Optional["AspectModel"] = None,

15

polarity_model: Optional["PolarityModel"] = None

16

):

17

"""

18

Initialize an ABSA model with aspect extraction and polarity classification.

19

20

Parameters:

21

- aspect_model: Model for extracting aspects from text

22

- polarity_model: Model for classifying sentiment polarity of aspects

23

"""

24

25

def predict(self, inputs: Union[str, List[str]]) -> List[Dict]:

26

"""

27

Perform aspect-based sentiment analysis on input texts.

28

29

Parameters:

30

- inputs: Input text(s) to analyze

31

32

Returns:

33

List of dictionaries containing aspect-polarity pairs for each input

34

"""

35

36

def __call__(self, inputs: Union[str, List[str]]) -> List[Dict]:

37

"""

38

Callable interface for prediction.

39

40

Parameters:

41

- inputs: Input text(s) to analyze

42

43

Returns:

44

ABSA predictions with aspects and their sentiment polarities

45

"""

46

```

47

48

### Aspect Model

49

50

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

51

52

```python { .api }

53

class AspectModel:

54

def __init__(

55

self,

56

model_body: Optional[SentenceTransformer] = None,

57

model_head: Optional[Union[SetFitHead, LogisticRegression]] = None,

58

spacy_model: str = "en_core_web_sm",

59

span_context: int = 0,

60

normalize_embeddings: bool = True,

61

labels: Optional[List[str]] = None,

62

model_card_data: Optional[SetFitModelCardData] = None

63

):

64

"""

65

Initialize an aspect extraction model.

66

67

Parameters:

68

- model_body: Sentence transformer for generating embeddings

69

- model_head: Classification head for aspect/non-aspect prediction

70

- spacy_model: SpaCy model name for tokenization and NER

71

- span_context: Number of context tokens around spans

72

- normalize_embeddings: Whether to normalize embeddings

73

- labels: Label names for aspect categories

74

- model_card_data: Metadata for model card generation

75

"""

76

77

def predict(self, inputs: Union[str, List[str]]) -> List[List[str]]:

78

"""

79

Extract aspect terms from input texts.

80

81

Parameters:

82

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

83

84

Returns:

85

List of aspect terms for each input text

86

"""

87

88

def prepend_aspects(

89

self,

90

sentences: List[str],

91

aspects: List[List[str]]

92

) -> List[str]:

93

"""

94

Prepend aspect terms to sentences for polarity classification.

95

96

Parameters:

97

- sentences: Original sentences

98

- aspects: Extracted aspects for each sentence

99

100

Returns:

101

Modified sentences with prepended aspects

102

"""

103

```

104

105

### Polarity Model

106

107

Model specialized for classifying sentiment polarity of extracted aspects.

108

109

```python { .api }

110

class PolarityModel:

111

def __init__(

112

self,

113

model_body: Optional[SentenceTransformer] = None,

114

model_head: Optional[Union[SetFitHead, LogisticRegression]] = None,

115

spacy_model: str = "en_core_web_sm",

116

span_context: int = 0,

117

normalize_embeddings: bool = True,

118

labels: Optional[List[str]] = None,

119

model_card_data: Optional[SetFitModelCardData] = None

120

):

121

"""

122

Initialize a polarity classification model for aspects.

123

124

Parameters:

125

- model_body: Sentence transformer for generating embeddings

126

- model_head: Classification head for polarity prediction

127

- spacy_model: SpaCy model name for tokenization

128

- span_context: Number of context tokens around aspects

129

- normalize_embeddings: Whether to normalize embeddings

130

- labels: Label names for polarity classes (e.g., ["negative", "neutral", "positive"])

131

- model_card_data: Metadata for model card generation

132

"""

133

134

def predict(self, inputs: Union[str, List[str]]) -> List[int]:

135

"""

136

Classify sentiment polarity of aspects in input texts.

137

138

Parameters:

139

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

140

141

Returns:

142

Polarity class predictions for each input

143

"""

144

```

145

146

### Span SetFit Model

147

148

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

149

150

```python { .api }

151

class SpanSetFitModel:

152

def __init__(

153

self,

154

model_body: Optional[SentenceTransformer] = None,

155

model_head: Optional[Union[SetFitHead, LogisticRegression]] = None,

156

spacy_model: str = "en_core_web_sm",

157

span_context: int = 0,

158

multi_target_strategy: Optional[str] = None,

159

normalize_embeddings: bool = True,

160

labels: Optional[List[str]] = None,

161

model_card_data: Optional[SetFitModelCardData] = None

162

):

163

"""

164

Initialize a span-based SetFit model.

165

166

Parameters:

167

- model_body: Sentence transformer for embeddings

168

- model_head: Classification head

169

- spacy_model: SpaCy model for text processing

170

- span_context: Number of context tokens around spans

171

- multi_target_strategy: Strategy for multi-target classification

172

- normalize_embeddings: Whether to normalize embeddings

173

- labels: Label names

174

- model_card_data: Model metadata

175

"""

176

177

def prepend_aspects(

178

self,

179

sentences: List[str],

180

aspects: List[List[str]]

181

) -> List[str]:

182

"""

183

Prepend aspect information to sentences.

184

185

Parameters:

186

- sentences: Input sentences

187

- aspects: Aspect terms for each sentence

188

189

Returns:

190

Sentences with prepended aspect information

191

"""

192

193

def __call__(self, inputs: Union[str, List[str]]) -> List:

194

"""

195

Process inputs through the span-based model.

196

197

Parameters:

198

- inputs: Input texts or spans

199

200

Returns:

201

Model predictions for the spans

202

"""

203

```

204

205

### ABSA Trainer

206

207

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

208

209

```python { .api }

210

class AbsaTrainer:

211

def __init__(

212

self,

213

model: Optional[Union[AspectModel, PolarityModel]] = None,

214

args: Optional[TrainingArguments] = None,

215

train_dataset: Optional[Dataset] = None,

216

eval_dataset: Optional[Dataset] = None,

217

compute_metrics: Optional[Callable] = None,

218

callbacks: Optional[List] = None,

219

column_mapping: Optional[Dict[str, str]] = None

220

):

221

"""

222

Initialize an ABSA trainer.

223

224

Parameters:

225

- model: ABSA model to train (aspect or polarity model)

226

- args: Training arguments

227

- train_dataset: Training dataset with span annotations

228

- eval_dataset: Evaluation dataset

229

- compute_metrics: Function to compute evaluation metrics

230

- callbacks: Training callbacks

231

- column_mapping: Mapping of dataset columns

232

"""

233

234

def train(self) -> None:

235

"""

236

Train the ABSA model on span-level data.

237

"""

238

239

def evaluate(self, eval_dataset: Optional[Dataset] = None) -> Dict[str, float]:

240

"""

241

Evaluate the ABSA model.

242

243

Parameters:

244

- eval_dataset: Evaluation dataset

245

246

Returns:

247

Evaluation metrics

248

"""

249

```

250

251

### Aspect Extractor

252

253

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

254

255

```python { .api }

256

class AspectExtractor:

257

def __init__(

258

self,

259

spacy_model: str = "en_core_web_sm",

260

aspect_model: Optional[AspectModel] = None

261

):

262

"""

263

Initialize aspect extractor.

264

265

Parameters:

266

- spacy_model: SpaCy model for NLP processing

267

- aspect_model: Trained aspect model for ML-based extraction

268

"""

269

270

def extract_aspects(

271

self,

272

text: str,

273

method: str = "model"

274

) -> List[str]:

275

"""

276

Extract aspect terms from text.

277

278

Parameters:

279

- text: Input text to extract aspects from

280

- method: Extraction method ("model", "pos", "ner", "hybrid")

281

282

Returns:

283

List of extracted aspect terms

284

"""

285

286

def extract_aspects_batch(

287

self,

288

texts: List[str],

289

method: str = "model"

290

) -> List[List[str]]:

291

"""

292

Extract aspects from multiple texts.

293

294

Parameters:

295

- texts: List of input texts

296

- method: Extraction method

297

298

Returns:

299

List of aspect lists for each text

300

"""

301

```

302

303

## Usage Examples

304

305

### Basic ABSA Pipeline

306

307

```python

308

from setfit import AbsaModel, AspectModel, PolarityModel, SetFitModel

309

from datasets import Dataset

310

311

# Prepare ABSA training data

312

absa_data = {

313

"text": [

314

"The food was great but the service was slow.",

315

"Amazing pizza, terrible atmosphere.",

316

"Good value for money, poor location."

317

],

318

"aspects": [

319

["food", "service"],

320

["pizza", "atmosphere"],

321

["value", "location"]

322

],

323

"polarities": [

324

[1, 0], # food: positive, service: negative

325

[1, 0], # pizza: positive, atmosphere: negative

326

[1, 0] # value: positive, location: negative

327

]

328

}

329

330

# Create datasets for aspect and polarity training

331

aspect_train_data = []

332

polarity_train_data = []

333

334

for text, aspects, polarities in zip(absa_data["text"], absa_data["aspects"], absa_data["polarities"]):

335

# Create aspect extraction examples (binary classification per span)

336

# This would need proper span annotation in practice

337

338

# Create polarity classification examples

339

for aspect, polarity in zip(aspects, polarities):

340

aspect_text = f"{aspect}: {text}"

341

polarity_train_data.append({

342

"text": aspect_text,

343

"label": polarity

344

})

345

346

polarity_dataset = Dataset.from_list(polarity_train_data)

347

348

# Train aspect model (simplified example)

349

aspect_model = AspectModel.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")

350

351

# Train polarity model

352

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

353

354

from setfit import AbsaTrainer, TrainingArguments

355

356

polarity_trainer = AbsaTrainer(

357

model=polarity_model,

358

train_dataset=polarity_dataset,

359

args=TrainingArguments(

360

batch_size=16,

361

num_epochs=4,

362

learning_rate=2e-5

363

),

364

column_mapping={"text": "text", "label": "label"}

365

)

366

367

polarity_trainer.train()

368

369

# Create combined ABSA model

370

absa_model = AbsaModel(

371

aspect_model=aspect_model,

372

polarity_model=polarity_model

373

)

374

375

# Perform ABSA

376

test_texts = [

377

"The pasta was delicious but the service was really poor.",

378

"Great ambiance, overpriced drinks."

379

]

380

381

results = absa_model.predict(test_texts)

382

for i, result in enumerate(results):

383

print(f"Text {i+1}: {test_texts[i]}")

384

print(f"ABSA Results: {result}")

385

print()

386

```

387

388

### Training Aspect Extraction Model

389

390

```python

391

from setfit import AspectModel, AbsaTrainer, TrainingArguments

392

from datasets import Dataset

393

import spacy

394

395

# Load SpaCy model for tokenization

396

nlp = spacy.load("en_core_web_sm")

397

398

def create_aspect_training_data(texts, aspect_annotations):

399

"""

400

Create training data for aspect extraction from span annotations.

401

402

Parameters:

403

- texts: List of input texts

404

- aspect_annotations: List of (start, end, label) tuples for each text

405

406

Returns:

407

Dataset with span-level labels

408

"""

409

training_examples = []

410

411

for text, annotations in zip(texts, aspect_annotations):

412

doc = nlp(text)

413

414

# Create binary labels for each token/span

415

for token in doc:

416

is_aspect = False

417

aspect_label = "O" # Outside

418

419

# Check if token is part of an aspect span

420

for start, end, label in annotations:

421

if start <= token.idx < end:

422

is_aspect = True

423

aspect_label = label

424

break

425

426

training_examples.append({

427

"text": token.text,

428

"context": text,

429

"label": 1 if is_aspect else 0,

430

"aspect_type": aspect_label

431

})

432

433

return Dataset.from_list(training_examples)

434

435

# Example aspect annotations (start_char, end_char, aspect_type)

436

texts = [

437

"The pizza was amazing but the service was slow.",

438

"Great atmosphere, terrible food quality."

439

]

440

441

annotations = [

442

[(4, 9, "food"), (33, 40, "service")], # "pizza", "service"

443

[(6, 16, "ambiance"), (27, 39, "food")] # "atmosphere", "food quality"

444

]

445

446

# Create training dataset

447

aspect_dataset = create_aspect_training_data(texts, annotations)

448

449

# Initialize and train aspect model

450

aspect_model = AspectModel(

451

spacy_model="en_core_web_sm",

452

span_context=2 # Include 2 tokens of context around spans

453

)

454

455

aspect_trainer = AbsaTrainer(

456

model=aspect_model,

457

train_dataset=aspect_dataset,

458

args=TrainingArguments(

459

batch_size=32,

460

num_epochs=5,

461

learning_rate=2e-5,

462

eval_strategy="epoch"

463

)

464

)

465

466

aspect_trainer.train()

467

468

# Test aspect extraction

469

test_sentences = [

470

"The burger was tasty but the fries were cold.",

471

"Excellent wine selection, poor table service."

472

]

473

474

extracted_aspects = aspect_model.predict(test_sentences)

475

for sentence, aspects in zip(test_sentences, extracted_aspects):

476

print(f"Sentence: {sentence}")

477

print(f"Extracted aspects: {aspects}")

478

```

479

480

### Fine-grained ABSA with Multiple Aspects

481

482

```python

483

from setfit import AbsaModel, AspectModel, PolarityModel

484

import json

485

486

# Define aspect categories for restaurant reviews

487

ASPECT_CATEGORIES = {

488

"food": ["taste", "quality", "freshness", "variety"],

489

"service": ["speed", "friendliness", "attentiveness", "professionalism"],

490

"ambiance": ["atmosphere", "noise", "lighting", "decor"],

491

"value": ["price", "portion", "worth", "deals"]

492

}

493

494

class DetailedAbsaModel:

495

def __init__(self, aspect_model, polarity_model, aspect_categories):

496

self.aspect_model = aspect_model

497

self.polarity_model = polarity_model

498

self.aspect_categories = aspect_categories

499

500

def predict_detailed(self, text):

501

"""Perform detailed ABSA with aspect categorization."""

502

# Extract aspects

503

aspects = self.aspect_model.predict([text])[0]

504

505

# Categorize aspects

506

categorized_aspects = {}

507

for aspect in aspects:

508

for category, subcategories in self.aspect_categories.items():

509

if any(sub in aspect.lower() for sub in subcategories):

510

if category not in categorized_aspects:

511

categorized_aspects[category] = []

512

categorized_aspects[category].append(aspect)

513

break

514

else:

515

# Uncategorized aspect

516

if "other" not in categorized_aspects:

517

categorized_aspects["other"] = []

518

categorized_aspects["other"].append(aspect)

519

520

# Get sentiment for each aspect

521

results = {}

522

for category, category_aspects in categorized_aspects.items():

523

category_results = []

524

for aspect in category_aspects:

525

aspect_text = f"{aspect}: {text}"

526

polarity_probs = self.polarity_model.predict_proba([aspect_text])[0]

527

sentiment_score = polarity_probs[1] - polarity_probs[0] # positive - negative

528

529

category_results.append({

530

"aspect": aspect,

531

"sentiment_score": float(sentiment_score),

532

"sentiment": "positive" if sentiment_score > 0.1 else "negative" if sentiment_score < -0.1 else "neutral",

533

"confidence": float(max(polarity_probs))

534

})

535

536

results[category] = category_results

537

538

return results

539

540

# Initialize detailed ABSA model

541

detailed_model = DetailedAbsaModel(

542

aspect_model=aspect_model,

543

polarity_model=polarity_model,

544

aspect_categories=ASPECT_CATEGORIES

545

)

546

547

# Analyze restaurant review

548

review = """

549

The seafood pasta was absolutely delicious and the portion size was generous.

550

However, our waiter was quite rude and it took forever to get our drinks.

551

The restaurant is beautifully decorated but it was way too noisy to have a conversation.

552

For the price we paid, I expected much better service.

553

"""

554

555

detailed_results = detailed_model.predict_detailed(review)

556

557

print("Detailed ABSA Results:")

558

print(json.dumps(detailed_results, indent=2))

559

```

560

561

### ABSA Model Evaluation

562

563

```python

564

from setfit import AbsaModel

565

from sklearn.metrics import classification_report, accuracy_score

566

import numpy as np

567

568

def evaluate_absa_model(absa_model, test_dataset):

569

"""

570

Comprehensive evaluation of ABSA model performance.

571

572

Parameters:

573

- absa_model: Trained ABSA model

574

- test_dataset: Test dataset with ground truth aspects and sentiments

575

576

Returns:

577

Dictionary with evaluation metrics

578

"""

579

580

# Prepare test data

581

test_texts = test_dataset["text"]

582

true_aspects = test_dataset["aspects"] # List of lists

583

true_sentiments = test_dataset["sentiments"] # List of lists

584

585

# Get predictions

586

predictions = absa_model.predict(test_texts)

587

588

# Extract predicted aspects and sentiments

589

pred_aspects = []

590

pred_sentiments = []

591

592

for pred in predictions:

593

aspects = list(pred.keys())

594

sentiments = [pred[aspect]["sentiment"] for aspect in aspects]

595

pred_aspects.append(aspects)

596

pred_sentiments.append(sentiments)

597

598

# Aspect extraction evaluation

599

aspect_precision_scores = []

600

aspect_recall_scores = []

601

aspect_f1_scores = []

602

603

for true_asp, pred_asp in zip(true_aspects, pred_aspects):

604

true_set = set(true_asp)

605

pred_set = set(pred_asp)

606

607

if len(pred_set) > 0:

608

precision = len(true_set & pred_set) / len(pred_set)

609

else:

610

precision = 0.0

611

612

if len(true_set) > 0:

613

recall = len(true_set & pred_set) / len(true_set)

614

else:

615

recall = 1.0 if len(pred_set) == 0 else 0.0

616

617

if precision + recall > 0:

618

f1 = 2 * precision * recall / (precision + recall)

619

else:

620

f1 = 0.0

621

622

aspect_precision_scores.append(precision)

623

aspect_recall_scores.append(recall)

624

aspect_f1_scores.append(f1)

625

626

# Sentiment classification evaluation (for correctly identified aspects)

627

sentiment_true = []

628

sentiment_pred = []

629

630

for i, (true_asp, pred_asp, true_sent, pred_sent) in enumerate(

631

zip(true_aspects, pred_aspects, true_sentiments, pred_sentiments)

632

):

633

# Only evaluate sentiment for aspects that were correctly identified

634

for j, aspect in enumerate(true_asp):

635

if aspect in pred_asp:

636

aspect_idx = pred_asp.index(aspect)

637

sentiment_true.append(true_sent[j])

638

sentiment_pred.append(pred_sent[aspect_idx])

639

640

# Calculate metrics

641

results = {

642

"aspect_extraction": {

643

"precision": np.mean(aspect_precision_scores),

644

"recall": np.mean(aspect_recall_scores),

645

"f1": np.mean(aspect_f1_scores)

646

},

647

"sentiment_classification": {

648

"accuracy": accuracy_score(sentiment_true, sentiment_pred) if sentiment_true else 0.0,

649

"classification_report": classification_report(sentiment_true, sentiment_pred, output_dict=True) if sentiment_true else {}

650

},

651

"overall_scores": {

652

"num_samples": len(test_texts),

653

"avg_aspects_per_sample": np.mean([len(aspects) for aspects in true_aspects]),

654

"aspect_coverage": len(sentiment_true) / sum(len(aspects) for aspects in true_aspects) if true_aspects else 0.0

655

}

656

}

657

658

return results

659

660

# Example evaluation

661

test_data = {

662

"text": [

663

"Great food, terrible service.",

664

"The ambiance was nice but the food was cold."

665

],

666

"aspects": [

667

["food", "service"],

668

["ambiance", "food"]

669

],

670

"sentiments": [

671

["positive", "negative"],

672

["positive", "negative"]

673

]

674

}

675

676

test_dataset = Dataset.from_dict(test_data)

677

evaluation_results = evaluate_absa_model(absa_model, test_dataset)

678

679

print("ABSA Model Evaluation Results:")

680

print(f"Aspect Extraction F1: {evaluation_results['aspect_extraction']['f1']:.3f}")

681

print(f"Sentiment Classification Accuracy: {evaluation_results['sentiment_classification']['accuracy']:.3f}")

682

print(f"Aspect Coverage: {evaluation_results['overall_scores']['aspect_coverage']:.3f}")

683

```