UNPKG

@huggingface/transformers

Version:

State-of-the-art Machine Learning for the web. Run 🤗 Transformers directly in your browser, with no need for a server!

156 lines (137 loc) • 7.24 kB
import { Pipeline } from './_base.js'; import { softmax } from '../utils/maths.js'; import { logger } from '../utils/logger.js'; /** * @typedef {import('./_base.js').TextPipelineConstructorArgs} TextPipelineConstructorArgs * @typedef {import('./_base.js').Disposable} Disposable */ /** * @typedef {Object} ZeroShotClassificationOutput * @property {string} sequence The sequence for which this is the output. * @property {string[]} labels The labels sorted by order of likelihood. * @property {number[]} scores The probabilities for each of the labels. * * @typedef {Object} ZeroShotClassificationPipelineOptions Parameters specific to zero-shot classification pipelines. * @property {string} [hypothesis_template="This example is {}."] The template used to turn each * candidate label into an NLI-style hypothesis. The candidate label will replace the {} placeholder. * @property {boolean} [multi_label=false] Whether or not multiple candidate labels can be true. * If `false`, the scores are normalized such that the sum of the label likelihoods for each sequence * is 1. If `true`, the labels are considered independent and probabilities are normalized for each * candidate by doing a softmax of the entailment score vs. the contradiction score. * * @typedef {TextPipelineConstructorArgs & ZeroShotClassificationPipelineCallback & Disposable} ZeroShotClassificationPipelineType */ /** * @template T * @typedef {T extends string[] ? ZeroShotClassificationOutput[] : ZeroShotClassificationOutput} ZeroShotClassificationPipelineResult */ /** * @typedef {<T extends string | string[]>(texts: T, candidate_labels: string | string[], options?: ZeroShotClassificationPipelineOptions) => Promise<ZeroShotClassificationPipelineResult<T>>} ZeroShotClassificationPipelineCallback */ /** * NLI-based zero-shot classification pipeline using a `ModelForSequenceClassification` * trained on NLI (natural language inference) tasks. Equivalent of `text-classification` * pipelines, but these models don't require a hardcoded number of potential classes, they * can be chosen at runtime. It usually means it's slower but it is **much** more flexible. * * **Example:** Zero shot classification with `Xenova/mobilebert-uncased-mnli`. * ```javascript * import { pipeline } from '@huggingface/transformers'; * * const classifier = await pipeline('zero-shot-classification', 'Xenova/mobilebert-uncased-mnli'); * const text = 'Last week I upgraded my iOS version and ever since then my phone has been overheating whenever I use your app.'; * const labels = [ 'mobile', 'billing', 'website', 'account access' ]; * const output = await classifier(text, labels); * // { * // sequence: 'Last week I upgraded my iOS version and ever since then my phone has been overheating whenever I use your app.', * // labels: [ 'mobile', 'website', 'billing', 'account access' ], * // scores: [ 0.5562091040482018, 0.1843621307860853, 0.13942646639336376, 0.12000229877234923 ] * // } * ``` * * **Example:** Zero shot classification with `Xenova/nli-deberta-v3-xsmall` (multi-label). * ```javascript * import { pipeline } from '@huggingface/transformers'; * * const classifier = await pipeline('zero-shot-classification', 'Xenova/nli-deberta-v3-xsmall'); * const text = 'I have a problem with my iphone that needs to be resolved asap!'; * const labels = [ 'urgent', 'not urgent', 'phone', 'tablet', 'computer' ]; * const output = await classifier(text, labels, { multi_label: true }); * // { * // sequence: 'I have a problem with my iphone that needs to be resolved asap!', * // labels: [ 'urgent', 'phone', 'computer', 'tablet', 'not urgent' ], * // scores: [ 0.9958870956360275, 0.9923963400697035, 0.002333537946160235, 0.0015134138567598765, 0.0010699384208377163 ] * // } * ``` */ export class ZeroShotClassificationPipeline extends /** @type {new (options: TextPipelineConstructorArgs) => ZeroShotClassificationPipelineType} */ (Pipeline) { /** * Create a new ZeroShotClassificationPipeline. * @param {TextPipelineConstructorArgs} options An object used to instantiate the pipeline. */ constructor(options) { super(options); // Use model config to get label2id mapping this.label2id = Object.fromEntries( Object.entries(/** @type {any} */ (this).model.config.label2id).map(([k, v]) => [k.toLowerCase(), v]), ); this.entailment_id = this.label2id['entailment']; if (this.entailment_id === undefined) { logger.warn("Could not find 'entailment' in label2id mapping. Using 2 as entailment_id."); this.entailment_id = 2; } this.contradiction_id = this.label2id['contradiction'] ?? this.label2id['not_entailment']; if (this.contradiction_id === undefined) { logger.warn("Could not find 'contradiction' in label2id mapping. Using 0 as contradiction_id."); this.contradiction_id = 0; } } async _call(texts, candidate_labels, { hypothesis_template = 'This example is {}.', multi_label = false } = {}) { const isBatched = Array.isArray(texts); if (!isBatched) { texts = [/** @type {string} */ (texts)]; } if (!Array.isArray(candidate_labels)) { candidate_labels = [candidate_labels]; } // Insert labels into hypothesis template const hypotheses = candidate_labels.map((x) => hypothesis_template.replace('{}', x)); // How to perform the softmax over the logits: // - true: softmax over the entailment vs. contradiction dim for each label independently // - false: softmax the "entailment" logits over all candidate labels const softmaxEach = multi_label || candidate_labels.length === 1; /** @type {ZeroShotClassificationOutput[]} */ const toReturn = []; for (const premise of texts) { const entails_logits = []; for (const hypothesis of hypotheses) { const inputs = this.tokenizer(premise, { text_pair: hypothesis, padding: true, truncation: true, }); const outputs = await this.model(inputs); if (softmaxEach) { entails_logits.push([ outputs.logits.data[this.contradiction_id], outputs.logits.data[this.entailment_id], ]); } else { entails_logits.push(outputs.logits.data[this.entailment_id]); } } /** @type {number[]} */ const scores = softmaxEach ? entails_logits.map((x) => softmax(x)[1]) : softmax(entails_logits); // Sort by scores (desc) and return scores with indices const scores_sorted = scores.map((x, i) => [x, i]).sort((a, b) => b[0] - a[0]); toReturn.push({ sequence: premise, labels: scores_sorted.map((x) => candidate_labels[x[1]]), scores: scores_sorted.map((x) => x[0]), }); } return isBatched ? toReturn : toReturn[0]; } }