基于OpenHarmony的智能相册应用开发(2)
上篇传送门:基于OpenHarmony的智能相册应用开发(1)
有了Tokenizer后,我们即可以对用户输入的文本进行编码:
torch::Tensor ClipPreprocessor::preprocessText(const std::string &text) {
std::vector<size_t> all_tokens;
// tokenize the text
all_tokens.emplace_back(tokenizer_->getVocabId(L"[CLS]"));
auto ids = tokenizer_->convertTokensToIds(tokenizer_->tokenize(text));
if (ids.size() > text_length - 2) {
ids.resize(text_length - 2);
}
all_tokens.insert(all_tokens.end(), ids.begin(), ids.end());
all_tokens.emplace_back(tokenizer_->getVocabId(L"[SEP]"));
// convert all_tokens to tensor
auto tensor_text = torch::zeros({1, text_length}, torch::kInt32);
for (auto i = 0; i < all_tokens.size(); i++) {
tensor_text[0][i] = all_tokens[i];
}
// std::cout << "tensor: " << tensor_text << std::endl;
return tensor_text;
}
这段代码定义了一个名为 preprocessText
的函数,它属于 ClipPreprocessor
类。这个函数的目的是将输入的文本字符串转换为一个 PyTorch 张量(Tensor),该张量可以用于后续的模型输入中。让我们详细解释一下这段代码的每一步:
函数签名
torch::Tensor ClipPreprocessor::preprocessText(const std::string &text)
- torch::Tensor:函数返回一个 PyTorch 张量。
- ClipPreprocessor:函数属于
ClipPreprocessor
类。 - preprocessText:函数名称。
- const std::string &text:函数接受一个常量引用的字符串作为输入。
函数内部逻辑
-
定义一个存储所有 token ID 的向量
cpp std::vector<size_t> all_tokens;
-
添加特殊的起始 token
[CLS]
cpp all_tokens.emplace_back(tokenizer_->getVocabId(L"[CLS]"));
- 使用tokenizer_
获取[CLS]
token 的词汇表 ID,并将其添加到all_tokens
中。 -
将输入文本进行分词并转换为 token ID
cpp auto ids = tokenizer_->convertTokensToIds(tokenizer_->tokenize(text));
-tokenizer_->tokenize(text)
将输入文本text
分词。
-tokenizer_->convertTokensToIds(...)
将这些分词转换为对应的词汇表 ID。 -
截断 token ID 列表以适应最大长度
cpp if (ids.size() > text_length - 2) { ids.resize(text_length - 2); }
- 如果分词后的 token ID 数量超过了text_length - 2
(因为要预留给[CLS]
和[SEP]
),则截断到合适长度。 -
添加分词后的 token ID 到
all_tokens
中
cpp all_tokens.insert(all_tokens.end(), ids.begin(), ids.end());
-
添加特殊的结束 token
[SEP]
cpp all_tokens.emplace_back(tokenizer_->getVocabId(L"[SEP]"));
-
将 token ID 转换为 PyTorch 张量
cpp auto tensor_text = torch::zeros({1, text_length}, torch::kInt32); for (auto i = 0; i < all_tokens.size(); i++) { tensor_text[0][i] = all_tokens[i]; }
- 创建一个大小为[1, text_length]
的张量tensor_text
,初始值为 0,数据类型为torch::kInt32
。
- 将all_tokens
中的值逐个赋值给tensor_text
。 -
返回处理后的张量
cpp return tensor_text;
处理完token后,即可以调用模型进行文本编码,如getTextFeature函数所示:
torch::Tensor ClipInferEngine::getTextFeature(const std::string& text) {
auto image_tensor = torch::zeros({1, 3, ClipPreprocessor::image_size, ClipPreprocessor::image_size});
auto text_tensor = preprocessor_.preprocessText(text);
std::vector<torch::jit::IValue> inputs;
inputs.push_back(image_tensor);
inputs.push_back(text_tensor);
auto outputs = model_.forward(inputs).toTuple()->elements();
assert(outputs.size() == 3);
return outputs[1].toTensor();
}
为了能在ArkTs代码内调用模型相关的推理、Tokenizer接口,我们需要定义napi(Node-API)接口来进行ArkTs和C++的交互:
// 文字转换成Feature
static napi_value GetTextFeature(napi_env env, napi_callback_info info) {
size_t requireArgc = 1;
size_t argc = 1;
napi_value args[1] = {nullptr};
napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
napi_valuetype valuetype0;
napi_typeof(env, args[0], &valuetype0);
// 保证valuetype0 是字符串类型,进行相应的操作
assert(valuetype0 == napi_string);
const size_t MAX_SIZE = 256;
char buf[MAX_SIZE];
size_t size = 0;
napi_get_value_string_utf8(env, args[0], buf, MAX_SIZE, &size);
std::string text(buf, size);
auto text_feature = CLIP::ClipEngineHelper::GetTextFeature(text);
return ConvertVectorToJSArray(env, text_feature);
}
这段代码的主要功能是:从 JavaScript 获取一个字符串参数,再将这个字符串转换为 C++ 字符串。调用 CLIP::ClipEngineHelper::GetTextFeature 获取该字符串的特征向量,将特征向量转换为 ArkTs数组并返回。
之后通过napi_module_register函数注册C++模块
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
napi_property_descriptor desc[] = {
{"initialize", nullptr, Initialize, nullptr, nullptr, nullptr, napi_default, nullptr},
{"getTextFeature", nullptr, GetTextFeature, nullptr, nullptr, nullptr, napi_default, nullptr}
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END
static napi_module clipNativeModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "clipnative",
.nm_priv = ((void*)0),
.reserved = { 0 },
};
extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
napi_module_register(&clipNativeModule);
}
(未完待续)
更多推荐
所有评论(0)