はちみつブログ

趣味や生活の話を好きなタイミングで書いていきたいと思います。

LM StudioでTranslateGemmaを動かしてみる

英語論文をMarkdownにして翻訳して読んでいる話のアップデートとして、TranslateGemmaによる翻訳を試してみた。いろいろつまづいたので笑 その記録を残した方がいいかと思い、今回はLM Studioで動かすまでを紹介する。

冷静に考えると、APIで利用したり、Ollamaなどを使うとそもそもこういう詰まり方をしないかもしれないので、LM Studioを使ったことがそもそも問題だったのかも。。とはいえ、自分の勉強にはなったのでよかった。

はじめに

TranslateGemmaはGoogleが公開している翻訳向けのモデルで、調べる限り 4B、12B、27Bのモデルが用意されている。2026年の1月公開だが、9月現在も特に変わってはいない。タイミング的にGemma 3をベースに翻訳特化に改良したモデルという位置付けのはず。

今回はMacBook Air M2、メモリ16GBの環境で試すため、12Bの4bit量子化されたMLXモデルを使うことにした。モデルはHugging FaceにあるバージョンをLM Studioからダウンロードし導入した。ライセンスについてはGemma Terms of Useが適用される。この点は最新のGemma 4の方が扱いやすいかも。

実行環境

今回の環境は以下。

  • MacBook Air
    • チップ: M2
    • メモリ: 16GB
  • macOS: 26.6.2
  • LM Studio: 0.4.23+1
    • 2026年9月現在、特にLM Studio自体を商用的に使うこと自体はCommercial Licenseを必要としない規約に変更はないようだ。
  • モデル: mlx-community/translategemma-12b-it-4bit

LM StudioでTranslateGemmaを動かす

そのまま試してみる

まずはLM Studioでモデルをロードして、普通にチャットUIから英文を入力してみた。

今回も以前と同じ文章を使うことにして。Ollamaのガイドなどにもあるが、プロンプトの最初に指示を入れ、

日本語への翻訳指示 I tried to cook pasta yesterday, but I accidentally made enough for ten people.

以前の翻訳でも使った文章で、まあ「パスタを10人分も作ってしまった」くらいになれば良さそう。しかし…

TranslateGemmaではこの英文をそのまま入力しても返答がなかった。

Prompt Templateを変更する

LM Studioのモデル設定を見ると、Prompt Templateという項目がある。表示されていない場合、空きスペースで右クリックし、表示させる。

TranslateGemmaではここのJinjaのタブにかなり長いテンプレートが設定されている。あとで細かい話は後で確認するとして、LM Studioの通常のチャットUIから使う場合、このテンプレートが利用されるが、エラーが出る原因はここになる。

そこで、今回は英語と日本語の翻訳に固定して、通常のチャットUIから使えるようにPrompt Templateを変更した。

英語から日本語の場合は以下。

{{ bos_token }}
{%- for message in messages %}

    {%- if message['role'] == 'user' %}

{{ '<start_of_turn>user\n' }}
{{ 'You are a professional English (en) to Japanese (ja) translator. Your goal is to accurately convey the meaning and nuances of the original English text while adhering to Japanese grammar, vocabulary, and cultural sensitivities.\n' }}
{{ 'Produce only the Japanese translation, without any additional explanations or commentary. Please translate the following English text into Japanese:\n\n\n' }}
{{ message['content'] | trim }}
{{ '<end_of_turn>\n' }}

    {%- elif message['role'] == 'assistant' %}

{{ '<start_of_turn>model\n' }}
{{ message['content'] | trim }}
{{ '<end_of_turn>\n' }}

    {%- endif %}

{%- endfor %}

{%- if add_generation_prompt %}
{{ '<start_of_turn>model\n' }}
{%- endif %}

Prompt Templateに貼り付けて保存した後、先ほどと同じ英文を入力すると、今度は日本語で返答されるはず。

日本語から英語へ翻訳したい場合は、以下のように逆向きのテンプレートにすればよい。

{{ bos_token }}
{%- for message in messages %}

    {%- if message['role'] == 'user' %}

{{ '<start_of_turn>user\n' }}
{{ 'You are a professional Japanese (ja) to English (en) translator. Your goal is to accurately convey the meaning and nuances of the original Japanese text while adhering to English grammar, vocabulary, and cultural sensitivities.\n' }}
{{ 'Produce only the English translation, without any additional explanations or commentary. Please translate the following Japanese text into English:\n\n\n' }}
{{ message['content'] | trim }}
{{ '<end_of_turn>\n' }}

    {%- elif message['role'] == 'assistant' %}

{{ '<start_of_turn>model\n' }}
{{ message['content'] | trim }}
{{ '<end_of_turn>\n' }}

    {%- endif %}

{%- endfor %}

{%- if add_generation_prompt %}
{{ '<start_of_turn>model\n' }}
{%- endif %}

なお、変更したPrompt Templateはゴミ箱のボタンから削除し、デフォルトに戻せるので安心してください。まあ、デフォルトのままでは動かないのだが…設定を保存もできるので、もしLM StudioのチャットUIで翻訳をする場合、保存してもいいかもしれない。

あまり翻訳的には本質ではないが、今回はこのテンプレートの箇所を少し深掘りした結果を以下で紹介しようと思う。LM Studioで動かさない場合はどうでもいいところかもしれないので、必要な方向けが以下となる。

Prompt Templateとは

ここからはLM StudioのPrompt Templateを少し深掘り、動かなかった理由や、どう書けば動くのかを確認した結果を紹介する。

標準的な例

LM StudioのチャットUIに入力した文章は、たとえば

こんにちは

と入力した場合、内部的にはたとえば以下のようなメッセージになるようだ。だいぶシンプル版だが。

<start_of_turn>user
こんにちは
<end_of_turn>
<start_of_turn>model

Prompt Templateは、チャットUIに入力した内容や、設定を変数的に入れ替えて変換するためのものだ。今回勉強したが、その際にJinja形式を使うのがLM Studioの標準機能として提供されている。先ほどの例はGemma 3の場合を超シンプル化したものにはなるが、当然モデルによって構造は異なると思ってほしい。Prompt Templateではroleやcontentを取り出しながら、この文字列を作っている。 role は設定のトグルにあったり、 content はチャットUIの中の文字列。

さも知っていたかのように書いているが、そういうわけではなく、今回Gemma 3などもベースに勉強してみたというところ。Jinja自体も、Flaskなどを触ってこなかったこともあり、今回はじめて勉強した。

たとえば、Gemma 3のPrompt Template(の正式版)は上記のようにチャットUIに書かれた内容を素直にというかそのままモデルに投げようとするので、もともと考えていた

日本語への翻訳指示 I tried to cook pasta yesterday, but I accidentally made enough for ten people.

こちらを入力してあげれば、そのまま

「昨日、パスタを作ろうとしたんだけど、うっかり10人分くらい作っちゃった。」

あるいは、もう少しカジュアルに:

「昨日、パスタを作ろうとしたんだけど、すごい量作っちゃった。10人分くらいかな。」

どちらの表現も「I tried to cook pasta yesterday, but I accidentally made enough for ten people」の意味を十分に伝えています。

こんな感じで翻訳をしてくれる。プロンプトをもっとちゃんとしてやれば、翻訳文のみを返答してくれるはずだ。

TranslateGemmaの場合

TranslateGemma、まあ今回 Hugging Face のMLX Communityからダウンロードしてきたモデルに標準で設定されているPrompt Templateを見る以下のような形式だった。

{%- set languages = {
    "aa": "Afar",
    ...
    "en": "English",
    ...
    "ja": "Japanese",
    "ja-JP": "Japanese",
    ...
}
-%}
{{ bos_token }}
{%- if (messages[0]['role'] != 'user') -%}
    {{ raise_exception("Conversations must start with a user prompt.") }}
{%- endif -%}
{%- for message in messages -%}
    {%- if (message['role'] == 'user') != (loop.index0 % 2 == 0) -%}
        {{ raise_exception("Conversation roles must alternate user/assistant/user/assistant/...") }}
    {%- endif -%}
    {%- if (message['role'] == 'assistant') -%}
        {%- if message['content'] is none or message['content'] is not string -%}
            {{ raise_exception("Assistant role must provide content as a string") }}
        {%- endif -%}
        {{ '<start_of_turn>model\n'}}
        {{ message["content"] | trim }}
    {%- elif (message['role'] == 'user') -%}
        {%- if message['content'] is none or message['content'] is not iterable or message['content'] | length != 1 -%}
            {{ raise_exception(
                "User role must provide `content` as an iterable with exactly one item. That item must be a " +
                "`mapping(type:'text' | 'image', source_lang_code:string, target_lang_code:string, " +
                "text:string | none, image:string | none)`."
            ) }}
        {%- endif -%}
        {%- set content = message["content"][0] -%}
        {%- set source_lang_code = content["source_lang_code"] | replace("_", "-")  -%}
        {%- set source_lang = languages[source_lang_code] -%}
        {%- set target_lang_code = content["target_lang_code"] | replace("_", "-") -%}
        {%- set target_lang = languages[target_lang_code] -%}
        {{ '<start_of_turn>user\nYou are a professional ' + source_lang + ' (' + source_lang_code + ') to ' +
           target_lang + ' (' + target_lang_code + ') translator. Your goal is to accurately convey the meaning and '
           'nuances of the original ' + source_lang + ' text while adhering to ' + target_lang + ' grammar, '
           'vocabulary, and cultural sensitivities.\n'
        }}
        {%- if content["type"] == 'text' -%}
            {{
                'Produce only the ' + target_lang + ' translation, without any additional explanations or ' +
                'commentary. Please translate the following ' + source_lang + ' text into ' + target_lang + ':\n\n\n' +
                content["text"] | trim
            }}
        {%- elif content["type"] == 'image' -%}
            {{
                'Please translate the ' + source_lang + ' text in the provided image into ' + target_lang + '. ' +
                'Produce only the ' + target_lang + ' translation, without any additional explanations, ' +
                'alternatives or commentary. Focus only on the text, do not output where the text is located, ' +
                'surrounding objects or any other explanation about the picture. Ignore symbols, pictogram, and ' +
                'arrows!\n\n\n<start_of_image>'
            }}
        {%- else -%}
            {{ raise_exception("User role `content` can contain either `text` or `image` data.") }}
        {%- endif -%}
    {%- else -%}
        {{ raise_exception("Conversations must only contain user or assistant roles.") }}
    {%- endif -%}
    {{ '<end_of_turn>\n' }}
{%- endfor -%}
{%- if add_generation_prompt -%}
    {{'<start_of_turn>model\n'}}
{%- endif -%}

完全に追い切れてはいないが、LM Studioでは上記のJinjaテンプレートを埋め切ることができず、きちんとモデルに渡すテキストを生成できないため、エラーが出たのだと思う。

ただ、上記のテンプレートには翻訳を効率的に実施するための、プロンプトの例は載っており、OllamaのTranslateGemmaのページのPrompt Guideがわかりやすいが

You are a professional {SOURCE_LANG} ({SOURCE_CODE}) to {TARGET_LANG} ({TARGET_CODE}) translator. Your goal is to accurately convey the meaning and nuances of the original {SOURCE_LANG} text while adhering to {TARGET_LANG} grammar, vocabulary, and cultural sensitivities.
Produce only the {TARGET_LANG} translation, without any additional explanations or commentary. Please translate the following {SOURCE_LANG} text into {TARGET_LANG}:

{TEXT}

これを守ると翻訳がうまくいくということが書かれている。そのため、英語→日本語の方のPrompt Tamplateを以下にすることで

{{ bos_token }}
{%- for message in messages %}

    {%- if message['role'] == 'user' %}

{{ '<start_of_turn>user\n' }}
{{ 'You are a professional English (en) to Japanese (ja) translator. Your goal is to accurately convey the meaning and nuances of the original English text while adhering to Japanese grammar, vocabulary, and cultural sensitivities.\n' }}
{{ 'Produce only the Japanese translation, without any additional explanations or commentary. Please translate the following English text into Japanese:\n\n\n' }}
{{ message['content'] | trim }}
{{ '<end_of_turn>\n' }}

    {%- elif message['role'] == 'assistant' %}

{{ '<start_of_turn>model\n' }}
{{ message['content'] | trim }}
{{ '<end_of_turn>\n' }}

    {%- endif %}

{%- endfor %}

{%- if add_generation_prompt %}
{{ '<start_of_turn>model\n' }}
{%- endif %}

チャットUIから渡されたmessage['content']をそのまま翻訳対象とすることができるというわけだ。

まとめ

今回はTranslateGemma 12BのMLXモデルをLM Studioで動かしてみた。モデルをロードして普通にチャットUIから英文を入力するだけでは返答がなかったが、Prompt Templateを変更することで英語から日本語への翻訳ができた。

Prompt Templateについても少し調べてみると、Jinjaテンプレートでモデルに渡す文字列がどのように生成されるかも理解することができた。最初は何で返事がないんだろう…という感じだったが、Prompt TemplateやJinjaについて確認する良い機会にはなった。今後、Pythonなどから呼び出す際にもよくJSONで指定している箇所になるので、なるほどなと思って活用できそうだ。

とはいえ、なんか…LM Studioのせいで時間がかかったところかもしれない。もし、同じようにLM StudioでTranslateGemmaを動かそうと思ったが、あれっ?、となった方が簡単に参考になってくれたら嬉しいと思う。

参考資料

DoclingとPandocでの画像付きEPUB作成

論文を読むにむけて、英語が苦手なのでMarkdownにして翻訳して読んでいることを前に書いた。引き続き読んでいるのだが、最近はEPUBにして、iPhoneのブックアプリで読んでいる。もとはポッドキャストも一緒にオーディオブックとして管理したかったからだが、EPUBをブックでスクロール読みにすると使い勝手がよかった。

唯一不満だなぁ…と思っていたのが画像の入れ方だったのだが、すぐに解決できることがわかった笑 ChatGPTに聞きながら調べてみると、Doclingでオプションを調整すれば、画像も抽出、参照するMarkdownを作成でき、Pandocで画像入りEPUBも作成できるとわかった。

今回は、この一連について簡単に紹介しようと思う。

はじめに

早速だが、今回はこれまでも使ってきたDocling、Pandocで、画像抽出や画像挿入のコマンドや関数を確認する感じになる。関数て呼び方は古いかもだが笑

今回の環境は以下の通り。

  • MacBook Air (M2、メモリ16GB)
  • Python 3.12.11
  • Docling 2.82.0
  • Pandoc 3.10.2

Macはやってる間に上げちゃったりして確認しそびれた。今の26.6.2でも一応動いてる。

作成したスクリプトは、次のような流れになっている。

PDF
 ↓
Docling
 ├─ Markdown
 └─ images/ 内の画像ファイル
 ↓
Pandoc
 ↓
画像付きEPUB

この辺の構成図?テキスト?はCodexが書いてくれたがありがたい。文章はなかなか自分の文体を覚えてくれなくて、ほぼ自分が書いているのがなんだかなぁだが。

実装

DoclingでPDFからMarkdownと画像を出力

あまりDoclingの仕様を細かく理解していなかったが、コードブロック PdfPipelineOptions(generate_picture_images=True) で画像を出力するか指定する。いろいろ入れ子だが、それを最終的には引数に DocumentConverter を作成して、関数を呼んで変換していくことになる。

pipeline_options = PdfPipelineOptions(generate_picture_images=True)
converter = DocumentConverter(
    format_options={
        InputFormat.PDF: PdfFormatOption(
            pipeline_options=pipeline_options
        )
    }
)
result = converter.convert(str(pdf_path))

その後、save_as_markdown()でMarkdownを書き出す。画像については別ファイルとして出すらしいREFERENCEDを指定した。指定しないとどうなるのかはテストしていない。

result.document.save_as_markdown(
    markdown_path,
    artifacts_dir=images_dir,
    image_mode=ImageRefMode.REFERENCED,
)

markdown_pathがMarkdownの保存先で、images_dirが画像の保存先になる。今回、自分の実装では、はじめにスクリプトの引数でPDFファイルを入力するようにしており、 argparse などでパスを処理して先ほどの markdown_path や images_dir は設定した。

ただ、DoclingでMarkdownを保存する際に、そのままだと画像の参照が絶対パスになってしまうことがわかった。そこで、一度ファイルを読んで、そのテキストを replace して相対パスにして書き込みを実施した。なんとなく、アナログ感に溢れているので、もっと良い解決があるのかもしれないなとも思った。今回、細かくは追わなかったのだが、Doclingの保存も相対パスいけそうだと思うので…

image_prefix = f"{images_dir.resolve().as_posix()}/"
markdown_path.write_text(
    markdown_path.read_text(encoding="utf-8").replace(
        image_prefix,
        "images/",
    ),
    encoding="utf-8",
)

Pandocで画像付きEPUBを作成

Pandocを呼び出す部分は次の通り。

pandoc_command = [
    "pandoc",
    str(markdown_path),
    "--from=markdown",
    "--to=epub3",
    f"--resource-path={markdown_path.parent}",
    "--output",
    str(epub_path),
]

subprocess.run(pandoc_command, check=True)

--resource-pathにはMarkdownファイルのディレクトリを指定している。画像はその中のimages/もあり、Markdown内で相対パスでimages/...対応する画像ファイルを見つけて埋め込む動きになるようだ。

コマンドかけよという感じだが、同じPythonファイル内で実装したため、上記の感じで subprocess を用いた。

まとめ

今回はDoclingとPandocを使って、PDFから画像付きEPUBを作ってみた。コードもまあ、Codexと相談しつつ、やってみたというところもあり、すぐにはできたものの、理解の範疇内のコードにするのが難しい笑 こちらの力不足なだけだとは思うが。

また、最近の感想だが、、、あまりLLM側も進化を感じなくなって来た気がするのは、こちらの認識に対してレベルが上がりすぎているのだろうか…?BLEACHの霊圧みたいだな… 書いているうちにFable 5.1も出たし、GPTも新しいの出るらしいし。この論文翻訳系の今後の課題としては、翻訳自体をローカルのLLMにやらせるところは試したいなと思っている。

また、そろそろ手描きのローカルLLMによるテキストかも最近のモデルだとどうか確かめたいなとも思ったりしている。この辺りで取り組みたいものを考えて、次回、と思ったりしている。

ChatGPTとNotionとCodexでポッドキャストを作る

継続案件だが、自分向けのポッドキャストを作成し聞くことを試し続けている。GPTに日本語の原稿を作らせてAivisSpeechで音声にする、というものだ。最近は調査から原稿作成、音声化まで、ほぼすべてをCodexで進めていた。Codexならリポジトリのファイルを読みながら原稿を作り、そのままローカルのAivisSpeechを呼び出せる。リモートでも触れるし。

ただ、それだけが理由ではないが…5.6 Solを使うとクレジット消費が激しく…何回か作るうちに、すべてをCodexへ任せる必要はないのではないかと思うようになった。今回は、ChatGPT、Notion、Codexの役割を分けて、ポッドキャストを作る流れを見直した話を紹介する。

調査・原稿作成は通常Chatへ任せる

自分が使った範囲での感触だが、最新情報を検索し、複数の候補から面白そうな題材を探す作業は、CodexよりChatGPTの通常チャットのほうが合っているように感じていた。検索結果の選び方や、今の自分の関心へ話をつなげる部分でも、通常チャットの回答のほうが読みたい方向に近いことが多かった。AI系のYouTuberのにゃんたさんのChatGPTのChat・Work・Codexの使い分けを紹介した動画でも、普段の調べ事や最新情報の検索にはChatの方が良さそうと紹介されていたり。使い方や評価は人によって変わると思うが、自分も近い感覚を持っていた。

それなら、 ポッドキャストの題材選びも通常チャットへ任せたくはなる。ただ、ChatGPTにもProjectsの機能はあるものの、そこに集約する気にはなれないし、たとえば原稿をデータベース的に整理するのにも課題を感じていた。また、Codexでローカルの AivisSpeechを叩くこともスキルアップ含めて継続したかった。まとめると、原稿作りはChatGPTの通常チャットが良さそうで、記録はNotion、TTS周りの仕上げはCodexが良さそうだ。

上記のテストをするのを加速させてくれたのが、GPT-5.6 SolをCodexで使った際のクレジット消費だ。それ以前はGPT-5.5で高いEffortを選んで作業することも多かったが、利用枠の消費をそれほど意識していなかった。ところが5.6 Solで同じような感覚のまま、広いWeb調査や長い原稿作成までCodexへ任せていると、クレジットの減りが急に大きくなったように感じた。完全に個人的な感覚だが…

TTS部分だけをCodexにまとめられれば、ふわっとした例外処理以外は正直Pythonスクリプトを実行するくらいなので、Codexの5.6 Terraのmediumでいけそうな感じ。あとはデータの受け渡しをどうするかで、テストも含めて間に置くデータベースとして、先日から接続したNotionを応用的に使うことにチャレンジした。

Notionを受け渡し場所にする

以前、NotionとChatGPTを接続するテストを行い、スマートフォンから接続して、データベースの内容を読んだり書き換えたりできることを確認していた。その時点では小さな動作確認だけだったが、今回はポッドキャストの本運用で使ってみることにした。

まず作成時のデータの流れの図になるが

通常チャットが調査と原稿作成を終えたら、作成した原稿をNotionへ保存する。保存が終わったら、そのNotionページのURLをCodexへ渡す。CodexはNotionから原稿を受け取り、 AivisSpeechを使ってポッドキャストを作る。

次は聞いた後の流れだが

聞いた後の感想を同じNotionデータベースに残す。感想をもとに作り方のデータベースがあるのでそれをCodexが修正。次回以降の話になるが、過去回と一緒に作り方をCodexがNotionから受け取る。

Notionをデータ的にハブにすることで、通常チャット側は最近扱った題材を確認し、同じ話題が続かないようにできる。

今回諦めた話→スケジュール設定

この流れを考えたとき、通常チャットで原稿を作る処理と、Codexで音声化する処理を定期実行にして、朝には音声が完成している状態も試そうとした。

だが、定期実行では通常チャットからNotionへの書き込みを最後まで完了できなかった。接続そのものの問題だったのか、書き込み時の確認や権限が関係したのかは、現時点では切り分けできていない…ので、またチャレンジはしてみようと思う。

現在は定期実行は使わず、通常チャットへ日付を指定して原稿を作り、Notionへの保存を確認した後、そのページURLをCodexへ渡している。完全な自動化ではないが、スマートフォンなどから依頼を出し、PCの前で操作し続けなくても、ローカルでの音声生成と検査まで進められる。

まとめ

今回は間にNotionを置くことで、ChatGPTの通常チャットとCodexを繋ぎながら、Codexのクレジットも節約してみた話を紹介した。そのうち改悪されるかもしれないが、自分のPlus プランだと、通常チャットの5.6 Sol Highにはあまり制限を感じない。リサーチの良さもだが、その面でもポッドキャストのクオリティを保てている気はする。

一方で自分の知識の問題と思うが…他サービスとの接続の権限的な部分は正直悩ましい。定期実行と組み合わせられないはずはないと思うので、調べ直してみたい。

あとは、これもちゃんと書くべきだったかもだが、コネクタ系を使えないと、Codexはゴリゴリブラウザを使って接続しようとする…NotionやChatGPTへも接続して書き換えやデータ取得を進めていて、まあ間違った内容はしていなかったが、やり過ぎと思う場面が多かった。最近はAGENTS.mdのおかげかやらなくなったが…

参考資料

TTS原稿のLLM調整

以前、MacのsayやAivis Speechを使って、テキストから音声を作る方法を試した。もともとの目的としては翻訳した文章などを簡易オーディオブック化、というかポッドキャスト化して、家事や移動中に聞きたいと言った一連の続きになる。

調べた内容から台本を作り、TTSで音声化することを進めているが、実際に聞いていると気になるのが、読み間違いだった。さっと書いてしまったが、台本はChatGPTやCodexで作っている。今回はTTSへ入力する原稿の読み間違いを少なくする表記へLLMで調整する方法の、自分の現在地を紹介する。正直似たようなネタは多いと思うが笑 今回はLM StudioからGemma 4を使った例を紹介する。

はじめに:今回やりたいこと

今回やりたいのは、文章のリライトや校正ではなく、TTS向けの読みに置き換えること。たとえば、以下のような調整をする。

入力 よくある間違い TTS向けの表記
次に じに つぎに
準備が整い次第 準備が整いつぎだい 準備が整いしだい
2026年 にまるにろく年 二千二十六年
LLM なんかネイティブ エルエルエム
Big Five なんかネイティブ ビッグファイブ

元の台本の内容には手を入れず、TTSに渡すための表記だけ整えた入力用のテキストを作るイメージになる。一応、自分の現在地のリポジトリも紹介するが、言葉として絶対に正しい読みという意味ではない。たとえば、依存は「いそん」が正しい読みかもなのだが、自分が「いぞん」と聞きたいから、そう読ませてるとかが普通にある。

形態素解析とか、辞書的なものの処理でできそうではあるが、意外と置換処理では無理なんだよな、ということでLLMでやってみることにした。

今回の実装

これまでTTS用に自分が使っているもののうち、公開できそうな候補を絞った。検証用として、各単語を含むいろはうた的な約1分のsample-input.mdを作った。さらに、正解かなという結果をsample-expected.txtとして用意している。

LLMに普通に文章を渡すと、良かれと思って校正や言い換えをする可能性もある。プロンプトは以下としてみた。

あなたは、日本語TTSへ渡す本文の表記調整器です。ユーザーが与えた本文を、次の条件に従って処理してください。

- 出力するのは調整後の本文だけです。説明、前置き、判断過程、引用符、コードフェンスを付けないでください。
- 下の対応一覧にない変換、読みの補足、言い換え、要約、校正をしてはいけません。
- 対応一覧を適用する箇所以外は、文字、語順、句読点、段落、改行、空白を入力のまま保持してください。
- 単純な部分文字列置換はしないでください。前後の文脈から、一覧にある語または表現として使われている箇所だけを調整してください。
- 候補が重なる場合は長い語を一つの単位として扱い、その内部へ短い候補を重ねて適用しないでください。たとえば「OpenAI」は一語として「オープンエーアイ」にし、その内部の「AI」を別に処理しません。
- 本文中で対応一覧に該当する箇所は、次の表記へ調整してください。

対応一覧:
- 次に → つぎに
- 準備が整い次第 → 準備が整いしだい
- 目次 → もくじ
- 体の状態 → からだの状態
- 依存 → いぞん
- 外れ値 → はずれち
- 粒度 → りゅうど
- 2026年 → 二千二十六年
- 1つ → ひとつ
- 3つ → みっつ
- AI → エーアイ
- LLM → エルエルエム
- CLI → シーエルアイ
- PR → ピーアール
- URL → ユーアールエル
- Microsoft → マイクロソフト
- OpenAI → オープンエーアイ
- ChatGPT → チャットジーピーティー
- GitHub Copilot → ギットハブ コパイロット
- Node.js → ノードジェーエス
- React → リアクト
- Big Five → ビッグファイブ
- Cronbach's alpha → クロンバックのアルファ

実際のMarkdown原稿の処理はscripts/normalize.pyというスクリプトにして、内部でLM StudioのLLMを呼ぶようにした。今回はGemma 4の12B QATを利用した。16GBメモリのM2 MacBook Airでも動く。そろそろメモリを増やした新しいマシンが欲しくなってきた笑

実行は以下の形で。

python scripts/normalize.py sample-input.md -o reading-output.txt

Codexとコードを作りながら、アドバイスをもらったのがGemma 4の思考モードだった。最初に実行した際は内部の思考処理が続いてしまい、かなり時間がかかってしまった。

表記の置き換えに、そこまで考え込んでもらわなくても良いというCodexのおすすめもあり、LM StudioのREST APIでreasoning: "off"を指定した。

正確に直してもらおうと思うと、モデルの性能≒大きさも気になるところではあるが、今回のような処理では、思考モードが必要かという点も確認した方がよさそうだ。

まとめ

今回は、TTS向けの読み調整をLM StudioとGemma 4 12B QATで表記を調整できるか試した。

Gemma 4も思ったよりいろいろ試せて良かった。本文ではカットしたが、小さいモデル(E4BやE2B)だと、うまく読み調整できない箇所があったりした。12B QATは完全には理解していないが、単なる12Bより低スペックマシンでも動くのがウリなのだろう。実際体感だが、E4Bとモデルの容量もそこまで変わらないのに性能がいい気がするし。

今回、サクッと試して公開を目指したので、あまり構造化出力とかを綺麗にしていない。もしGemma 4以外のモデルでやる際は全然動かないリポジトリになっているかもしれないので、せいぜい参考程度にしてもらえると嬉しい。

Codexのモバイル連携テスト

少し前からデスクトップアプリで存在を確認していたCodexのモバイル連携を試してみた。この辺や、この辺が公式情報と思う。まだプレビュー版の位置付けのようだ。(7月頭時点で、もう公式機能(GAっていうのかな)だったらごめんなさい)

結論としては自分にはかなり使い方が合っていて、Mac miniみたいな小さめのデスクトップ機が欲しくなった笑

※今回この記事を書き始めたのが6月下旬から7月頭にかけてになる。公開に向けて7/11に準備していたが、GPT5.6の解禁などに合わせてデスクトップもモバイルもデザイン・文言がだいぶ変わった。そのため、メニュー上の文言など、かなり違う点が多いかもしれないので、その点は少し古いバージョンの話だと思って読んで欲しい。途中スクショを撮っておけばよかったなぁ…と少し後悔しつつ、逆に古いのでその画面見て得する人もいないか…と思ったりしている。

はじめに-使い始めるまで

もしかすると誤解があるかもなので先に自分の使っている構成を伝えると、自分のモバイル利用は正確には「リモート」利用になる。基本的に動いているのは母艦になるPC側のCodexアプリになる。Codexもいろいろな利用法があると思うが、自分が試したのはCodexアプリ版とスマホのChatGPTアプリの接続になる。

自分の場合は、Codex アプリ側で、 設定 -> コーディング -> 接続 から、 この Mac の操作 からデバイスを追加していき、モバイルアプリ側でログインすると使えるようになった。簡単すぎて、、Mac側、iPhoneのChatGPTアプリ側のスクショを撮り忘れた笑 スマホ側でChatGPTアプリのメニューからCodexを開く際にFace IDやパスコード認証は求めてくるものの、一部とはいえ簡単に自分のPCが触れるのは脅威すら感じるかも…まあ、 Codexで触れる範囲をいかに適切にできるかなんだろうな…

感想

本当に簡単に動いたので、、、ここから先はほぼ感想になるが、素直に感じた内容を書いていこうと思う。

便利

まあ、すごく単純な言い方になるが、めちゃくちゃ便利だと感じた。PC の前にいなくても、リポジトリ内のファイルの編集、場合によってはコマンド実行が可能だ。コマンドによっては、サンドボックスから出ることも普通に可能だと思うし、かなりどこでも何でもできる感覚になる。

もちろんこれまでもたとえばAWSとか、クラウドに環境を持たせて…リモートで何かやらせるとか、PCであってもリモート接続して…みたいなことは可能だったのかもしれないが、手軽さが違うなと感じた。

たとえば、少しでも気軽にメモを残すのに、「Apple Watchの音声入力したあと、Notionに飛ばす」などは今までもあったし、試したこともあるのだが続かなかった。アプリケーションの問題で本質的ではないかもだが、手段(音声/テキスト/etc)に合わせていくつか入り口を用意するのも面倒だし、その後Notionだけで処理させるわけでもないだろうし、どう連携するかも面倒だった。

それが、ChatGPTというかCodexにまとめられるとすごく楽に感じる。よくあるObsidianに整理させようなどもかなり現実的だなと感じた。まだまだいろいろトライ中だが、ノウハウが溜まってきたらここでもまとめようかと思っている。

Codexアプリを最初に見た時も思ったが、中のファイルの作りはLLMに任せて、人間は「何をして欲しいかを伝える」インターフェイスが現実的になってきているんだろうな。そう考えた際、モバイル連携はとても使いやすい。

まだテスト中だが、日常メモ用のリポジトリを用意して、そこに雑多な入力をしてCodexに整理させるようなことを試している。まだまだ自分はライトユーザーなのだと思うが、現状クレジット不足にも陥っていないので、十分可能だなと思う。

音声入力と相性がよい

この使い方だから感じるのかもしれないが、スマホで使うなら、音声入力との相性がよい。長い文章をスマホのキーボードで打つのは大変だし、Codexの機能なのかChatGPTアプリ機能なのか何ともだが、とりあえずOpenAIの音声入力を利用可能だ。導線としてもわかりやすく、ある程度適当に入力しても、GPT5.5などは全然解釈してくれるので、あまり気にしなくても動く。

ただ、音声入力について、不満がないわけでもない。Codexの音声入力はリアルタイムというよりは一度録音して、高速で文字起こしする動作をする。そのあとで、修正できるもののちょっと待たされるのはユーザー体験が悪いし、できればリアルタイム性も持ってほしい。

加えて、あまり、そのチャットのコンテキストは読み取ってくれないようだ。単語などはチャット内のテキストもうまく使って校正して欲しいのに、相変わらず「コーデックス」を連発されるのはいい加減、「Codex」にしてくれよと思う。まあ、こっちがネイティブ的発音をすれば良いのかもしれないが笑

これだけ絶賛しておいて難だが、自分の場合、今のところは iPhone 標準の音声入力のほうを多く使っている。即時で入力してくれるし、辞書登録も少ししているし、9割くらい話して入力し、少し手で直すフローに合っているように感じる。内部処理なので、通信環境もあまり関係ないし。

ブラウザ確認は難しい

これにこちらのスキル不足かもしれない。

自作アプリを作っていて、Codexは自分でブラウザを起動して、スクリーンショットを撮って動きを確認してくれる。ただ、モバイルからPC側のブラウザを開けるかというと正直よくわからない。聞いたら教えてはくれそうだが笑

ただ現実問題として、画面レイアウトや画面サイズも違うし、画面確認はPCが近くにある方がやりやすい気はする。モバイルアプリの開発であればうまくいくのかもしれないが、課題はありつつ、何か解決策もありそうな気がしている。

まとめと今後やりたいこと

まずはモバイル連携はめちゃくちゃ便利。今までのリモート接続との技術革新ほどの差はなく、少しUIが違うだけだと思うが、操作性という意味ではやりやすさが全然違うなと感じた。

今後に向けてだが、手元にPCが無いからこそかもしれないが、リポジトリ間のファイルのやり取りをどうにかできないか考えたいと思っている。あまり権限を強くしない、複数のリポジトリを覆うようなリポジトリを作って、ファイルのやり取りは覆っているリポジトリを開いているCodexとやるのがいいか。それか、覆われているリポジトリにAPI的なインターフェイスを作ってやり取りするといいか。などを考えたりしているが、良い運用方法を調べつつ自分に合う方法を探したいと思っている。

あとは、金額の抑えられるデスクトップ機が欲しくなってくる。かなり簡単な処理はローカルLLMを使うのも視野に入ると思っていて、そうなってくるとメモリやストレージ容量も欲しくなってくるかも。ただ、大きい賢いモデルを使うレベルになれば、クラウドのモデルを使う方が良い気はするので塩梅が難しい。

ほぼ文字のみで、感想記事になってしまったが、参考になる方がいたら嬉しい。

というか前段のリード文に書いたが、公開までにアプリの構成自体も変わってしまい古すぎる文章になってしまった笑 公開しないことも考えたが、アプリの更新的には1日くらいだし(自分がモバイルアプリ側の更新に気が付いたのが7/11の公開当日)、ちゃんと書いておけば画面が違うよ!!と怒る人もいないかなと、思いまあ弔い含めてちゃんと公開しておくかと思った笑 月並みすぎるけどAIの進化は早いなぁ…

さらに感想になるが、ここ数ヶ月OpenAIを見直している。年始にディズニーSoraのために契約したのに悲しい目にあった。そんな中だったが、Codexを使い始めて、ここまで継続するとは思わなかった。Fableは使ったことがないものの、Opusレベルは仕事の方で触ることもありレベルもなんとなくわかるが、ちゃんとThinkingできる時のGPTはまあまあ性能が高いし、検索能力は正直一番よいと思っている。なんとなく冬の頃はAnthoropicとGoogleの方がすごそうに見えていたので、Soraに引っ張られて貧乏くじを引いてしまったかと思っていたが、意外とそんなこともなく過ごせているかもしれない。完全にラッキーなだけだが…

まあ、そんな感じで、おそらく私の記事は最新であることはないと思うのだが笑 逆にそこに追いつけはしないもののという方にはちょうどいい情報量かもしれないので、お役に立つ方がいらっしゃれば嬉しいなと思う。最後は全く、Codexのモバイルに関係ないが…よろしくお願いします笑

クロンバックのα

久しぶりの統計・因子分析関連。今回はクロンバックのαについて紹介する。

はじめに-Cronbach(クロンバック)のαとは

因子分析ではひとつの因子から複数の観測できる変数が見えてくるという形になる。たとえば、性格検査を考えるとして、たくさんの似た質問に答えるイメージが持てるのではないかと思う。例えば「社交性」みたいなのを測りそうな似た質問がいくつかあって、それに答えると数値が上がるのかなぁ?みたいな。他だと、「理系因子」があって、その因子から数学や物理の点数に出てくるイメージだ。

先ほどの性格検査における似た質問という部分が、今回のクロンバックのαに関係する。ある因子があったとき、それに関わる質問、数値が複数ある場合、その複数の値がどれぐらい一貫性があるか、という値を表す。まあ、誤差というか、独立因子が大きすぎないよね、という意味なのかなと思ったりする。ちゃんとした性格検査の項目を作ったり、テスト項目を作ろうと思った際には、重要な内容になってくるはず。

クロンバックのαは、他でもよくあるが、0〜1の範囲の数値を取る。1に近いほど、一貫性のある値になっていることを示すようだ。(ただ、1は逆に同じ質問過ぎ) 相関でも一緒だと思うが、絶対的な数値の指標があるわけではないようだ(0.x以上だとオッケーみたいなのはないということ)。

ここからはまずは動かしてみて、どんな感じかな、と考えてみよう。

実際にどうやるか

テストデータを元にやり方を簡単に紹介。

テストデータ

たとえば、極端かもだが、以下のようなテストの点数があったとして、文系因子から国語、社会の点数が表れ、理系因子から数学と理科の点数が表れるとする。

ID 国語 社会 数学 理科
A 80 78 85 83
B 75 72 65 68
C 60 62 80 78
D 55 57 55 58
E 90 88 70 72

まあ、このままだと扱いにくいと思うので、標準化して扱うとは思うが、これのαを文系因子、理系因子ごとにPythonとRで求めてみようと思う。

Python

Pythonでやってみる。簡単な方法は pingouin を使うことのようだ。 pip でインストールする場合、以下のコマンドでインストールできる。

pip install pingouin

そして、こんなコードで求めることができる。

import pandas as pd
import pingouin as pg

df = pd.read_csv("07_cronbach-alpha/data/cronbach_alpha_test_data.csv")

bunkei = df[["Kokugo", "Shakai"]]
rikei = df[["Sugaku", "Rika"]]

bunkei_alpha, bunkei_ci = pg.cronbach_alpha(data=bunkei)
rikei_alpha, rikei_ci = pg.cronbach_alpha(data=rikei)

print("Bunkei alpha:", bunkei_alpha)
print("Bunkei CI:", bunkei_ci)

print("Rikei alpha:", rikei_alpha)
print("Rikei CI:", rikei_ci)

結果は以下のような感じ。

% python 07_cronbach-alpha/Python/cronbach_alpha.py 
Bunkei alpha: 0.991908482142857
Bunkei CI: [0.922 0.999]
Rikei alpha: 0.9855197752323319
Rikei CI: [0.861 0.998]

とりあえず文系に絞ると、αは0.991(以下略)、CIは信頼区間を表していて、95%信頼区間での数値が出てきている。

R

Rでやってみる。Rの場合、 psychパッケージを使うと求められる。 renv を使って、仮想環境に入れるとすると、該当のディレクトリで

R -q -e 'renv::init()'
R -q -e 'renv::install("psych")'
R -q -e 'renv::snapshot()'

これで概ね準備はできると思う。もし、自分のリポジトリからクローンして再現する場合は

R -q -e 'renv::restore()'

で renv の状況を戻せるようなので、そのコマンドの方が良いかもしれない。

そして、本題のクロンバックのαは以下のようなコードで求められる。

library(psych)

df <- read.csv("../data/cronbach_alpha_test_data.csv")

bunkei <- df[, c("Kokugo", "Shakai")]
rikei <- df[, c("Sugaku", "Rika")]

bunkei_result <- alpha(bunkei)
rikei_result <- alpha(rikei)

print(bunkei_result)
print(rikei_result)

Rの方が、細かくいろいろ求められ、出力はこんな感じになるが

Reliability analysis   
Call: alpha(x = bunkei)

  raw_alpha std.alpha G6(smc) average_r S/N   ase mean sd median_r
      0.99         1    0.99      0.99 398 0.004   72 13     0.99

    95% confidence boundaries 
         lower alpha upper
Feldt     0.92  0.99     1
Duhachek  0.98  0.99     1

 Reliability if an item is dropped:
       raw_alpha std.alpha G6(smc) average_r S/N alpha se var.r med.r
Kokugo      1.16      0.99    0.99      0.99 199       NA     0  0.99
Shakai      0.86      0.99    0.99      0.99 199       NA     0  0.99

 Item statistics 
       n raw.r std.r r.cor r.drop mean sd
Kokugo 5     1     1     1   0.99   72 14
Shakai 5     1     1     1   0.99   71 12

Reliability analysis   
Call: alpha(x = rikei)

  raw_alpha std.alpha G6(smc) average_r S/N    ase mean sd median_r
      0.99         1    0.99      0.99 366 0.0056   71 11     0.99

    95% confidence boundaries 
         lower alpha upper
Feldt     0.86  0.99     1
Duhachek  0.97  0.99     1

 Reliability if an item is dropped:
       raw_alpha std.alpha G6(smc) average_r S/N alpha se var.r med.r
Sugaku       1.2      0.99    0.99      0.99 183       NA     0  0.99
Rika         0.8      0.99    0.99      0.99 183       NA     0  0.99

 Item statistics 
       n raw.r std.r r.cor r.drop mean   sd
Sugaku 5     1     1     1   0.99   71 11.9
Rika   5     1     1     1   0.99   72  9.6

ちょっと情報量が…笑 という感じだ。95%信頼区間も2手法で求めてくれていたりして、今後それぞれがどういう意味かは確認してみようかと思う。

情報量を落とす意味でも、αのみを抜き出すコードを足すと

bunkei_alpha <- bunkei_result$total$raw_alpha
rikei_alpha <- rikei_result$total$raw_alpha

cat("Bunkei alpha:", sprintf("%.16g", bunkei_alpha), "\n")
cat("Rikei alpha:", sprintf("%.16g", rikei_alpha), "\n")

こんな感じで、 raw_alpha を出力すればいいんだなとわかり、

Bunkei alpha: 0.991908482142857 
Rikei alpha: 0.9855197752323319 

桁数を指定しているからだが、Python版と同様の値を出力できる。

注意点

今回紹介した pingouin も psych もGPLのライセンスとなっている。単純にデータ分析として、αの計算にのみ用いるのであれば、商用利用でも何も問題ないと思う。一方で、もしこのαをはじめとした数値を使ったアプリケーションなどを作成して配布したい、などとなった場合は注意が必要になる。まあ、RはそもそもRがGPLなので、どちらかというとPythonで何かしたいケースとは思うし…まあ、クロンバックのαで何かすることを思いついていないが笑

感想になるが、MITライセンスとかの実装もあれば使いやすいのになぁと思ってしまうが…まあ、計測しかしないケースが多く、GPLでも誰も困っていないのだろうな。

クロンバックのαの出自について

1951年にCronbachさんが「Cronbach, Lee J. "Coefficient alpha and the internal structure of tests." psychometrika 16.3 (1951): 297-334. 」で提案したもののようだ。名前としても、Cronbachのαというのはそういうことなのだろう。簡単に中身も見てみたが、以下の式がはじめの方に出てきて、これがこれまで扱ってきたクロンバックのαになる。

$$ \alpha = \frac{n}{n-1} \left(1-\frac{\sum_{i=1}^{n} V}{V_t}\right) $$

$n$ は項目数。 $V_t$ の方が合計の方の分散。例えば国語と社会の合計の分散。 $V$の方が項目ごとの分散。例えば国語のみの分散や、社会のみの分散の合計だ。70年以上前の指標だが、今でもよく見る。なかなかに偉大な提案なんだろうな…

まとめ

今回はクロンバックのαを紹介した。久しぶりの統計系の回になったが、こんな感じでいろいろ調べているうちに気になったものを紹介するのもありかな、と思ってくるようになった。自分の勉強にもなるし。

今でもよく使われていて、自分も使っていきたいと思っているものの、一方で別な指標(McDonaldのωというのが調べて出てきた)も提案されてはいるようだ。おいおいこういうものも調べつつ、どれを使うのが良いか判断できるようなところを目指したい。

参考資料

NotionとChatGPTの接続

ChatGPTを使っていて、完全個人的感想だが、ディープリサーチを使わずに通常のThinkingレベルの質問でもいい感じに調査をしてくれると感じている。そのため、もちろんCodexでコーディングなどはするものの、Webアプリ版のメインは調査に使っている。

せっかく調査をするのであれば、調査結果をある程度綺麗に整理したいし、それこそ自分の調査結果をChatGPTに検索するようなこともしてほしい。元々それに合うのが、ChatGPTのプロジェクト機能だと思うのだが、思うように使いこなせていない。

そこで、今回は外部DBとして、Notionを使うこととして、NotionとChatGPTを接続する方法を試してみた。MCPが1年半くらいになるか…前に出てきたので、周回遅れ過ぎだろう…という内容にはなるのだが(笑) 今までやろうやろうと後ろに倒してきた内容を試してみたので、2026年の5月末・6月頭情報としての参考に残しておこうと思う。

はじめに

まあ、さっさと本題に入れば良いが、もう少し文章を書いておこうと思う。

今の所、Codexは置いておいて、ChatGPTの利用方法は「調査」が多い。先述したが、GeminiやClaudeで検索するよりも検索結果が自分の望みに合っているなとは感じていた。それもあり、ChatGPT課金する際の目的の一つはAI的なWeb検索だった。(もちろん、一番の目的のSoraでのディズニー生成は泡に消えたわけだが…笑)

こうなってくると、これまでの調査結果の整理、それを受けた新しい調査項目の提案などもやりたくなってくる。おそらく、この目的に最も合うChatGPTのソリューションとして「プロジェクト」機能が用意されている。ただ、自分が使いこなせていないだけと思うが、なかなか思うような動きをしてくれない。…まあ、自分がメモリ機能を切っていることも大きな原因な気がするのだが。特に気になるのが、チャット履歴自体はそれなりに残っていくのだが、それを整理したり、結局何を調べられたのか整理してまとめられるデータベース・DB的な機能が無いことかなと思ってきた。

こうなった時に候補になるのが、Notionだった。将来的にどうなるかは不明だが、現状Notionについては容量は気にせずにDBとして利用可能だと思っている。まあ、API・MCPアクセスで何かしらこれまで気にならなかった制限にかかってくるかもだが…もう1つの候補として考えてきたObsidianはNotionで何かNGが出た際に候補として考えると良いかなと思っている。ただ、結局Notion → Obsidianに移行したり、一部を抽出してObsidian的なリンク処理を組み合わせてみたりなどを考えた際にもNotionのAPI・MCPを勉強しておくのは損はないかなと思うこととした。

ということで、今回ChatGPTとNotionを接続することをテストしてみた。まあ、何度も他でやられている内容に違いないとは思うのだが、今回…単に自分に時間がなかったり、PCを開くのが面倒だったことがあるのだが、スマホで接続をしてみた。その辺りの紹介をしてみて、誰かの参考になってくれたらと思っている。

ChatGPTのスマホアプリからNotionへ接続

ChatGPTでは「アプリ」からNotionと接続する。アプリから検索にNotionと入れると出てくるはずなので選択。そして、「接続する」を選択して接続を進める。

あとはNotionへのログインなどを求められていくという流れ、事前にAPIキーとかいるのかな?と思ったりもしたが、基本的には通常のメールアドレスとパスワードなどでログインする形。この前後で以下のスクショのように結局はMCPの機能を使っているようだった。

テスト用に

  • 名前
  • ステータス: デフォルトの3つ
  • 調査内容
  • 調査結果

この項目を持つDB「ChatGPTテスト」をNotionに作成した。

「ChatGPTテスト」に、テストになるような内容を入れて、以下のチャットでテストしてみた。アプリでNotionを指定しないと動かないのかはよく分からなかったが、おいおいテンプレ化しようと思ってるので、その際に調整しようと思う。

ChatGPTテストのDBからステータスが 未着手 の項目を1件探してください。 その 調査内容 を読んだ上で、簡単な調査結果を 調査結果 に書き込み、 ステータスを 進行中 に変更してください。

やってみると、確認のボタンが出てくる。OKとして確認をすると、先に進み、「アプリから応答がありました」なども表示され、GPTで調べてきた内容をDBに書き込むという流れが確認できた。

これで、Notionからデータを読み込んだ上で(Readでその際は特に確認なく実施)、Notionに書き込みをする(Writeの際は確認が出てくる)、という一連の動作テストができた。この確認待ちが、どれぐらいの時間待ってくれるのかは分からない笑 動かして放置してしまうケースもけっこうあると思うので、この辺はおいおい確認したいと思う。

まとめ

今回はNotionとChatGPTの超簡単な接続テストを実施した。だんだんCodexとも接続したくなる気がするが、こちらは今後の課題として考えようと思う。正直、何番煎じだよ、という内容かと思うが、スマホからも設定できたという記録にでもなればと思う。