Skip to content

Commit 5023e58

Browse files
feat: add average aggregation
1 parent 11d6f1f commit 5023e58

3 files changed

Lines changed: 109 additions & 14 deletions

File tree

pipelineBackends/tokenizer.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ func AllInputTokens(pipeline *BasePipeline) {
4141
}
4242
}
4343

44-
func Decode(tokens []uint32, tokenizer *Tokenizer) string {
44+
func Decode(tokens []uint32, tokenizer *Tokenizer, skipSpecialTokens bool) (string, error) {
4545
switch tokenizer.Runtime {
4646
case "RUST":
47-
return decodeRust(tokens, tokenizer)
47+
return decodeRust(tokens, tokenizer, skipSpecialTokens), nil
4848
}
49-
return ""
49+
return "", fmt.Errorf("runtime %s not recognized", tokenizer.Runtime)
5050
}

pipelineBackends/tokenizer_rust.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ func tokenizeInputsRust(batch *PipelineBatch, tk *Tokenizer, inputs []string) {
8282
batch.MaxSequenceLength = maxSequence + 1
8383
}
8484

85-
func decodeRust(tokens []uint32, tokenizer *Tokenizer) string {
86-
return tokenizer.RustTokenizer.Tokenizer.Decode(tokens, false)
85+
func decodeRust(tokens []uint32, tokenizer *Tokenizer, skipSpecialTokens bool) string {
86+
return tokenizer.RustTokenizer.Tokenizer.Decode(tokens, skipSpecialTokens)
8787
}
8888

8989
func convertRustOffsets(input []tokenizers.Offset) [][2]uint {

pipelines/tokenClassification.go

Lines changed: 104 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type Entity struct {
3535
Scores []float32
3636
Index int
3737
Word string
38-
TokenID uint32
38+
TokenID []uint32
3939
Start uint
4040
End uint
4141
IsSubword bool
@@ -65,6 +65,12 @@ func WithSimpleAggregation() pipelineBackends.PipelineOption[*TokenClassificatio
6565
}
6666
}
6767

68+
func WithAverageAggregation() pipelineBackends.PipelineOption[*TokenClassificationPipeline] {
69+
return func(pipeline *TokenClassificationPipeline) {
70+
pipeline.AggregationStrategy = "AVERAGE"
71+
}
72+
}
73+
6874
// WithoutAggregation returns the token labels.
6975
func WithoutAggregation() pipelineBackends.PipelineOption[*TokenClassificationPipeline] {
7076
return func(pipeline *TokenClassificationPipeline) {
@@ -264,7 +270,7 @@ func (p *TokenClassificationPipeline) GatherPreEntities(input pipelineBackends.T
264270
// in that case set the subword as in the python code
265271
preEntities = append(preEntities, Entity{
266272
Word: word,
267-
TokenID: tokenID,
273+
TokenID: []uint32{tokenID},
268274
Scores: tokenScores,
269275
Start: startInd,
270276
End: endInd,
@@ -275,8 +281,82 @@ func (p *TokenClassificationPipeline) GatherPreEntities(input pipelineBackends.T
275281
return preEntities
276282
}
277283

284+
func (p *TokenClassificationPipeline) aggregateWord(entities []Entity) (Entity, error) {
285+
tokens := make([]uint32, len(entities))
286+
for i, e := range entities {
287+
tokens[i] = e.TokenID[0]
288+
}
289+
var newEntity Entity
290+
291+
word, err := pipelineBackends.Decode(tokens, p.Model.Tokenizer, true)
292+
if err != nil {
293+
return newEntity, err
294+
}
295+
296+
switch p.AggregationStrategy {
297+
case "AVERAGE":
298+
scores := make([][]float32, len(p.IDLabelMap))
299+
for _, e := range entities {
300+
for i, score := range e.Scores {
301+
scores[i] = append(scores[i], score)
302+
}
303+
}
304+
averages := make([]float32, len(p.IDLabelMap))
305+
for i, s := range scores {
306+
averages[i] = util.Mean(s)
307+
}
308+
entityIdx, score, _ := util.ArgMax(averages)
309+
label, ok := p.IDLabelMap[entityIdx]
310+
if !ok {
311+
return Entity{}, fmt.Errorf("could not determine entity type for input %s, predicted entity index %d", word, entityIdx)
312+
}
313+
newEntity = Entity{
314+
Entity: label,
315+
Score: score,
316+
Word: word,
317+
TokenID: tokens,
318+
Start: entities[0].Start,
319+
End: entities[len(entities)-1].End,
320+
}
321+
default:
322+
return Entity{}, fmt.Errorf("aggregation strategy %s not recognized", p.AggregationStrategy)
323+
}
324+
return newEntity, nil
325+
}
326+
327+
func (p *TokenClassificationPipeline) aggregateWords(entities []Entity) ([]Entity, error) {
328+
var wordGroup []Entity
329+
var wordEntities []Entity
330+
331+
for _, entity := range entities {
332+
if len(wordGroup) == 0 {
333+
wordGroup = []Entity{entity}
334+
} else if entity.IsSubword {
335+
wordGroup = append(wordGroup, entity)
336+
} else {
337+
aggregated, err := p.aggregateWord(wordGroup)
338+
if err != nil {
339+
return nil, err
340+
}
341+
wordEntities = append(wordEntities, aggregated)
342+
wordGroup = []Entity{entity}
343+
}
344+
}
345+
346+
if len(wordGroup) > 0 {
347+
aggregated, err := p.aggregateWord(wordGroup)
348+
if err != nil {
349+
return nil, err
350+
}
351+
wordEntities = append(wordEntities, aggregated)
352+
}
353+
return wordEntities, nil
354+
}
355+
278356
func (p *TokenClassificationPipeline) Aggregate(input pipelineBackends.TokenizedInput, preEntities []Entity) ([]Entity, error) {
279357
entities := make([]Entity, len(preEntities))
358+
var aggregationError error
359+
280360
if p.AggregationStrategy == "SIMPLE" || p.AggregationStrategy == "NONE" {
281361
for i, preEntity := range preEntities {
282362
entityIdx, score, argMaxErr := util.ArgMax(preEntity.Scores)
@@ -298,8 +378,12 @@ func (p *TokenClassificationPipeline) Aggregate(input pipelineBackends.Tokenized
298378
}
299379
}
300380
} else {
301-
return nil, errors.New("aggregation strategies other than SIMPLE and NONE are not implemented")
381+
entities, aggregationError = p.aggregateWords(preEntities)
382+
if aggregationError != nil {
383+
return nil, aggregationError
384+
}
302385
}
386+
303387
if p.AggregationStrategy == "NONE" {
304388
return entities, nil
305389
}
@@ -323,7 +407,7 @@ func (p *TokenClassificationPipeline) getTag(entityName string) (string, string)
323407
return bi, tag
324408
}
325409

326-
func (p *TokenClassificationPipeline) groupSubEntities(entities []Entity) Entity {
410+
func (p *TokenClassificationPipeline) groupSubEntities(entities []Entity) (Entity, error) {
327411
splits := strings.Split(entities[0].Entity, "-")
328412
var entityType string
329413
if len(splits) == 1 {
@@ -335,20 +419,23 @@ func (p *TokenClassificationPipeline) groupSubEntities(entities []Entity) Entity
335419
tokens := make([]uint32, len(entities))
336420
for i, s := range entities {
337421
scores[i] = s.Score
338-
tokens[i] = s.TokenID
422+
tokens = slices.Concat(tokens, s.TokenID)
339423
}
340424
score := util.Mean(scores)
341425
// note: here we directly appeal to the tokenizer decoder with the tokenIds
342426
// in the python code they pass the words to a token_to_string_method
343-
word := pipelineBackends.Decode(tokens, p.Model.Tokenizer)
427+
word, err := pipelineBackends.Decode(tokens, p.Model.Tokenizer, true)
428+
if err != nil {
429+
return Entity{}, err
430+
}
344431

345432
return Entity{
346433
Entity: entityType,
347434
Score: score,
348435
Word: word,
349436
Start: entities[0].Start,
350437
End: entities[len(entities)-1].End,
351-
}
438+
}, nil
352439
}
353440

354441
// GroupEntities group together adjacent tokens with the same entity predicted.
@@ -368,14 +455,22 @@ func (p *TokenClassificationPipeline) GroupEntities(entities []Entity) ([]Entity
368455
currentGroupDisagg = append(currentGroupDisagg, e)
369456
} else {
370457
// create the grouped entity
371-
entityGroups = append(entityGroups, p.groupSubEntities(currentGroupDisagg))
458+
groupedEntity, err := p.groupSubEntities(currentGroupDisagg)
459+
if err != nil {
460+
return nil, err
461+
}
462+
entityGroups = append(entityGroups, groupedEntity)
372463
currentGroupDisagg = []Entity{e}
373464
}
374465
}
375466

376467
if len(currentGroupDisagg) > 0 {
377468
// last entity remaining
378-
entityGroups = append(entityGroups, p.groupSubEntities(currentGroupDisagg))
469+
groupedEntity, err := p.groupSubEntities(currentGroupDisagg)
470+
if err != nil {
471+
return nil, err
472+
}
473+
entityGroups = append(entityGroups, groupedEntity)
379474
}
380475
return entityGroups, nil
381476
}

0 commit comments

Comments
 (0)