💬

機械翻訳APIコンテナの実装

2024/02/09に公開

動機

ローカルで動くLLMがほしいなと思って、いろいろと調べていました。
しかし、軽量のLLMは日本語が怪しくなるという話をよく聞きます

そこで、一度機械翻訳を間に入れて、
日本語指示 → 機械翻訳 → 英語指示 → LLM→英語結果 → 機械翻訳 → 日本語結果
としたいと思います。

(実際に、機械翻訳を入れたときの性能がどうなるかは要検証です)

機械翻訳について調べてみると、気軽に使えるAPIでは、DeepLもしくはGoogle Translateが最も性能が良いようです。時点で、MicrosoftのTranslate。

また、LLMを使った機械翻訳も研究されているようですが、DeepLのほうが性能が良さそうです

上記のLLMの構成で呼び出す機械翻訳部分をローカルのAPIサーバーを経由することにしました。
そして、ローカルのAPIサーバーの中で、機械翻訳の種類を選択できるようにします

また、ローカルのAPIとしてサーバーを立てることで、LLM以外の用途にも利用できるようにしておきます

この実装では以下の種類の機械翻訳を扱えるようになりました

  • DeepL
  • Google Translate
  • Argos Translate

ディレクトリ構成

Dev Containerで開発しています。そうじゃない人には、.devcontainerは必要ないです

.
├── .devcontainer
│   ├── Dockerfile
│   ├── devcontainer.json
│   └── docker-compose.yml
├── app
│   ├── __init__.py
│   ├── argostoranslate_bind.py
│   ├── main.py
│   └── other_api_bind.py
└── client.py

.devcontainer ディレクトリ

名前付けとかはお好きなものをどうぞ

devcontainer.json
{
    "name": "python-translator-api-docker",
    "service": "translator-api",
    "dockerComposeFile": "docker-compose.yml",
    "remoteUser": "vscode",
    "workspaceFolder": "/work",
    "customizations": {
      "vscode": {
        "extensions": [
          "ms-python.python"
        ]
      }
    }
}

ポートは8282につながるようにしました。好きなポートでどうぞ

docker-compose.yml
services:
  translator-api:
    container_name: 'python-translator-api-container'  
    hostname: 'python-translator-api-container'        
    build: .
    restart: always
    working_dir: '/work' 
    tty: true
    volumes:                  
      - type: bind            
        source: ..
        target: /work
    ports:
      - 8282:8000

pythonのスリムをベースにしました。
fastapiを使ってapiサーバーにします

Dockerfile
FROM python:3.10-slim

ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID

ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
ENV TZ JST-9
ENV TERM xterm

RUN apt-get update \
    && groupadd --gid $USER_GID $USERNAME \
    && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \
    && apt-get install -y sudo \
    && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
    && chmod 0440 /etc/sudoers.d/$USERNAME \
    && apt-get -y install locales \
    && localedef -f UTF-8 -i ja_JP ja_JP.UTF-8

RUN apt-get -y install git

RUN pip install -U pip &&\
  pip install --no-cache-dir fastapi uvicorn requests

RUN pip install --no-cache-dir googletrans==4.0.0-rc1 argostranslate

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

DeepLだけ利用するならgoogletransとargostranslateはいらない
argostranslateに5.0GBくらい容量を割かれました。
pytorchとかcudnnとかも内包してる機械学習モデルを利用した機械翻訳エンジンなので、まぁ妥当か?

ほかのエンジンを利用したい場合には、その都度拡張してください

app ディレクトリ

__init__.pyは空のファイルです。pythonがディレクトリをモジュールとして認識するためのマーカーですね。早くこの仕様なくなってほしい

__init__.py

メインのapiの実装はfastapiの記法に任せてます。fastapiとかflaskとかを使うと、こんなに短いコードでapiサーバーの処理が書ける時代になったんだなーって毎回思います。

main.py
from fastapi import FastAPI
from pydantic import BaseModel
import app.other_api_bind as other_api
import app.argostoranslate_bind as argostoranslate_bind
class Info(BaseModel):
    translator_type: str
    source_lang: str
    target_lang: str
    text: str 

app = FastAPI()

@app.post('/api/translate')
async def translate(info: Info):
    ret_text = ''
    if info.translator_type == 'DeepL':
        ret_text = other_api.deepL_free_requests(info.source_lang, info.target_lang, info.text)
    if info.translator_type == 'Googletrans_py':
        ret_text = other_api.googletrans_requests(info.source_lang, info.target_lang, info.text)
    if info.translator_type == 'Argos_translate':
        ret_text = argostoranslate_bind.call_argostranslate(info.source_lang, info.target_lang, info.text)
    ret = {
        'text':ret_text
    }

    return ret

DeepLとかの外部apiを呼び出す処理です。他のapiを利用したい場合はここに追記すればヨロシ

DeepLを利用したい場合は、apiキーを取得して、プログラム中のdeepl_api_keyを変更してください
また、DeepLの無料版と有料版でapiのURLが違うので注意。以下のコードのURLは無料版のもの

google翻訳は、googletransというライブラリを利用した。本当は、Google CloudにCloud Translationというapiがあるのだけど、登録が面倒でこれを使いました。

googletransは文字数制約が厳し目なのと、バージョン指定をしないとまともに動かないのが欠点。

other_api_bind.py
import requests
import json

deepl_api_key = "xxx"
deepl_api_url = "https://api-free.deepl.com/v2/translate"
def deepL_free_requests(source_lang: str, target_lang: str, text: str):
    post_data = {
        "auth_key":deepl_api_key,
        "source_lang":source_lang,
        "target_lang":target_lang,
        "text":text
    }
    ret = requests.post(deepl_api_url, data=post_data)
    if ret.status_code == requests.codes.ok:
        ret = ret.json()
        return ret["translations"][0]["text"]
    else:
        return f'error : status_code={ret.status_code}'


from googletrans import Translator
googletrans_param_dict = {
    'JA':'ja',
    'EN':'en'
}
def googletrans_requests(source_lang: str, target_lang: str, text: str):
    translator = Translator()
    ret = translator.translate(text, dest=googletrans_param_dict[target_lang], src=googletrans_param_dict[source_lang])
    return ret.text

argostransを呼び出すプログラム。公式のサンプルだと、一方通行の翻訳パッケージしかダウンロードしてくれないので、そこを書き換えました

argostransも短いコードで書けるし、短文の翻訳ならDeepLやGoogle翻訳の代替にはなりそう。

argostoranslate_bind.py
import argostranslate.package
import argostranslate.translate

from_code = "ja"
to_code = "en"

def check_download_package(package):
    if package.from_code == to_code and package.to_code == from_code:
        return True
    if package.from_code == from_code and package.to_code == to_code:
        return True
    return False


# Download and install Argos Translate package
argostranslate.package.update_package_index()
available_packages = argostranslate.package.get_available_packages()

for package in available_packages:
    if check_download_package(package):
        argostranslate.package.install_from_path(package.download())

argostranslate_param_dict = {
    'JA':'ja',
    'EN':'en'
}
def call_argostranslate(source_lang: str, target_lang: str, text: str):
    ret = argostranslate.translate.translate(text, argostranslate_param_dict[source_lang], argostranslate_param_dict[target_lang])
    return ret

client.py

テストするためのサンプルプログラムです
translator_typeを

  • "DeepL"
  • "Googletrans_py"
  • "Argos_translate"

で指定して、テストしてみてください。DeepLは月ごとの使用文字数制限があるのでテストしすぎないようにしてください。
urlのlocalhostはサーバーのipアドレスを指定してください

client.py
import json
import requests
import pprint
url = "http://localhost:8282/api/translate"

translator_type = "Googletrans_py"

test_info = {
    "translator_type":translator_type,
    "source_lang":"EN",
    "target_lang":"JA",

    "text":"this is test message"
}

print("post text")
pprint.pprint(test_info)

ret = requests.post(url, json.dumps(test_info))
ret_info = json.loads(ret.text)

print("respons text")
pprint.pprint(ret_info)


test_info = {
    "translator_type":translator_type,
    "source_lang":"JA",
    "target_lang":"EN",

    "text":"これはテストメッセージです"
}

print("post text")
pprint.pprint(test_info)

ret = requests.post(url, json.dumps(test_info))
ret_info = json.loads(ret.text)

print("respons text")
pprint.pprint(ret_info)

成功すれば以下のような出力が帰ってきます

post text
{'source_lang': 'EN',
 'target_lang': 'JA',
 'text': 'this is test message',
 'translator_type': 'Googletrans_py'}
respons text
{'text': 'これはテストメッセージです'}
post text
{'source_lang': 'JA',
 'target_lang': 'EN',
 'text': 'これはテストメッセージです',
 'translator_type': 'Googletrans_py'}
respons text
{'text': 'This is a test message'}

Discussion