Places365 ResNet18 β€” LiteRT (on-device scene recognition, fully-GPU)

ResNet18 trained on Places365 (CSAILVision), converted to LiteRT and running fully on the CompiledModel GPU (ML Drift) on Android. Scene/place recognition across 365 categories (beach, kitchen, forest, office, restaurant, …) β€” a distinct task from object classification.

Places365 β€” image | top-5 scenes (on-device LiteRT GPU)

On-device (Pixel 8a, Tensor G3 β€” verified)

nodes on GPU 61 / 61 LITERT_CL (full residency)
inference ~2 ms (224Γ—224)
size 22.8 MB (fp16)
accuracy device-vs-PyTorch corr 1.0, top-1 match
image[1,3,224,224] (ImageNet-normalized) β†’[GPU: ResNet18]β†’ logits[1,365]

Minimal usage

Android (Kotlin, CompiledModel GPU)

val model = CompiledModel.create(context.assets, "places_fp16.tflite",
    CompiledModel.Options(Accelerator.GPU), null)
val inputs = model.createInputBuffers()
val outputs = model.createOutputBuffers()
inputs[0].writeFloat(chw)            // [1,3,224,224] ImageNet-normalized, NCHW
model.run(inputs, outputs)
val logits = outputs[0].readFloat()    // [1,365] scene logits -> softmax top-k

Python (desktop verification)

MEAN = np.array([0.485, 0.456, 0.406], np.float32)
STD  = np.array([0.229, 0.224, 0.225], np.float32)
import numpy as np
from PIL import Image
from ai_edge_litert.interpreter import Interpreter

# labels: https://raw.githubusercontent.com/CSAILVision/places365/master/categories_places365.txt
labels = [l.split(" ")[0][3:] for l in open("categories_places365.txt")]

img = Image.open("scene.jpg").convert("RGB").resize((224, 224))
x = ((np.asarray(img, np.float32) / 255 - MEAN) / STD).transpose(2, 0, 1)[None]

it = Interpreter(model_path="places_fp16.tflite"); it.allocate_tensors()
it.set_tensor(it.get_input_details()[0]["index"], x); it.invoke()
z = it.get_tensor(it.get_output_details()[0]["index"])[0]        # [365]
p = np.exp(z - z.max()); p /= p.sum()
for i in p.argsort()[-5:][::-1]:
    print(f"{labels[i]}: {p[i]:.3f}")

How it converts (litert-torch) β€” two numerically-exact re-authorings

  1. global AdaptiveAvgPool2d(1) β†’ mean(3).mean(2) (multi-axis-pool fix).
  2. ResNet stem MaxPool2d(3,s2,p1) β†’ zero-pad + valid max-pool. PyTorch's max-pool pads with -inf β†’ a PADV2 op the Mali delegate won't delegate (splits the graph β†’ compile fail). Since the pool follows a ReLU (inputs β‰₯ 0), a 0-pad is exactly equivalent and emits a delegatable PAD β†’ full GPU residency.

Result: banned ops NONE, all tensors ≀4D, tflite-vs-torch corr 1.0, device-vs-torch corr 1.0.

Preprocessing

Center-crop to square, resize to 224Γ—224, /255, ImageNet mean/std, NCHW. Output 365-class scene logits; softmax + argmax for top-k.

License

MIT. Upstream: CSAILVision/places365.

Downloads last month
25
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support