mirror of
https://github.com/neuml/txtai.git
synced 2026-10-11 21:39:55 +00:00
46 KiB
46 KiB
In [25]:
%%capture
!pip install datasets git+https://github.com/neuml/txtai#egg=txtai[pipeline]In [26]:
import numpy as np
from onnxruntime import InferenceSession, SessionOptions
from transformers import AutoTokenizer
from txtai.pipeline import HFOnnx
# Normalize logits using sigmoid function
sigmoid = lambda x: 1.0 / (1.0 + np.exp(-x))
# Export to ONNX
onnx = HFOnnx()
model = onnx("distilbert-base-uncased-finetuned-sst-2-english", "text-classification")
# Start inference session
options = SessionOptions()
session = InferenceSession(model, options)
# Tokenize
tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased-finetuned-sst-2-english")
tokens = tokenizer(["I am happy", "I am mad"], return_tensors="np")
# Print results
outputs = session.run(None, dict(tokens))
print(sigmoid(outputs[0]))[[0.01295124 0.9909526 ] [0.9874723 0.0297817 ]]
In [27]:
from datasets import load_dataset
from txtai.pipeline import HFTrainer
trainer = HFTrainer()
# Hugging Face dataset
ds = load_dataset("glue", "sst2")
data = ds["train"].select(range(10000)).flatten_indices()
# Train new model using 10,000 SST2 records (in-memory)
model, tokenizer = trainer("google/electra-base-discriminator", data, columns=("sentence", "label"))
# Export model trained in-memory to ONNX (still in-memory)
output = onnx((model, tokenizer), "text-classification", quantize=True)
# Start inference session
options = SessionOptions()
session = InferenceSession(output, options)
# Tokenize
tokens = tokenizer(["I am happy", "I am mad"], return_tensors="np")
# Print results
outputs = session.run(None, dict(tokens))
print(sigmoid(outputs[0]))WARNING:datasets.arrow_dataset:Loading cached processed dataset at /root/.cache/huggingface/datasets/glue/sst2/1.0.0/dacbe3125aa31d7f70367a07a8a9e72a5a0bfeb5fc42e75c9db75b96da6053ad/cache-e28d0e20a676bad0.arrow WARNING:datasets.arrow_dataset:Loading cached processed dataset at /root/.cache/huggingface/datasets/glue/sst2/1.0.0/dacbe3125aa31d7f70367a07a8a9e72a5a0bfeb5fc42e75c9db75b96da6053ad/cache-d7b5d80ca22204f9.arrow Some weights of the model checkpoint at google/electra-base-discriminator were not used when initializing ElectraForSequenceClassification: ['discriminator_predictions.dense_prediction.weight', 'discriminator_predictions.dense_prediction.bias', 'discriminator_predictions.dense.weight', 'discriminator_predictions.dense.bias'] - This IS expected if you are initializing ElectraForSequenceClassification from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model). - This IS NOT expected if you are initializing ElectraForSequenceClassification from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model). Some weights of ElectraForSequenceClassification were not initialized from the model checkpoint at google/electra-base-discriminator and are newly initialized: ['classifier.out_proj.bias', 'classifier.dense.weight', 'classifier.out_proj.weight', 'classifier.dense.bias'] You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference. /usr/local/lib/python3.7/dist-packages/transformers/optimization.py:310: FutureWarning: This implementation of AdamW is deprecated and will be removed in a future version. Use the PyTorch implementation torch.optim.AdamW instead, or set `no_deprecation_warning=True` to disable this warning FutureWarning, You're using a ElectraTokenizerFast tokenizer. Please note that with a fast tokenizer, using the `__call__` method is faster than using a method to encode the text followed by a call to the `pad` method to get a padded encoding.
[3750/3750 07:56, Epoch 3/3]
| Step | Training Loss |
|---|---|
| 500 | 0.396800 |
| 1000 | 0.330900 |
| 1500 | 0.232400 |
| 2000 | 0.188200 |
| 2500 | 0.173600 |
| 3000 | 0.068600 |
| 3500 | 0.069800 |
[[0.01525715 0.975399 ] [0.97395283 0.04432926]]
In [29]:
onnx = HFOnnx()
text = onnx((model, tokenizer), "text-classification", "text-classify.onnx", quantize=True)In [30]:
embeddings = onnx("sentence-transformers/paraphrase-MiniLM-L6-v2", "pooling", "embeddings.onnx", quantize=True)In [31]:
from sklearn.metrics.pairwise import cosine_similarity
options = SessionOptions()
session = InferenceSession(embeddings, options)
tokens = tokenizer(["I am happy", "I am glad"], return_tensors="np")
outputs = session.run(None, dict(tokens))[0]
print(cosine_similarity(outputs))[[0.99999994 0.8474637 ] [0.8474637 0.9999997 ]]
In [32]:
from txtai.embeddings import Embeddings
from txtai.pipeline import Labels
labels = Labels(("text-classify.onnx", "google/electra-base-discriminator"), dynamic=False)
print(labels(["I am happy", "I am mad"]))
embeddings = Embeddings({"path": "embeddings.onnx", "tokenizer": "sentence-transformers/paraphrase-MiniLM-L6-v2"})
print(embeddings.similarity("I am happy", ["I am glad"]))[[(1, 0.999687910079956), (0, 0.0003121310146525502)], [(0, 0.9991233944892883), (1, 0.0008765518432483077)]] [(0, 0.8298245072364807)]
In [33]:
%%capture
import os
os.chdir("/content")
!mkdir js
os.chdir("/content/js")
# Copy ONNX models
!cp ../text-classify.onnx .
!cp ../embeddings.onnx .
# Get tokenizers project
!git clone https://github.com/huggingface/tokenizers.git
os.chdir("/content/js/tokenizers/bindings/node")
# Install Rust to compile tokenizer bindings
!apt-get install rustc cargo
# Build tokenizers package locally as binary version on npm doesn't work for latest version of Node.js
!npm install --also=dev
!npm run dev
os.chdir("/content/js")In [34]:
%%writefile package.json
{
"name": "onnx-test",
"private": true,
"version": "1.0.0",
"description": "ONNX Runtime Node.js test",
"main": "index.js",
"dependencies": {
"onnxruntime-node": ">=1.12.1",
"tokenizers": "file:tokenizers/bindings/node"
}
}Writing package.json
In [35]:
%%capture
# Install all dependencies
!npm installIn [36]:
#@title
%%writefile index.js
const ort = require('onnxruntime-node');
const { promisify } = require('util');
const { Tokenizer } = require("tokenizers/dist/bindings/tokenizer");
function sigmoid(data) {
return data.map(x => 1 / (1 + Math.exp(-x)))
}
function softmax(data) {
return data.map(x => Math.exp(x) / (data.map(y => Math.exp(y))).reduce((a,b) => a+b))
}
function similarity(v1, v2) {
let dot = 0.0;
let norm1 = 0.0;
let norm2 = 0.0;
for (let x = 0; x < v1.length; x++) {
dot += v1[x] * v2[x];
norm1 += Math.pow(v1[x], 2);
norm2 += Math.pow(v2[x], 2);
}
return dot / (Math.sqrt(norm1) * Math.sqrt(norm2));
}
function tokenizer() {
let tokenizer = Tokenizer.fromPretrained("bert-base-uncased");
return promisify(tokenizer.encode.bind(tokenizer));
}
async function predict(session, text) {
try {
// Tokenize input
let encode = tokenizer();
let output = await encode(text);
let ids = output.getIds().map(x => BigInt(x))
let mask = output.getAttentionMask().map(x => BigInt(x))
let tids = output.getTypeIds().map(x => BigInt(x))
// Convert inputs to tensors
let tensorIds = new ort.Tensor('int64', BigInt64Array.from(ids), [1, ids.length]);
let tensorMask = new ort.Tensor('int64', BigInt64Array.from(mask), [1, mask.length]);
let tensorTids = new ort.Tensor('int64', BigInt64Array.from(tids), [1, tids.length]);
let inputs = null;
if (session.inputNames.length > 2) {
inputs = { input_ids: tensorIds, attention_mask: tensorMask, token_type_ids: tensorTids};
}
else {
inputs = { input_ids: tensorIds, attention_mask: tensorMask};
}
return await session.run(inputs);
} catch (e) {
console.error(`failed to inference ONNX model: ${e}.`);
}
}
async function main() {
let args = process.argv.slice(2);
if (args.length > 1) {
// Run sentence embeddings
const session = await ort.InferenceSession.create('./embeddings.onnx');
let v1 = await predict(session, args[0]);
let v2 = await predict(session, args[1]);
// Unpack results
v1 = v1.embeddings.data;
v2 = v2.embeddings.data;
// Print similarity
console.log(similarity(Array.from(v1), Array.from(v2)));
}
else {
// Run text classifier
const session = await ort.InferenceSession.create('./text-classify.onnx');
let results = await predict(session, args[0]);
// Normalize results using softmax and print
console.log(softmax(results.logits.data));
}
}
main();Writing index.js
In [37]:
!node . "I am happy"
!node . "I am mad"Float32Array(2) [ [33m0.0003121308400295675[39m, [33m0.9996878504753113[39m ] Float32Array(2) [ [33m0.9991234540939331[39m, [33m0.0008765519596636295[39m ]
In [38]:
!node . "I am happy", "I am glad"[33m0.8285076844387538[39m
In [39]:
%%capture
import os
os.chdir("/content")
!mkdir java
os.chdir("/content/java")
# Copy ONNX models
!cp ../text-classify.onnx .
!cp ../embeddings.onnx .
# Save copy of Bert Tokenizer
tokenizer.save_pretrained("bert")
!mkdir -p src/main/java
# Install gradle
!wget https://services.gradle.org/distributions/gradle-7.5.1-bin.zip
!unzip -o gradle-7.5.1-bin.zipIn [40]:
%%writefile build.gradle
apply plugin: "java"
repositories {
mavenCentral()
}
dependencies {
implementation "com.robrua.nlp:easy-bert:1.0.3"
implementation "com.microsoft.onnxruntime:onnxruntime:1.12.1"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
}
jar {
archiveBaseName = "onnxjava"
}
task onnx(type: JavaExec) {
description = "Runs ONNX demo"
classpath = sourceSets.main.runtimeClasspath
main = "OnnxDemo"
}Writing build.gradle
In [41]:
%%capture
# Create environment
!gradle-7.5.1/bin/gradle wrapperIn [42]:
#@title
%%writefile src/main/java/OnnxDemo.java
import java.io.File;
import java.nio.LongBuffer;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import ai.onnxruntime.OnnxTensor;
import ai.onnxruntime.OrtEnvironment;
import ai.onnxruntime.OrtSession;
import ai.onnxruntime.OrtSession.Result;
import com.robrua.nlp.bert.FullTokenizer;
class Tokens {
public long[] ids;
public long[] mask;
public long[] types;
}
class Tokenizer {
private FullTokenizer tokenizer;
public Tokenizer(String path) {
File vocab = new File(path);
this.tokenizer = new FullTokenizer(vocab, true);
}
public Tokens tokenize(String text) {
// Build list of tokens
List<String> tokensList = new ArrayList();
tokensList.add("[CLS]");
tokensList.addAll(Arrays.asList(tokenizer.tokenize(text)));
tokensList.add("[SEP]");
int[] ids = tokenizer.convert(tokensList.toArray(new String[0]));
Tokens tokens = new Tokens();
// input ids
tokens.ids = Arrays.stream(ids).mapToLong(i -> i).toArray();
// attention mask
tokens.mask = new long[ids.length];
Arrays.fill(tokens.mask, 1);
// token type ids
tokens.types = new long[ids.length];
Arrays.fill(tokens.types, 0);
return tokens;
}
}
class Inference {
private Tokenizer tokenizer;
private OrtEnvironment env;
private OrtSession session;
public Inference(String model) throws Exception {
this.tokenizer = new Tokenizer("bert/vocab.txt");
this.env = OrtEnvironment.getEnvironment();
this.session = env.createSession(model, new OrtSession.SessionOptions());
}
public float[][] predict(String text) throws Exception {
Tokens tokens = this.tokenizer.tokenize(text);
Map<String, OnnxTensor> inputs = new HashMap<String, OnnxTensor>();
inputs.put("input_ids", OnnxTensor.createTensor(env, LongBuffer.wrap(tokens.ids), new long[]{1, tokens.ids.length}));
inputs.put("attention_mask", OnnxTensor.createTensor(env, LongBuffer.wrap(tokens.mask), new long[]{1, tokens.mask.length}));
inputs.put("token_type_ids", OnnxTensor.createTensor(env, LongBuffer.wrap(tokens.types), new long[]{1, tokens.types.length}));
return (float[][])session.run(inputs).get(0).getValue();
}
}
class Vectors {
public static double similarity(float[] v1, float[] v2) {
double dot = 0.0;
double norm1 = 0.0;
double norm2 = 0.0;
for (int x = 0; x < v1.length; x++) {
dot += v1[x] * v2[x];
norm1 += Math.pow(v1[x], 2);
norm2 += Math.pow(v2[x], 2);
}
return dot / (Math.sqrt(norm1) * Math.sqrt(norm2));
}
public static float[] softmax(float[] input) {
double[] t = new double[input.length];
double sum = 0.0;
for (int x = 0; x < input.length; x++) {
double val = Math.exp(input[x]);
sum += val;
t[x] = val;
}
float[] output = new float[input.length];
for (int x = 0; x < output.length; x++) {
output[x] = (float) (t[x] / sum);
}
return output;
}
}
public class OnnxDemo {
public static void main(String[] args) {
try {
if (args.length < 2) {
Inference inference = new Inference("text-classify.onnx");
float[][] v1 = inference.predict(args[0]);
System.out.println(Arrays.toString(Vectors.softmax(v1[0])));
}
else {
Inference inference = new Inference("embeddings.onnx");
float[][] v1 = inference.predict(args[0]);
float[][] v2 = inference.predict(args[1]);
System.out.println(Vectors.similarity(v1[0], v2[0]));
}
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}Writing src/main/java/OnnxDemo.java
In [43]:
!./gradlew -q --console=plain onnx --args='"I am happy"' 2> /dev/null
!./gradlew -q --console=plain onnx --args='"I am mad"' 2> /dev/null[3.1213084E-4, 0.99968785] [m[0.99912345, 8.7655196E-4] [m
In [44]:
!./gradlew -q --console=plain onnx --args='"I am happy" "I am glad"' 2> /dev/null0.8298244656285757 [m
In [45]:
%%capture
import os
os.chdir("/content")
!mkdir rust
os.chdir("/content/rust")
# Copy ONNX models
!cp ../text-classify.onnx .
!cp ../embeddings.onnx .
# Install Rust
!apt-get install rustc cargo
!mkdir -p srcIn [46]:
%%writefile Cargo.toml
[package]
name = "onnx-test"
version = "1.0.0"
description = """
ONNX Runtime Rust test
"""
edition = "2018"
[dependencies]
onnxruntime = { version = "0.0.14"}
tokenizers = { version = "0.13.1"}Writing Cargo.toml
In [47]:
#@title
%%writefile src/main.rs
use onnxruntime::environment::Environment;
use onnxruntime::GraphOptimizationLevel;
use onnxruntime::ndarray::{Array2, Axis};
use onnxruntime::tensor::OrtOwnedTensor;
use std::env;
use tokenizers::tokenizer::{Result, Tokenizer};
fn tokenize(text: String, inputs: usize) -> Vec<Array2<i64>> {
// Load tokenizer from HF Hub
let tokenizer = Tokenizer::from_pretrained("bert-base-uncased", None).unwrap();
// Encode input text
let encoding = tokenizer.encode(text, true).unwrap();
let v1: Vec<i64> = encoding.get_ids().to_vec().into_iter().map(|x| x as i64).collect();
let v2: Vec<i64> = encoding.get_attention_mask().to_vec().into_iter().map(|x| x as i64).collect();
let v3: Vec<i64> = encoding.get_type_ids().to_vec().into_iter().map(|x| x as i64).collect();
let ids = Array2::from_shape_vec((1, v1.len()), v1).unwrap();
let mask = Array2::from_shape_vec((1, v2.len()), v2).unwrap();
let tids = Array2::from_shape_vec((1, v3.len()), v3).unwrap();
return if inputs > 2 { vec![ids, mask, tids] } else { vec![ids, mask] };
}
fn predict(text: String, softmax: bool) -> Vec<f32> {
// Start onnx session
let environment = Environment::builder()
.with_name("test")
.build().unwrap();
// Derive model path
let model = if softmax { "text-classify.onnx" } else { "embeddings.onnx" };
let mut session = environment
.new_session_builder().unwrap()
.with_optimization_level(GraphOptimizationLevel::Basic).unwrap()
.with_number_threads(1).unwrap()
.with_model_from_file(model).unwrap();
let inputs = tokenize(text, session.inputs.len());
// Run inference and print result
let outputs: Vec<OrtOwnedTensor<f32, _>> = session.run(inputs).unwrap();
let output: &OrtOwnedTensor<f32, _> = &outputs[0];
let probabilities: Vec<f32>;
if softmax {
probabilities = output
.softmax(Axis(1))
.iter()
.copied()
.collect::<Vec<_>>();
}
else {
probabilities= output
.iter()
.copied()
.collect::<Vec<_>>();
}
return probabilities;
}
fn similarity(v1: &Vec<f32>, v2: &Vec<f32>) -> f64 {
let mut dot = 0.0;
let mut norm1 = 0.0;
let mut norm2 = 0.0;
for x in 0..v1.len() {
dot += v1[x] * v2[x];
norm1 += v1[x].powf(2.0);
norm2 += v2[x].powf(2.0);
}
return dot as f64 / (norm1.sqrt() * norm2.sqrt()) as f64
}
fn main() -> Result<()> {
// Tokenize input string
let args: Vec<String> = env::args().collect();
if args.len() <= 2 {
let v1 = predict(args[1].to_string(), true);
println!("{:?}", v1);
}
else {
let v1 = predict(args[1].to_string(), false);
let v2 = predict(args[2].to_string(), false);
println!("{:?}", similarity(&v1, &v2));
}
Ok(())
}Writing src/main.rs
In [48]:
!cargo run "I am happy" 2> /dev/null
!cargo run "I am mad" 2> /dev/null[0.00030939875, 0.9996906] [0.99912345, 0.0008765513]
In [49]:
!cargo run "I am happy" "I am glad" 2> /dev/null0.8298246060854143