在使用Huggingface的BERT模型与ML.NET结合时,我偶然发现了几个难题。问题不在于加载模型和使用它,而在于准备数据和为该过程建立所有其他助手。
到目前为止,最大的挑战是我需要实现我自己的标记器,并将它们与正确的词汇配对。因此,我决定用其他可能的情况来扩展它,并将我的实现作为一个开源项目发布。如果你觉得这很有用,可以随时贡献出来。在这篇文章中,我将重点介绍支持的模型和用例。
在这篇文章中,我们涵盖了:
1.这个项目的动机
2.BERT Tokenizer NuGet软件包
3.使用案例实例
1.本项目的动机
如果我们在Python 中使用Huggingface模型,我们可以像这样从Huggingface加载标记器和模型。
# Tokenize sentences
encoded_input = tokenizer(sentences, padding=True, truncation=True,return_tensors='pt')
# Use the model
with torch.no_grad():
model_output = model(**encoded_input)
tokenizer = AutoTokenizer.from_pretrained('bert-base-cased')
model = AutoModel.from_pretrained('bert-base-cased')
然后像这样使用它:
# Tokenize sentences
encoded_input = tokenizer(sentences, padding=True, truncation=True,return_tensors='pt')
# Calculate embeddings
with torch.no_grad():
model_output = model(**encoded_input)
正如我提到的,我想在ML.NET中使用Huggingface的BERT模型。然而,在ML.NET中,我们没有那么好的选择。由于有了 可用的工具它很容易将Huggingface模型导出为ONNX文件,并从那里将它们导入ML.NET。真正的问题来自于标记,因为ML.NET中没有标记器 。
BERT模型需要专门的结构化数据。最糟糕的是,不同的模型使用不同的词汇表进行标记化。例如,德语的BERT,不会理解与BERT多语言模型相同的标记。这意味着,一个标记化器的实现对另一个标记化器来说是不够好的。

除此之外,一些Huggingface BERT模型使用的是套语词汇,而其他的则使用非套语词汇。错误的空间很大,实验的灵活性太小。例如,让我们分析一下BERT基础模型,来自Huggingface。
它的 "官方 "名称是*bert-base-cases。*这个名字表明它使用大写字母词汇,即模型对小写和大写字母进行区分。它的输出和产出是:

让我们关注一下输入。我们需要提供令牌*(input_ids),* 以及注意力掩码*(attention_mask)*和分段索引。
在ML.NET中,首先,我们需要建立处理模型输入和输出的类,并使用ApplyONNXModel 函数来加载模型。你可以在以下内容中找到更多的信息 前一篇文章:
public class ModelInput
{
[VectorType(1, 32)]
[ColumnName("input_ids")]
public long[] InputIds { get; set; }
[VectorType(1, 32)]
[ColumnName("attention_mask")]
public long[] AttentionMask { get; set; }
[VectorType(1, 32)]
[ColumnName("token_type_ids")]
public long[] TokenTypeIds { get; set; }
}
public class ModelOutput
{
[VectorType(1, 32, 768)]
[ColumnName("last_hidden_state")]
public long[] LastHiddenState { get; set; }
[VectorType(1, 768)]
[ColumnName("poller_output")]
public long[] PollerOutput { get; set; }
}
var pipeline = _mlContext.Transforms
.ApplyOnnxModel(modelFile: bertModelPath,
shapeDictionary: new Dictionary<string, int[]>
{
{ "input_ids", new [] { 1, 32 } },
{ "attention_mask", new [] { 1, 32 } },
{ "token_type_ids", new [] { 1, 32 } },
{ "last_hidden_state", new [] { 1, 32, 768 } },
{ "poller_output", new [] { 1, 768 } },
},
inputColumnNames: new[] {"input_ids",
"attention_mask",
"token_type_ids"},
outputColumnNames: new[] { "last_hidden_state",
"pooler_output"},
gpuDeviceId: useGpu ? 0 : (int?)null,
fallbackToCpu: true);
尽管如此,我们仍然没有一个简单的方法来创建令牌并将其作为输入提供给这个模型。要创建一个正确的ModelInput对象是一个挑战。另外,如何从模型的输出中获得意义也是一个挑战。现在我们可以使用 BERTTokenizer NuGet软件包来实现这一目的。
2.BERT Tokenizers NuGet Package
这个NuGet包 应该能让你的生活更轻松。我们的目标是尽可能地接近Python的易用性。Huggingface的Python API中提供的完整堆栈对用户非常友好,它为许多人以直接的方式使用SOTA NLP模型铺平了道路。希望有一天,我们也能用C#做同样的事情。

要安装BERTTokenizers NuGet软件包,请使用这个命令:
dotnet add package BERTTokenizers
或者你可以用软件包管理器来安装它:
Install-Package BERTTokenizers
2.1 支持的模型和词汇表
目前,BertTokenizers支持以下词汇表:
- BERT Base Cased - classBertBaseTokenizer
- BERT Large Cased - classBertLargeTokenizer
- BERT 德语词汇表 - classBertGermanTokenizer
- BERT多语言编码--类BertMultilingualTokenizer
- BERT基本无序--类BertBaseUncasedTokenizer
- BERT Large Uncased - classBertLargeUncasedTokenizer
有了这个广泛的模型的支持。不仅仅是BERT模型,而且比方说和DistilBERT模型。
2.2 可用的方法

每个类都提供相同的三个功能:
- Encode- 这个方法的输入是序列长度(这是必须的,因为ML.NET不支持可变长度的输入)和需要被编码的句子。作为输出,该方法为编码后的句子中的每个标记提供一个图元列表,其中包括:标记ID、标记类型和注意掩码。
- Tokenize - 如果你需要更多关于标记的信息,或者你想自己进行填充,这个方法会起到作用。输入的是一个需要标记化的句子,输出的是句子中每个标记的图元列表--标记、标记ID和标记类型。
- Untokenize - 该方法用于反向处理,将标记列表放入有意义的单词中。它被用于模型的输出。
这个项目的UML看起来是这样的:

3.用例实例
让我们回到BERT基础模型。对于它,我们像这样建立我们的输入类:
public class ModelInput
{
[VectorType(1, 32)]
[ColumnName("input_ids")]
public long[] InputIds { get; set; }
[VectorType(1, 32)]
[ColumnName("attention_mask")]
public long[] AttentionMask { get; set; }
[VectorType(1, 32)]
[ColumnName("token_type_ids")]
public long[] TokenTypeIds { get; set; }
}

在我们创建一个要送入管道的对象之前,我们需要创建令牌,我们可以这样做:
var tokenizer = new BertBaseTokenizer();
var encoded = tokenizer.Encode(32, sentence);
var bertInput = new ModelInput()
{
InputIds = encoded.InputIds,
AttentionMask = encoded.AttentionMask,
TypeIds = encoded.TokenTypeIds,
};
重要说明: Encode 方法中的第一个参数与ModelInput类中VectorType装饰器中的序列大小相同。
总结
在这篇文章中,我们看到了如何使用BERTTokenizer NuGet包,为BERT输入轻松建立令牌。
谢谢你的阅读!