Repository navigation
Conversation
rzo1
left a comment
There was a problem hiding this comment.
Hi @krickert, thanks for the PR. Before going through the details, a devil's advocate question on the approach:
- The removed API (
TokenizerFactory(…, Pattern),TokenizerFactory.create(…, Pattern),getAlphaNumericPattern(), theTokSpanEventStreamconstructor andFactory.getAlphanumeric) has a user-supplied regular expression as its contract, and the pattern is stored in the model manifest. This is exactly the case that #1348 and CONTRIBUTING.md cover with@SuppressForbiddeninstead of a rewrite. - A model trained with a custom
alphaNumericPatternno longer loads in 3.0, and a custom policy written by 3.0 is silently ignored by 2.x. Removing public API that exists since 1.x and shipped in M6 without a deprecation step is a hard break for these users. - The main benefit I see is performance, since the pattern is matched for every candidate token. Could you add a JMH benchmark to quantify it?
Suggestion: map the known patterns to TokenizerCharacterPolicy, which covers the released models and gives the faster path for them, but keep the Pattern API as @Deprecated(forRemoval = true) and as fallback for custom patterns under @SuppressForbidden. Existing models keep loading, and the removal can follow in 4.0. WDYT?
ff7c076 to
b928348
Compare
|
I'm typically against fallbacks but the transition is a good compromise. The speed is a clear winner too :) Thanks for the suggestion. |
b928348 to
1cb19b3
Compare
Thank you! All were good suggestions and updated in AI supplied evidence:
|
mawiesne
left a comment
There was a problem hiding this comment.
No actual changes requested for now, still I'm throwing a blockage in here.
I need (time) to check some of the proposed changes in detail locally, hopefully confirming nothing breaks. Pls allow for 1-2 weeks time for deeper validation. Will come back to this PR.
1da21cd to
83958f6
Compare
rzo1
left a comment
There was a problem hiding this comment.
Code is fine from my side. Still missing the eval run the description asks for.
| TokenizerFactory factory = model.getFactory(); | ||
| this.cg = factory.getContextGenerator(); | ||
| this.alphanumeric = factory.getAlphaNumericPattern(); | ||
| this.alphanumeric = factory.alphanumericTest(); |
There was a problem hiding this comment.
Subclasses overriding getAlphaNumericPattern() still compile, but the override is now ignored here and in createManifestEntries. Needs a README breaking-changes entry.
| if (pattern == null) { | ||
| return; | ||
| } | ||
| TokenizerCharacterPolicy known = Factory.policyOfLegacyExpression(patternText(pattern)); |
There was a problem hiding this comment.
Ignores pattern.flags(). A preset compiled with CASE_INSENSITIVE gets swapped for a stricter policy. Only map when flags() == 0?
| readManifest(); | ||
| } | ||
| } catch (IllegalArgumentException e) { | ||
| throw new InvalidFormatException(e.getMessage(), e); |
There was a problem hiding this comment.
A bad custom pattern now fails at model load with InvalidFormatException instead of PatternSyntaxException in TokenizerME. Fine, but put it in the README.
| @Deprecated(since = "3.0.0", forRemoval = true) | ||
| @SuppressForbidden("alphanumeric pattern of earlier releases, kept for the deprecated API") | ||
| public Pattern getAlphanumeric(String languageCode) { | ||
| return compile(LEGACY_EXPRESSIONS.get(getCharacterPolicy(languageCode))); |
There was a problem hiding this comment.
Compiles a new Pattern on every call. These used to be constants, so getAlphanumeric(null) == DEFAULT_ALPHANUMERIC no longer holds. Cache them?
83958f6 to
559dbac
Compare
…izerCharacterPolicy instead of a regex TokenizerME, TokSpanEventStream, TokenizerFactory and the language Factory take a TokenizerCharacterPolicy for the alphanumeric optimization. The Pattern variants of the TokenizerFactory constructor, TokenizerFactory.create and the TokSpanEventStream constructor, getAlphaNumericPattern(), Factory.getAlphanumeric(String) and Factory.DEFAULT_ALPHANUMERIC stay as deprecated for removal in 4.0, and only they keep @SuppressForbidden. New overloads without the last argument use the policy of the language. The lang.Factory lookups that serve only the deprecated Pattern path are @internal and deprecated for removal with it. The factory writes the policy to the model manifest as characterPolicy, in the text form of TokenizerCharacterPolicy.toString(), which TokenizerCharacterPolicy.parse(String) reads; for a language policy it also writes the alphaNumericPattern of earlier releases. A model with one of the alphaNumericPattern values OpenNLP wrote loads with the matching language policy, and a model with a custom pattern loads and tests candidate tokens with that regular expression. A manifest without either property uses the policy for its language code. A pattern with the text of a language pattern given to the deprecated constructor or create is replaced by that policy only when it has no flags. The deprecated getters return the patterns of earlier releases compiled once, so Factory.getAlphanumeric(null) is Factory.DEFAULT_ALPHANUMERIC. The language policies are tested against their old regular expressions, TokenizerME output is pinned with and without the optimization, and the manifest tests cover the policy, every legacy pattern, a custom and an invalid pattern, a malformed policy and a manifest without either, plus the deprecated constructors, create and getters. TokenizerCharacterPolicyBenchmark compares the policy with the regular expressions. The tokenizer chapter and the README describe the policy, the deprecation, the migration, and the breaking edges: an ignored getAlphaNumericPattern() override and an invalid custom pattern failing at model load. Fixes #1479 Signed-off-by: Kristian Rickert <kristian@apache.org> Co-authored-by: Claude Code 2.1.296 (Opus 5.5) <noreply@anthropic.com>
559dbac to
2da6c92
Compare
The tokenizer's alphanumeric optimization now tests candidate tokens with a TokenizerCharacterPolicy instead of a java.util.regex Pattern. TokenizerME applies the policy through its model's factory; TokSpanEventStream, TokenizerFactory, and the language Factory take it directly.
The Pattern API stays as a deprecated path for removal in 4.0: the Pattern variants of the TokenizerFactory constructor, TokenizerFactory.create, and the TokSpanEventStream constructor, plus TokenizerFactory.getAlphaNumericPattern(), Factory.getAlphanumeric(String), and Factory.DEFAULT_ALPHANUMERIC. Regex use is now confined to that path and the legacy manifest handling. The protected TokenizerFactory.init now takes a policy, so subclasses that override it must update. New TokenizerFactory overloads without the last argument use the policy for the language. Passing a null literal no longer compiles because it matches both the Pattern and the policy variants; use the new overloads without that argument.
The factory writes the policy to the model manifest as characterPolicy, using the text form of TokenizerCharacterPolicy.toString(), which TokenizerCharacterPolicy.parse(String) reads. For a language policy it also writes the alphaNumericPattern that earlier releases wrote for that language, so OpenNLP 2.x tokenizes such a model the same way. A model with one of those alphaNumericPattern values loads with the matching language policy; a model with a custom pattern still loads and tests candidates with that regular expression. A manifest with neither property uses the policy for its language code.
TokenizerCharacterPolicyBenchmark (JMH, JDK 25, nanoseconds per call over 50 candidate tokens) against the regular expressions of earlier releases:
TokenizerMEBenchmark.instancePerThread on English: 110,685 ops/s against 97,647 ops/s on main.
Tests pin each language policy against its old regular expression, pin TokenizerME output with and without the optimization, and cover a custom policy, the policy text form, the manifest with a policy, every legacy pattern, a custom and an invalid pattern, a malformed policy, a manifest with neither property, and the deprecated constructors, create, and getters. The tokenizer chapter and the README describe the policy, the deprecation, and the migration.
An eval build is needed before merge, since the tokenizer training and inference path changes.
Fixes #1479