Compromise: Batching `./corrections` for performance

Created on 20 Jan 2020  路  9Comments  路  Source: spencermountain/compromise

Performance is obviously great :D But just wanted to start a discussion.

  • I noticed a lot of time is spent on the corrections,
    image

  • UnTag is called surprisingly often (6th place in nlp, and 1st place in lookups) - I'm probably wrong, but is this called because compromise changes its mind about tags after they were added on first pass?

nlp
image

lookups
image

Obviously these times are pretty tiny, but curious if any more room to improve!

Discussion enhancement

All 9 comments

Hey, this is a great observation. You may be on to something.

You're right, ./corrections accounts for >3/4 of the parsing time. It's definitely the place to look at improving performance.

You may have seen this, in ./world/tags theres a messy tag-conflict concept, (called notA ). At runtime, each tag is given a list of tags it is in conflict with:

console.log(nlp().world.tags.Person)

/*{ notA: [ 'Place',
     'Organization',
     'Date',
     ...
  ]
*/

Anyways, each time a term gets a tag, we go through this notA list, and untag any conflicts it may have.
Yeah, I had thought it was simply going to be a .filter() call, to remove any unwanted tags. I hadn't expected this to be a major performance issue, as you discovered.

So if you put a console.log ontop of the untag method (in term/tag/untag.js), you can isolate how often it is run - 19 times, in this case.

nlp.tokenize('word').tag('Person')

Just throwing out some ideas -

  • certainly, we could optimize untag()
  • we could clean-up the notA data - to avoid redundancies - make these lists shorter. (i'm happy to do this part)
  • collect all the notA tags, and wait to call untag() after all the tags are set?
  • do something dramatically different??

Take a look. If it's helpful, I sometimes run npm i --no-save nlp-corpus then use npm run speed. Maybe there's a better way of doing that.

i believe compromise could definitely be twice as fast as it is now. That would be a big deal.

ok last thing -
if you look at nlp().world.tags.Auxiliary.notA, for example, you'll see it is exhaustive, and has redundant tags ['Person', 'LastName'].

v11 had just ['Person'], but then recursed through the notA data for Person, etc.

That may be faster, that may be slower. I have no idea.

I guess what i'm saying is there may be _conceptual_ wins to make here, if you can find them.
Something tells me you're the right person to find them.

haha thanks 馃槀 Sounds like fun!
I would imagine top 3 sounds fair, if we can avoid running it, probably should. I think I'll take a look at your 3rd point especially, we can probably do more to prepare the tags ahead of time to avoid tooo much processing, just need to keep the prep very concise...

I was looking a little and published my test branch I started: feature/672/perf
Basically wget the corpus and then get an average processing time for each file size. The test is long enough that the file is set to ignore. Going to update it to calculate some kind of single number based on the length vs time....maths anyone?

Last thing; are tags prioritised? So Person is not a Place, and Place is not a Person, so which one do you get? :D

I'll make a PR to play with results....

yeah, the conflicts are inferred from 'descendant' tags - 'LastName' collects all the assumptions of 'Person' (in ./words/tags/inference) - that works.

but to be honest, I can't remember if the _'incoming conflicts'_ are inferred as well. Maybe they should be. I reckon any conflict ought to be symmetric, like Person != Place, therefor Place!=Person.

Yeh that would make sense, haven't had a chance to look yet. But that might be a good place to start, store the conflicts in a map so we can skip some steps

To continue discussion about "batching"...

I'm thinking about corrections being pre-compiled down, so we can remove all the overlaps at build time.
This probably needs some proper thought, but some obvious targets (part of this could manually...but...it's 2020 :D )

Example from fixPerson

<brain-dump>

  • if '^#Honorific$' is matched, no point doing anything else - because we're checking then entire text...
  • The whole file is for adding Person, we can skip adding it where it's already been done (don't tag), or perhaps skip some steps/terms in the match since we don't care about looking at them again since the result will be the same.
  • Probably impossible for an input to meet the needs of EVERY match in here, how can we know more ahead of time?
  • If we have a data object basically describing all the matching => tag/untag at compile time, then can we create a tree of compatible/incompatible/impossible matches? So we only go down a path at runtime that can work?.....
  • If we create an object to contain the corrections - it can be efrted
  • Can we and would it be performant to drop corrections at nlp/tokenize time based on small checks to invalid large parts of the correction tree?
  • Direct lookup of words is faster, can we do a direct lookup of corrections also?

</brain-dump>

been thinking about this.
ya, the corrections files have a bunch of ad-hoc conditions, which help

hon = doc.if('#Honorific')
hon.match().match().match().match()...

but

  • A) this done in a sloppy way, but also
  • B) a child Doc doesn't benefit (as much?) from document-level caching.

it may be kind of one or the other - we reduce redundant checks by slicing the Doc a bunch, or we reduce redundant checks by doing them up-front?

and yeah, your point about optimizing it for tag-invalidation - that is galaxy-brain.

efrt got me thinking in this direction! Feels really close to compress all 1s and 0s to 0....then worry about uncompressing 馃槀

Yeh I think it's a question of optimise the matching at build, optimise before running (so again, data has to be stored in a way so we can cut things out fast), then maybe what we're left with, we do more directly rather than using the user focused .match.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

struesdell picture struesdell  路  3Comments

shamoons picture shamoons  路  4Comments

josephrocca picture josephrocca  路  6Comments

josephrocca picture josephrocca  路  6Comments

Xetanai picture Xetanai  路  4Comments