はちみつブログ

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

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_pathimages_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版と同様の値を出力できる。

注意点

今回紹介した pingouinpsych も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とも接続したくなる気がするが、こちらは今後の課題として考えようと思う。正直、何番煎じだよ、という内容かと思うが、スマホからも設定できたという記録にでもなればと思う。

Aivis SpeechをAPIで使う

以前、TTS (Text-to-Speech) の例として、Macのデフォルトでインストールされている「say」を試してみた記事を書いた。今回はその続き。今回は「Aivis Speech」を試してみた。どちらかというとGUIアプリケーションからというよりは、スクリプトから利用して自動音声合成をしたいので、REST APIから利用する方法の記録となる。

はじめに

繰り返しになるが、もともとの流れとしては「翻訳して英語の文章を読みたい系」。前回の「say」と同じ理由にはなるが、自作で簡易オーディオブック化、というかポッドキャスト化して、家事の最中や移動中に聞きたいというのがモチベーションになる。

「say」も良かったのだが、使っているとだんだん欲も出てくる。いくつかTTSのアプリケーションを試そうと思ったが、今回は悩みつつAivis Speechを選ぶことにした。

理由としては、概ね使い方が「VOICEVOX」と共通していること。VOICEVOXはYouTubeはじめ、まあまあ実社会でも聞かない日がないくらいよく使われているTTSだと思う。「じゃあ、VOICEVOX使えばいいじゃん」でその通りなのだが、Aivis Speechの方が後発なのは知っており、かつUIやAPIは似ていても、発話がより自然という話を聞いたこともあり、今回試してみたいと思った。

以前は、インストールしてデフォルトで入っているモデルの学習環境などで問題があったらしいということも、ネットニュース等で見ていた(とりあえずASCIIの記事をリンク)ので、使うか悩んでいたが…VOICEVOXは以前に利用したことがあり、せっかくなら比較・違いも見てみたいと思い、手を出してみることにした。

インストールまわり

インストールについてはほとんど言うことはないかも。普通にインストーラでインストールした。Homebrewなどでは見つからなかった。

FAQにも載っているが、macOS Sequoia 以降では、セキュリティとプライバシーから開くための認証が必要になる。公式からも詳しい手順へは外部リンクが紹介されていたりする。自分の手元のTahoeでも同様の動きだった。

あとはLaunchpadとかから起動できる。

音声モデルの追加

音声モデルはAivisHubからダウンロードできる。まあ、ライセンスはそれぞれ確認してもらい、ということになるかと思う。

ダウンロードの際にもアプリケーションは起動している方がスムーズ。ダウンロード後に、アプリケーション側で読み込み操作が必要になる。

先にAPIを出すと、以下の形で叩くことで、選択可能なモデルのリストがJSONで取得できる。

curl -X 'GET' \
  'http://127.0.0.1:10101/speakers' \
  -H 'accept: application/json'

結果のイメージとしては

[
  {
    "name": "音声モデル1",
    "speaker_uuid": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx",
    "styles": [
      {
        "name": "スタイル名",
        "id": xxxxxxxxx,
        "type": "talk"
      },
      {
        ...
      }
    ],
    "version": "x.x.x",
    "supported_features": {
      "permitted_synthesis_morphing": "NOTHING"
    }
  },
   ...
]

音声を合成する際のモデル選択は、このJSONで分かる id 番号が必要になる( speaker_uuid の方は特に使ってはいない)。とりあえず、curlで書いたが、自分はPythonから叩くようにしている。とはいえ、音声モデルは決まったのを選んでるので、最初に id を確認した以降はほとんど叩いていないが。。

APIでの音声合成

まずアプリケーションを起動すると、裏でWebサーバーが起動し、REST APIで音声合成をすることが可能となる。これにより、先ほど少し書いたPythonなどから叩くことが可能になる。

すでに書いてしまっているが、デフォルトではポート番号 10101 でサーバーが起動する。そのため、 http://127.0.0.1:10101 配下を叩いていくことになる。ちなみに、 http://127.0.0.1:10101/docs を叩くとSwagger UIでAPIのテストも可能。

あるテキストをAPIで読ませる場合、2つの手順を踏むことになる。1) 最初がいわゆる発話させたいテキストを入力し、実際に発話するための情報に変換する処理。2) 2つ目がその発話のための情報をもとに、実際に音声ファイルを生成する処理。この流れはVOICEVOXも共通だ。

例えば「test」という文字を音声合成するとする。これを

http://127.0.0.1:10101/audio_query?text=test&speaker=xxxxxxxxx

上記に対して POST すると以下のようなJSONが返ってくる。

{
  "accent_phrases": [
    {
      "moras": [
        {
          "text": "テ",
          "consonant": "t",
          "consonant_length": 0,
          "vowel": "e",
          "vowel_length": 0,
          "pitch": 0
        },
        {
          "text": "ス",
          "consonant": "s",
          "consonant_length": 0,
          "vowel": "u",
          "vowel_length": 0,
          "pitch": 0
        },
        {
          "text": "ト",
          "consonant": "t",
          "consonant_length": 0,
          "vowel": "o",
          "vowel_length": 0,
          "pitch": 0
        }
      ],
      "accent": 1,
      "pause_mora": null,
      "is_interrogative": false
    }
  ],
  "speedScale": 1,
  "intonationScale": 1,
  "tempoDynamicsScale": 1,
  "pitchScale": 0,
  "volumeScale": 1,
  "prePhonemeLength": 0.1,
  "postPhonemeLength": 0.1,
  "pauseLength": null,
  "pauseLengthScale": 1,
  "outputSamplingRate": 44100,
  "outputStereo": false,
  "kana": "test"
}

もともと「test」だったテキストが、1音ずつ「テ」「ス」「ト」になっていることから、音声合成用のデータになっていることが読み取れるだろう。

GUI上で動作させた際も、アクセントの変更などが可能だが、おそらく "accent": 1 この辺だろうと思う。何音目がアクセントかという意味だろう。

他にも、母音や子音の情報などもあり、なるほどなと読める。

さて、音声を作る際は

http://127.0.0.1:10101/synthesis?speaker=xxxxxxxxx&enable_interrogative_upspeak=true

こちらのAPIを叩く。bodyの方に、先ほどのJSONを含める。そうするとWAVで音声が返ってくるという形だ。

GUI側の方は、「。」など文章の切れ目で勝手にテキストを区切ってくれたりするが、APIで自分で叩く場合、無限に長い文章を送れてしまう。ただ、その場合、特に synthesis 側の方が返答に時間を要するので、ある程度の長さで切ったものをAPIには投げて、WAVファイルを結合するようにした方がエラーは少ないと思う。

まとめ

今回は簡単になるが以上。

まあ、他のところにもいくらでも情報がありそうな内容ではあるが、今後他のTTSを試す中でもいい比較になるかなと思い、書いてみることにした。

実際「say」とはかなり違う。「say」の場合は2段ではなく、直接テキストを入力するとaiffが出力される。簡単・直感的だが、Aivis SpeechやVOICEVOXのように、中間の調整可能なファイルがある方が、アクセントの調整など細かい設定も可能であるということは見た目でわかる。…まあ、特にいじらないのだが笑

追加の情報として、Aivis Speechには辞書機能もあるようだ。そのため、よく間違われる単語については登録することができるらしい。リンクは公式noteのものだが、他のTTSも試した上で長期的に使っていこうと思ってきた際には、この辞書登録も考えながら進めていこうと思う。

参考資料