大数跨境
0
0

《国产AI崛起》:价值 100,000¥的项目,我让 AI 帮我做了

《国产AI崛起》:价值 100,000¥的项目,我让 AI 帮我做了 数翼
2023-12-14
1
导读:经历了一年的发展,国产 AI 已经慢慢地走向成熟。这个系列文章和大家探讨如何用国产AI产品来解决日常工作的具

经历了一年的发展,国产 AI 已经慢慢地走向成熟。这个系列文章和大家探讨如何用国产AI产品来解决日常工作的具体问题。让 LLM 不仅仅存在研究学习 角度,通过案例给大家日后大模型落地提供一些参考。

事件背景

事情的起因是最近销售那边忽然拉来了个日本客户, 并想以此打开日本市场,大家都很开心的准备国际化的相关事宜,然后就有个事情卡着了。

我们的资源包没有日文的版本,公司联系之前的渠道,资源文件给过去,因为时间比较紧词条比较多,按照人家的流程核算了一下, 竟然要 100,000 块(人民币),这个比我们之前别的小语种语言翻译贵了不止一倍。

PS: 英文翻译我们产品部门自己做的,繁体中文、韩语、泰文、阿拉伯文等等语言都是和我们合作的翻译工作室做的。

日文翻译这个事情合计来合计去,最后落回到了研发部头上。下面我跟大家聊聊我怎么让大模型帮我把这个活干了。

翻译的方案

方案一:机器翻译

提到翻译,我们首先想到的肯定是机器翻译,比如国内的百度翻译、有道翻译等,国外的谷歌翻译、DeepL翻译等。

国内外翻译服务厂商

但是自动的机器翻译有个问题,多义词经常翻译出错,这个在专业文档里面经常出现,经常会被翻译成统一词汇。

比如: Performance 这个单词,在技术文档里面一般是 性能 的意思, 但是在机器翻译没有上下文(比如作为标题)的时候常常被翻译成演出

而我们翻译软件的UI恰恰是很多没有上下文句子的单词或者短语。所以机器翻译不是很好的一个选择。

除了多义词,专业领域的词汇经常会有一些专业的叫法,或者是某个软件独有的叫法。这种情况也和通常的翻译不一样,设置中文都要跟人解释了大家才能明白,比如托盘踢单。这些也是机器翻译不能处理的。

ps: 当前机器翻译支不支持行业接口和词汇表等等设定,我还没调研过哈,暂且当不支持吧。

行业术语词汇表 对于行业术语,我们一般要人工准备一个术语表, 由专人整理列出,同时提供中英两种语言,可以相互对照,后续多语言的翻译以此为基础。

方案一:人工翻译

专业的事情交给专业的人,所以由专业人士翻译是最靠谱的,不过,也为专业人士翻译是最好的。

我们拿着这次翻译的数据给经常合作的供应商看了之后,三万左右的词语和句子要大概 10,000 块, 没错就是 10W 块,对方也觉得 10W 块翻译这点儿东西有点儿贵,但是人家的核算方法费用算下来就这么多。

在这个经济不咋好的年份,10W 搞个软件多语言,公司也不太想花这个钱。

方案三:大模型翻译

如果是之前我们可能就硬着头皮找翻译公司去翻译了。不过在 2023 年,大语言模型横空出世的年代,我们有了第三个选择:大语言模型。

前面我们说过,中文也有很多大模型,智谱、百川、百度等等。

我们还是先问问最熟悉的智谱AI,依然使用 ChatGLM-Turbo 这个模型:

ChatGLM-Turbo

看看他能不能胜任翻译这个工作。

测试日文能力

好的,它是懂日文的,这个就好说。时间原因,再说我也评估不出来 LLM 日文能力的好坏,就直接用智谱AI。

LLM 翻译方案

翻译的整体方案大概如下:

LLM 翻译流程

资源文件

数据格式

资源文件我这里用 csv 格式的数据,配合 Pandas 这个 Python 库的 DataFrame,能够方便的进行数据处理。

文件里面有中文、日文、英文三列,分别是 简体中文,English,日本語/にほんご

简体中文 English 日本語/にほんご
规则 rules ルール
参数 parameters パラメータ
质检 quarantine 品質チェック
任务号 mission number

没有翻译的地方日文这个位置为空。

Pandas 读取数据

import pandas as pd

df = pd.read_csv(csv_file_path)

Pandas 保存数据为 CSV 文件

import pandas as pd

df.to_csv(file_to_save, index=False)

Streamlit 显示 Pandas 数据

import streamlit as st

st.dataframe(st)

分批调用

文案文字数量大概是 10W 这个量级的,超出了常规的 Token 数量限制,调用 LLM 接口肯定不能一次翻译完成。就需要把资源文件拆分成一段段的进行翻译,智谱AI的 Token 限制可能是 4096(从UI上看到的,没有找到文档说明)。

每个批次的大小可以根据实际情况进行调整,比如 80 。

结果保存

结果我们也就保存为 CSV 文件,直接覆盖之前的文件,保证格式的统一。

使用大模型

接下来就是用大模型进行翻译。

提示工程

第一步当然是编写提示语,随着大模型越来越聪明,我们编写提示语也变得越来越简单。

零样本提示

你作为供应链行业日文翻译助手, 帮我进行供应链行业的专业术语从中文到日文的翻译。输入格式是 JSON 对象, 把 Json Key 翻译成日文,输出格式是对应翻译好的 JSON 格式。翻译如下字段:订单号 订单日期 订单状态 订单金额 订单商品 订单客户 订单收货人 订单支付方式 订单发货方式 订单备注 订单优惠 订单运费 订单税费 订单关税 订单退货 订单退款 订单评价

然后我们看下返回:

零样本提示测试

效果还真不赖。

LLM 的一些问题

我接连试了几次,还真好。不过当我用程序调用的时候,偶尔会出现奇怪的错误,比如Json格式不正确:

{
"订单号": "注文番号",
"订单日期": "注文日",
"订单状态": "注文状態",
"订单金额": "注文金额",
"订单商品": "注文商品",
"订单客户": "注文顧客",
]

注意后面这个字符,它是竟然是一个 ]

但是这个问题我在其网页版本的体验中心却没有重试出来这个问题。

无奈之下我给提示语加上一个:

你作为供应链行业日文翻译助手, 帮我进行供应链行业的专业术语从中文到日文的翻译。输入格式是 JSON 对象, 把 Json Key 翻译成日文,输出格式是对应翻译好的 JSON 格式。请认真校验并确保JSON格式的正确性。翻译如下字段:订单号 ...

过了一会儿,他又出现了一个别的问题,返回的数据大概是这样:

{
"订单号""注文番号",
"订单日期""注文日",
"订单状态""注文状態",
"订单金额""注文金额"
"订单商品""注文商品"
"订单客户""注文顧客"
]

不知道你们看出问题了没,刚开始每行末尾的逗号是英文半角 ,的,从某一行开始,他的逗号变成中文全角逗号  了, 我也不知道咋回事。反正就会这样。

关于一觉醒来模型变聪明了 对翻译这个事儿的输出,关于JSON 格式的问题,一晚上就变聪明了, 现在不管怎么问都是按照我期望的方式回答。直接让他返回JSON也是我需要的JSON格式, 可能是玄学吧,但我还是呼吁 大家多用用国产的模型,只有用的多了他才能变得更好

少样本提示

当然你也可以用少样本提示,主要原因是昨天我的零样本提示他根本不返回正确的数据。

比如还是那个提示语:

你作为供应链行业日文翻译助手, 帮我进行供应链行业的专业术语从中文到日文的翻译。输入格式是 JSON 对象, 把 Json Key 翻译成日文,输出格式是对应翻译好的 JSON 格式。翻译如下字段:订单号 订单日期 订单状态 订单金额 订单商品 订单客户 订单收货人 订单支付方式

他会给你这样的返回:

[
{"Chinese":"订单号", "Japanese":"注文番号"}, 
{"Chinese":"订单日期", "Japanese":"注文日"}, 
{"Chinese":"订单状态", "Japanese":"注文状態"}, 
{"Chinese":"订单金额, "Japanese":"注文金额"}
]

总之就是你也不能说他错,但是就主打一个随意,每次接错又不一样。

我当时的少样本提示大概是这样的,比较简单直接在 Prompt 里面写:

你作为供应链行业日文翻译助手,帮我进行供应链行业的专业术语从中文到日文的翻译。
输入格式是 JSON 对象,把 Json Key 翻译成日文,输出格式是对应翻译好的 JSON 格式。

当输入:
{
  "车号": "",
  "续重": "",
  "任务号": ""
}

返回:

{
  "车号": "車号",
  "续重": "続重",
  "任务号": "タスク番号"
}

请翻译一下内容。

也基本没啥问题,效果和之前一样。

使用多轮对话

上篇文章我们提过,chatglm_turbo 接口原生就支持多轮对话,我最后用多轮对话的方式来实现少样本提示:

prompt_template = [
    {"role""user",
     "content""""你作为供应链行业日文翻译助手,帮我进行供应链行业的专业术语从中文到日文的翻译。输入格式是 JSON 对象,把 Json Key 翻译成日文,输出格式是对应翻译好的 JSON 格式。
 请一定认真校验返回数据 JSON 格式的正确性。"""
},
    {"role""assistant""content""好的"},
    {"role""user""content""""{
  "车号": "",
  "续重": "",
  "任务号": ""
}"""
},
    {"role""assistant""content""""{
  "车号": "車号",
  "续重": "続重",
  "任务号": "タスク番号"
}"""
},
    {"role""user""content""""{
    "签收": "",
    "发货": "",
    "收货": "",
    "包裹": "",
    "重量": ""
}"""
},
    {"role""assistant""content""""{
    "签收": "署名を受け取る",
    "发货": "発送",
    "收货": "受け取り",
    "包裹": "小包",
    "重量": "重量"
}"""
},
]

Prompt 的问题我们就聊到这儿,整体来说 LLM 还是很不错的。

界面

让大模型帮我们干活,界面当然少不了,我们仍然使用 Streamlit 来做界面,又简单又快速又没关。

侧边栏

首先还是侧边栏,我们先在顶部放个图片当做应用的标题:

import streamlit as st

with st.sidebar:
    st.image('assets/logo-title.png', use_column_width=True)
    st.subheader('', divider='rainbow')
侧边栏标题

然后给个输入框可以自定义 API Key 如果没有就用系统环境变量的。以及一个支持多选的上传文件的按钮。

with st.sidebar:
    ...
    st.text_input('智谱API Key', key='api-key', type='password')
    uploaded_files = st.file_uploader("Choose CSV files to translate", accept_multiple_files=True)

效果如下:

侧边栏文件选择

选择文件后的效果:

侧边栏文件选择

当选择文件后,我们给出一个「翻译」按钮,用 if 判断只有当上传了文件才显示:

with st.sidebar:
    ...
    uploaded_files = st.file_uploader("Choose CSV files to translate", accept_multiple_files=True)
    if uploaded_files:
        st.button('Translate', use_container_width=Truetype="primary")
侧边栏和翻译按钮

显示上传文件数据

既然做 UI,那么尽量让数据可视,在主区域用多Tab的方式显示一下上传文件的数据。程序如下:

先定义变量。

# 上传文件的目录
upload_path = '/tmp/uploads/'
os.makedirs(upload_path, exist_ok=True)

# 多Tab缓存
st.session_state.data_containers = {}

然后在上传之后直接用多 Tab 数据表格把数据显示出来。

if uploaded_files:
    # 显示上传成功的提示
    with st.sidebar:
        st.info('Uploaded %s csv files!' % len(uploaded_files))
    # 使用聊天的UI样式呈现
    with st.chat_message("assistant"):
        tab_names = [f.name for f in uploaded_files]
        tabs = st.tabs(tab_names)
        for index, uploaded_file in enumerate(uploaded_files):
            # 获取文件内容
            bytes_data = uploaded_file.read()
            tempfile = os.path.join(upload_path, uploaded_file.name)
            # 保存文件
            with open(tempfile, 'wb'as f:
                f.write(bytes_data)
            df = pd.read_csv(tempfile)
            # 用表格显示文件内容
            with tabs[index]:
                empty = st.empty()
                empty.dataframe(df, use_container_width=True)
                st.session_state.data_containers[index] = empty

效果如下:

上传文件数据浏览

Token 调用的显示

翻译的过程中我还想看一看 Token 的使用情况,来大致估算下费用。在侧边栏增加几个 Metric 来试试更新 Token 的使用。

Token 使用指标展示

实现上面效果,我们先定义一个 ZhipuUsage 类,并创建一个对象:

class ZhipuUsage:
    prompt = 0
    completion = 0
    total = 0
    
# 创建 Usage 对象
zhipu_usage = ZhipuUsage()

在每次接口返回的时候更新下统计信息:

获取接口返回的用量
usage = response['data']['usage']

更新用量
zhipu_usage.prompt += usage['prompt_tokens']
zhipu_usage.completion += usage['completion_tokens']
zhipu_usage.total += usage['total_tokens']

然后在上传成功的提示下面增加一个容器,并调用更新方法显示 Usage 用量:

    with st.sidebar:
        st.info('Uploaded %s csv files!' % len(uploaded_files))
        st.subheader('Token Usage')
        metrics1 = st.empty()
        update_metrics()

update_metrics 方法如下:

def update_metrics():
    col1, col2, col3 = metrics1.columns(3)
    col1.metric("Prompt", zhipu_usage.prompt)
    col2.metric("Completion", zhipu_usage.completion)
    col3.metric("Total", zhipu_usage.total)

同样的方法定义一个 翻译结果的展示类:

class MetricsTranslate:
    success = 0
    fail = 0
    total = 0

展示下翻译情况:

翻译结果指标展示

为什么显示翻译结果 显示翻译结果的 Metric 最主要的原因是 大模型的不确定性,有可能你给他100个单词,他只返回给90个。或者你给他的单词是  A,他给你翻译了  B,这些异常情况都是我在调用过程中遇到的, 我们需要对这些异常进行 兼容处理,记录错误,并进行异常数据重试。

翻译进行式

前面所有的准备工作都做好了,后面的翻译就好说了。

分批翻译

分批翻译的主要步骤如下:

  • • 依次读取所有文件

  • • 每个文件一行一行的读取

  • • 到达一个批次的时候调用 LLM 接口

  • • 解析接口数据

  • • 记录翻译结果

  • • 记录异常数据

  • • 更新翻译进度

  • • 文件读完的时候把剩余数据翻译一下

先定义一下翻译方法:

def translate_by_llm_and_save():
    prompt = make_prompt(batch_sentences)
    response = invoke_prompt(prompt)
    try:
        json_response = json.loads(response)
        return json_response
    except:
        return {}

然后就是实现翻译的流程,程序不复杂,但是代码缩进的层次多了点儿。

if translate_button:
    with st.chat_message("assistant"):
        with st.spinner("正在努力翻译中,别着急,马上就好 :lollipop: ... "):
            for index, uploaded_file in enumerate(uploaded_files):
                progress_text = "%s" % uploaded_file.name
                progress = st.progress(0, text=progress_text)
                df = st.session_state.data_frames[index]
                word_idx = {}
                batch_sentences = []
                TRANSLATE_BATCH_SIZE = 1
                for idx, row in df.iterrows():
                    progress.progress(idx / len(df.index), text=progress_text)
                    chinese = str(row[COL_CHINESE]).strip()
                    japanese = str(row[COL_JAPANESE]).strip()
                    if japanese == None or japanese == 'nan' or japanese == '':
                        word_idx[chinese] = idx
                        batch_sentences.append(chinese)
                        if len(word_idx.keys()) >= TRANSLATE_BATCH_SIZE:
                            translate_by_llm_and_save(df)
                            word_idx = {}
                            batch_sentences = []
                            st.session_state.data_containers[index].dataframe(
                                df,
                                use_container_width=True
                            )
                if len(word_idx.keys()) > 0:
                    translate_by_llm_and_save(df)
                progress.progress(1.0, text=progress_text)
            st.success('已经全部翻译完成!')
    st.balloons()

我大致讲一下逻辑,首先再翻译按钮点击的时候,提示一下正在翻译

翻译状态和提示

然后循环翻译文件,并显示翻译进度:

progress_text = "%s" % uploaded_file.name
progress = st.progress(0, text=progress_text)

并且在翻译过程中更新进度:

progress.progress(idx / len(df.index), text=progress_text)
翻译进度

每次翻译成功之后把翻译成功的数据更新回 UI。

st.session_state.data_containers[index].dataframe(
                                    df,
                                    use_container_width=True
                                )
更新翻译成功的数据回界面

因为翻译过程比较长,我们顺便记录一下当前正在翻译的数据,以及翻译失败的数据:

翻译过程中显示下翻译中的数据和翻译失败的数据

PS: 文章中的数据都是虚拟数据哦

最后给大家看看完整的效果:

视频预览

后记

这次翻译的工程还有一些东西文章中没写:

  • • 中文和英文加上字典一起作为 Prompt 会得到更好的效果

  • • 翻译之后还需要机器和人工校验,也可以再把日文结果翻译成中文和英文进行二次校验

  • • 实际翻译耗时还是挺久的,每次调用可能都在半分钟以上(瞎估计的,但肯定没有视频中那么快),可能换成 SSR 调用会好,

  • • 另外大规模翻译还是比较耗 Token 的,不过好在智谱送了不少 Token。

这次我没有用 OpenAI 的 API 做对比,直接用上面的方案当成生成使用的,国内的大模型现阶段可能和国外的还有一些差距, 但是鉴于大模型越用越好的特性,只要大家多多使用,我相信他们能很快满足我们的生产需求的。


【声明】内容源于网络
0
0
数翼
专注 AIGC 人工智能知识传播和实践
内容 228
粉丝 0
数翼 专注 AIGC 人工智能知识传播和实践
总阅读61
粉丝0
内容228