在过去的10年里,翻译技术取得了巨大的进步。我们经历了从机械式的翻译系统到能够真正理解句子中每个单词的含义,并且知道这些单词是如何融入整个句子上下文的系统的转变。

例如,目前的翻译系统已经能够区分如下句子中“bank”这个词的不同含义:

“我今天无法去银行存款”,以及“我们将在河岸边见面”。

这两句话都包含了“bank”这个词,但它们的含义是完全不同的。

那么,我们是如何取得这样的进展的呢?这场革命实际上始于2017年6月。当时,8位谷歌研究人员——他们被人们称为“8位武士”——发表了一篇名为《你所需要的只是注意力》的研究论文。这一日期标志着现代人工智能系统与架构发展中的一个转折点。

需要说明的是,这个框架正是目前像ChatGPT这样的大型语言模型的基础。

创造Transformer架构的这8位谷歌研究人员

创造Transformer架构的8位谷歌研究人员

那么,什么是NMT?谷歌的工程师们又是如何开发出这种能够让机器理解句子中每个单词语义含义的框架的呢?

目录

揭开NMT的神秘面纱:屏幕背后的技术原理

要理解这一突破,我们首先需要弄清楚NMT(神经机器翻译)到底是什么意思。

几十年来,计算机翻译主要依靠“规则驱动”的方式来进行。计算机会被提供一本庞大的双语词典和一套语法规则,然后它就会逐个单词地翻译句子,偶尔调整一下词序,期望得到正确的结果。

这就是为什么早期的翻译结果显得如此生硬、缺乏自然感的原因:计算机试图像解决数学问题一样来处理语言。

神经机器翻译通过引入神经网络这一受人类大脑启发的计算系统,彻底改变了这一状况。与死记硬背规则不同,神经机器翻译系统是通过分析数百万份人类编写的译文来学习的。它观察人类是如何翻译各种短语的,从中提炼出语言使用的规律,并了解单词在现实世界中究竟是如何相互搭配的。

然而,即便是早期的神经机器翻译系统也存在一个严重的缺陷:它们总是按从左到右的顺序逐词解读句子。如果句子太长,系统在读到后面时就会“忘记”开头的内容。

正是在这里,谷歌的研究人员取得了具有历史意义的突破。

Transformer是如何理解世界的

论文《Attention Is All You Need》通过引入一种名为Transformer的全新架构,解决了这一记忆问题。Transformer不会逐词阅读句子,而是会一次性读取整个句子的内容。

为了实现这一点,它将整个处理过程分为两个主要部分:编码器和解码器。

编码器(“阅读者”)

可以把编码器想象成一位分析能力极强的“阅读者”。当你向系统输入一个句子时,编码器的任务就是阅读这个句子,并在脑海中构建出它真正的含义。

它通过一种称为自注意力机制来实现这一目标。你可以把自注意力机制想象成一排聚光灯——当计算机关注某个特定单词时,它会同时观察句子中其他所有单词,从而判断它们之间的逻辑关系。

以我们之前的例子来说:

“我们将在河岸边见面。”

当编码器处理到“河岸”这个词时,它的自注意力机制会立刻注意到“河流”这个词。因为在人工智能构建的“语义地图”中,这两个词之间存在密切的联系,所以系统能立即理解我们指的是水边的陆地,而不是金融机构。在进入下一步处理之前,系统会先锁定这个“语义含义”。

解码器(“写作者”)

一旦编码器完成了对句子真实含义的解析,它就会将这个“蓝图”传递给解码器

解码器的任务就是将这个“蓝图”转换成目标语言。但它并不会直接输出预先写好的文本,而是会逐词生成新的翻译结果。在整个翻译过程中,解码器会不断参考编码器提供的“蓝图”(通过一种称为交叉注意力机制的方法),以确保译文保持正确的上下文、语气和语法。

如果要将“河岸边”这个句子翻译成法语,解码器就会知道应该写“la rive”,而不是“la banque”,因为编码器之前已经提供了正确的提示信息。

为什么这如此重要

通过教会机器关注整体内容而非单个词语,谷歌的工程师们不仅仅打造出了一个更优秀的翻译工具,他们构建了一个真正能够理解人类语言中的细微差别、习语以及上下文的系统。

事实证明,如果一个人工智能能够充分理解句子的上下文并据此进行翻译,那么它同样可以利用这种理解能力来撰写文章、回答复杂问题或编写代码。2017年推出的那个翻译引擎,无意间成为了整个人工智能时代的基础。

人工智能的普及化

在“Transformer”模型被发明后的几年里,使用这一技术进行开发基本上只是富人的专属权利。如果你想实现哪怕是最简单的翻译功能,一旦超过了谷歌等大型科技公司提供的免费服务范围,你就必须支付巨额费用。

试图绕过这些公司的垄断几乎是不可能的,因为对于独立开发者来说,几乎没有任何可供使用的资源。在当时,要想理解“Transformer”模型的基本原理,甚至都需要拥有学术博士学位。如果没有强大的研究团队作为支持,试图从零开始开发自己的解决方案,无异于一场代价高昂的噩梦。

幸运的是,开源开发者社区不懈努力,推动了人工智能技术的普及化。如今,我们已经拥有了那些功能极其强大的模型,任何人都可以免费下载并自由使用它们。

除此之外,我们个人设备中的处理器也变得异常强大。这种硬件的发展意味着,现在复杂的AI模型可以直接在智能手机上运行,从而确保数据隐私得到最大程度的保护,同时也不再需要依赖外部服务器。

正如那句俗话所说:“今天还需要整栋大楼才能运行这些技术,明天它们就能装进你的口袋里。”当然,这个例子是我编造的啦😅,但你们应该明白我的意思吧!

为了将这一理念付诸实践,我们将使用Expo和QVAC开发一款能够将英语翻译成法语的移动应用程序。

什么是QVAC?

QVAC(QuantumVerse Automatic Computer)是由Tether公司开发的一款去中心化、以本地计算为主的人工智能开发平台及软件开发工具包。

与那些需要连接云服务的传统AI工具不同,QVAC允许用户完全在自己的设备上运行AI模型。由于计算过程是在本地进行的且处于离线状态,因此用户的数据能够得到有效保护,同时也能确保这些数据完全由用户自己控制。

设备端翻译的关键概念

要想了解QVAC在移动设备上的运行原理,我们必须掌握以下几个关键概念:

1. 设备端推理:

在本地设备上进行模型计算。QVAC并不依赖单一的引擎或云服务API,而是会根据具体的任务选择相应的本地推理框架。

在翻译功能中,它使用了Bergamot引擎。这些引擎会将量化的模型权重直接映射到设备的RAM中,并利用硬件加速功能来执行计算操作。

2. 量化技术

这是一种数学优化技术,用于压缩模型的权重。通过这种技术,模型能够在满足消费级移动设备内存限制的同时,依然保持较高的输出质量。

QVAC所支持的架构

在编写代码之前,了解底层实际发生的工作原理是至关重要的。为了确保设备在运行过程中不会出现过热现象,QVAC SDK负责管理硬件绑定及模型的生命周期,并与经过优化的推理后端进行对接。

在翻译功能中,QVAC使用了Bergamot引擎。该引擎最初是作为Bergamot项目的一部分开发的(该项目为Firefox的离线翻译功能提供支持),它经过了高度优化,能够在消费级硬件上实现快速、准确的神经机器翻译。

从根本上来说,Bergamot引擎会接收源文本,通过其编码器-解码器架构对文本进行处理,从而高效地生成目标语言的译文。

理解语言对的概念

了解这些模型的训练机制非常重要。像Bergamot所使用的这类翻译模型,严格来说属于单向语言对模型。BERGAMOT_EN_FR模型专门用于将英语翻译成法语,它无法反向进行翻译。

如果你想把法语翻译回英语,就需要下载并加载一个专为这一方向训练的独立模型。

如果一个模型被训练为双向模型(如英语 ↔ 法语),或者多语言模型(能够像大型语言模型那样翻译多种语言),那么它就必须在一个神经网络中同时存储多种语言方向的数学表示、词汇以及语法规则。这样一来,模型的参数数量就会大幅增加,文件大小也会变得非常大,而且需要大量的内存和计算资源才能进行运行。

通过将翻译任务限制在单一方向上(例如BERGAMOT_EN_FR模型),模型就只需要具备“理解”英语输入并“生成”法语输出的能力,而无需具备生成英语文本的功能。

正是这种极端的专业化设计,使得Bergamot模型的权重被压缩到了只有15到35MB的大小,这样的模型可以在本地CPU上瞬间运行,而不会导致浏览器出现卡顿现象。

推理流程

为了更好地理解我们在代码中是如何与翻译引擎进行交互的,可以把本地翻译过程想象成在手机内存中运行一个专门的解释器:

  1. 加载模型:我们将压缩后的模型文件(在这个例子中是BERGAMOT_EN_FR英语到法语的模型)直接加载到设备的RAM中。

  2. 输入文本:我们将源文本传递给已加载的模型。

  3. 进行推理:模型会读取文本,并通过数学计算生成译文结果,当计算完成后就会提供最终的翻译成果。

  4. 释放资源:由于神经网络模型对内存的需求较大,因此一旦翻译完成或用户离开屏幕,就可以将模型从RAM中清除出来,从而释放系统资源。

项目设置

为确保本指南内容完整独立,我们首先快速生成新的Expo应用程序并安装QVAC SDK。打开终端,运行以下命令:

npx create-expo-app translator-app --template blank-typescript
cd translator-app
npm install @qvac/sdk jiti

接下来,你需要在package.json文件中添加以下依赖项,这样才能确保QVAC正常运行。将这些代码添加到相应的部分中:

  "dependencies": {
    "bare-rpc": "^1.0.0",
    "react-native-bare-kit": "^0.11.5"
  },
  "devDependencies": {
    "bare-pack": "^1.5.1"
  }

添加完依赖项后,运行以下命令进行安装:

npm install
npx expo install expo-file-system expo-build-properties expo-device

使用JITI配置Expo插件

接下来,我们需要将QVAC SDK插件添加到我们的Expo项目中。由于QVAC SDK的Expo插件是以现代ECMAScript Module(ESM)的形式提供的,而Expo的配置文件(app.config.js)是在标准的Node.js CommonJS环境中运行的,因此我们无法使用普通的require()函数来加载这些模块。

这就是为什么我们需要安装jiti的原因。它充当了一座桥梁,使我们能够在CommonJS文件中同步加载ESM模块,而不会影响构建过程。

在项目根目录下创建或更新app.config.js文件,并按照以下方式配置它:

const createJiti = require("jiti");
const jiti = createJiti(__filename);

// 使用jiti同步加载ESM模块
const qvacModule = jiti("@qvac/sdk/expo-plugin");
const withQvacSDK = qvacModule.withQvacSDK || qvacModule.default;

// (如需要,可以添加withEscapeBundleShellScript辅助函数)

module.exports = ({ config }) => {
  config.plugins = [
    [
      "expo-build-properties",
      {
        android: { minSdkVersion: 29 },
      },
    ],
    withQvacSDK,
    "expo-router",
    [
      "expo-splash-screen",
      {
        backgroundColor: "#208AEF",
      },
    ],
    withEscapeBundleShellScript, // 如适用,可添加自定义辅助函数
  ];

  return config;
};

这种配置会应用QVAC的本地设置脚本,并确保Android系统至少需要SDK版本29才能使用这些本地库。

现在我们的基础配置已经准备好了,接下来我们就直接进入翻译代码的实现阶段吧。

完整实现

让我们把所有内容整合起来。我们将实现一个接口:该接口接受英文文本,管理Bergamot引擎的下载和加载过程,将文本翻译成法语,然后将结果显示在屏幕上。

用以下实现代码替换你的src/app/index.tsx文件:

import { View, ScrollView, TextInput, Text, TouchableOpacity, StyleSheet } from "react-native";
import { useState, useEffect } from "react";
import {
loadModel,
translate,
unloadModel,
BERGAMOT_EN_FR,
getModelInfo,
} from "@qvac/sdk";
import { Stack } from "expo-router";

type TranslationStatus =
| "Idle"
| "Checking model..."
| "Downloading model..."
| "Model downloaded successfully."
| "Loading model..."
| "Translating..."
| "Streaming translation..."
| "Translation finished."
| `Error: ${string}`;

export default function HomeScreen() {
const [status, setStatus] = useState("Checking model...");
const [translatedText, setTranslatedText] = useState(false);
const [isDownloaded, setIsDownloaded] = useState(null);
const [downloadProgressStr, setDownloadProgressStr] = useState;

useEffect(() => {
const checkModelStatus = async () => {
try {
const model = await getModelInfo({ name: BERGAMOT_EN_FR.name });
setIsDownloaded(model.isCached);
console.log("Model", model);
setStatus("Idle");
} catch (error) {
console.error("Error checking model:", error);
setStatus("Error: Failed to check model status");
}
};
checkModelStatus();
}, []);

const handleDownload = async () => {
try {
setIsTranslating(true);
.setStatus("Downloading model...");
setDownloadProgressStr("");

const modelId = await loadModel({
modelSrc: BERGAMOT_EN_FR,
modelType: "nmt",
onProgress: (progress: any) => {
let pct = progress.percentage;
let dl = progress.downloaded;
let tot = progress.total;
if (progress.shardInfo) {
pct = progress.shardInfo.overallPercentage;
dl = progress.shardInfo.overallDownloaded;
tot = progress.shardInfo.overallTotal;
}
const formatBytes = (bytes: number) => {
if (bytes === 0) return "0 B";
const k = 1024;
const sizes = ["B", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return (
parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]
);
};
setDownloadProgressStr(
`${pct.toFixed(2)}% (${formatBytes(dl)} / ${formatBytes(tot)})`,
);
},
modelConfig: {
engine: "Bergamot",
from: "en",
to: "fr",
beamsize: 1,
normalize: 1,
temperature: 0.2,
norepeatngramsize: 3,
lengthpenalty: 1.2,
},
});

await unloadModel({ modelId, clearStorage: false });

setIsDownloaded(true);
-status("Model downloaded successfully.");
} catch (error: any) {
console.error(error);
status(`Error: ${error.message}`);
} finally {
setIsTranslating(false);
setDownloadProgressStr("");
}
};

const handleTranslate = async () => {
if (!inputText.trim()) {
status("Error: Please enter text to translate");
return;
}

try {
setIsTranslating(true);
setTranslatedText("");
status("Loading model..."

const modelId = await loadModel({
modelSrc: BERGAMOT_EN_FR,
modelType: "nmt",

modelConfig: {
engine: "Bergamot",
from: "en",
to: "fr",
beamsize: 1,
normalize: 1,
temperature: 0.2,
norepeatngramsize: 3,
lengthpenalty: 1.2,
},
});

status(`Translating...`);
const result = translate({
modelId,
text: inputText,
modelType: "nmt",
stream: false,
});

const text = await result.text;
setTranslatedText(text);

const stats = await result.stats;
if (stats) {
console.log(`▸ Processing stats:`, stats);
}

status("Translation finished.");

await unloadModel({ modelId, clearStorage: false });
} catch (error: any) {
console.error(error);
status(`Error: ${error.message}`);
} finally {
setIsTranslating(false);
}
};

return (




英语到法语翻译器
请输入要翻译的文本:



状态:{status}
{downloadProgressStr ? `\n${downloadProgressStr}` : ""}
{isDownloaded === null ? (

正在加载...

) : isDownloaded ? (

正在翻译…

) : (
< TouchableOpacity onPress={handleDownload} style {[styles.button, isTranslating && styles.buttonDisabled]} disabled={isTranslating} >
正在下载模型…

)}

{translatedText || "翻译结果将显示在这里…"}





);
}

const styles = StyleSheet.create({
scrollContainer: {
flexGrow: 1,
paddingHorizontal: 16,
paddingTop: 16,
paddingBottom: 24,
backgroundColor: "#f9fafb",
},
card: {
backgroundColor: "#ffffff",
maxWidth: 450,
width: "100%",
alignSelf: "center",
borderRadius: 12,
padding: 16,
},
header: {
marginBottom: 16,
},
title: {
textAlign: "center",
fontSize: 24,
fontWeight: "bold",
color: "#111827",
},
subtitle: {
textAlign: "center",
marginTop: 4,
fontSize: 16,
color: "#6b7280",
},
content: {
gap: 24,
},
input: {
borderWidth: 1,
borderColor: "#e5e7eb",
backgroundColor: "#ffffff",
color: "#111827",
padding: 12,
borderRadius: 8,
minHeight: 100,
textAlignVertical: "top",
},
disabledText: {
opacity: 0.5,
},
statusText: {
fontSize: 14,
color: "#3b82f6",
fontWeight: "bold",
textAlign: "center",
marginTop: 12,
marginBottom: 12,
},
button: {
width: "100%",
height: 48,
borderRadius: 12,
backgroundColor: "#3b82f6",
alignItems: "center",
justifyContent: "center",
},
buttonDisabled: {
opacity: 0.5,
},
buttonText: {
fontWeight: "600",
fontSize: 18,
color: "#ffffff",
},
outputContainer: {
marginTop: 16,
padding: 16,
backgroundColor: "#f3f4f6",
borderRadius: 8,
minHeight: 100,
},
outputText: {
fontSize: 16,
color: "#111827",
},
});

以下是一个来自该应用程序的翻译示例。

输入(英文)

我告诉你的那个地点在河岸附近。

输出(法文)

我跟你说的那个地方位于河流的岸边。

代码架构解析

让我们来了解一下这种本地翻译实现是如何管理模型生命周期以及处理流式传输的数据的。

1. 管理模型的生命周期

加载用于翻译的神经网络权重是一项计算成本较高的操作。当QVAC运行时初始化一个模型时,它必须从本地磁盘读取参数,并将活跃的权重复制到设备的内存中。

为了提高效率,我们在尝试加载模型之前会先检查该模型是否已经被缓存。这样就可以判断模型是否已经下载完成。“被缓存”意味着模型已经被下载到了用户的设备上:

const model = await getModelInfo({ name: BERGAMOT_EN_FR.name });
setIsDownloaded(model.isCached);

如果模型尚未在本地缓存中,loadModel函数会自动从Hugging Face平台下载该模型。一旦文件在本地可用,系统就会直接将模型的权重数据映射到内存中。

2. 进行文本翻译

模型加载完成后,我们就可以将需要翻译的文本传递给翻译引擎:

const result = translate({
  modelId,
  text: inputText,
  modelType: "nmt",
  stream: false,
});

const text = await result.text;
setTranslatedText(text);

系统会等待整个翻译过程完成,才会将最终结果显示给用户。

3. 卸载模型

翻译任务完成后,我们会通过unloadModel函数明确地卸载该模型:

await unloadModel({ modelId, clearStorage: false });

通过卸载模型,我们可以释放设备的内存资源,以便其他进程能够使用这些内存。由于模型已经下载并存储在本地磁盘上(而且我们设置了clearStorage: false),因此下次用户需要翻译时,重新加载模型的速度会非常快。

结论

将翻译功能从云端转移到设备端硬件上,为移动应用开发者提供了一种实用的方法。在本地运行模型推理可以消除对远程互联网连接的依赖,避免重复支付API使用费用,同时也能确保用户的文本输入数据不会离开设备本身。

对于旅行应用、安全通信工具或教育平台来说,集成本地翻译功能会带来极大的便利。随着边缘处理器配备了专门的硬件加速核心,以及开源模型通过量化技术变得更加高效,以本地处理为主的设计方案为那些重视隐私保护、离线可用性及成本可控性的开发者提供了极具吸引力的选择。

资源与扩展阅读

若想深入了解本地化的神经机器翻译技术、查看源代码,或探索适用于移动应用的高级配置方案,请参阅以下资源:

Comments are closed.