Abstract: 主要采用nllb-600M的蒸馏模型进行部署。目的是为了在无网络链接的时候也能进行翻译,准确度肯定是不如在线的ai翻译,只能说辅助作用,过年期间或者什么时候需要大量翻译内容,节约流量;根据结果去人工修改校验就可以了(反正AI翻译的结果也需要校验)。
一.模型选择
Helsinki-NLP
一开始我找到的是Helsinki-NLP/opus-mt-en-zh和Helsinki-NLP/opus-mt-zh-en。看名字也知道,是两个模型,进行中文和英文的翻译,毕竟这也是我的主要目的。这个模型的话,特点是轻量级,很小的内容,翻译的速度很快。
zh-en模型的大小如下, en-zh也差不多:
opus-mt-zh-en (master) $ ls -lh
total 1.2G
-rw-r--r-- 1 uid03619 1049089 3.3K Jan 5 13:34 README.md
-rw-r--r-- 1 uid03619 1049089 1.5K Jan 5 13:34 config.json
-rw-r--r-- 1 uid03619 1049089 69 Jan 5 13:34 configuration.json
-rw-r--r-- 1 uid03619 1049089 309 Jan 5 13:34 generation_config.json
-rw-r--r-- 1 uid03619 1049089 1.5K Jan 5 13:34 metadata.json
-rw-r--r-- 1 uid03619 1049089 298M Jan 5 13:39 pytorch_model.bin
-rw-r--r-- 1 uid03619 1049089 552M Jan 5 13:39 rust_model.ot
-rw-r--r-- 1 uid03619 1049089 786K Jan 5 13:34 source.spm
-rw-r--r-- 1 uid03619 1049089 788K Jan 5 13:34 target.spm
-rw-r--r-- 1 uid03619 1049089 299M Jan 5 13:39 tf_model.h5
-rw-r--r-- 1 uid03619 1049089 44 Jan 5 13:34 tokenizer_config.json
-rw-r--r-- 1 uid03619 1049089 1.6M Jan 5 13:34 vocab.json
也就1.2G。
这个模型的翻译质量有点差,需要人工校验的部分过多,所以我就没有使用。我是翻译了一个定语从句大致如下
" sth that do sth through sth "
这个模型直接翻译成(断句):
" 啥, 做啥, 通过啥 "
我无法接受!
NLLB-200-DISTILLED-600M
这个模型的介绍我就不说了,它翻译更准确,语言也更多(200种)。我没有采用py代码的方式下载,它下载的默认地址在外网,访问不方便而且速度很慢,所以我是先把模型下载到本地工作目录的:
git clone https://gitee.com/hf-models/nllb-200-distilled-600M.git
在当前目录下就会有nllb-200-distilled-600M/的文件夹,里面就是要调用的模型文件。
二.安装NLLB
环境配置
由于是模型,所以调用的话,要下载依赖包,pytorch与transformer。
python -m pip install torch torchvision torchaudio
python -m pip install transformers
直接装的cpu版本,带出门用的,是轻薄本,本身是没有nvidia显卡加速的。mac或者linux记得指令用python3
测试模型
在当前目录下有模型的文件夹,创建一个nllb-tanslate.py的测试脚本来检测是否可以正常使用
$ ls -lrth
drwxr-xr-x 1 uid03619 1049089 0 Jan 5 10:55 nllb-200-distilled-600M/
-rw-r--r-- 1 uid03619 1049089 865 Jan 5 11:33 nllb-tanslate.py
脚本内容如下:
from transformers import pipeline
model_path = "./nllb-200-distilled-600M"
translator = pipeline(
"translation",
model=model_path,
device=-1
)
print("✓ 本地模型加载成功!")
def translate(text, src_lang="zho_Hans", tgt_lang="eng_Latn"):
"""
NLLB 模型必须使用语言代码,例如:
- 中文: zho_Hans
- 英文: eng_Latn
- 日文: jpn_Jpan
- 法文: fra_Latn
"""
result = translator(
text,
src_lang=src_lang,
tgt_lang=tgt_lang,
max_length=200
)
return result[0]['translation_text']
print("测试翻译...")
try:
result = translate("你好,世界!", src_lang="zho_Hans", tgt_lang="eng_Latn")
print(f"翻译结果: {result}")
except Exception as e:
print(f"错误: {e}")
运行脚本,输出如下:
$ python .\nllb-tanslate.py
Device set to use cpu
✓ 本地模型加载成功!
测试翻译...
翻译结果: Hello, the world!
我忘了,在运行的时候可能会有一个错误,是关于numpy版本的,目前我这个numpy版本是1.23.5,原来是1.23.0会报错。
三.GUI设定
我直接让AI写了份代码,自己做了点debug,可以直接运行,运行前记得python -m pip install tkinter
import tkinter as tk
from tkinter import ttk, scrolledtext
import threading
import queue
import time
from transformers import pipeline
class TranslationApp:
def __init__(self):
self.model_path = "./nllb-200-distilled-600M"
self.translator = None
self.translation_queue = queue.Queue()
self.result_queue = queue.Queue()
self.is_loaded = False
# 创建界面
self.create_gui()
# 在后台线程中加载模型
self.start_model_loading()
# 启动处理队列的循环
self.root.after(100, self.process_queue)
def create_gui(self):
"""创建图形界面"""
self.root = tk.Tk()
self.root.title("NLLB翻译器")
self.root.geometry("600x500")
self.root.bind('<Escape>', lambda e: self.quit_app())
# 设置窗口图标(可选)
try:
self.root.iconbitmap(default='icon.ico')
except:
pass
# 使窗口最小化到系统托盘
self.root.protocol('WM_DELETE_WINDOW', self.minimize_to_tray)
# 创建菜单
self.create_menu()
# 创建主框架
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 状态标签
self.status_label = ttk.Label(
main_frame,
text="⏳ 正在加载翻译模型...",
foreground="orange"
)
self.status_label.grid(row=0, column=0, columnspan=2, pady=(0, 10))
# 源语言选择
ttk.Label(main_frame, text="源语言:").grid(row=1, column=0, sticky=tk.W)
self.src_lang = ttk.Combobox(
main_frame,
values=[
("中文", "zho_Hans"),
("英文", "eng_Latn"),
("日文", "jpn_Jpan"),
("法文", "fra_Latn"),
("德文", "deu_Latn"),
("韩文", "kor_Hang")
],
width=15,
state="readonly"
)
self.src_lang.grid(row=1, column=1, sticky=tk.W, pady=5)
self.src_lang.set("英文 eng_Latn")
# 目标语言选择
ttk.Label(main_frame, text="目标语言:").grid(row=2, column=0, sticky=tk.W)
self.tgt_lang = ttk.Combobox(
main_frame,
values=[
("英文", "eng_Latn"),
("中文", "zho_Hans"),
("日文", "jpn_Jpan"),
("法文", "fra_Latn"),
("德文", "deu_Latn"),
("韩文", "kor_Hang")
],
width=15,
state="readonly"
)
self.tgt_lang.grid(row=2, column=1, sticky=tk.W, pady=5)
self.tgt_lang.set("中文 zho_Hans")
# 输入文本框
ttk.Label(main_frame, text="输入文本:").grid(row=3, column=0, sticky=tk.NW, pady=(10, 0))
self.input_text = scrolledtext.ScrolledText(
main_frame,
width=50,
height=8,
wrap=tk.WORD
)
self.input_text.grid(row=4, column=0, columnspan=2, pady=(0, 10))
# 翻译按钮
self.translate_btn = ttk.Button(
main_frame,
text="翻译",
command=self.start_translation,
state="disabled"
)
self.translate_btn.grid(row=5, column=0, columnspan=2, pady=(0, 10))
# 输出标签
ttk.Label(main_frame, text="翻译结果:").grid(row=6, column=0, sticky=tk.NW)
self.output_text = scrolledtext.ScrolledText(
main_frame,
width=50,
height=8,
wrap=tk.WORD,
state="normal"
)
self.output_text.grid(row=7, column=0, columnspan=2, pady=(0, 10))
# 配置网格权重
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
# 绑定Ctrl+Enter快捷键翻译
self.root.bind('<Control-Return>', lambda e: self.start_translation())
def create_menu(self):
"""创建系统托盘菜单"""
self.menu_bar = tk.Menu(self.root)
# 文件菜单
file_menu = tk.Menu(self.menu_bar, tearoff=0)
file_menu.add_command(label="显示窗口", command=self.show_window)
file_menu.add_separator()
file_menu.add_command(label="退出", command=self.quit_app)
self.menu_bar.add_cascade(label="文件", menu=file_menu)
# 语言菜单
lang_menu = tk.Menu(self.menu_bar, tearoff=0)
lang_menu.add_command(label="中 → 英", command=lambda: self.set_languages("zho_Hans", "eng_Latn"))
lang_menu.add_command(label="英 → 中", command=lambda: self.set_languages("eng_Latn", "zho_Hans"))
lang_menu.add_command(label="中 → 日", command=lambda: self.set_languages("zho_Hans", "jpn_Jpan"))
lang_menu.add_command(label="日 → 中", command=lambda: self.set_languages("jpn_Jpan", "zho_Hans"))
self.menu_bar.add_cascade(label="快速设置", menu=lang_menu)
self.root.config(menu=self.menu_bar)
def set_languages(self, src, tgt):
"""设置语言对"""
lang_map = {
"zho_Hans": "中文",
"eng_Latn": "英文",
"jpn_Jpan": "日文",
"fra_Latn": "法文"
}
for i, (name, code) in enumerate(self.src_lang['values']):
if code == src:
self.src_lang.current(i)
break
for i, (name, code) in enumerate(self.tgt_lang['values']):
if code == tgt:
self.tgt_lang.current(i)
break
self.show_window()
def start_model_loading(self):
"""在后台线程中加载模型"""
def load_model():
try:
self.translator = pipeline(
"translation",
model=self.model_path,
device=0
)
self.is_loaded = True
self.result_queue.put(("status", "✅ 模型加载成功!可以开始翻译"))
self.result_queue.put(("enable_button", True))
except Exception as e:
self.result_queue.put(("status", f"❌ 模型加载失败: {str(e)}"))
self.result_queue.put(("enable_button", False))
thread = threading.Thread(target=load_model, daemon=True)
thread.start()
def start_translation(self):
"""开始翻译(在后台线程中执行)"""
if not self.is_loaded:
self.output_text.delete(1.0, tk.END)
self.output_text.insert(tk.END, "模型尚未加载完成,请稍候...")
return
text = self.input_text.get(1.0, tk.END).strip()
if not text:
return
# 获取语言代码
src_code = self.src_lang.get().split(" ")[-1] if " " in self.src_lang.get() else self.get_lang_code(self.src_lang.get())
tgt_code = self.tgt_lang.get().split(" ")[-1] if " " in self.tgt_lang.get() else self.get_lang_code(self.tgt_lang.get())
# 禁用按钮,显示加载状态
self.translate_btn.config(state="disabled", text="翻译中...")
self.output_text.delete(1.0, tk.END)
self.output_text.insert(tk.END, "正在翻译...")
# 将翻译任务放入队列
self.translation_queue.put((text, src_code, tgt_code))
def get_lang_code(self, lang_name):
"""从语言名称获取代码"""
lang_map = {
"中文": "zho_Hans",
"英文": "eng_Latn",
"日文": "jpn_Jpan",
"法文": "fra_Latn",
"德文": "deu_Latn",
"韩文": "kor_Hang"
}
return lang_map.get(lang_name, "eng_Latn")
def process_translation(self, text, src_lang, tgt_lang):
"""在后台线程中执行翻译"""
try:
result = self.translator(
text,
src_lang=src_lang,
tgt_lang=tgt_lang,
max_length=200
)
self.result_queue.put(("result", result[0]['translation_text']))
except Exception as e:
self.result_queue.put(("error", f"翻译错误: {str(e)}"))
def process_queue(self):
"""处理队列中的结果"""
# 启动翻译线程
while not self.translation_queue.empty():
text, src, tgt = self.translation_queue.get()
thread = threading.Thread(
target=self.process_translation,
args=(text, src, tgt),
daemon=True
)
thread.start()
# 处理结果
try:
while True:
msg_type, content = self.result_queue.get_nowait()
if msg_type == "status":
self.status_label.config(text=content, foreground="green")
elif msg_type == "enable_button":
self.translate_btn.config(state="normal" if content else "disabled")
if content:
self.status_label.config(text="✅ 就绪", foreground="green")
elif msg_type == "result":
self.output_text.delete(1.0, tk.END)
self.output_text.insert(tk.END, content)
self.translate_btn.config(state="normal", text="翻译")
elif msg_type == "error":
self.output_text.delete(1.0, tk.END)
self.output_text.insert(tk.END, content)
self.translate_btn.config(state="normal", text="翻译")
except queue.Empty:
pass
# 每100毫秒检查一次队列
self.root.after(100, self.process_queue)
def minimize_to_tray(self):
"""最小化到系统托盘"""
self.root.withdraw() # 隐藏窗口
def show_window(self):
"""显示窗口"""
self.root.deiconify() # 显示窗口
self.root.lift() # 提到最前面
def quit_app(self):
"""退出应用"""
self.root.quit()
self.root.destroy()
def run(self):
"""运行应用"""
self.root.mainloop()
def main():
app = TranslationApp()
app.run()
if __name__ == "__main__":
main()
四.模型加速
NLLB这个模型的翻译速度实在难以恭维,所以转换了一下,进行加速。直接走流程了:
python -m pip install ctranslate2
编写脚本,进行模型转换:
from transformers import AutoModelForSeq2SeqLM
import ctranslate2
def convert_nllb_to_ct2():
"""将 NLLB 模型转换为 CTranslate2 格式"""
converter = ctranslate2.converters.TransformersConverter("./nllb-200-distilled-600M")
converter.convert("./nllb-ct2", quantization="int8")
if __name__ == "__main__":
convert_nllb_to_ct2()
五.修改GUI
主要是将加速后的模型部署到gui上
修改这几个函数还有头文件即可:
from transformers import AutoTokenizer
import ctranslate2
def __init__(self):
self.model_path = "./nllb-ct2" # 改为你的ct2模型路径
self.translator = None
self.tokenizer = None
self.translation_queue = queue.Queue()
self.result_queue = queue.Queue()
self.is_loaded = False
# 创建界面
self.create_gui()
# 在后台线程中加载模型
self.start_model_loading()
# 启动处理队列的循环
self.root.after(100, self.process_queue)
def start_model_loading(self):
"""在后台线程中加载模型"""
def load_model():
try:
# 加载分词器
self.tokenizer = AutoTokenizer.from_pretrained(
"./nllb-200-distilled-600M"
)
# 加载CTranslate2模型
self.translator = ctranslate2.Translator(
self.model_path,
device="cpu",
compute_type="int8"
)
self.is_loaded = True
self.result_queue.put(("status", "✅ CT2模型加载成功!"))
self.result_queue.put(("enable_button", True))
except Exception as e:
self.result_queue.put(("status", f"❌ 模型加载失败: {str(e)}"))
self.result_queue.put(("enable_button", False))
thread = threading.Thread(target=load_model, daemon=True)
thread.start()
def process_translation(self, text, src_lang, tgt_lang):
"""在后台线程中执行翻译"""
try:
# 设置源语言
self.tokenizer.src_lang = src_lang
# 分词
source = self.tokenizer.convert_ids_to_tokens(
self.tokenizer.encode(text)
)
# 使用CT2翻译
results = self.translator.translate_batch(
[source],
target_prefix=[[tgt_lang]],
beam_size=2,
max_decoding_length=200
)
# 解码
target = results[0].hypotheses[0][1:] # 跳过语言标记
translation = self.tokenizer.decode(
self.tokenizer.convert_tokens_to_ids(target)
)
self.result_queue.put(("result", translation))
except Exception as e:
self.result_queue.put(("error", f"翻译错误: {str(e)}"))
然后就可以用了。
Last modified on 2026-01-05