Audio Classification
PyTorch
LiteRT
LiteRT
kernel-insect-cnn
insect
bioacoustics
experimental
local-inference
Instructions to use ganesh333/kernel-insect-classifier with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LiteRT
How to use ganesh333/kernel-insect-classifier with LiteRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
Download export_litert.py from ganesh333/kernel-insect-classifier: direct link, hf CLI and curl.
- Browser
- Download file 7.71 kB
-
https://huggingface.co/ganesh333/kernel-insect-classifier/resolve/main/export_litert.py
- Command line
-
hf download hf://ganesh333/kernel-insect-classifier/export_litert.py
-
curl -L -o export_litert.py https://huggingface.co/ganesh333/kernel-insect-classifier/resolve/main/export_litert.py
7.71 kB
| #!/usr/bin/env python3 | |
| """ | |
| Convert InsectCNN PyTorch weights into a Google LiteRT / TensorFlow Lite (.tflite) model. | |
| Validates numerical parity between PyTorch and LiteRT outputs. | |
| """ | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| import numpy as np | |
| import torch | |
| import tensorflow as tf | |
| from src.inference import load_model | |
| class InsectLiteRT(tf.Module): | |
| """Exact TensorFlow representation of InsectCNN for LiteRT export.""" | |
| def __init__(self, pt_net): | |
| super().__init__() | |
| sd = pt_net.state_dict() | |
| # Block 1: Conv2d(1, 16, 3, pad 1) + BatchNorm2d(16) | |
| # PyTorch Conv2d weight: (out_channels, in_channels, kH, kW) | |
| # TF Conv2D weight: (kH, kW, in_channels, out_channels) | |
| self.w0 = tf.Variable(sd['features.0.weight'].cpu().numpy().transpose(2, 3, 1, 0), name='conv0_w') | |
| self.b0 = tf.Variable(sd['features.0.bias'].cpu().numpy(), name='conv0_b') | |
| self.bn0_gamma = tf.Variable(sd['features.1.weight'].cpu().numpy(), name='bn0_gamma') | |
| self.bn0_beta = tf.Variable(sd['features.1.bias'].cpu().numpy(), name='bn0_beta') | |
| self.bn0_mean = tf.Variable(sd['features.1.running_mean'].cpu().numpy(), name='bn0_mean') | |
| self.bn0_var = tf.Variable(sd['features.1.running_var'].cpu().numpy(), name='bn0_var') | |
| self.bn0_eps = tf.constant(1e-5, dtype=tf.float32) | |
| # Block 2: Conv2d(16, 32, 3, pad 1) + BatchNorm2d(32) | |
| self.w4 = tf.Variable(sd['features.4.weight'].cpu().numpy().transpose(2, 3, 1, 0), name='conv4_w') | |
| self.b4 = tf.Variable(sd['features.4.bias'].cpu().numpy(), name='conv4_b') | |
| self.bn4_gamma = tf.Variable(sd['features.5.weight'].cpu().numpy(), name='bn4_gamma') | |
| self.bn4_beta = tf.Variable(sd['features.5.bias'].cpu().numpy(), name='bn4_beta') | |
| self.bn4_mean = tf.Variable(sd['features.5.running_mean'].cpu().numpy(), name='bn4_mean') | |
| self.bn4_var = tf.Variable(sd['features.5.running_var'].cpu().numpy(), name='bn4_var') | |
| self.bn4_eps = tf.constant(1e-5, dtype=tf.float32) | |
| # Block 3: Conv2d(32, 64, 3, pad 1) + BatchNorm2d(64) | |
| self.w8 = tf.Variable(sd['features.8.weight'].cpu().numpy().transpose(2, 3, 1, 0), name='conv8_w') | |
| self.b8 = tf.Variable(sd['features.8.bias'].cpu().numpy(), name='conv8_b') | |
| self.bn8_gamma = tf.Variable(sd['features.9.weight'].cpu().numpy(), name='bn8_gamma') | |
| self.bn8_beta = tf.Variable(sd['features.9.bias'].cpu().numpy(), name='bn8_beta') | |
| self.bn8_mean = tf.Variable(sd['features.9.running_mean'].cpu().numpy(), name='bn8_mean') | |
| self.bn8_var = tf.Variable(sd['features.9.running_var'].cpu().numpy(), name='bn8_var') | |
| self.bn8_eps = tf.constant(1e-5, dtype=tf.float32) | |
| # Classifier Linear 1: (1024 -> 128) | |
| self.fc2_w = tf.Variable(sd['classifier.2.weight'].cpu().numpy().T, name='fc2_w') | |
| self.fc2_b = tf.Variable(sd['classifier.2.bias'].cpu().numpy(), name='fc2_b') | |
| # Classifier Linear 2: (128 -> num_classes) | |
| self.fc5_w = tf.Variable(sd['classifier.5.weight'].cpu().numpy().T, name='fc5_w') | |
| self.fc5_b = tf.Variable(sd['classifier.5.bias'].cpu().numpy(), name='fc5_b') | |
| def __call__(self, x): | |
| # Input x is NCHW: (1, 1, 64, 201) -> convert to NHWC: (1, 64, 201, 1) | |
| x = tf.transpose(x, [0, 2, 3, 1]) | |
| # Block 1 | |
| x = tf.nn.conv2d(x, self.w0, strides=1, padding='SAME') + self.b0 | |
| x = (x - self.bn0_mean) / tf.sqrt(self.bn0_var + self.bn0_eps) * self.bn0_gamma + self.bn0_beta | |
| x = tf.nn.relu(x) | |
| x = tf.nn.max_pool2d(x, ksize=2, strides=2, padding='VALID') | |
| # Block 2 | |
| x = tf.nn.conv2d(x, self.w4, strides=1, padding='SAME') + self.b4 | |
| x = (x - self.bn4_mean) / tf.sqrt(self.bn4_var + self.bn4_eps) * self.bn4_gamma + self.bn4_beta | |
| x = tf.nn.relu(x) | |
| x = tf.nn.max_pool2d(x, ksize=2, strides=2, padding='VALID') | |
| # Block 3 | |
| x = tf.nn.conv2d(x, self.w8, strides=1, padding='SAME') + self.b8 | |
| x = (x - self.bn8_mean) / tf.sqrt(self.bn8_var + self.bn8_eps) * self.bn8_gamma + self.bn8_beta | |
| x = tf.nn.relu(x) | |
| # AdaptiveAvgPool2d((4, 4)) over feature map shape (16, 50) | |
| # Convert back to NCHW for matching PyTorch index ordering: (1, 64, 16, 50) | |
| x_nchw = tf.transpose(x, [0, 3, 1, 2]) | |
| slices = [] | |
| for oh in range(4): | |
| sh = oh * 4 | |
| eh = (oh + 1) * 4 | |
| for ow in range(4): | |
| sw = (ow * 50) // 4 | |
| ew = ((ow + 1) * 50 + 3) // 4 | |
| pool_slice = tf.reduce_mean(x_nchw[:, :, sh:eh, sw:ew], axis=[2, 3]) | |
| slices.append(pool_slice) | |
| pooled = tf.stack(slices, axis=-1) # (1, 64, 16) | |
| flat = tf.reshape(pooled, [1, 1024]) | |
| # Dense classifier | |
| h = tf.matmul(flat, self.fc2_w) + self.fc2_b | |
| h = tf.nn.relu(h) | |
| logits = tf.matmul(h, self.fc5_w) + self.fc5_b | |
| return logits | |
| def export_litert(model_dir='.', output_file='model.tflite', quantize=False): | |
| model_dir = Path(model_dir) | |
| output_path = Path(output_file) | |
| if not output_path.is_absolute(): | |
| output_path = model_dir / output_path | |
| print(f"[*] Loading PyTorch model from '{model_dir}'...") | |
| pt_net, labels, cfg = load_model(model_dir) | |
| pt_net.eval() | |
| print("[*] Constructing TensorFlow graph with transferred weights...") | |
| tf_module = InsectLiteRT(pt_net) | |
| print(f"[*] Converting graph to LiteRT / TFLite format (quantize={quantize})...") | |
| concrete_func = tf_module.__call__.get_concrete_function() | |
| converter = tf.lite.TFLiteConverter.from_concrete_functions([concrete_func], trackable_obj=tf_module) | |
| if quantize: | |
| converter.optimizations = [tf.lite.Optimize.DEFAULT] | |
| tflite_bytes = converter.convert() | |
| output_path.write_bytes(tflite_bytes) | |
| print(f"[+] Saved LiteRT model to '{output_path}' ({len(tflite_bytes):,} bytes).") | |
| # Numerical verification | |
| print("[*] Running numerical parity test across 5 random inputs...") | |
| interpreter = tf.lite.Interpreter(model_content=tflite_bytes) | |
| interpreter.allocate_tensors() | |
| in_idx = interpreter.get_input_details()[0]['index'] | |
| out_idx = interpreter.get_output_details()[0]['index'] | |
| rng = np.random.default_rng(42) | |
| max_diff = 0.0 | |
| for _ in range(5): | |
| sample = rng.standard_normal((1, 1, 64, 201)).astype(np.float32) | |
| with torch.no_grad(): | |
| pt_out = pt_net(torch.from_numpy(sample)).cpu().numpy() | |
| interpreter.set_tensor(in_idx, sample) | |
| interpreter.invoke() | |
| tflite_out = interpreter.get_tensor(out_idx) | |
| diff = np.max(np.abs(pt_out - tflite_out)) | |
| if diff > max_diff: | |
| max_diff = diff | |
| print(f"[+] Maximum absolute difference between PyTorch and LiteRT: {max_diff:.8e}") | |
| tol = 0.2 if quantize else 1e-4 | |
| assert max_diff < tol, f"Parity check failed: max_diff {max_diff} exceeds {tol} tolerance!" | |
| print("[+] Numerical parity verified successfully!") | |
| return str(output_path) | |
| if __name__ == '__main__': | |
| parser = argparse.ArgumentParser(description="Export PyTorch model to LiteRT / TFLite") | |
| parser.add_argument('--model-dir', default='.', help="Directory containing config.json and model.pt/model.h5") | |
| parser.add_argument('--output', default='model.tflite', help="Output filename for .tflite model") | |
| parser.add_argument('--quantize', action='store_true', help="Enable dynamic-range INT8 quantization") | |
| args = parser.parse_args() | |
| export_litert(args.model_dir, args.output, args.quantize) | |