ChatGPT 私房菜:翻译内容,不翻译格式,OpenAI Api 翻译电子书详细教程
如何优雅地翻译一整本电子书
前言
OpenAI 接口上下文,根据不同的模型,是由长度限制的。而一本电子书,轻轻松松几十K,上百K。所以,api 是无法一次处理完成的。
对于普通的文本,纯内容,没有格式,处理起来不需要额外的技术手段。本文使用一本斯洛文尼亚语的《欧几里得平面几何》的 LaTex 文件,将其翻译为英文。
处理流程
基于上下文长度限制,需要对原始文件内容分块处理,每一块大约一页大小。
1,读取数据
使用以下Python代码:
1
2
3
4
5
6
7
8
|
import openai
from transformers import GPT2Tokenizer
# 计算token数量
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
with open("data/geometry_slovenian.tex", "r") as f:
text = f.read()
|
2,根据换行分块并计算token数量
上一段代码,把整个文件以只读方式,读取到 text 变量内。使用\n\n
分隔符对原始文本切割,并计算每一分块的token数量。
1
2
3
4
5
|
chunks = text.split('\n\n')
ntokens = []
for chunk in chunks:
ntokens.append(len(tokenizer.encode(chunk)))
max(ntokens)
|
之所以选用两个换行符作为分割符,是根据文本内容确定的、比较好的方案。翻译文案,使用 text-davinci-002
模型,上下文是 4K token。
下面实现分块的逻辑,每一块最多 1000 个token。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
def group_chunks(chunks, ntokens, max_len=1000, hard_max_len=3000):
"""
Group very short chunks, to form approximately page long chunks.
"""
batches = []
cur_batch = ""
cur_tokens = 0
# iterate over chunks, and group the short ones together
for chunk, ntoken in zip(chunks, ntokens):
# discard chunks that exceed hard max length
if ntoken > hard_max_len:
print(f"Warning: Chunk discarded for being too long ({ntoken} tokens > {hard_max_len} token limit). Preview: '{chunk[:50]}...'")
continue
# if room in current batch, add new chunk
if cur_tokens + 1 + ntoken <= max_len:
cur_batch += "\n\n" + chunk
cur_tokens += 1 + ntoken # adds 1 token for the two newlines
# otherwise, record the batch and start a new one
else:
batches.append(cur_batch)
cur_batch = chunk
cur_tokens = ntoken
if cur_batch: # add the last batch if it's not empty
batches.append(cur_batch)
return batches
chunks = group_chunks(chunks, ntokens)
len(chunks)
|
上述代码中,group_chunks 函数内,先把接收到的区块数据,与每一块所对应的 token 数组合并为新的列表——zip(chunks, ntokens)
,结果类似于:
1
|
[('Alice', 85), ('Bob', 92), ('Charlie', 78)]
|
如果,区块长度, 如 Alice
,长度大于 hard_max_len
,此处是 3000,直接废弃。太长了无法解析,需要手动先调整一下原始文件,能多加个换行就多加一个换行。
依次遍历,汇总几个区块字符串,比如 Alice Bob Charlie
,连起来总长度没有超过 max_len
1000,就视为一个块,放到 batches
数组内。
如果再加一个区块字符串,超过 1000 了,就放入下一个 batches
。
3,提示词
根据ChatGPT的工作原理,要结合上下文,给ChatGPT合适的提示词,还有简单示例。限定要求包括:
- 只翻译内容,不要翻译LaTex的格式化标签
- 给ChatGPT举一些简单的例子
编写提示词,下面是一个样例:
1
2
3
4
5
6
7
8
|
Translate only the text from the following LaTeX document into English. Leave all LaTeX commands unchanged
"""
\poglavje{Osnove Geometrije} \label{osn9Geom}"
\item Naj bodo $P$, $Q$ in $R$ notranje točke stranic trikotnika
"""
\poglavje{The basics of Geometry} \label{osn9Geom}
|
把提示词内变动的部分,提取出,作为函数的参数。下面是示例Python代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
def translate_chunk(chunk, engine='text-davinci-002', dest_language='English'):
prompt = f'''Translate only the text from the following LaTeX document into {dest_language}. Leave all LaTeX commands unchanged
"""
\poglavje{Osnove Geometrije} \label{osn9Geom}
{chunk}"""
\poglavje{The basics of Geometry} \label{osn9Geom}
'''
response = openai.Completion.create(
prompt=prompt,
engine=engine,
temperature=0,
top_p=1,
max_tokens=1500,
)
result = response['choices'][0]['text'].strip()
return result.replace('"""', '')
|
方法准备就绪,就可以对分块之后的数据循环处理了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
dest_language = "English"
translated_chunks = []
for i, chunk in enumerate(chunks):
print(str(i+1) + " / " + str(len(chunks)))
# translate each chunk
translated_chunks.append(translate_chunk(chunk, engine='text-davinci-002', dest_language=dest_language))
# join the chunks together
result = '\n\n'.join(translated_chunks)
# save the final result
with open(f"data/geometry_{dest_language}.tex", "w") as f:
f.write(result)
|
这一段示例代码,只是展示功能用的。所以简单地使用了 for...in
循环,而且每次处理成功的内容,临时存储在 translated_chunks
变量内,作为运行脚本,这是不完善的。你要加一些容错处理,让程序更健壮。
- 如果失败,记录区块的索引,以便之后单独处理;
- 每个区块翻译成功后,立即写入目标文件,防止丢失;
- OpenAI api token 是收费的,所以尽量节省。
最后
根据区块的多少,电子文档的长短,执行一整本数的翻译,同步阻塞处理,往往需要很长时间。你可以使用上一篇文章提供的批处理思路,对上述代码进行改进。
我是@程序员小助手,专注编程知识,圈子动态的IT领域原创作者。