Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
ae79d15
added the files
carrycooldude Mar 30, 2025
737f03a
Restructure LayoutLMv3 implementation to match KerasHub style
carrycooldude Apr 25, 2025
455a140
Refactor: Move LayoutLMv3 files to models directory and make code bac…
carrycooldude Apr 27, 2025
d92c8c4
refactor: Move LayoutLMv3 files to dedicated directory
carrycooldude Apr 27, 2025
0948f95
fix: Update LayoutLMv3 init files to follow correct format
carrycooldude Apr 30, 2025
3c02f78
fix: Update LayoutLMv3 backbone to follow project standards
carrycooldude Apr 30, 2025
4a79d9b
refactor: remove unnecessary files and fix imports in LayoutLMv3 module
carrycooldude May 26, 2025
c2fed4c
Add minimal stub for LayoutLMv3TransformerLayer
carrycooldude May 29, 2025
e828047
fix: resolve merge conflicts and complete rebase
carrycooldude May 30, 2025
063054d
refactor(layoutlmv3): move usage examples to class docstrings and rem…
carrycooldude Jul 4, 2025
476c0fd
style: apply code formatting and lint fixes via pre-commit
carrycooldude Jul 4, 2025
4439fad
made some changes
carrycooldude Jul 7, 2025
ad3c758
resolve the conflict issue
carrycooldude Jul 7, 2025
885f2fe
chore: update API directory and fix ruff line length in checkpoint co…
carrycooldude Jul 7, 2025
5019abb
update models
carrycooldude Jul 7, 2025
e1fc266
made changes
carrycooldude Jul 7, 2025
a32555c
chore: trigger CI
carrycooldude Jul 7, 2025
a885afa
Update API files
carrycooldude Jul 7, 2025
ad004f7
changed
carrycooldude Jul 7, 2025
6fb0fdc
chore: pre-commit fixes for layoutlmv3 __init__.py
carrycooldude Jul 7, 2025
5aaadab
chore: commit api directory after pre-commit run
carrycooldude Jul 8, 2025
8c7e989
update models
carrycooldude Jul 8, 2025
5a371a5
update layoutlmv3
carrycooldude Jul 9, 2025
bcad8d7
Fix all LayoutLMv3 issues from PR review
carrycooldude Jul 22, 2025
ca96183
Final formatting fixes for CI/CD
carrycooldude Jul 22, 2025
9c90753
Fix final ruff formatting issues
carrycooldude Jul 22, 2025
cf4b20b
Fix PyTorch backend compatibility issues - Separate ops.arange and o…
carrycooldude Jul 22, 2025
193496a
Fix PyTorch compatibility and test implementation
carrycooldude Jul 22, 2025
4d8604e
Simplify tests and fix imports to isolate PyTorch backend issue
carrycooldude Jul 22, 2025
e07224c
Fix PyTorch backend compatibility issues
carrycooldude Jul 22, 2025
6187459
Auto-fix ruff formatting issues
carrycooldude Jul 22, 2025
00fc976
Simplify LayoutLMv3 to use standard KerasHub patterns
carrycooldude Jul 22, 2025
0d3099d
Trigger fresh push - LayoutLMv3 implementation complete
carrycooldude Jul 22, 2025
82b9b93
🔧 Enhance backend compatibility and error handling
carrycooldude Jul 22, 2025
e40a6a0
Add comprehensive import error handling and fallbacks
carrycooldude Jul 22, 2025
7796cbf
Fix all code formatting issues
carrycooldude Jul 22, 2025
ae239c7
Add LayoutLMv3 exports to public API
carrycooldude Jul 22, 2025
6671da2
Revert " Add LayoutLMv3 exports to public API"
carrycooldude Jul 22, 2025
f1ac61a
Fix CI issues: bash syntax, formatting, and API generation
carrycooldude Jul 24, 2025
c83c124
Remove manual API imports - let auto-generation handle it
carrycooldude Jul 24, 2025
2ff3157
Restructure LayoutLMv3 backbone following KerasHub patterns - Follow …
carrycooldude Jul 24, 2025
87359e5
Apply comprehensive LayoutLMv3 fixes from commit bcad8d7e
carrycooldude Jul 24, 2025
e610073
Address Gemini review feedback for LayoutLMv3 implementation
carrycooldude Oct 18, 2025
47167e8
Fix line length violations in LayoutLMv3 tokenizer docstring
carrycooldude Oct 18, 2025
704dad2
Fix remaining line length violations in LayoutLMv3 tokenizer
carrycooldude Oct 18, 2025
4856b47
Add LayoutLMv3 models to public API exports
carrycooldude Oct 18, 2025
4159cd6
Resolve merge conflict in api/models/__init__.py - keep LayoutLMv3 im…
carrycooldude Oct 18, 2025
76fdc13
Merge branch 'master' into feature/layoutlmv3-port
carrycooldude Oct 18, 2025
2e506eb
Fix LayoutLMv3 test failures
carrycooldude Oct 18, 2025
8ff847e
Fix remaining LayoutLMv3 test failures
carrycooldude Oct 18, 2025
d5e28f1
Fix line length violations and format LayoutLMv3 files
carrycooldude Oct 18, 2025
8378631
Apply ruff formatting to LayoutLMv3 files and fix API imports
carrycooldude Oct 18, 2025
e424b07
Fix backend compatibility issues in LayoutLMv3 tests
carrycooldude Oct 18, 2025
e9cbdfb
Fix remaining backend compatibility issues in LayoutLMv3
carrycooldude Oct 18, 2025
eae3e40
Fix LayoutLMv3 implementation and API generation script
carrycooldude Oct 18, 2025
3d7d4c1
Fix pre-commit configuration to use Python API generation script
carrycooldude Oct 18, 2025
a5be852
Fix LayoutLMv3 implementation issues
carrycooldude Oct 18, 2025
ddf2618
Fix linting and formatting issues
carrycooldude Oct 18, 2025
08c7090
Fix remaining LayoutLMv3 test failures
carrycooldude Oct 18, 2025
7e10fab
Fix final LayoutLMv3 test failures
carrycooldude Oct 19, 2025
b3280f3
Implement LayoutLMv3 model with backbone and tokenizer
carrycooldude Oct 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/layoutlmv3_document_classification.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this directory and file

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still needs to be removed

4 changes: 4 additions & 0 deletions keras_hub/src/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""LayoutLMv3 document classifier."""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file needs to be empty, all the import is handled in keras_hub/api directory and will be automatically generated whenever you run git commit -m "<message>"
Make sure you run pre-commit install for the first time.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pending


from keras_hub.src.models.layoutlmv3.document_classifier.layoutlmv3_document_classifier import LayoutLMv3DocumentClassifier
from keras_hub.src.models.layoutlmv3.document_classifier.layoutlmv3_document_classifier_preprocessor import LayoutLMv3DocumentClassifierPreprocessor
15 changes: 15 additions & 0 deletions keras_hub/src/models/layoutlmv3/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from keras_hub.src.models.layoutlmv3.layoutlmv3_backbone import LayoutLMv3Backbone
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is mainly to register presets, follow other models to understand the format we follow.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pending

from keras_hub.src.models.layoutlmv3.layoutlmv3_document_classifier import LayoutLMv3DocumentClassifier
from keras_hub.src.models.layoutlmv3.layoutlmv3_document_classifier_preprocessor import LayoutLMv3DocumentClassifierPreprocessor
from keras_hub.src.models.layoutlmv3.layoutlmv3_tokenizer import LayoutLMv3Tokenizer
from keras_hub.src.models.layoutlmv3.layoutlmv3_transformer import LayoutLMv3Transformer
from keras_hub.src.models.layoutlmv3.layoutlmv3_presets import layoutlmv3_presets

__all__ = [
"LayoutLMv3Backbone",
"LayoutLMv3DocumentClassifier",
"LayoutLMv3DocumentClassifierPreprocessor",
"LayoutLMv3Tokenizer",
"LayoutLMv3Transformer",
"layoutlmv3_presets",
]
381 changes: 381 additions & 0 deletions keras_hub/src/models/layoutlmv3/layoutlmv3_backbone.py

Large diffs are not rendered by default.

158 changes: 158 additions & 0 deletions keras_hub/src/models/layoutlmv3/layoutlmv3_backbone_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Copyright 2024 The Keras Hub Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this

import os
import numpy as np
from keras import testing_utils
from keras import ops
from keras import backend
from keras.testing import test_case
from ..layoutlmv3.layoutlmv3_backbone import LayoutLMv3Backbone
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No relative imports


class LayoutLMv3BackboneTest(test_case.TestCase):
def setUp(self):
super().setUp()
self.backbone = LayoutLMv3Backbone(
vocab_size=100,
hidden_size=64,
num_hidden_layers=2,
num_attention_heads=2,
intermediate_size=128,
image_size=(112, 112),
patch_size=16,
)

# Create dummy inputs
self.batch_size = 2
self.seq_length = 16
self.input_ids = ops.random.uniform(
(self.batch_size, self.seq_length), minval=0, maxval=100, dtype="int32"
)
self.bbox = ops.random.uniform(
(self.batch_size, self.seq_length, 4), minval=0, maxval=100, dtype="int32"
)
self.attention_mask = ops.ones((self.batch_size, self.seq_length), dtype="int32")
self.image = ops.random.uniform(
(self.batch_size, 112, 112, 3), minval=0, maxval=1, dtype="float32"
)

self.inputs = {
"input_ids": self.input_ids,
"bbox": self.bbox,
"attention_mask": self.attention_mask,
"image": self.image,
}

def test_valid_call(self):
"""Test the backbone with valid inputs."""
outputs = self.backbone(self.inputs)
self.assertIn("sequence_output", outputs)
self.assertIn("pooled_output", outputs)
self.assertEqual(outputs["sequence_output"].shape, (self.batch_size, self.seq_length + 49 + 1, 64)) # text + image patches + cls
self.assertEqual(outputs["pooled_output"].shape, (self.batch_size, 64))

def test_save_and_load(self):
"""Test saving and loading the backbone."""
outputs = self.backbone(self.inputs)
path = self.get_temp_dir()
self.backbone.save(path)
restored_backbone = backend.saving.load_model(path)
restored_outputs = restored_backbone(self.inputs)
self.assertAllClose(outputs["sequence_output"], restored_outputs["sequence_output"])
self.assertAllClose(outputs["pooled_output"], restored_outputs["pooled_output"])

def test_from_preset(self):
"""Test creating a backbone from a preset."""
backbone = LayoutLMv3Backbone.from_preset("layoutlmv3_base")
inputs = {
"input_ids": ops.random.uniform((2, 16), 0, 100, dtype="int32"),
"bbox": ops.random.uniform((2, 16, 4), 0, 100, dtype="int32"),
"attention_mask": ops.ones((2, 16), dtype="int32"),
"image": ops.random.uniform((2, 112, 112, 3), dtype="float32"),
}
outputs = backbone(inputs)
self.assertIn("sequence_output", outputs)
self.assertIn("pooled_output", outputs)

def test_backbone_with_different_input_shapes(self):
"""Test the backbone with different input shapes."""
# Test with different sequence lengths
seq_lengths = [32, 128]
for seq_len in seq_lengths:
inputs = {
"input_ids": ops.random.uniform(
(self.batch_size, seq_len), minval=0, maxval=100, dtype="int32"
),
"bbox": ops.random.uniform(
(self.batch_size, seq_len, 4), minval=0, maxval=100, dtype="int32"
),
"attention_mask": ops.ones((self.batch_size, seq_len), dtype="int32"),
"image": self.image,
}
outputs = self.backbone(inputs)
expected_seq_length = seq_len + 49 + 1
self.assertEqual(outputs["sequence_output"].shape, (self.batch_size, expected_seq_length, 64))

# Test with different batch sizes
batch_sizes = [1, 4]
for batch_size in batch_sizes:
inputs = {
"input_ids": ops.random.uniform(
(batch_size, self.seq_length), minval=0, maxval=100, dtype="int32"
),
"bbox": ops.random.uniform(
(batch_size, self.seq_length, 4), minval=0, maxval=100, dtype="int32"
),
"attention_mask": ops.ones((batch_size, self.seq_length), dtype="int32"),
"image": ops.random.uniform(
(batch_size, 112, 112, 3), minval=0, maxval=1, dtype="float32"
),
}
outputs = self.backbone(inputs)
expected_seq_length = self.seq_length + 49 + 1
self.assertEqual(outputs["sequence_output"].shape, (batch_size, expected_seq_length, 64))

def test_backbone_with_attention_mask(self):
"""Test the backbone with different attention masks."""
# Create a mask with some padding
attention_mask = ops.ones((self.batch_size, self.seq_length), dtype="int32")
indices = ops.array([[0, 32], [1, 48]], dtype="int32")
updates = ops.array([0, 0], dtype="int32")
attention_mask = ops.scatter_nd(indices, updates, attention_mask.shape)

inputs = {
"input_ids": self.input_ids,
"bbox": self.bbox,
"attention_mask": attention_mask,
"image": self.image,
}

outputs = self.backbone(inputs)
self.assertIsInstance(outputs, dict)
self.assertIn("sequence_output", outputs)
self.assertIn("pooled_output", outputs)

def test_backbone_gradient(self):
"""Test that the backbone produces gradients."""
with backend.GradientTape() as tape:
outputs = self.backbone(self.inputs)
loss = ops.mean(outputs["pooled_output"])

# Check if gradients exist for all trainable variables
gradients = tape.gradient(loss, self.backbone.trainable_variables)
for grad in gradients:
self.assertIsNotNone(grad)
self.assertFalse(ops.all(ops.isnan(grad)))
self.assertFalse(ops.all(ops.isinf(grad)))
106 changes: 106 additions & 0 deletions keras_hub/src/models/layoutlmv3/layoutlmv3_document_classifier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""LayoutLMv3 document classifier implementation.
This module implements a document classification model using the LayoutLMv3 backbone.
"""

from typing import Dict, List, Optional, Union
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need of this


from keras import backend, layers, ops
from keras.saving import register_keras_serializable
from keras_hub.src.api_export import keras_hub_export
from keras_hub.src.models.backbone import Backbone

from .layoutlmv3_backbone import LayoutLMv3Backbone
from .layoutlmv3_document_classifier_preprocessor import LayoutLMv3DocumentClassifierPreprocessor

@keras_hub_export("keras_hub.models.LayoutLMv3DocumentClassifier")
class LayoutLMv3DocumentClassifier(layers.Layer):
"""Document classifier using LayoutLMv3 backbone.
This model uses the LayoutLMv3 backbone for document classification tasks,
adding a classification head on top of the backbone's pooled output.
Args:
backbone: LayoutLMv3Backbone instance or string preset name.
num_classes: int, defaults to 2. Number of output classes.
dropout: float, defaults to 0.1. Dropout rate for the classification head.
**kwargs: Additional keyword arguments passed to the parent class.
Example:
```python
# Initialize classifier from preset
classifier = LayoutLMv3DocumentClassifier.from_preset("layoutlmv3_base")
# Process document
outputs = classifier({
"input_ids": input_ids,
"bbox": bbox,
"attention_mask": attention_mask,
"image": image
})
```
"""

def __init__(
self,
backbone,
num_classes=2,
dropout=0.1,
**kwargs,
):
super().__init__(**kwargs)
self.backbone = backbone
self.num_classes = num_classes
self.dropout = dropout

def call(self, inputs):
# Get backbone outputs
backbone_outputs = self.backbone(inputs)
sequence_output = backbone_outputs["sequence_output"]
pooled_output = backbone_outputs["pooled_output"]

# Classification head
x = layers.Dropout(self.dropout)(pooled_output)
outputs = layers.Dense(
self.num_classes,
activation="softmax",
name="classifier",
)(x)

return outputs

def get_config(self):
config = super().get_config()
config.update({
"backbone": self.backbone,
"num_classes": self.num_classes,
"dropout": self.dropout,
})
return config

@classmethod
def from_preset(
cls,
preset,
num_classes=2,
dropout=0.1,
**kwargs,
):
"""Create a LayoutLMv3 document classifier from a preset.
Args:
preset: string. Must be one of "layoutlmv3_base", "layoutlmv3_large".
num_classes: int. Number of classes to classify documents into.
dropout: float. Dropout probability for the classification head.
**kwargs: Additional keyword arguments.
Returns:
A LayoutLMv3DocumentClassifier instance.
"""
backbone = LayoutLMv3Backbone.from_preset(preset)
return cls(
backbone=backbone,
num_classes=num_classes,
dropout=dropout,
**kwargs,
)
Loading