#nlp #ngrams #combinator #composable #bare-metal #features #creature

bin+lib creature_feature

易于使用且裸机快速的可组合 n-gram 组合器

7 个版本

0.1.7 2023 年 7 月 12 日
0.1.6 2023 年 7 月 6 日
0.1.4 2023 年 5 月 3 日
0.1.2 2022 年 12 月 21 日
0.1.1 2021 年 12 月 24 日

#113文本处理

Download history 1/week @ 2024-03-09 16/week @ 2024-03-30 3/week @ 2024-04-06

53 每月下载次数

MPL-2.0 许可证

145KB
2K SLoC

CREATURE 特性(化)

一个用于多态 ML & NLP 特征化的 crate,利用零成本抽象。它提供易于使用且裸机快速的可组合 n-gram 组合器。尽管它是为 NLP 而创建的,但它非常通用,可以应用于计算机视觉等多个领域。

有很多 n-gram crate,但大多数都强制进行堆分配或锁定到不适合您的用例或性能需求的具体类型。在大多数基准测试中,creature_feature 的速度比其他任何东西快 4x - 60x。

Image

查看实时演示

这里 是 creature_feature 使用 WASM 的实时演示

特征化的瑞士军刀

use creature_feature::traits::Ftzr;
use creature_feature::ftzrs::{bigram, bislice, for_each, whole};
use creature_feature::HashedAs;
use creature_feature::convert::Bag;
use std::collections::{HashMap, HashSet, BTreeMap, LinkedList, BTreeSet, BinaryHeap, VecDeque};

let int_data = &[1, 2, 3, 4, 5];
let str_data = "one fish two fish red fish blue fish";

// notice how the left-hand side remains almost unchanged.

// we're using 'bislice' right now (which is a 2-gram of referenced data), 'ftzrs::bigram' would yield owned data instead of references

let ref_feats: Vec<&str>                  = bislice().featurize(str_data);
let ref_feats: LinkedList<&[u8]>          = bislice().featurize(str_data);
let ref_bag:   Bag<HashMap<&[usize], u8>> = bislice().featurize(int_data);
let ref_trigram_bag:   Bag<BTreeMap<&str, i16>>   = for_each(whole()).featurize(str_data.split_ascii_whitespace());
let hashed_trigrams: BTreeSet<HashedAs<u64>> = trislice().featurize(int_data);

上面的五个将分别具有以下值。

["on", "ne", "e ", " f", "fi", "is", "sh", "h ", " t", "tw", "wo", "o ", " f", "fi", "is", "sh", "h ", " r", "re", "ed", "d ", " f", "fi", "is", "sh", "h ", " b", "bl", "lu", "ue", "e ", " f", "fi", "is", "sh"]

[[111, 110], [110, 101], [101, 32], [32, 102], [102, 105], [105, 115], [115, 104], [104, 32], [32, 116], [116, 119], [119, 111], [111, 32], [32, 102], [102, 105], [105, 115], [115, 104], [104, 32], [32, 114], [114, 101], [101, 100], [100, 32], [32, 102], [102, 105], [105, 115], [115, 104], [104, 32], [32, 98], [98, 108], [108, 117], [117, 101], [101, 32], [32, 102], [102, 105], [105, 115], [115, 104]]

Bag({[2, 3, 4]: 1, [3, 4, 5]: 1, [1, 2, 3]: 1})

Bag({"blue": 1, "fish": 4, "one": 1, "red": 1, "two": 1})

{HashedAs(3939941806544028562), HashedAs(7191405660579021101), HashedAs(16403185381100005216)}

以下是更多可能的示例

 // let's now switch to 'bigram'
let owned_feats: BTreeSet<[u8; 2]>        = bigram().featurize(str_data);
let owned_feats: Vec<String>              = bigram().featurize(str_data);
let owned_feats: HashSet<Vec<usize>>      = bigram().featurize(int_data);
let owned_bag:   Bag<HashMap<Vec<usize>, u16>>      = bigram().featurize(int_data);

let hashed_feats: BinaryHeap<HashedAs<u32>> = bislice().featurize(str_data);
let hashed_feats: VecDeque<HashedAs<u64>>   =  bigram().featurize(int_data);

let sentence = str_data.split_ascii_whitespace();
let bag_of_words: Bag<HashMap<String, u128>> = for_each(bigram()) .featurize(sentence.clone());
let bag_of_words: Bag<HashMap<&str, u8>>     = for_each(bislice()).featurize(sentence.clone());

 // and many, MANY more posibilities

我们甚至可以在只特征化输入一次的同时产生多个输出

let (set, list): (BTreeSet<HashedAs<u64>>, Vec<&str>) = bislice().featurize_x2(str_data);

creature_feature 提供了三种通用的特征化器类型

  1. NGram<const N: usize> 提供了复制的数据上的 n-gram,并生成所有者数据或多个 [T;N]。示例包括 ftzrs::n_gramftzrs::bigramftzrs::trigram

  2. SliceGram 提供了引用数据上的 n-gram,并生成所有者数据或多个 &[T]。示例包括 ftzrs::n_sliceftzrs::bisliceftzrs::trislice

  3. 由一个或多个特征化器组成并返回具有不同行为的新的特征化器的组合器。例如包括 ftzrs::for_eachftzrs::gap_gramfeaturizers!ftzrs::bookends

为什么多态 == 性能

这里有一个小测验,以展示多态特征化和 快速 特征化是如何相辅相成的。

这里有四种不同的字符串特征化方式,基本上是等效的。但是,哪一种是最快的?快了多少?

let sentence = "It is a truth universally acknowledged that Jane Austin must be used in nlp examples";

let one:   Vec<String> = trigram().featurize(sentence);
let two:   Vec<[u8;3]> = trigram().featurize(sentence);
let three: Vec<&str>   = trislice().featurize(sentence); // same performance as &[u8]
let four:  Vec<HashedAs<u64>> = trislice().featurize(sentence); // could have used trigram

String 的三元组,HashedAs<u64>&str[u8; 3] 各自都有它们的应用场合,但最快和最慢之间可能存在大约 两个数量级的性能差异。如果你选择了不适合你的需求(或者使用了一个更少多态的crate),你就在速度上失去了优势!

我应该使用什么类型来表示我的特征?

  • 如果 TN 都很小,则通过 ftzrs::n_gram 使用 Collection<[T; N]>。这通常是大多数情况。

  • 如果 [T; N] 在字节大小上会比 (usize, usize) 大,则使用 Collection<&[T]>(或者 Collection<&str>)通过 ftzrs::n_slice。这在 N 很大或你使用 char 而不是 u8 时更为常见。这也取决于原始数据与生成的特征的生存期。

  • HashedAs<u64>&[T] 具有相反的时间复杂度,线性创建和 O(1) 等价。如果你对百万分之一左右的哈希冲突无异议,这可以是一个很好的折衷方案。

  • 永远不要在性能关键部分使用 Collection<String>Collection<Vec<T>>

creature_feature 在其他分词器中如何定位?

creature_feature 非常灵活,traits::Ftzr/traits::IterFtzr 可以通过为任何你喜欢的其他分词器/特征提取器创建一个新类型轻松实现。任何东西都可以进行特征提取:图像、文档、时间序列数据等。

示例:特征提取书籍

考虑一个自定义的结构来表示一本书

struct Book {
   author: String,
   genre: Genre,
   sub_genre: SubGenre,
   year: u16,
}

#[derive(Hash)]
enum Genre {
   Fiction,
   NonFiction,
   Religion,
}

#[derive(Hash)]
enum SubGenre {
   Romance,
   History,
   DataScience,
}

impl Book {
   fn decade(&self) -> u8 {
       unimplemented!()
   }
}

我们可以通过使用 traits::Ftzr 来轻松地为 Book 创建一个自定义特征提取器。

use creature_feature::ftzrs::whole;
use creature_feature::traits::*;
use creature_feature::HashedAs;

struct BookFtzr;

impl<'a> Ftzr<&'a Book> for BookFtzr {
   type TokenGroup = HashedAs<u64>;
   fn push_tokens<Push: FnMut(Self::TokenGroup)>(&self, book: &'a Book, push: &mut Push) {
       whole().push_tokens_from(&book.author, push);
       push(FeatureFrom::from(&book.genre));
       push(FeatureFrom::from(&book.sub_genre));
       push(FeatureFrom::from(book.year));
       push(FeatureFrom::from(book.decade()));
   }
}

现在,我们可以通过 Vec<u64>>(如余弦或贾卡德)轻松实现 Book 的相似度度量。

使用说明

  • 不执行边界检查。这是用户的责任。
  • 为了处理 Unicode,将其转换为 Vec<char>

你可以帮忙

我实际上是一名英语老师,不是开发者。所以任何 PR、观察或反馈都非常欢迎。我已经尽力将一切都记录下来,但如果你有任何问题,请随时联系。你对于解决少量当前问题(当前问题)的帮助将非常受欢迎 :)

依赖项

~115–440KB