<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://kishiyamat.work/feed.xml" rel="self" type="application/atom+xml" /><link href="https://kishiyamat.work/" rel="alternate" type="text/html" /><updated>2026-02-17T02:27:58+00:00</updated><id>https://kishiyamat.work/feed.xml</id><title type="html">絵とか言葉とか</title><subtitle>隠れ家的チラ裏</subtitle><entry><title type="html">Vision Pro体験から考える、アイトラッキングの日常使い</title><link href="https://kishiyamat.work/2026/02/14/eye-tracking.html" rel="alternate" type="text/html" title="Vision Pro体験から考える、アイトラッキングの日常使い" /><published>2026-02-14T03:00:00+00:00</published><updated>2026-02-14T03:00:00+00:00</updated><id>https://kishiyamat.work/2026/02/14/eye-tracking</id><content type="html" xml:base="https://kishiyamat.work/2026/02/14/eye-tracking.html">&lt;h2 id=&quot;はじめに&quot;&gt;はじめに&lt;/h2&gt;

&lt;p&gt;最近、久々に東京に行く機会があって
新宿のAppleで
&lt;a href=&quot;https://www.apple.com/jp/apple-vision-pro/&quot;&gt;Apple Vision Pro&lt;/a&gt;
を体験する機会がありました。
予約は必要だったのですが、ウェブですぐにできたので妻と行ってきました。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/20260214/eye-tracker.png&quot; alt=&quot;VisionPro&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Apple Vision Proは公式で以下のように紹介されています。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Apple Vision Proは、
デジタルコンテンツを現実空間に
シームレスに融合します。
作業する。映画を観る。
思い出を体験する。みんなとつながる。
そのすべてを、かつてない方法で。
ようこそ、空間コンピューティングの時代へ。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;この「空間コンピューティング」に、
60万円、1日あたり500円の価値はあるのでしょうか&lt;sup id=&quot;fnref:500yen&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:500yen&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;。
体験してきた感想としては「その価値はある」になるのですが、
塵も積もればというかタジタジしてしまいます。&lt;/p&gt;

&lt;p&gt;で、結局買ったのは&lt;a href=&quot;https://gaming.tobii.com/product/eye-tracker-5&quot;&gt;Tobii Eye Tracker 5&lt;/a&gt;
だったりします。かなり飛躍したので、
Apple Vision Proの何が良かったのかという話と、
その良かった部分をTobii Eye Tracker 5で再現できる
理由と方法を残しておこうと思います。&lt;/p&gt;

&lt;h3 id=&quot;vision-proで良かったところ&quot;&gt;Vision Proで良かったところ&lt;/h3&gt;

&lt;p&gt;たしか一時間くらいのチュートリアルだったと思うのですが、
ゴーグル越しに見える映像の解像度も良く、
何より操作性がとても良かったです。&lt;/p&gt;

&lt;p&gt;AppleのRetinaは映りが綺麗ですが、
それが目の前にある感じで、
しかも体験した限りはかなりの枚数を召喚できそうでした。
それを仮想空間上で複数枚並べられるのは、
たしかに良さそうでした。&lt;/p&gt;

&lt;p&gt;また、事前にARグラスやVRゴーグルも
新宿のヨドバシで体験しておいたのですが、
それと比べても別物の解像度の良さで、
まったく酔いませんでした。
また、ヨドバシは単に商品を置いてあるだけなので
どう使っていいかがわからなかったのですが、
Vision Proはマンツーマン指導が入るので
体験として非常に良かったです。&lt;/p&gt;

&lt;p&gt;なにより好印象だったのは、
アイトラッキングによる操作の自然さでした。
見ている対象にフォーカスが移り、軽く指をつまむだけでクリックできる。
ウィンドウの移動や削除なども直感的で、
余計なマウス移動もなく、とても快適でした。&lt;/p&gt;

&lt;p&gt;ただ、ふと思いました。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;この操作性は、デスクトップ環境でも再現できるのでは？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;空間コンピューティングそのものというより、
僕にとっては「視線入力」こそが求めていた機能なのではないか。
そんな感覚が残りました。&lt;/p&gt;

&lt;h3 id=&quot;tobiiで再現してみる&quot;&gt;Tobiiで再現してみる&lt;/h3&gt;

&lt;p&gt;そこで導入したのが &lt;strong&gt;Tobii Eye Tracker 5&lt;/strong&gt; です。
新品だと徐々に値段は上がってきますが、
現在は45,000円くらいです(在庫が切れると値段が一時的に倍になります)。
中古だと約3万円くらいなので、
僕はメルカリで買いました。
Vision Proと比べると、かなり現実的な価格だと思います。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/20260214/price.png&quot; alt=&quot;mercari&quot; /&gt;&lt;/p&gt;

&lt;p&gt;僕は修士の頃から研究室でアイトラッカーを使っていたので、
たしかにWindows Updateの影響で実験期間中に使えなくなって
泣きそうになったこともありますが、
かなりTobiiには信頼を寄せています。&lt;/p&gt;

&lt;p&gt;使ったことのない人向けに説明すると、
アイトラッカーはモニターの下に置くセンサーで、
「今どこを見ていそうか」をカメラで推定するデバイスです。
多くの機種は赤外線（目には見えない光）で目元を照らして、
瞳孔や角膜の反射の位置関係から視線方向を計算します。&lt;/p&gt;

&lt;p&gt;赤外線は可視光の範囲外なので、照らされてもまぶしく感じにくいです。
赤い光がトラッカーから見えます。
また、赤外線カメラの映像では瞳孔が真っ黒で若干怖いですが、
あんな感じで瞳孔の部分は反射が少ないので、
瞳孔と虹彩を分離しやすい、というメカニズムだった気がします。
最初にキャリブレーションという、
その人の「目の特徴」と「画面上の位置」を対応づける操作をして
プロファイルを作ります。&lt;/p&gt;

&lt;p&gt;得意/苦手もあって、例えば&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;姿勢が大きく変わったり、逆光が強いと精度が落ちる&lt;/li&gt;
  &lt;li&gt;メガネの反射や、縦方向の微妙な移動はやや苦手&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;みたいな癖があります。自分は乱視が入っていて
視力が0.1以下で分厚い眼鏡をしていますが、
それでも機能しています。
問題は、これをWindows環境で「マウス」として気持ちよく使えるのか、という点です。&lt;/p&gt;

&lt;h3 id=&quot;tobii-eye-tracker-5-をマウスとして使うために&quot;&gt;Tobii Eye Tracker 5 をマウスとして使うために&lt;/h3&gt;

&lt;p&gt;Windows 11 には標準で「視線制御（Eye Control）」機能があるのですが、
Tobii Eye Tracker 5 は（少なくとも標準機能の範囲では）
サポート対象外として扱われています。
一個前の機種だったら使えたのですが、
5はゲーム用の機器という扱いで除外されています。
なので「買ったらそのままWindowsの視線制御でマウス代わりにできる」という話ではないです&lt;sup id=&quot;fnref:windows&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:windows&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;

&lt;p&gt;一方で、視線入力でマウス操作を実現するソフトはいくつかあって、
例えば「見るマウス（Mill Mouse）」のような外部ソフトを使うと、
Tobii Eye Tracker 5 でもWindows操作に持ち込めます&lt;sup id=&quot;fnref:millmouse&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:millmouse&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;

&lt;p&gt;実際に使ってみると、
視線で「狙う」動作を日常に持ち込めて、
満足度の高い環境になりました。&lt;/p&gt;

&lt;h2 id=&quot;実際の使用感設定&quot;&gt;実際の使用感・設定&lt;/h2&gt;

&lt;h3 id=&quot;使いどころ弱み強み&quot;&gt;使いどころ、弱み強み&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://www.xbitlabs.com/ja/aim-trainer/&quot;&gt;Aim Trainer - 無料オンラインエイム練習&lt;/a&gt;
というサイトがあるので、そこでの様子を共有します。
トレーニング設定は、ターゲットの大きさを「大」、
制限時間を15sにしています。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/20260214/eyetracking.gif&quot; alt=&quot;GIF&quot; /&gt;&lt;/p&gt;

&lt;p&gt;こういうゲームをしたことがないので、
多分ゲーマーの人からしたら遅く見えると思うのですが、
僕がやっているのは
目で追ってトラックパッドでタップしているだけです。&lt;/p&gt;

&lt;p&gt;また、アプリケーションを利用していて思ったのですが、
横方向の動きはとても安定しています。
これは実験でも同じで、横の精度は高い一方で縦の精度が微妙にずれます。
おそらく顔の傾きが変わったり、
瞼の影響が大きいのではと思っています。
なので、横並びのカードやメニューの操作との相性はとても良いです。&lt;/p&gt;

&lt;p&gt;また、あとで説明するような利用頻度の高いアプリはショートカットが便利ですが、
そこまで頻度が高くなかったり、自作アプリの場合は特に便利です。
既存アプリはフォーカス設計がバラバラな場合があります。
そのため、視線でポインターを直接飛ばせるのはとても便利です。
また、自作アプリもショートカットは基本的に未整備なので、
そこでも動作がスムーズになります&lt;sup id=&quot;fnref:vimium&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:vimium&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;

&lt;p&gt;ただ、ファイル名が縦に並んでいてそれを選択、
みたいな操作にはめっぽう弱いです。
なので、おおよそのところにマウスを視線で移動させて解除、
あとはトラックパッドで微調整、というのが
現実的な使い方になります。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/20260214/spot.gif&quot; alt=&quot;spot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;その他で面白かったのは、自分の視線の動きです。
意識していないのに、視線はするすると動いています。
自分の視線を可視化すると、とても興味深い体験になります。
次のGIFは僕の視線の動きで、
もちろん座標はとれませんし取ったとしても
実験ではないので有益な情報は得られませんが、
変な部分を二度見していてなかなか面白いです。
無意識に動かしているので、UIの開発に活かせるかもしれません。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/20260214/reading.gif&quot; alt=&quot;reading&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;mill-mouse-と設定&quot;&gt;Mill Mouse と設定&lt;/h3&gt;

&lt;p&gt;実際にアイトラッカーのデータをマウス操作として扱えるようにしてくれるのは、
Mill Mouse（見るマウス）というアプリです。
Tobii側が推定した視線位置（もしくは頭部追跡の情報）を受け取って、
Windowsのマウスカーソル位置に変換してくれます。
Windows標準の「視線制御」が使えない機種でも、
この手の外部ソフトを噛ませることで「視線でポインターを動かす」操作が成立します。&lt;/p&gt;

&lt;p&gt;「見るマウス」の良いところは、単にポインターを追従させるだけでなく、
日常利用で困りがちな部分に対して設定の逃げ道が多いことです。
例えば以下みたいなケースが挙げられます。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;視線が揺れるので、そのままだとカーソルが落ち着かない（平滑化・感度調整が必要）&lt;/li&gt;
  &lt;li&gt;視線だけで操作し続けると疲れる（オン/オフを切り替えるショートカットが必要）&lt;/li&gt;
  &lt;li&gt;クリックの方式をどうするか悩む（物理クリック/滞留クリック/ショートカットなど）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;僕の運用としては、&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ポインター移動だけを視線に寄せる&lt;/li&gt;
  &lt;li&gt;クリック・微調整はトラックパッド（またはマウス）でやる&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;という分業に落ち着いています。
「視線で狙って、手で確定する」にすると、
誤クリックが減ってかなり気が楽です。
ショートカットは &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl + G&lt;/code&gt; にしています(g for gaze)。
利用しているSlackやVSCodeといったアプリで
競合が少ないキーを選び、必要なら既存のショートカットを無効化しています。&lt;/p&gt;

&lt;p&gt;ソフトや設定の詳細は更新が早いので、
公式の配布・更新情報は以下を参照するとよいです。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;https://millmouse.wordpress.com/&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;注意点&quot;&gt;注意点&lt;/h2&gt;

&lt;h3 id=&quot;ショートカットの理解が先&quot;&gt;ショートカットの理解が先&lt;/h3&gt;

&lt;p&gt;アイトラッカーは便利なんですが、
たとえば、&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;コピーは &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl + C&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;タブ移動は &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl + Tab&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;戻るは &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt + ←&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;といった基本操作は、
目でポチポチするものではないですよね。
すべてを目で操作するのは効率的ではないので、
「視線で選ぶ」と「キーボードで飛ぶ」をうまく分けるのがよさそうです。
逆にここが整理できていないと、目が忙しくなってしんどいです。&lt;/p&gt;

&lt;h3 id=&quot;chromeのタブ周り&quot;&gt;Chromeのタブ周り&lt;/h3&gt;

&lt;p&gt;Chromeの基本ショートカットとしては、任意のタブにジャンプできますよね。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl + 1&lt;/code&gt; ～ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl + 8&lt;/code&gt;（左から順番）&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl + 9&lt;/code&gt;（一番右のタブ）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;タブをたくさん開いていると番号が分からなくなりますが、
よく使うものを左に固定すると使いやすくなります。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;次のタブ → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl + Tab&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;前のタブ → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl + Shift + Tab&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;履歴の戻る・進むも大事です。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;戻る → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt + ←&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;進む → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt + →&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;これはとても便利です。
マウスで戻るボタンを探すより速いです。&lt;/p&gt;

&lt;p&gt;タブの操作も大事ですね。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;新規タブ → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl + T&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;タブを閉じる → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl + W&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;閉じたタブを復元 → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl + Shift + T&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;復元は特に覚えておくと安心です。
このくらいでしょうか。&lt;/p&gt;

&lt;h3 id=&quot;windowsの基本ショートカット&quot;&gt;Windowsの基本ショートカット&lt;/h3&gt;

&lt;p&gt;タスクバーのアプリを番号で起動できますね。
これも目で見てではなく、ショートカットを使った方が早いです。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Win + 1&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Win + 2&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Win + 3&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;タスクバーの左から順番に番号が振られます。
ここを整理しておくと、とても快適になります。&lt;/p&gt;

&lt;p&gt;アプリの切替は &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt + Tab&lt;/code&gt; ですね。
アイトラッキングとは併用できない認識です。&lt;/p&gt;

&lt;p&gt;ウィンドウの分割なんかもショートカットのほうが早いです。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;左に配置 → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Win + ←&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;右に配置 → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Win + →&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;最大化 → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Win + ↑&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;最小化 → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Win + ↓&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;ショートカット設計の悩み&quot;&gt;ショートカット設計の悩み&lt;/h3&gt;

&lt;p&gt;視線位置にポインターを移動する
（カーソルをワープさせる）操作を、
どのキーに割り当てるかは悩みどころです。
いろいろ試しましたが、&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl + Shift + E&lt;/code&gt; → Slackで衝突&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl + Shift + F&lt;/code&gt; → Google Docsで衝突&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl + Alt + Shift&lt;/code&gt; → 押しにくい&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift + Alt + F&lt;/code&gt; → 手首が少しつらい&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift + Alt + V&lt;/code&gt; → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl&lt;/code&gt; から指を話したくない&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;という理由で最終的には、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl + G&lt;/code&gt; になりました。
VSCodeで衝突しますが、
VSCodeはかなり調整が効くので衝突した
ショートカットを別のものに割り当てました。&lt;/p&gt;

&lt;h2 id=&quot;まとめ&quot;&gt;まとめ&lt;/h2&gt;

&lt;p&gt;Vision Proを触ってわかったのは、
現状で自分が求めている機能は
「視線入力」だということでした。
すべてを目で操作するのではなく、&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;大きな移動は視線&lt;/li&gt;
  &lt;li&gt;細かい選択はトラックパッド&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;という組み合わせが、とても自然でした。&lt;/p&gt;

&lt;p&gt;アイトラッキングは未来の技術というより、
すでに実用域にある補助入力デバイスだと感じています。
アイトラッカーが使えないPCを触っていると、
無意識に「視線でポインターを飛ばしたい」と思ってしまうくらいには、よく使っています。&lt;/p&gt;

&lt;hr /&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:500yen&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;価格は60万円なので、5年くらい使うとして
年間で12万円、月で考えると1万円です。
月にだいたい20日くらい稼働すると考えると、
1日あたりのコストは500円。 &lt;a href=&quot;#fnref:500yen&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:windows&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.assdr.kyoto-u.ac.jp/heap/tobii-eye-tracker-5/&quot;&gt;「Tobii Eye Tracker 5」でWindowsを操作する&lt;/a&gt; &lt;a href=&quot;#fnref:windows&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:millmouse&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://millmouse.wordpress.com/tag/tobii-eye-tracker-5/&quot;&gt;Mill Mouse (見るマウス): Tobii Eye Tracker 5 関連&lt;/a&gt; &lt;a href=&quot;#fnref:millmouse&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:vimium&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Vimiumを使うのもありですが、
あれもやっぱりウェブアプリによっては競合してしまったり、
ウェブアプリ以外には使えないというのが難点です。 &lt;a href=&quot;#fnref:vimium&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name></name></author><category term="eyetracking" /><category term="tobii" /><category term="visionpro" /><category term="shortcuts" /><summary type="html">はじめに</summary></entry><entry><title type="html">ローカルLLMによるMultiple-Choice Cloze Taskのシミュレーション</title><link href="https://kishiyamat.work/2025/11/05/llm-proba.html" rel="alternate" type="text/html" title="ローカルLLMによるMultiple-Choice Cloze Taskのシミュレーション" /><published>2025-11-05T10:00:00+00:00</published><updated>2025-11-05T10:00:00+00:00</updated><id>https://kishiyamat.work/2025/11/05/llm-proba</id><content type="html" xml:base="https://kishiyamat.work/2025/11/05/llm-proba.html">&lt;h2 id=&quot;はじめに-動機とよくなさそうな方法&quot;&gt;はじめに: 動機とよくなさそうな方法&lt;/h2&gt;

&lt;h3 id=&quot;動機-multiple-choice-cloze-taskを効率的にシミュレートしたい&quot;&gt;動機: Multiple-Choice Cloze Taskを効率的にシミュレートしたい&lt;/h3&gt;

&lt;p&gt;この記事では、
(i) 言語学関連の研究/開発において
(ii) Multiple-Choice Cloze Task（選択式穴埋め問題）をシミュレーションしたい人向けに、
(iii) ローカル環境でLLMを活用する方法について解説します。
備忘録と実装の整理を兼ねて記事にしています&lt;sup id=&quot;fnref:log&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:log&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;

&lt;p&gt;今回紹介する実装では、ローカルで動かしているLLMに直接リクエストを送信することで、
OpenAI APIライブラリに依存せずにCloze Taskの評価を実現しています。
最低でも16GBのGPUが必要ですが、RTX 5070 Tiなどのコンシューマーグレードの
GPUでも動作します。また試していませんが、おそらくGoogle ColabのT4リソースでも実行可能です。
&lt;!-- TODO: 試す --&gt;&lt;/p&gt;

&lt;p&gt;本題ですが、人を対象としたCloze Taskの評価では主観性と効率性から困る点がありました。
つまり、人の直感的判断に依存することと（だから数をとるのですが）、
大規模データセットの評価に時間（とお金）がかかってしまいます。
そこで、無料で数を打てる代替手段を用意しておきたかったわけです。&lt;/p&gt;

&lt;p&gt;この時、LLMを使うやり方は複数あると思います（
LLMの説明自体は
&lt;a href=&quot;https://www.nec-solutioninnovators.co.jp/sp/contents/column/20240229_llm.html&quot;&gt;NEC&lt;/a&gt; とか
&lt;a href=&quot;https://www.ibm.com/jp-ja/think/topics/large-language-models&quot;&gt;IBM&lt;/a&gt;
のページを参照するとわかりやすいかと思います）。
そこで、まずはうまくいかなさそうな方法2つを
紹介した後で、本命の方法を共有しようと思います。&lt;/p&gt;

&lt;h3 id=&quot;プランa-llmに確率も出力させよう&quot;&gt;プランA: LLMに確率も出力させよう&lt;/h3&gt;

&lt;p&gt;プランAは、LLMに確率を聞くことです。
例を挙げると、LLMに以下のようなプロンプトを入力して出力を得ます。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;昨日、渋谷の名物である_を見に行った。&lt;/code&gt; の &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_&lt;/code&gt; に入る単語の候補として
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ハチ公 スクランブル交差点 スタバ 公園&lt;/code&gt; があったとき、それぞれの確率を出力してください&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;コーディングは簡単なので、Google ColabでOpenAIのGPT4.1を使ってみましょう。
参考までですが、&lt;a href=&quot;https://colab.research.google.com/drive/1q82ZJDE3bfPGAZXTKuxSmMo455svs40M?usp=sharing&quot;&gt;ノートブック&lt;/a&gt;もあります。
事前に「APIキー」を設定する必要はあるのですが、
APIの取得方法や設定方法は色々な方が解説しているのと、
すぐに情報が古くなるので省きます。
OpenAIのAPIキーの取得と、Google ColabでAPIキーをuserdataに格納する方法を
調べると出てきます。なお、このAPIキーの扱いは銀行口座のパスワードと同じくらい注意してください。&lt;/p&gt;

&lt;p&gt;改めてですが、ここで紹介する方法の2つ（Plan A/B）は個人的に
「批判を受け止めきれないだろうから、
自分は使わないだろうな」
という方法です。ただ、ユースケースによっては十分かもしれないので、
厳密さを求めない場合は十分選択肢になるかもしれません。&lt;/p&gt;

&lt;div class=&quot;language-py highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;openai&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;google.colab&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userdata&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# OpenAI APIキーを取得
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;openai_api_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userdata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;OPENAI_API&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# OpenAIクライアントを初期化
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;openai&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OpenAI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;openai_api_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# プロンプトと単語候補
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prompt_text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;`昨日、渋谷の名物である_を見に行った。` の `_` に入る単語の候補として `ハチ公 スクランブル交差点 スタバ 公園` があったとき、それぞれの確率を出力してください。&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;gpt-4.1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# または利用可能な他のGPTモデル
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prompt_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;choices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;このコードの実行結果は以下になりました。
システムとして使いたいならJSON形式などで出力させるのがよいでしょう。
この出力で満足する人もいるかもしれません。
たしかに、出力としてはそれっぽいですね。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;この確率は、各単語が「渋谷の名物」に関連している度合いや一般的な登場頻度などに基づいています。それを考慮に入れ、私の主観で以下のような確率を設定します。

ハチ公: 0.4,
スクランブル交差点: 0.35,
スタバ: 0.1,
公園: 0.15.

これはあくまで一例で、公的な調査結果ではないため、具体的な数値は異なる可能性があります。渋谷の他の名物やランドマーク、企業なども確率の対象となることを考慮してください。
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;ちゃんと和が1になるので、挙げた候補の中で確率分布を作ってくれています。
つまり、「4択を作ったら、どの単語が選ばれるか」
という確率をだしてくれているのです。
さすがに賢いですが、いくつか問題があります。&lt;/p&gt;

&lt;h4 id=&quot;問題点1-ブレる&quot;&gt;問題点1: ブレる&lt;/h4&gt;

&lt;p&gt;上記の方法（テキストで確率を出力させる）の問題の1つは、
出力が結構ゆれることです。
上のほかに3回まわしてみました。
どれもだいたい0.4くらいがハチ公ではあります。
しかし、無視（システムとして許容）できるレベルの誤差かもしれませんが揺れています。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# sample 2
  &quot;ハチ公&quot;: 0.42,
  &quot;スクランブル交差点&quot;: 0.42,
  &quot;スタバ&quot;: 0.10,
  &quot;公園&quot;: 0.06

# sample 3
  &quot;ハチ公&quot;: 0.45,
  &quot;スクランブル交差点&quot;: 0.40,
  &quot;スタバ&quot;: 0.10,
  &quot;公園&quot;: 0.05

# sample 4
- ハチ公：0.4
- スクランブル交差点：0.4
- スタバ：0.1
- 公園：0.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;もう少しシビアな問題は、
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;出力されている数値&lt;/code&gt; が &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;候補の確率&lt;/code&gt; ではないというところです。
どちらかというと、「それっぽい確率を表すテキスト」ということですね&lt;sup id=&quot;fnref:like&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:like&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;。
説明が複雑になるので、理想と実際の状態を考えてみましょう。&lt;/p&gt;

&lt;p&gt;理想としては、文脈を与えたときの単語の確率（例えば&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;P(ハチ公|文脈)&lt;/code&gt;） を
モデルが計算してくれる状態です（それが最後に共有する本命です）。
しかし、実際には &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;P( &quot;0.4&quot; | ...ハチ公: )&lt;/code&gt; を計算し、
その &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;0.4&quot;&lt;/code&gt; という「テキストとしての確率」が最も確率が高かった…というだけです。&lt;/p&gt;

&lt;p&gt;これは確率というより、
「確率を示すテキスト（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;0.1&quot;&lt;/code&gt;とか&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;0.4&quot;&lt;/code&gt;とか）」の出力ですね。
ややこしいですが、別物だということは伝わると思います。
このいい加減さを確かめるために一つ実験をしてみましょう。&lt;/p&gt;

&lt;h4 id=&quot;問題点2-選択肢の提示順序の効果&quot;&gt;問題点2: 選択肢の提示順序の効果&lt;/h4&gt;

&lt;p&gt;いい加減さを見るために実験します。
以下のように、「ハチ公」を提示する候補の一番後ろにしてみましょう。&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# prompt_text = &quot;`昨日、渋谷の名物である_を見に行った。` の `_` に入る単語の候補として `ハチ公 スタバ 公園 スクランブル交差点` があったとき、それぞれの確率を出力してください。&quot;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prompt_text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;`昨日、渋谷の名物である_を見に行った。` の `_` に入る単語の候補として `スクランブル交差点 スタバ 公園 ハチ公` があったとき、それぞれの確率を出力してください。&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;そうすると、以下のようにスクランブル交差点のほうが確率が上になってしまいます。
まぁ人間らしいといえば人間らしいのかもしれませんが、
Cloze Taskで提示した候補の順番で確率が変わってしまうのは
挙動として好ましくありません。&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;### 確率の推定

- スクランブル交差点：45%
- ハチ公：40%
- スタバ：10%
- 公園：5%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;人間でも起こりうるのかもしれませんが、
確率を返してほしい身としては困ってしまいます。&lt;/p&gt;

&lt;h4 id=&quot;補足-ある概念を表象する単語が複数ある場合&quot;&gt;補足: ある概念を表象する単語が複数ある場合&lt;/h4&gt;

&lt;p&gt;こちらは「そもそも問題なのか」という考え方もあるので
提案する方法でも解消させていないのですが、
選択肢に &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;忠犬ハチ公像 ハチ公像 忠犬ハチ公の像 ハチ公の像&lt;/code&gt;
を加えてみます。
どうなるでしょうか。
概念としては「ハチ公」と同じものなので、
「ハチ公」のパイを食い合って和が0.4（=40%）くらいになることを期待します。&lt;/p&gt;

&lt;p&gt;しかし結果としては、次のような結果となって和が75%になります。&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;| 候補                 | 確率(%) |
|----------------------|---------|
| ハチ公               | 20      |  # 20%
| ハチ公像             | 25      |  # 45%
| 忠犬ハチ公像         | 15      |  # 60%
| ハチ公の像           | 10      |  # 70%
| 忠犬ハチ公の像       | 5       |  # 75%
| スクランブル交差点   | 20      |
| スタバ               | 3       |
| 公園                 | 2       |
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;この直感と異なる挙動を説明するために、少し話をシンプルにしましょう。
候補が「ハチ公」と「スクランブル交差点」だけだったとして、
これらが50/50と最初に推定されたとしましょう。
概念としてどちらを選択するか、という話です。&lt;/p&gt;

&lt;p&gt;ここに「ハチ公」の言い換えである
「忠犬ハチ公」とか「ハチ公像」とか、
要は「ハチ公という概念」を示す別の表記が増えても、
概念の別の表記をしているだけです。
なので、候補の数が増えたからといって
「ハチ公」を示す概念が選ばれる確率が
上がっていくのはおかしな話に聞こえます。
逆に「スクランブル交差点」の確率が下がるのも変な話ですね。&lt;/p&gt;

&lt;p&gt;ただ、実際はそこまでおかしくありません。
というのも、上の話は前提がずれてしまっているのです。
実際にしたいこととしては、
文脈を与えた時「(i)どの概念が (ii)どのくらいの確率で選ばれるのか」
を知りたいのです。
ただ、上でやっていることは「概念」ではなく
「単語」の確率を聞いているのですね。&lt;/p&gt;

&lt;p&gt;したがって、実際に運用する場合は少なくとも3種類の方略が取れます。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;「ある概念をもっとも代表するような、プロトタイプ的な表現」を概念ごとに一つずつ採用&lt;/li&gt;
  &lt;li&gt;ある概念を示しうる単語をすべて洗い出してからまとめてあげる作業（周辺化ともいえますが）&lt;/li&gt;
  &lt;li&gt;概念で聞いて確率を求めさせ、そのパイのなかで個々の表現方法の確率を取得&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;プランb-選択肢を選ぶ確率で考えよう&quot;&gt;プランB: 選択肢を選ぶ確率で考えよう&lt;/h3&gt;

&lt;p&gt;ほかにも、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;top_logprobs&lt;/code&gt;という機能を使うのもありえます。
これは出力の候補の確率を返してくれるというものです。
ただ、後述する「トークン」という特殊な単位を使っているので、
単語を返してくれることは期待できません&lt;sup id=&quot;fnref:logprobs&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:logprobs&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;。
そこで、数字の候補を出力させることを考えましょう。&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;openai&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;google.colab&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userdata&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;math&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# OpenAI APIキーを取得
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;openai_api_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userdata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;OPENAI_API&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# OpenAIクライアントを初期化
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;openai&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OpenAI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;openai_api_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 候補語リスト
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;candidates&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ハチ公&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;スクランブル交差点&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;スタバ&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;公園&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;candidates_str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;. &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cand_i&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cand_i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;candidates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)])&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ids_str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;candidates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# top_logprobsを使って候補語の確率を直接取得
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;「昨日、渋谷の名物である_を見に行った。」の _ には何が入りますか。次の候補から、適切な候補の数字のみ出力してください。&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;候補: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;candidates_str&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;gpt-4.1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logprobs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;top_logprobs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 上位20個のトークン候補の確率を取得
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;max_tokens&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;temperature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 数字トークンの確率を抽出
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;number_probs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;first_token_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;choices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;数字トークンの確率:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;top_token&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first_token_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;top_logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;token_text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;top_token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token_text&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ids_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;prob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;top_token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logprob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;number_probs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prob&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;candidate_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;  &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token_text&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;. &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;candidate_name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prob&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; (logprob: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;top_token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logprob&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 結果表示
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;最終確率:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;number_probs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sorted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;number_probs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;normalized_prob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number_probs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;candidates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;normalized_prob&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;normalized_prob&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;これの結果は次のようになります。
圧倒的にハチ公ですね。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;数字トークンの確率:
  1. ハチ公: 1.0000 (logprob: -0.0000)
  2. スクランブル交差点: 0.0000 (logprob: -15.0000)

最終確率:
ハチ公: 1.0000 (100.00%)
スクランブル交差点: 0.0000 (0.00%)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;ただ、これもあまりよくありません。
候補の種類や数、順番をいじると、確率が変わります。ハロウィンを試しに入れてみたのですが、
ハロウィン自体に確率は入らないのに確率分布が大きく変わってしまいました。
同じ概念の別の表記が増えてもその概念の確率が高くならないのは好印象ですが、
挙動としては信頼できないですね。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;「昨日、渋谷の名物である_を見に行った。」の _ には何が入りますか。次の候補から、適切な候補の数字のみ出力してください。

候補: 1. スタバ 2. 公園 3. ハチ公像 4. 忠犬ハチ公の像 5. ハロウィン 6. 忠犬ハチ公像 7. ハチ公 8. スクランブル交差点

数字トークンの確率:
  3. ハチ公像: 0.6218 (logprob: -0.4751)
  8. スクランブル交差点: 0.3771 (logprob: -0.9751)
  6. 忠犬ハチ公像: 0.0000 (logprob: -12.6001)

最終確率:
ハチ公像: 0.6225 (62.25%)
忠犬ハチ公像: 0.0000 (0.00%)
スクランブル交差点: 0.3775 (37.75%)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;ちなみに、「なんでわざわざ数字を出力させるの？」と思うかもしれません。
この疑問はいくつかのパターンに細分化できそうです。
まず一つのパターンは、以下のようなオープンな回答を許したほうがよくないか、
というパターンです。&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;「昨日、渋谷の名物である_を見に行った。」の _ には何が入りますか。答え: &quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ただ、これだとLLMの出力はオープンすぎるので、
最大20種類という制限では所望の出力が候補として得られないかもしれません。
また、最初のトークンの確率（対数ですが）しかわからないので、
最初のトークンが同じ候補が区別できません。&lt;/p&gt;

&lt;p&gt;また、以下のようにYes/Noとさせたほうがよくないか、というパターンもあり得ます。
ただ、こちらはそもそもYes/Noの確率自体が違うので、
単純な比較ができなくなってしまいます。&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;「昨日、渋谷の名物である_を見に行った。」はそれぞれの候補で日本語として自然ですか。 Yes/No&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ということで、選択肢の出力がPlan Bでは妥当なのかなぁという感じです。&lt;/p&gt;

&lt;h3 id=&quot;提案手法-前後の文脈を所与とした候補の確率&quot;&gt;提案手法: 前後の文脈を所与とした候補の確率&lt;/h3&gt;

&lt;p&gt;さて、ここまでうまくいかなさそうな方法2つを共有しました。
普段からLLMに触っていないと、
こういう局所的な解を最適と思ってしまいそうな気もするので、
問題点を指摘する意味はあるかなと思います。
ただ、もっとうまくいくし、理論上も突っ込みどころの少ない方法があります。
そんな複雑な話ではなく、
以下のような式で双方向の文脈評価を採用する、というだけです。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Score = P(候補語 | 左文脈) × P(右文脈 | 左文脈 + 候補語)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;詳しくは後述しますが、これを実現するには、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;左文脈|候補語|右文脈&lt;/code&gt; としたとき
（例: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;昨日、渋谷の名物である|ハチ公|を見に行った。&lt;/code&gt;）、
それぞれのトークンの確率を求めないといけません。
これは、OpenAIが提供しているAPIに沿って言うと
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo=True&lt;/code&gt; と &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logprobs=True&lt;/code&gt; という2つのオプションを
指定しないといけません。&lt;/p&gt;

&lt;p&gt;しかし &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo=True&lt;/code&gt; は &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text-davinci-003&lt;/code&gt; というモデルで見たのが最後で、
それ以降はほとんど使われていません。
なにより、&lt;a href=&quot;https://community.openai.com/t/why-will-gpt-3-5-turbo-instruct-no-longer-support-echo-true-and-logprobs-1/404932/5&quot;&gt;コミュニティで指摘されている通り&lt;/a&gt;、
この組み合わせは2023年の10月5日から利用できなくなっているのです。&lt;/p&gt;

&lt;p&gt;朗報として、今回紹介する&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpt-oss&lt;/code&gt;のシリーズでは利用可能です。
なんせパラメータがオープンなわけで、
かなり自由度の高い使い方ができます。&lt;/p&gt;

&lt;h3 id=&quot;補足-gpt-ossとはオープンソース版gpt&quot;&gt;補足: GPT-OSSとは：オープンソース版GPT&lt;/h3&gt;

&lt;p&gt;使用するGPT-OSS（GPT Open Source Software）を補足しておきます。
これは&lt;a href=&quot;https://openai.com/ja-JP/index/introducing-gpt-oss/&quot;&gt;OpenAIが2025年8月5日に公開した&lt;/a&gt;
オープンソースの言語モデル (language model) です。
言語モデルとは、文 (string) を構成する要素の確率を表現するものです。
極論、単語の頻度だけでも言語モデルと呼べます（unigramと呼びますね）。
パラメータによって定義でき、たくさんのパラメータを持ったLMを
LLM (large language model) と呼びます。&lt;/p&gt;

&lt;p&gt;GPT-OSSの特徴として、
モデルの重み（＝パラメータ）が公開されており、ローカル環境で実行できる点があります。
また、上で述べたように&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo=True&lt;/code&gt;に対応しているので
任意の文字列に対するトークンの確率取得が可能です。
これは商用版では廃止済みなのでありがたいですね。&lt;/p&gt;

&lt;p&gt;モデルサイズは&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpt-oss-120b&lt;/code&gt;と&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpt-oss-20b&lt;/code&gt;が2025年10月30日の段階では公開されており、
今回は後者の200億パラメータのものを使っています。
驚くべきは、メモリ要件がGPU直結の16GB VRAMで動くという点です。
かなり現実的に家庭で買えるレベルのPCで動作します。&lt;/p&gt;

&lt;p&gt;今回の実装ではvLLMを使っています。
このvLLM（Virtual Large Language Model）とは、
大規模言語モデルに使える高速な推論エンジンです。
システム構成は以下のようになっています。
ソースコードは載せていませんが、
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cloze.py&lt;/code&gt;にCLIも載せていて、Streamlitというパッケージを使って
ブラウザ経由でGUIなどから呼び出せるようにしています。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;┌──────────────────┐
│  Python Client   │  cloze.py
│  (本実装)         │
└────────┬─────────┘
         │ HTTP Request (/v1/completions)
         ↓
┌──────────────────┐
│   vLLM Server    │  OpenAI互換API提供
│                  │  echo=True対応
└────────┬─────────┘
         │
         ↓
┌──────────────────┐
│   GPT-OSS-20B    │  オープンソースモデル
│   (16GB VRAM)    │  ローカル推論
└──────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;この組み合わせにより、ローカル環境でCloze Taskが実現できます。&lt;/p&gt;

&lt;h2 id=&quot;共有する手法-双方向の文脈評価&quot;&gt;共有する手法: 双方向の文脈評価&lt;/h2&gt;

&lt;p&gt;さて、この双方向の文脈評価が技術的に可能だとして、
どんな理屈で求めるのでしょうか。
この式がやっていることは
「左文脈→候補語の確率」と、「左文脈+候補語→右文脈の確率」の積を求めたいわけです。
どちらも、いわゆる「条件付き確率」というやつですね。
英語だと &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A|B&lt;/code&gt; は “A given B” と読んだりします。
日本語だと「Bを所与としたときのA」ですね。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Score = P(候補語 | 左文脈) × P(右文脈 | 左文脈, 候補語)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;「&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;候補語&lt;/code&gt;が&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;左文脈&lt;/code&gt;から発生する確率」と
「&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;左文脈＋候補語&lt;/code&gt;が&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;右文脈&lt;/code&gt;を発生させる確率」を計算すると、
候補語と左右の文脈の「座りの良さ」が数値化できます。
でもこれって、文脈を与えた時の確率なのでしょうか。&lt;/p&gt;

&lt;p&gt;以降のサブセクションでは、
どうして上記の計算を妥当と考えるのか、
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpt-oss&lt;/code&gt;とはなんなのか、
実装方法について述べていきます。
環境構築や必要なハード周りの話は、
実際に回す人はあまりいないと思うので、
最後のパートでしようと思います。&lt;/p&gt;

&lt;h3 id=&quot;前後の文脈を所与とした候補の確率とは何なのか&quot;&gt;前後の文脈を所与とした候補の確率とは何なのか&lt;/h3&gt;

&lt;p&gt;上で述べましたが、求めるスコアは
候補語と左右の文脈の「座りの良さ」を数値化したものです (下に示す式の右辺)。
これは左右の文脈を与えた時の$x$の確率に比例します (同式の左辺)。
先行する文脈（Context）は $C_{\mathrm{pre}}$、
後続する文脈は $C_{\mathrm{post}}$ と表記しています。&lt;/p&gt;

&lt;p&gt;$
P(x\mid C_{\mathrm{pre}},C_{\mathrm{post}})
\propto P(C_{\mathrm{post}}\mid C_{\mathrm{pre}},x)P(x\mid C_{\mathrm{pre}})
$&lt;/p&gt;

&lt;p&gt;もちろん、これは（すくなくとも僕にとっては）自明な式ではありません。
そこで、左辺の $P(x\mid C_{\mathrm{pre}},C_{\mathrm{post}})$ から
右辺を導出する過程を考えていきましょう。&lt;/p&gt;

&lt;p&gt;式の左辺はベイズの定理により、次の式のように分解できます
（ベイズの定理自体が初見な方には
&lt;a href=&quot;https://www.youtube.com/watch?v=mX_NpDD7wwg&quot;&gt;アイシア先生の動画&lt;/a&gt;が
おすすめです）。&lt;/p&gt;

&lt;p&gt;$
P(x\mid C_{\mathrm{pre}},C_{\mathrm{post}})
=\frac{P(C_{\mathrm{pre}},C_{\mathrm{post}}\mid x)P(x)}{P(C_{\mathrm{pre}},C_{\mathrm{post}})}
$&lt;/p&gt;

&lt;p&gt;候補それぞれの$x$でこの確率の値を計算して比較していくのですが、
分母$P(C_{\mathrm{pre}},C_{\mathrm{post}})$は共通しています。
なので、この計算がめんどくさそうな、実態のわからない分母も
比較の際は無視できます(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1/?&lt;/code&gt;と&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2/?&lt;/code&gt;の比較で&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?&lt;/code&gt;がわからなくても&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1:2&lt;/code&gt;となるのと同じです)。
よって、次のように比例関係($\propto$で示します。)に落とし込めます。&lt;/p&gt;

&lt;p&gt;$
P(x\mid C_{\mathrm{pre}},C_{\mathrm{post}})
\propto P(C_{\mathrm{pre}},C_{\mathrm{post}}\mid x)P(x)
…(1)
$&lt;/p&gt;

&lt;p&gt;この右辺に出てくる $P(C_{\mathrm{pre}}C_{\mathrm{post}}\mid x)$ ですが、
ベン図を描くとわかりやすいのですが、さらに次のように分解できます。&lt;/p&gt;

&lt;p&gt;$
P(C_{\mathrm{pre}},C_{\mathrm{post}}\mid x)
= P(C_{\mathrm{pre}}\mid x)P(C_{\mathrm{post}}\mid C_{\mathrm{pre}},x)
…(2)
$&lt;/p&gt;

&lt;p&gt;まず、三つの円が重なるように描きます。
そして、それぞれに $C_{\mathrm{pre}}, C_{\mathrm{post}}, x$ と
ラベルを貼ります。
$x$という円の中にある
「$C_{\mathrm{pre}}, C_{\mathrm{post}}$ が重なっている部分」が
$P(C_{\mathrm{pre}},C_{\mathrm{post}}\mid x)$ です。一番小さい部分ですね。
これは、「$x$という円の中にある $C_{\mathrm{pre}}$ の面積
(=$P(C_{\mathrm{pre}}\mid x)$)」と
「その$C_{\mathrm{pre}}, x$ の中にある $C_{\mathrm{post}}$ の割合
(=$P(C_{\mathrm{post}}\mid C_{\mathrm{pre}},x)$)」の積ですね。&lt;/p&gt;

&lt;p&gt;抽象的で申し訳ないのですが、上のように図を描いていけば伝わると思います。
そして以上の式(2)を式(1)と組み合わせると、最終的に次の値を求めればよいことになります。&lt;/p&gt;

&lt;p&gt;$
P(x\mid C_{\mathrm{pre}},C_{\mathrm{post}})
\propto P(C_{\mathrm{pre}}\mid x)P(C_{\mathrm{post}}\mid C_{\mathrm{pre}},x)P(x)
…(A)
$&lt;/p&gt;

&lt;p&gt;あと一息なのですが、式(A)の右辺にある$P(C_{\mathrm{pre}}\mid x)$ も
$x \to C_{\mathrm{pre}}$ みたいで時間と逆行していて嫌です。
しかもモデルが直接求める値ではないので排除したいです。
幸い、これもベイズでひっくり返せます（式B）。&lt;/p&gt;

&lt;p&gt;$
P(C_{\mathrm{pre}}\mid x)=\frac{P(x\mid C_{\mathrm{pre}})P(C_{\mathrm{pre}})}{P(x)}
…(B)
$&lt;/p&gt;

&lt;p&gt;この式(B)を式(A)の右辺に代入すると、式(B)の分母と式(A)の分子にある$P(x)$が消しあってくれます。
そして候補間の比較において、$P(C_{\mathrm{pre}})$ は共通しているので無視できます。
そうすると、最終的に式(C)がえられるので、これを採用します。&lt;/p&gt;

&lt;p&gt;$
P(x\mid C_{\mathrm{pre}},C_{\mathrm{post}})
\propto P(C_{\mathrm{post}}\mid C_{\mathrm{pre}},x)P(x\mid C_{\mathrm{pre}})
…(C)
$&lt;/p&gt;

&lt;p&gt;この確率を反映するスコアを求めるにあたっては対数を使います。
対数を使うと確率の掛け算が足し算になって便利ですね。
もはや確率ではないので尤度（ゆうど）という名前で、
さらに言うとスケールが対数なので「対数尤度」です。&lt;/p&gt;

&lt;p&gt;$
s(x)=\log P(C_{\mathrm{post}}\mid C_{\mathrm{pre}}, x)+\log P(x\mid C_{\mathrm{pre}})
…(Score)
$&lt;/p&gt;

&lt;p&gt;最後に、候補集合 ($X$) 上で softmaxという操作をして事後分布とみなします。
急に雑な説明になりましたが、
ここも&lt;a href=&quot;https://www.youtube.com/watch?v=5CwLT-IQB9E&quot;&gt;アイシア先生の動画&lt;/a&gt;を
見るとソフトマックスの気持ちがわかります。&lt;/p&gt;

&lt;p&gt;$
P(x\mid C_{\mathrm{pre}},C_{\mathrm{post}})
=\frac{\exp(s(x))}{\sum_{x\in X}\exp(s(x))}
…(Softmax)
$&lt;/p&gt;

&lt;p&gt;こうすることで、文脈を与えた時の候補の確率分布が得られるわけです。&lt;/p&gt;

&lt;h3 id=&quot;ソフトマックス時の注意-概念を代表させる単語&quot;&gt;ソフトマックス時の注意: 概念を代表させる単語&lt;/h3&gt;

&lt;p&gt;ただソフトマックスは、(i)文脈を与えた上の単語の確率をもとめて、
(ii)その確率の大きさで確率分布をつくっているだけ、という点に注意です。
なので、例えば「ハチ公」という概念を$c_\mathrm{hachi}$としたとき、
$P(c_\mathrm{hachi}\mid C_{\mathrm{pre}},C_{\mathrm{post}})$
を求められているわけではないのです。
あくまでも、求まるのは $P(x\mid C_{\mathrm{pre}},C_{\mathrm{post}})$ です。&lt;/p&gt;

&lt;p&gt;すこし話を具体的にしましょう。
たとえば、「ハチ公」という概念を単語で表現するとき、
色々な$x$が考えられるわけです。 これを $P(x \mid c_\mathrm{hachi})$ としましょう。
この$x$には「ハチ公」だったり「忠犬ハチ公」だったり、
いろいろな単語が入りうるわけですね。
そのいろんな $P(x \mid c_\mathrm{hachi})$ の和をとって1となるなら、
その概念のあらゆる表現方法を網羅したことになります。&lt;/p&gt;

&lt;p&gt;でも、上で求めた
$P(x\mid C_{\mathrm{pre}},C_{\mathrm{post}})$はそんなことを一切考慮していません。
なので、$P(c_\mathrm{hachi}\mid C_{\mathrm{pre}},C_{\mathrm{post}})$ において
$P(x_\mathrm{chuuken-hachi} \mid c_\mathrm{hachi})$ と
$P(x_\mathrm{hachikou} \mid c_\mathrm{hachi})$ の確率が
それぞれ0.8、0.2なんてこともありうるわけですね。
なので概念の代表とする単語によって、
最終的なソフトマックスの値が全然違う、ということはありうる点に注意が必要です。&lt;/p&gt;

&lt;h3 id=&quot;各項の求め方とトークンについて&quot;&gt;各項の求め方と「トークン」について&lt;/h3&gt;

&lt;p&gt;おさらいですが、上のScoreにあるように、まずは以下の2つを求めることになります。
以下の計算において、一筋縄ではいかない部分を&lt;u&gt;**太字+下線**&lt;/u&gt;にしています。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;$\log P(x\mid C_{\mathrm{pre}})$:
入力に $C_{\mathrm{pre}}+ x$ を与え、
&lt;u&gt;**x部分の**&lt;/u&gt;トークン対数確率の総和を取得。&lt;/li&gt;
  &lt;li&gt;$\log P(C_{\mathrm{post}}\mid x, C_{\mathrm{pre}})$:
入力に $C_{\mathrm{pre}}+ x+ C_{\mathrm{post}}$ を与え、
&lt;u&gt;**C_post 部分の**&lt;/u&gt;総和を取得。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;日本語では「トークン」の境界が前後の文字で変わりやすいです。
なので、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prefix&lt;/code&gt;のみと &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prefix+target&lt;/code&gt; をそれぞれトークン化し、
対象区間だけをトークン単位で切り出して合算しなくてはなりません。
後述しますが、トークンは単語でも形態素でも文字でもありません。&lt;/p&gt;

&lt;p&gt;さて、ようやく「トークン」に関してですが、
大規模言語モデル（GPT系列を含む）では、
テキストをトークンという単位に分割して処理します。
トークンは文字でも単語でも形態素でもなく、
BPE（Byte-Pair Encoding）というアルゴリズムで決定される
文字列単位です。&lt;/p&gt;

&lt;p&gt;日本語のトークン化の例は以下のようになります（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;�&lt;/code&gt;は文字として表現されない情報を示します）。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;「渋谷の」→ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;['�', '�', '谷', 'の']&lt;/code&gt; (なんでやねんという感じですが、4トークン)&lt;/li&gt;
  &lt;li&gt;「ハチ公」→ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;['ハ', 'チ', '公']&lt;/code&gt; (3トークン)&lt;/li&gt;
  &lt;li&gt;「スクランブル交差点」→ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;['スクランブル', '交差', '点']&lt;/code&gt; (3トークン)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;このトークン化には、いくつか特徴があります。
まず、同じ文字列でも、前後の文脈によって分割が変わる場合があります（文脈依存性）。
そして、1文字が1トークンとは限りません（頻出する文字列は長いトークンになります）。
そして、語彙サイズは制限されており、例えばGPT-OSSだったら
20万トークンという固定された語彙からモデルは選択します。
LLMは次のトークンを予測するように学習されています。&lt;/p&gt;

&lt;p&gt;個人的に便利だ思っているのがOpenAIが公開している
&lt;a href=&quot;https://platform.openai.com/tokenizer&quot;&gt;OpenAI Tokenizer&lt;/a&gt;です。
あと、以前からの同僚に&lt;a href=&quot;https://github.com/openai/tiktoken&quot;&gt;tiktoken&lt;/a&gt;
というツールも教えていただきました。
こうしたツールを使うと、
任意のテキストがどのようにトークン化されるか視覚的に確認できます。
異なるモデル（GPT-3.5、GPT-4など）のトークナイザーを比較できて、
GPT-OSSはGPT-4に近いと思います（「渋谷の」が4に分割されるという挙動が同じなので）。&lt;/p&gt;

&lt;p&gt;使用例として、以下のようなトークン化になります。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;入力: 「昨日、渋谷の名物であるハチ公を見に行った。」

トークン化結果（GPT-3.5/GPT-4の場合）:
['昨日', '、', '�', '�', '谷', 'の', '名', '物', 'である', 'ハ', 'チ', '公', 'を', '見', 'に', '行', 'った', '。']
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ちなみに、おもしろいのが英語におけるスペースの扱いです。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;I like cats&quot; → [&quot;I&quot;, &quot; like&quot;, &quot; cats&quot;]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;パーサーで単語に区切って構造をあてる時代は単語にスペースで区切る、
という処理が当たり前でした。日本語もそうですね。
ただ、この違いが形態素解析だったり分かち書きする処理の必要性を生んでました。
他方、このようにスペースを特別視しないことで、
英語も日本語も同じモデルが適用可能になるわけですね。
YANSで聴講したグーグル合同会社の工藤拓先生の講義で
聞いた話なのですが、勉強になりました。
こういう一般化、学んでいきたいですね。&lt;/p&gt;

&lt;p&gt;実装では、以下のようにして対数尤度をトークンを考慮して求めます。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;左文脈のみ&lt;/strong&gt;でトークン化 → トークン数 $n_{\text{left}}$ を取得&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;左文脈+候補語&lt;/strong&gt;でトークン化 → トークン数 $n_{\text{left+cand}}$ を取得&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;候補語部分&lt;/strong&gt;のトークン数を計算 = $n_{\text{left+cand}} - n_{\text{left}}$&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;候補語部分の対数尤度&lt;/strong&gt; = トークン[$n_{\text{left}}$:$n_{\text{left+cand}}$]の対数尤度の和&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;この方法により、「候補語がどのようにトークン化されるか」を事前に知る必要がなくなります。&lt;/p&gt;

&lt;h3 id=&quot;注意点-長さバイアスへの対処&quot;&gt;注意点: 長さバイアスへの対処&lt;/h3&gt;

&lt;p&gt;トークンの幅を考える際、その長さによる確率の和の変化も気を付けないとなりません。
そもそもとして、確率の積や対数尤度の和を考える場合、
短い候補が有利になりやすいという特徴があります。
確率の積なら小さい値の掛け算が長くなるということですし、
対数尤度なら負の値を足し続けることになるからです。&lt;/p&gt;

&lt;p&gt;長くなるほど確率が下がる、というのは概念に対しては不適切なので、
実装では、候補語部分（$x$）トークン長による正規化を適用します。&lt;/p&gt;

&lt;p&gt;$
\text{normalized-logp-candidate} = \frac{\log P(x \mid C_{\mathrm{pre}})}{\text{divisor}}
$&lt;/p&gt;

&lt;p&gt;なお、右文脈 $\log P(C_{\mathrm{post}} \mid C_{\mathrm{pre}}, x)$は
候補語間での比較において公平なので正規化を適用しません。&lt;/p&gt;

&lt;p&gt;正規化手法の種類としては、以下のパターンを用意してあります。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;NONE&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;divisor = 1.0&lt;/code&gt; （正規化なし。短い候補が有利になりますが計算は単純）&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;TOKEN&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;divisor = max(1, token_length)&lt;/code&gt; （トークン数で除算。BPE分割に基づく正規化、技術的に最も適切）&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;CHAR&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;divisor = max(1, len(candidate))&lt;/code&gt; （文字数で除算。人間の直感的な長さ感覚に近い評価ですが技術的には不適切。対数尤度の足し算の数と、割り算の分母が一致しない場合もあります）&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;実装の特徴とパフォーマンス最適化結果など&quot;&gt;実装の特徴とパフォーマンス最適化、結果など&lt;/h2&gt;

&lt;p&gt;計算を実装するにあたり以下の点を工夫しています。
実装する方がいらっしゃれば参考にしていただければと思います。
以下では、実装するときの工夫や実際に動かしてみた結果などを共有します。&lt;/p&gt;

&lt;h3 id=&quot;工夫した点&quot;&gt;工夫した点&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;&amp;lt;左文脈&amp;gt;_&amp;lt;右文脈&amp;gt;&quot;&lt;/code&gt; という入力形式で文脈を与え、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;split(&quot;_&quot;)&lt;/code&gt; でleft/right contextに分割&lt;/li&gt;
  &lt;li&gt;両文脈がそれぞれ空文字の場合も対処（例えばですが、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;_&amp;lt;右文脈&amp;gt;&quot;&lt;/code&gt; を許します）&lt;/li&gt;
  &lt;li&gt;左文脈のキャッシュ化(候補数だけ計算させるの無駄なので&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;joblib&lt;/code&gt;の&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Memory&lt;/code&gt;を使ってます)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# サンプルコード
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;evaluate_candidates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;left_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;right_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;candidates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;include_right_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;length_norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LengthNormalization&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LengthNormalization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NONE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CandidateResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Evaluate all candidates for the cloze task.

    Unified implementation that handles both cases (with/without left context)
    using the same logic. When left_context is empty, left_echo becomes an
    empty EchoLogProbs and left_token_count becomes 0, making the logic
    naturally handle the no-left-context case.
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Note: include_right_context is allowed even when left_context is empty.
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# In that case, we compute logP(right | candidate).
&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Get left context logprobs (skip API call if no left context)
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;left_token_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# なければこのまま0になる
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;left_echo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EchoLogProbs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_echo_logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;left_token_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left_echo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Calculate logP(candidate | left) - when left is empty, this becomes logP(candidate)
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;left_cand_echo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EchoLogProbs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_echo_logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left_context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;left_cand_token_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left_cand_echo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;candidate_token_length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left_cand_token_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left_token_count&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logp_candidate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sum_segment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;left_cand_echo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left_token_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left_cand_token_count&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# Calculate logP(right | left + candidate) - 0.0 if no right_context
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;full_echo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EchoLogProbs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_echo_logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left_context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;full_token_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;full_echo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logp_right&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sum_segment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;full_echo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left_cand_token_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;full_token_count&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# Apply include_right_context using multiplication (more efficient than branching)
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;logp_right_weighted&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logp_right&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;include_right_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# Apply length normalization
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;divisor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_normalization_divisor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length_norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate_token_length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;normalized_logp_candidate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logp_candidate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;divisor&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# Calculate final score
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;score&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalized_logp_candidate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logp_right_weighted&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;CandidateResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;logp_cand_normalized&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;normalized_logp_candidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;logp_right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logp_right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Store original value for transparency
&lt;/span&gt;                &lt;span class=&quot;n&quot;&gt;token_length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;candidate_token_length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Calculate relative probabilities
&lt;/span&gt;    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_calculate_relative_probabilities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;実装のポイントとしては、APIを呼ぶときにキャッシュしている部分です。
この関数は以下のキャッシュしていない関数の引数と返り値を
一つ上の処理でキャッシュしているので、
同じモデル、同じプロンプトが来たときは
同じ&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EchoLogProbs&lt;/code&gt;を返します。&lt;/p&gt;

&lt;p&gt;確率を計算しているのは以下の三つの行ですね。&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;left_echo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EchoLogProbs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_echo_logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;left_cand_echo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EchoLogProbs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_echo_logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left_context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;full_echo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EchoLogProbs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_echo_logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left_context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;なお、この実装は公開しています（GitHub: https://github.com/kishiyamat/llm-proba）。
本実装では、上述したvLLMが提供するOpenAI互換APIを使用しています。
具体的には &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/v1/completions&lt;/code&gt; エンドポイントを利用することで、
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo=True&lt;/code&gt; と &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logprobs&lt;/code&gt; パラメータによるプロンプトトークンの確率取得を実現しています。&lt;/p&gt;

&lt;p&gt;技術的制約として、 OpenAIの新しいChat Completions API (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/v1/chat/completions&lt;/code&gt;) では
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo&lt;/code&gt; パラメータがサポートされていません。
なので、OpenAI公式のGPT-4やGPT-3.5-turboでは、
従来の Completions API が非推奨・廃止されているため、
vLLMとオープンソースモデルの組み合わせが必須となるのでした。&lt;/p&gt;

&lt;p&gt;APIパラメータの詳細は以下の通りです。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo&lt;/code&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;True&lt;/code&gt; に設定することで、プロンプト全体の各トークンの対数尤度を返させています。
    &lt;ul&gt;
      &lt;li&gt;例: 「渋谷の」を入力 → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;['�', '�', '谷', 'の']&lt;/code&gt; の4トークンに分割され、各トークンの対数尤度を取得できます。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logprobs&lt;/code&gt;: 整数値（0-5）で、各トークンの上位N個の候補と確率を返す数を指定します（本実装では &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; を使用）。
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logprobs=1&lt;/code&gt;: 最も確率の高い1つの候補のみ（本実装で使用）
        &lt;ul&gt;
          &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logprobs=5&lt;/code&gt;: 上位5つの候補とその確率（デバッグや詳細分析に有用ですが、今回は使っていません）&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;temperature&lt;/code&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.0&lt;/code&gt; に設定して決定的(“deterministic”の意味です)な出力を得ています。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;呼ぶときのキャッシュしないバージョンは以下です。
これを&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;joblib&lt;/code&gt;の&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Memory&lt;/code&gt;でキャッシュさせています。&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_get_echo_logprobs_uncached&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EchoLogProbs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Get echo logprobs using direct HTTP request.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Make direct HTTP request to /v1/completions endpoint (OpenAI compatible)
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base_url&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/v1/completions&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;prompt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;max_tokens&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;echo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;logprobs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Number of top logprobs to return (0-5)
&lt;/span&gt;            &lt;span class=&quot;s&quot;&gt;&quot;temperature&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;60.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;raise_for_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;choices&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;choices&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;APIError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Invalid response format from API&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;choices&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logprobs_obj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;logprobs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logprobs_obj&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;tokens&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logprobs_obj&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;token_logprobs&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logprobs_obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;APIError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;No logprobs returned from API&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# Extract tokens and logprobs from the response
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logprobs_obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;tokens&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logprobs_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logprobs_obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;token_logprobs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logprobs_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;APIError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;No tokens or logprobs returned from API&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# Handle None values in logprobs by replacing with log(MIN_PROBABILITY)
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;logprobs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lp&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MIN_PROBABILITY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lp&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logprobs_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EchoLogProbs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;logprobs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RequestException&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;error_msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;404&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error_msg&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Not Found&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error_msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;APIError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Model &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; endpoint not found&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;APIError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Error getting logprobs for prompt: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_msg&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;APIError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Error getting logprobs for prompt: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;パラメータはわかりづらいので、いくつか例を見てみましょう。&lt;/p&gt;

&lt;h3 id=&quot;echotrue-と-logprobs1-による確率計算の具体例&quot;&gt;echo=True と logprobs=1 による確率計算の具体例&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo=True&lt;/code&gt; を使うことで、プロンプト全体の各トークンの対数尤度が得られます。
これを利用して、候補語部分だけの確率を抽出する方法を示します。
「渋谷のハチ公」を例に考えていきましょう。まず、左文脈として「渋谷の」
を入力したとします。その際のAPIレスポンスは以下のようになります。
実装上は&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dict&lt;/code&gt;だと中身がわかりづらいので&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EchoLogProbs&lt;/code&gt;という型を作っています。&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;EchoLogProbsとして格納&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tokens&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;�&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;�&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;谷&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;の&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;token_logprobs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;-3.824&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;-0.900&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;-1.684&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;このようなレスポンスになるので、
(i) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;左文脈&lt;/code&gt; (ii) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;左文脈+候補語&lt;/code&gt;
と二回計算します。この(i)と(ii)のトークン数の差分が
候補語のトークン数となり、
また足すべき幅もわかるわけです。&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# 1. 左文脈「渋谷の」のトークン数を取得
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left_echo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_echo_logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;渋谷の&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;left_token_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left_echo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# = 4
&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 2. 「渋谷の」+「ハチ公」のトークンと対数尤度を取得
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left_cand_echo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_echo_logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;渋谷のハチ公&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# tokens: ['�', '�', '谷', 'の', 'ハ', 'チ', '公']
# logprobs: [None, -3.8, -0.9, -1.7, -2.1, -1.5, -0.8]
&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 3. 「ハチ公」部分（トークン4-7）の対数尤度を合計
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logp_candidate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left_cand_echo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logprobs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# = -2.1 + -1.5 + -0.8 = -4.4
&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# これがP(ハチ公|渋谷の)の対数確率
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;この手法により、トークン境界に依存せず任意の文字列区間の確率を計算できます。&lt;/p&gt;

&lt;h3 id=&quot;結果&quot;&gt;結果&lt;/h3&gt;

&lt;p&gt;ツールにする場合は以下のようにCLI化したり、
StreamlitやGradioなどを使ってGUIを作るのもよいと思います。
いくつか面白そうな例で回してみましょう。&lt;/p&gt;

&lt;h4 id=&quot;渋谷の名物は&quot;&gt;渋谷の名物は？&lt;/h4&gt;

&lt;p&gt;CLIを作成したのですが、いくつか引数を渡せるようにすると便利で、
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--with-right&lt;/code&gt;で右の文脈を考慮するようにしてます。
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--length-norm&lt;/code&gt;で標準化の方法ですね。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 基本的な評価&lt;/span&gt;
uv run python cloze.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://localhost:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; openai/gpt-oss-20b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cloze&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;昨日、渋谷の名物である_を見に行った。&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cands&lt;/span&gt; ハチ公 スクランブル交差点 スタバ 公園&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--with-right&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--length-norm&lt;/span&gt; token
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;この結果は以下になります。
ハチ公が多いですね。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cand      score   P_rel   logP_norm  logP_r  tok_len
ハチ公    &lt;span class=&quot;nt&quot;&gt;-11&lt;/span&gt;.152  0.598   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.857    &lt;span class=&quot;nt&quot;&gt;-10&lt;/span&gt;.295   3
スクラ... &lt;span class=&quot;nt&quot;&gt;-11&lt;/span&gt;.771  0.322   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.394    &lt;span class=&quot;nt&quot;&gt;-11&lt;/span&gt;.377   6
公園      &lt;span class=&quot;nt&quot;&gt;-13&lt;/span&gt;.313  0.069   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.805    &lt;span class=&quot;nt&quot;&gt;-12&lt;/span&gt;.508   2
スタバ    &lt;span class=&quot;nt&quot;&gt;-15&lt;/span&gt;.080  0.012   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.777    &lt;span class=&quot;nt&quot;&gt;-13&lt;/span&gt;.304   2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;先述した通り、概念ではなく単語なので、
単語を足していくとどんどんと
ソフトマックスした確率は上がっていきます。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 基本的な評価&lt;/span&gt;
uv run python cloze.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://localhost:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; openai/gpt-oss-20b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cloze&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;昨日、渋谷の名物である_を見に行った。&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cands&lt;/span&gt; ハチ公 スクランブル交差点 スタバ 公園 ハチ公像 ハチ公の像 忠犬ハチ公 忠犬ハチ公の像&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--with-right&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--length-norm&lt;/span&gt; token

cand           score    P_rel   logP_norm  logP_r   tok_len
ハチ公の像      &lt;span class=&quot;nt&quot;&gt;-9&lt;/span&gt;.612   0.317   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.947     &lt;span class=&quot;nt&quot;&gt;-8&lt;/span&gt;.665   5
忠犬ハチ公...   &lt;span class=&quot;nt&quot;&gt;-9&lt;/span&gt;.773   0.270   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.423     &lt;span class=&quot;nt&quot;&gt;-9&lt;/span&gt;.350   7
忠犬ハチ公      &lt;span class=&quot;nt&quot;&gt;-10&lt;/span&gt;.313  0.158   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.280     &lt;span class=&quot;nt&quot;&gt;-10&lt;/span&gt;.033  5
ハチ公像        &lt;span class=&quot;nt&quot;&gt;-10&lt;/span&gt;.427  0.141   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.031     &lt;span class=&quot;nt&quot;&gt;-9&lt;/span&gt;.396   4
ハチ公          &lt;span class=&quot;nt&quot;&gt;-11&lt;/span&gt;.152  0.068   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.857     &lt;span class=&quot;nt&quot;&gt;-10&lt;/span&gt;.295  3
スクラ...       &lt;span class=&quot;nt&quot;&gt;-11&lt;/span&gt;.771  0.037   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.394     &lt;span class=&quot;nt&quot;&gt;-11&lt;/span&gt;.377  6
公園            &lt;span class=&quot;nt&quot;&gt;-13&lt;/span&gt;.313  0.008   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.805     &lt;span class=&quot;nt&quot;&gt;-12&lt;/span&gt;.508  2
スタバ          &lt;span class=&quot;nt&quot;&gt;-15&lt;/span&gt;.080  0.001   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.777     &lt;span class=&quot;nt&quot;&gt;-13&lt;/span&gt;.304  2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;これをみると、「ハチ公」よりも「ハチ公の像」の方が
あてはまりがよさそうですね。
結局、この候補なら87.4%でハチ公が選択されることになります。
まぁ「スタバ」を「スターバックス」にしたら、
そっちの確率が高くなったりしそうなので
候補に依存する指標ではあるのですが…。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 基本的な評価&lt;/span&gt;
uv run python cloze.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://localhost:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; openai/gpt-oss-20b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cloze&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;昨日、渋谷の名物である_を見に行った。&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cands&lt;/span&gt; ハチ公の像 スクランブル交差点 スタバ 公園 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--with-right&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--length-norm&lt;/span&gt; token

cand       score     P_rel  logP_norm  logP_r  tok_len
ハチ公の像   &lt;span class=&quot;nt&quot;&gt;-9&lt;/span&gt;.612   0.874   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.947   &lt;span class=&quot;nt&quot;&gt;-8&lt;/span&gt;.665   5
スクラ...    &lt;span class=&quot;nt&quot;&gt;-11&lt;/span&gt;.771  0.101   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.394   &lt;span class=&quot;nt&quot;&gt;-11&lt;/span&gt;.377  6
公園         &lt;span class=&quot;nt&quot;&gt;-13&lt;/span&gt;.313  0.022   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.805   &lt;span class=&quot;nt&quot;&gt;-12&lt;/span&gt;.508  2
スタバ       &lt;span class=&quot;nt&quot;&gt;-15&lt;/span&gt;.080  0.004   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.777   &lt;span class=&quot;nt&quot;&gt;-13&lt;/span&gt;.304  2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;あと、ちゃんと右の文脈を考慮できているかをチェックするために
「を見に行った。」ではなく「を歩いた。」とかにしてみましょうか。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 基本的な評価&lt;/span&gt;
uv run python cloze.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://localhost:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; openai/gpt-oss-20b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cloze&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;昨日、渋谷の名物である_を歩いた。&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cands&lt;/span&gt; ハチ公の像 スクランブル交差点 スタバ 公園 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--length-norm&lt;/span&gt; token

cand       score     P_rel  logP_norm  logP_r  tok_len
スクラ...   &lt;span class=&quot;nt&quot;&gt;-11&lt;/span&gt;.061   0.747   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.394  &lt;span class=&quot;nt&quot;&gt;-10&lt;/span&gt;.667 6
公園        &lt;span class=&quot;nt&quot;&gt;-12&lt;/span&gt;.504   0.176   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.805  &lt;span class=&quot;nt&quot;&gt;-11&lt;/span&gt;.699 2
ハチ公の像   &lt;span class=&quot;nt&quot;&gt;-13&lt;/span&gt;.360   0.075   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.947  &lt;span class=&quot;nt&quot;&gt;-12&lt;/span&gt;.413 5
スタバ      &lt;span class=&quot;nt&quot;&gt;-17&lt;/span&gt;.054   0.002   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.777  &lt;span class=&quot;nt&quot;&gt;-15&lt;/span&gt;.277 2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ちゃんと考慮できていますね。&lt;/p&gt;

&lt;h4 id=&quot;日比谷渋谷&quot;&gt;日比谷？　渋谷&lt;/h4&gt;

&lt;p&gt;手前みそなのですが、
『ITエンジニアと眺める言語学』
という本を出しました。
そこで「日比谷」と「渋谷」の聞き間違えの話があるので、
例にしてみましょう。&lt;/p&gt;

&lt;p&gt;そのままだと、以下のように「渋谷」が優勢です。
でも、実際の発話は「日比谷」だったとします。
さて、どうしたらよいでしょうか。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 基本的な評価&lt;/span&gt;
uv run python cloze.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://localhost:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; openai/gpt-oss-20b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cloze&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;_の公園に行った。&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cands&lt;/span&gt; 日比谷 渋谷 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--with-right&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--length-norm&lt;/span&gt; token

cand    score   P_rel   logP_cand_norm  logP_right      tok_len
渋谷    &lt;span class=&quot;nt&quot;&gt;-26&lt;/span&gt;.478 0.684   &lt;span class=&quot;nt&quot;&gt;-6&lt;/span&gt;.786  &lt;span class=&quot;nt&quot;&gt;-19&lt;/span&gt;.692 4
日比谷  &lt;span class=&quot;nt&quot;&gt;-27&lt;/span&gt;.248 0.316   &lt;span class=&quot;nt&quot;&gt;-8&lt;/span&gt;.135  &lt;span class=&quot;nt&quot;&gt;-19&lt;/span&gt;.114 4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;さて、どうしたらこの「日比谷」を優位にできるでしょうか。
例えば「自然に触れたくて」とかはどうでしょう。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run python cloze.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://localhost:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; openai/gpt-oss-20b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cloze&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;自然に触れたくて_の公園に行った。&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cands&lt;/span&gt; 日比谷 渋谷 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--with-right&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--length-norm&lt;/span&gt; token

cand    score   P_rel   logP_cand_norm  logP_right      tok_len
渋谷    &lt;span class=&quot;nt&quot;&gt;-12&lt;/span&gt;.626 0.828   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.795  &lt;span class=&quot;nt&quot;&gt;-11&lt;/span&gt;.831 3
日比谷  &lt;span class=&quot;nt&quot;&gt;-14&lt;/span&gt;.198 0.172   &lt;span class=&quot;nt&quot;&gt;-2&lt;/span&gt;.897  &lt;span class=&quot;nt&quot;&gt;-11&lt;/span&gt;.301 3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;だめですね。そもそも日比谷公園に行ったことがないし、
いい直感がありません。日比谷は千代田区にありますが、
「千代田区の」とつけてみてはどうでしょうか。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run python cloze.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://localhost:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; openai/gpt-oss-20b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cloze&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;千代田区にある_の公園に行った。&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cands&lt;/span&gt; 日比谷 渋谷 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--with-right&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--length-norm&lt;/span&gt; token

cand    score   P_rel   logP_cand_norm  logP_right      tok_len
日比谷  &lt;span class=&quot;nt&quot;&gt;-19&lt;/span&gt;.411 0.632   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.741  &lt;span class=&quot;nt&quot;&gt;-18&lt;/span&gt;.670 3
渋谷    &lt;span class=&quot;nt&quot;&gt;-19&lt;/span&gt;.952 0.368   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.425  &lt;span class=&quot;nt&quot;&gt;-19&lt;/span&gt;.527 3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;えらい！　ちゃんと日比谷が優位になりました。&lt;/p&gt;

&lt;h4 id=&quot;拍手と握手&quot;&gt;拍手と握手&lt;/h4&gt;

&lt;p&gt;ほかには拍手と握手なんてのもありますね。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run python cloze.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://localhost:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; openai/gpt-oss-20b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cloze&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;太郎は_拍手した。&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cands&lt;/span&gt; パチパチと ぎゅっと &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--with-right&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--length-norm&lt;/span&gt; token

cand    score   P_rel   logP_cand_norm  logP_right      tok_len
ぎゅっと        &lt;span class=&quot;nt&quot;&gt;-6&lt;/span&gt;.948  0.868   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.315  &lt;span class=&quot;nt&quot;&gt;-5&lt;/span&gt;.633  3
パチパチと      &lt;span class=&quot;nt&quot;&gt;-8&lt;/span&gt;.831  0.132   &lt;span class=&quot;nt&quot;&gt;-2&lt;/span&gt;.644  &lt;span class=&quot;nt&quot;&gt;-6&lt;/span&gt;.186  5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;これはうまくいっていませんね。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run python cloze.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://localhost:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; openai/gpt-oss-20b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cloze&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;太郎はパチパチ_した。&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cands&lt;/span&gt; 握手 拍手 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--with-right&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--length-norm&lt;/span&gt; token

cand    score   P_rel   logP_cand_norm  logP_right      tok_len
拍手    &lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;.812  0.692   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.653  &lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;.160  2
握手    &lt;span class=&quot;nt&quot;&gt;-4&lt;/span&gt;.622  0.308   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.367  &lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;.255  2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run python cloze.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://localhost:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; openai/gpt-oss-20b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cloze&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;太郎はガッシリ_した。&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cands&lt;/span&gt; 握手 拍手 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--with-right&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--length-norm&lt;/span&gt; token

cand    score   P_rel   logP_cand_norm  logP_right      tok_len
拍手    &lt;span class=&quot;nt&quot;&gt;-4&lt;/span&gt;.038  0.637   &lt;span class=&quot;nt&quot;&gt;-0&lt;/span&gt;.661  &lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;.378  2
握手    &lt;span class=&quot;nt&quot;&gt;-4&lt;/span&gt;.602  0.363   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.282  &lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;.321  2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;これらもダメそうです。
「ガッシリ拍手」なんて聞いたこともないのですが…。
ちょっとそもそもの確率から見てみましょう。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run python cloze.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://localhost:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; openai/gpt-oss-20b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cloze&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;太郎は_と拍手した。&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cands&lt;/span&gt; パチパチ ぎゅっ ガッシリ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--length-norm&lt;/span&gt; token
    &lt;span class=&quot;c&quot;&gt;# --with-right \&lt;/span&gt;

cand      score   P_rel   logP_cand_norm  tok_len
ぎゅっ    &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.846  0.478   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.846  3
ガッシリ  &lt;span class=&quot;nt&quot;&gt;-2&lt;/span&gt;.275  0.311   &lt;span class=&quot;nt&quot;&gt;-2&lt;/span&gt;.275  4
パチパチ  &lt;span class=&quot;nt&quot;&gt;-2&lt;/span&gt;.666  0.211   &lt;span class=&quot;nt&quot;&gt;-2&lt;/span&gt;.666  4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;右の文脈を見なければ、「ぎゅっ」や「ガッシリ」のほうが
頻度が高い状態です。
いろんなパターンも見てみましょう。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run python cloze.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://localhost:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; openai/gpt-oss-20b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cloze&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;太郎は_と拍手した。&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cands&lt;/span&gt; パチパチ ぎゅっ ガッシリ ぱちぱち ぱちぱちと がっしり がしっ ぱちっ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--length-norm&lt;/span&gt; token
    &lt;span class=&quot;c&quot;&gt;# --with-right \&lt;/span&gt;

cand    score   P_rel   logP_cand_norm  tok_len
がっしり   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.425  0.258   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.425  4
ぱちぱち   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.767  0.183   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.767  4
ぎゅっ     &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.846  0.169   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.846  3
ぱちぱちと &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.992  0.146   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.992  5
ガッシリ   &lt;span class=&quot;nt&quot;&gt;-2&lt;/span&gt;.275  0.110   &lt;span class=&quot;nt&quot;&gt;-2&lt;/span&gt;.275  4
パチパチ   &lt;span class=&quot;nt&quot;&gt;-2&lt;/span&gt;.666  0.075   &lt;span class=&quot;nt&quot;&gt;-2&lt;/span&gt;.666  4
ぱちっ     &lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;.365  0.037   &lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;.365  3
がしっ     &lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;.917  0.021   &lt;span class=&quot;nt&quot;&gt;-3&lt;/span&gt;.917  3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;うーむ、ふしぎですね。とりあえず、様態を表す代表は
ひらがなにしてみましょう。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run python cloze.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://localhost:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; openai/gpt-oss-20b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cloze&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;太郎は_と拍手した。&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cands&lt;/span&gt; ぱちぱち ぎゅっ がっしり &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--with-right&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--length-norm&lt;/span&gt; token

cand    score   P_rel   logP_cand_norm  logP_right      tok_len
ぎゅっ    &lt;span class=&quot;nt&quot;&gt;-7&lt;/span&gt;.479  0.995   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.846  &lt;span class=&quot;nt&quot;&gt;-5&lt;/span&gt;.633  3
ぱちぱち  &lt;span class=&quot;nt&quot;&gt;-12&lt;/span&gt;.785 0.005   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.767  &lt;span class=&quot;nt&quot;&gt;-11&lt;/span&gt;.018 4
がっしり  &lt;span class=&quot;nt&quot;&gt;-17&lt;/span&gt;.152 0.000   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.425  &lt;span class=&quot;nt&quot;&gt;-15&lt;/span&gt;.727 4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;どうすればこうなるのでしょうか。
理由を入れてみましょうか。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run python cloze.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://localhost:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; openai/gpt-oss-20b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cloze&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;太郎は感激して_と拍手した。&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cands&lt;/span&gt; ぱちぱち ぎゅっ がっしり &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--with-right&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--length-norm&lt;/span&gt; token

cand    score   P_rel   logP_cand_norm  logP_right      tok_len
ぱちぱち        &lt;span class=&quot;nt&quot;&gt;-6&lt;/span&gt;.411  0.602   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.358  &lt;span class=&quot;nt&quot;&gt;-5&lt;/span&gt;.053  4
ぎゅっ  &lt;span class=&quot;nt&quot;&gt;-6&lt;/span&gt;.842  0.391   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.715  &lt;span class=&quot;nt&quot;&gt;-5&lt;/span&gt;.127  3
がっしり        &lt;span class=&quot;nt&quot;&gt;-10&lt;/span&gt;.983 0.006   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.330  &lt;span class=&quot;nt&quot;&gt;-9&lt;/span&gt;.652  4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ようやく「ぱちぱち」が上になりました。
色々と工夫が必要そうです。&lt;/p&gt;

&lt;p&gt;ほかにも、「握手」を優位させるためにいくつか試してみました。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“太郎は自分の名前を言った後に_した。” -&amp;gt; 0.449&lt;/li&gt;
  &lt;li&gt;“太郎は相手の目を見て_した。” -&amp;gt; 0.509&lt;/li&gt;
  &lt;li&gt;“太郎はぎゅっと_した。”  -&amp;gt;  0.172&lt;/li&gt;
  &lt;li&gt;“太郎は「初めまして」と言いながら_した。” -&amp;gt;  0.516&lt;/li&gt;
  &lt;li&gt;“太郎は取引先の営業と_した。” -&amp;gt;  0.532&lt;/li&gt;
  &lt;li&gt;“取引先の営業と_するのはマナーだ。” -&amp;gt; 0.851&lt;/li&gt;
  &lt;li&gt;“太郎は礼儀正しく取引先の営業と_した。” -&amp;gt;  0.450&lt;/li&gt;
  &lt;li&gt;“取引先の営業と_して名刺を交換した。” -&amp;gt;  0.688&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;うーむ、わかりませんね。
そもそも「太郎は」みたいなジェネリックな、
よくプレースホルダーとして使われるものを使うのがよくない気もしてきました。
今後も使っていくツールになるので、
特徴はつかんでいこうと思います。&lt;/p&gt;

&lt;!--
uv run python cloze.py \
    --base-url http://localhost:8000 \
    --model openai/gpt-oss-20b \
    --cloze &quot;取引先の営業と_して名刺を交換した。&quot; \
    --cands 握手 拍手 \
    --with-right \
    --length-norm token
--&gt;

&lt;h4 id=&quot;英語のテスト&quot;&gt;英語のテスト&lt;/h4&gt;

&lt;p&gt;まぁこれは得意ですよね。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run python cloze.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://localhost:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; openai/gpt-oss-20b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cloze&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;The machine can be very dangerous, especially when it _ in motion.&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cands&lt;/span&gt; is moves goes has &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--with-right&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--length-norm&lt;/span&gt; token

&lt;span class=&quot;c&quot;&gt;# 英語だと &quot;The machine can be very dangerous, especially when it &quot;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# を入力していて最後の &quot; &quot; が1tokenになっている。&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# そこに &quot;is&quot; がはいってもトークン数は変わらない&lt;/span&gt;
cand    score   P_rel   logP_cand_norm  logP_right      tok_len
is      &lt;span class=&quot;nt&quot;&gt;-5&lt;/span&gt;.059  0.886   0.000   &lt;span class=&quot;nt&quot;&gt;-5&lt;/span&gt;.059  0
has     &lt;span class=&quot;nt&quot;&gt;-7&lt;/span&gt;.688  0.064   0.000   &lt;span class=&quot;nt&quot;&gt;-7&lt;/span&gt;.688  0
goes    &lt;span class=&quot;nt&quot;&gt;-8&lt;/span&gt;.027  0.046   0.000   &lt;span class=&quot;nt&quot;&gt;-8&lt;/span&gt;.027  0
moves   &lt;span class=&quot;nt&quot;&gt;-10&lt;/span&gt;.408 0.004   0.000   &lt;span class=&quot;nt&quot;&gt;-10&lt;/span&gt;.408 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run python cloze.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://localhost:8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; openai/gpt-oss-20b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cloze&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;All parrots have one thing in _.&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--cands&lt;/span&gt; addition fact advance common &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--with-right&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--length-norm&lt;/span&gt; token

cand     score   P_rel   logP_cand_norm  logP_right      tok_len
common   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.004  0.324   0.000   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.004  0
fact     &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.172  0.274   0.000   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.172  0
addition &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.253  0.253   0.000   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.253  0
advance  &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.787  0.148   0.000   &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;.787  0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;実験結果のまとめ&quot;&gt;実験結果のまとめ&lt;/h3&gt;

&lt;p&gt;期待通りに動いてくれるケースが多くありましたが、
擬音語や擬態語に関しては想定と違う挙動をすることがありました。
また単純な動詞に関しても、ジェネリックというか、
文脈を絞り切らないケースで期待と違う挙動をすることがありました。
ただ、気軽に試せるのは間違いないので
今後も挙動を実験してみて理解する必要がありそうです。&lt;/p&gt;

&lt;h2 id=&quot;環境構築&quot;&gt;環境構築&lt;/h2&gt;

&lt;p&gt;最後に、環境に関しての補足です。&lt;/p&gt;

&lt;h3 id=&quot;ハードウェア要件&quot;&gt;ハードウェア要件&lt;/h3&gt;

&lt;p&gt;GPUの選択は細心の注意が必要です。
対応していないものを導入すると、
そもそも動かないなんてことになります。
自分はハードウェアに関して素人なので、
まずは一通りセットになっているものを購入しました。
GPT-OSS-20Bは16GB VRAM必須です。
自分が使っているのは&lt;strong&gt;RTX 5070 Ti&lt;/strong&gt;なのですが、
理屈では4080でも動くはずで、5070だと動かないはずです。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;GPU選択肢&lt;/th&gt;
      &lt;th&gt;VRAM&lt;/th&gt;
      &lt;th&gt;価格帯&lt;/th&gt;
      &lt;th&gt;適用性&lt;/th&gt;
      &lt;th&gt;推奨度&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;RTX 4070 Ti&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;12GB&lt;/td&gt;
      &lt;td&gt;~12万円&lt;/td&gt;
      &lt;td&gt;❌ &lt;strong&gt;不足&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;使用不可&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;RTX 4080&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;16GB&lt;/td&gt;
      &lt;td&gt;~15万円&lt;/td&gt;
      &lt;td&gt;✅ 可能(?)&lt;/td&gt;
      &lt;td&gt;安定動作&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;RTX 5070&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;12GB&lt;/td&gt;
      &lt;td&gt;~10万円&lt;/td&gt;
      &lt;td&gt;❌ &lt;strong&gt;不足&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;使用不可&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;RTX 5070 Ti&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;16GB&lt;/td&gt;
      &lt;td&gt;~13万円&lt;/td&gt;
      &lt;td&gt;✅ 最新&lt;/td&gt;
      &lt;td&gt;最適選択&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;ソフトウェア環境構築&quot;&gt;ソフトウェア環境構築&lt;/h3&gt;

&lt;p&gt;これに関してはREADMEに記述しています。&lt;/p&gt;

&lt;h2 id=&quot;まとめ&quot;&gt;まとめ&lt;/h2&gt;

&lt;p&gt;この記事では、
Cloze Taskにおける候補集合 $X$ の各候補 $x$ にスコアを付与し、
相対確率を得るまでの一連の手順を、実装と実験を交えて整理しました。
目標は文脈 $C_{\mathrm{pre}}, C_{\mathrm{post}}$ に対して候補 $x$ の尤もらしさを比較することで、
これは $s(x)=\log P(x\mid C_{\mathrm{pre}})$ と、
必要に応じて $\log P(C_{\mathrm{post}}\mid C_{\mathrm{pre}}+x)$ を合算しました。
そして、候補間の相対分布は $\mathrm{softmax}(s(x))$ で得る、という流れです。&lt;/p&gt;

&lt;p&gt;注意点としては、別表記の候補を増やすとsoftmax の分母が増えたり、
同一概念内で確率が割れて他概念の見かけの確率まで変動したり、という問題があります。
対策としては、代表する概念を探すか各概念において候補を羅列する方法がありますが、
代表する概念を選択するほうが楽そうです。&lt;/p&gt;

&lt;p&gt;いくつか実験もしましたが、一般的なケースでは直感に合う順位づけが得られました。
ただ、擬音語や擬態語などが出てくる場合では期待と違う挙動も見られました。
今後の展望はハイパーパラメータの調整や人の感覚と近づく条件の調査などがタスクとしてあります。
実装は公開していますので(https://github.com/kishiyamat/llm-proba)、
バグ報告や改善提案、実験レポートの共有を歓迎します。&lt;/p&gt;

&lt;hr /&gt;
&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:log&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;記事を書いたおかげで、
実装や挙動の想定外だった部分に複数気づくことができました。 &lt;a href=&quot;#fnref:log&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:like&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;あえて適当さを出すために「それっぽい」と表現しています。 &lt;a href=&quot;#fnref:like&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:logprobs&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;工夫すればできるかもしれません。
N個のトークンを出力させてマッチをとるとか。
なお似た例としては、
&lt;a href=&quot;https://cookbook.openai.com/examples/using_logprobs#1-using-logprobs-to-assess-confidence-for-classification-tasks&quot;&gt;分類タスクにLLMを用いる&lt;/a&gt;ケースがあります。
コードを読み慣れている人はそちらを一読してもよいかもしれません。 &lt;a href=&quot;#fnref:logprobs&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name></name></author><category term="python" /><category term="llm" /><category term="cloze" /><summary type="html">はじめに: 動機とよくなさそうな方法</summary></entry><entry><title type="html">書籍『ITエンジニアと眺める言語学』が出版されました</title><link href="https://kishiyamat.work/2025/10/22/yaminabe-book.html" rel="alternate" type="text/html" title="書籍『ITエンジニアと眺める言語学』が出版されました" /><published>2025-10-22T10:00:00+00:00</published><updated>2025-10-22T10:00:00+00:00</updated><id>https://kishiyamat.work/2025/10/22/yaminabe-book</id><content type="html" xml:base="https://kishiyamat.work/2025/10/22/yaminabe-book.html">&lt;p&gt;以前、&lt;a href=&quot;https://la-kentei.com/kotoba_special&quot;&gt;リベラルアーツ検定クイズ&lt;/a&gt;
というサイトで『言語学闇鍋エンジニアリング』という名前のブログを
掲載させてもらっていました。
内容は、居酒屋でちょっとネタになるような話題を
少し時間をとってプログラミングを使いながら考察してみる、
というものです。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/book/front.jpg&quot; alt=&quot;本がでました&quot; /&gt;&lt;/p&gt;

&lt;p&gt;プログラミング自体は書籍には載せない方針で、
内容に興味を持った人はGitHubに掲載しているコードを
使って遊べるようにしています。
各章の内容のPythonコードをのっけています。&lt;/p&gt;

&lt;p&gt;https://github.com/kishiyamat/la-kentei-yaminabe/tree/main/notebooks&lt;/p&gt;

&lt;p&gt;少し言語に興味のある大学生とか友人とか、
そこらへんの人を考えて書いたものです。
ただ、言語学をしっかりやった人でも
「へぇ、エンジニアは言語学をこういう目で見てるのね」
と思うと面白く読めるのではと思います。&lt;/p&gt;

&lt;p&gt;個人的には、人文系の大学ではプログラミングを教える人材は不足しているので、
ある程度は学生の自習に任せるしかないんじゃないかなと思ってます。
言語に興味のある学生がプログラミングに触れたいと思ったときに、
ちょうどよいネタを提供できればと思って書きました。&lt;/p&gt;

&lt;p&gt;僕が大学生のころに知りたかった話題は大体抑えていて、
単語の表現方法とか音の扱いとか、構文木の処理の仕方とか、
そこらへんまでが対象です。
談話と意味のレベルは、わからなさ過ぎて言及すらできませんでした。&lt;/p&gt;

&lt;p&gt;言語学的なスコープはさておき、扱っているネタ的なトピックとしては
タヌキとムジナとか、ハリポタツアーとか、聞き間違えとか、カードゲームとか、
それなりにオリジナリティのある内容になっているとは思います。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/book/last.jpg&quot; alt=&quot;後ろの文言&quot; /&gt;&lt;/p&gt;

&lt;p&gt;しかし、タイポはかなりの回数読み直したのですが残ってしまいました。
出版したあとに生えてきてるんじゃないかと思いましたが、
完全な力不足です。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/book/typo.jpg&quot; alt=&quot;後ろの文言&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;p.5 下から6行目: 観察できます→観察できる&lt;/li&gt;
  &lt;li&gt;p.11 第二段落3行目: 図3上の→図２上の&lt;/li&gt;
  &lt;li&gt;p.23 第二段落: （傾きと読んでいた）→（傾きと呼んでいた）&lt;/li&gt;
  &lt;li&gt;p.25 5行目: さて、→　さて（段落始まりのスペースが落ちている）&lt;/li&gt;
  &lt;li&gt;p.36 「ウポポイという…」（図2のキャプションが本文になっている）&lt;/li&gt;
  &lt;li&gt;p.129 頭語構造→統語構造&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;その他、わかりづらい点などは以下です。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;p.42 同じ高さの声を出す、というのはいうほど簡単ではないことに気づきました。&lt;/li&gt;
  &lt;li&gt;p.120 写真の色の、と書いていますが白黒なので、”orange cat” を検索してみて、
という旨のコメントをしたほうが良かったかもしれません。
日本語母語話者の感覚としては茶色の猫が出てきます。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;出版まで、編集の方にはかなりご迷惑をかけてしまいました。
もともとがブログだったので、脇道にそれる頻度がそれなりにあり、
補修工事が大変だったと思います。
あと、帯に採用させていただいたのは&lt;a href=&quot;https://x.com/ART_takahara&quot;&gt;高原さと&lt;/a&gt;様のイラストです。
昔、イラストを描いていた時期があったのですがその時のご縁でお声がけさせていただきました。
かわいいイラストで眼福でした。&lt;/p&gt;

&lt;p&gt;なお、内容は所属は関係なく個人のものです。
念のため事務室に問い合わせて、今の所属の前に書いたものなので許可は不要との返事をいただいております。
今後は、学会だけじゃなくて技術書典などの技術系の同人イベントに顔を出していけたらなと思います。
あと、今の事業のしかただとスケールしないので、そこも考えていきます。&lt;/p&gt;</content><author><name></name></author><category term="book" /><category term="yaminabe" /><summary type="html">以前、リベラルアーツ検定クイズ というサイトで『言語学闇鍋エンジニアリング』という名前のブログを 掲載させてもらっていました。 内容は、居酒屋でちょっとネタになるような話題を 少し時間をとってプログラミングを使いながら考察してみる、 というものです。 プログラミング自体は書籍には載せない方針で、 内容に興味を持った人はGitHubに掲載しているコードを 使って遊べるようにしています。 各章の内容のPythonコードをのっけています。 https://github.com/kishiyamat/la-kentei-yaminabe/tree/main/notebooks 少し言語に興味のある大学生とか友人とか、 そこらへんの人を考えて書いたものです。 ただ、言語学をしっかりやった人でも 「へぇ、エンジニアは言語学をこういう目で見てるのね」 と思うと面白く読めるのではと思います。 個人的には、人文系の大学ではプログラミングを教える人材は不足しているので、 ある程度は学生の自習に任せるしかないんじゃないかなと思ってます。 言語に興味のある学生がプログラミングに触れたいと思ったときに、 ちょうどよいネタを提供できればと思って書きました。 僕が大学生のころに知りたかった話題は大体抑えていて、 単語の表現方法とか音の扱いとか、構文木の処理の仕方とか、 そこらへんまでが対象です。 談話と意味のレベルは、わからなさ過ぎて言及すらできませんでした。 言語学的なスコープはさておき、扱っているネタ的なトピックとしては タヌキとムジナとか、ハリポタツアーとか、聞き間違えとか、カードゲームとか、 それなりにオリジナリティのある内容になっているとは思います。 しかし、タイポはかなりの回数読み直したのですが残ってしまいました。 出版したあとに生えてきてるんじゃないかと思いましたが、 完全な力不足です。 p.5 下から6行目: 観察できます→観察できる p.11 第二段落3行目: 図3上の→図２上の p.23 第二段落: （傾きと読んでいた）→（傾きと呼んでいた） p.25 5行目: さて、→　さて（段落始まりのスペースが落ちている） p.36 「ウポポイという…」（図2のキャプションが本文になっている） p.129 頭語構造→統語構造 その他、わかりづらい点などは以下です。 p.42 同じ高さの声を出す、というのはいうほど簡単ではないことに気づきました。 p.120 写真の色の、と書いていますが白黒なので、”orange cat” を検索してみて、 という旨のコメントをしたほうが良かったかもしれません。 日本語母語話者の感覚としては茶色の猫が出てきます。 出版まで、編集の方にはかなりご迷惑をかけてしまいました。 もともとがブログだったので、脇道にそれる頻度がそれなりにあり、 補修工事が大変だったと思います。 あと、帯に採用させていただいたのは高原さと様のイラストです。 昔、イラストを描いていた時期があったのですがその時のご縁でお声がけさせていただきました。 かわいいイラストで眼福でした。 なお、内容は所属は関係なく個人のものです。 念のため事務室に問い合わせて、今の所属の前に書いたものなので許可は不要との返事をいただいております。 今後は、学会だけじゃなくて技術書典などの技術系の同人イベントに顔を出していけたらなと思います。 あと、今の事業のしかただとスケールしないので、そこも考えていきます。</summary></entry><entry><title type="html">第20回言語処理若手シンポジウム（YANS）で発表してきました</title><link href="https://kishiyamat.work/2025/10/22/yans2025.html" rel="alternate" type="text/html" title="第20回言語処理若手シンポジウム（YANS）で発表してきました" /><published>2025-10-22T09:00:00+00:00</published><updated>2025-10-22T09:00:00+00:00</updated><id>https://kishiyamat.work/2025/10/22/yans2025</id><content type="html" xml:base="https://kishiyamat.work/2025/10/22/yans2025.html">&lt;p&gt;第20回言語処理若手シンポジウム（YANS）で発表してきました。
「聞き間違い事例に基づく日本語異聴コーパスの構築と事例の予測」というタイトルで、
ネットや速記に見つかる聞き間違えのデータを4490件集めて整備した、
という内容です。また、個人の音素弁別能力から、
そうした事例を再現できるか、また別の聞き間違えが予測できるか、
という予測のモデルを発表しました。&lt;/p&gt;

&lt;p&gt;また、ありがたいことに奨励賞をいただきました。
おそらく、作成したコーパスを検索できるデモを作成したので
研究の内容が伝わりやすかったのかなと思います。
StreamlitのCommunity版の環境でデプロイしています。
直近のアクセスがなければ落ちる使用ですが、起こせば使えます。&lt;/p&gt;

&lt;p&gt;https://mishearing-corpus-dev.streamlit.app/&lt;/p&gt;

&lt;p&gt;今後、このデータを使っていろいろと実験・分析していく予定です。
発表を聞いてくださった方、遅ればせながら感謝いたします。
なにより、データを提供してくださった
&lt;a href=&quot;https://www.yamatosokki.co.jp/&quot;&gt;大和速記情報センター&lt;/a&gt;社長の
津田様には感謝してもしきれません。
社会実装がんばります。&lt;/p&gt;

&lt;p&gt;引き続きよろしくお願いいたします。&lt;/p&gt;</content><author><name></name></author><category term="yans" /><summary type="html">第20回言語処理若手シンポジウム（YANS）で発表してきました。 「聞き間違い事例に基づく日本語異聴コーパスの構築と事例の予測」というタイトルで、 ネットや速記に見つかる聞き間違えのデータを4490件集めて整備した、 という内容です。また、個人の音素弁別能力から、 そうした事例を再現できるか、また別の聞き間違えが予測できるか、 という予測のモデルを発表しました。 また、ありがたいことに奨励賞をいただきました。 おそらく、作成したコーパスを検索できるデモを作成したので 研究の内容が伝わりやすかったのかなと思います。 StreamlitのCommunity版の環境でデプロイしています。 直近のアクセスがなければ落ちる使用ですが、起こせば使えます。 https://mishearing-corpus-dev.streamlit.app/ 今後、このデータを使っていろいろと実験・分析していく予定です。 発表を聞いてくださった方、遅ればせながら感謝いたします。 なにより、データを提供してくださった 大和速記情報センター社長の 津田様には感謝してもしきれません。 社会実装がんばります。 引き続きよろしくお願いいたします。</summary></entry><entry><title type="html">日本音声学会2024で発表してきました</title><link href="https://kishiyamat.work/2024/09/29/psj2024.html" rel="alternate" type="text/html" title="日本音声学会2024で発表してきました" /><published>2024-09-29T03:00:00+00:00</published><updated>2024-09-29T03:00:00+00:00</updated><id>https://kishiyamat.work/2024/09/29/psj2024</id><content type="html" xml:base="https://kishiyamat.work/2024/09/29/psj2024.html">&lt;p&gt;たくさんの方に聞いていただき、
発表後にもコメントをいただくことができて
非常に有意義な時間となりました。&lt;/p&gt;

&lt;p&gt;健聴者の「空耳」とか研究できないかというコメントや、
エンジニアリングの観点から今後の展望の
視座を少し高くできるコメント、
今後に活かせそうだなぁと感じました。
ありがたい限りです。&lt;/p&gt;

&lt;p&gt;実験のベースとなったソースコードは以下から
参照できます。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/kishiyamat/kikoepred&quot;&gt;https://github.com/kishiyamat/kikoepred&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;デモは以下から遊べます。
研究を動かせる形にしたいという気持ちは昔からあったので、
今回は実現できて少し成長した感があります。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.kikoepred.com&quot;&gt;https://www.kikoepred.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;まだまだ始めたばかりのプロジェクトですが、
ゆっくり育てていきます。
かなり難聴という現象に対する理解は浅いので、
今後多方面に助けを求めながらブランチを生やしていきます。
ありがとうございました！&lt;/p&gt;</content><author><name></name></author><category term="psj" /><summary type="html">たくさんの方に聞いていただき、 発表後にもコメントをいただくことができて 非常に有意義な時間となりました。</summary></entry><entry><title type="html">PCIbexでEmpty project から実験を作ると newController is not defined と怒られる場合</title><link href="https://kishiyamat.work/2023/10/13/error-pcibex-module.html" rel="alternate" type="text/html" title="PCIbexでEmpty project から実験を作ると newController is not defined と怒られる場合" /><published>2023-10-13T03:00:00+00:00</published><updated>2023-10-13T03:00:00+00:00</updated><id>https://kishiyamat.work/2023/10/13/error-pcibex-module</id><content type="html" xml:base="https://kishiyamat.work/2023/10/13/error-pcibex-module.html">&lt;p&gt;大学の授業準備の際にPCIbexを使おうとしたところ、
エラーになって非常に困ったので
エラーの概要と解決方法、原因の考察のログを残しておきます。
学期期間中かつToDoが鬼のように溜まっているので
推敲なしの書きなぐりですがご容赦ください。&lt;/p&gt;

&lt;h2 id=&quot;エラーの概要&quot;&gt;エラーの概要&lt;/h2&gt;

&lt;p&gt;今回対象とするのは、
PCIbexのEmpty projectから実験を示した場合に起こるエラーの一つです。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/pcibex-error/01_empty.png&quot; alt=&quot;キャプション&quot; /&gt;&lt;/p&gt;

&lt;p&gt;ミニマルな再現のためのケースとして、以下のような容認性判断課題
のControllerを作成しています。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/pcibex-error/02_paste.png&quot; alt=&quot;キャプション&quot; /&gt;&lt;/p&gt;

&lt;p&gt;なお全体のスクリプトは以下です。&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/kishiyamat/cd62e2d58354338c753eab616c3ad930.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;ただ、このスクリプトを実行させてもIbex部分が実行されることはありません。
Ibex部分しかない場合はTrialがないというエラーになりますが、
ある場合はただただスキップされてしまいます。
このエラーを探るため開発者ツールを見てみると、
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;newController is not defined&lt;/code&gt;
というエラーが出ています。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/pcibex-error/03_error.png&quot; alt=&quot;キャプション&quot; /&gt;&lt;/p&gt;

&lt;p&gt;これがエラーの概要です。&lt;/p&gt;

&lt;h2 id=&quot;解決方法&quot;&gt;解決方法&lt;/h2&gt;

&lt;p&gt;解決方法はシンプルです。
左下にModulesがあるので、
そこのPennController.jsを機能するバージョンで置き換えます。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/pcibex-error/04_penn.png&quot; alt=&quot;キャプション&quot; /&gt;&lt;/p&gt;

&lt;p&gt;機能するファイルとしては、少なくとも2023/10/13の段階では、
以下のURLにある PennController.js が有効に思えます。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/PennController/penncontroller/blob/master/releases/2.0/PennController.js&quot;&gt;https://github.com/PennController/penncontroller/blob/master/releases/2.0/PennController.js&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;このURLにアクセスすると、以下のような画面に遷移すると思います。
コードを表示しているペインの右上に Raw, Copy, Download を示す
ボタンがあるのが見えると思います。
このダウンロードボタンを押すと、機能するバージョンをダウンロードできます。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/pcibex-error/06_dl.png&quot; alt=&quot;キャプション&quot; /&gt;&lt;/p&gt;

&lt;p&gt;ダウンロードしたファイルを、
先ほど言及した左下のModulesにアップロードします。
ダウンロードしたファイルをドラッグ・アンド・ドロップしても良いですし、
アップロードボタンから選択しても良いでしょう。
個人的にはブラウザのダウンロードしたファイルを確認する画面を表示させ、
そこから引っ張ってドロップするのが一番簡単に思えます。&lt;/p&gt;

&lt;p&gt;さてドロップしたりアップロードすると、
既存のファイルを置き換えても良いかと問われます。
置き換えることが目的なので、Yesを選択しましょう。
これで作業は終わりです。
もし問われない場合は、ファイル名がPennController.jsになっていない
可能性があるので注意しましょう。
ファイル名はPennController.jsでなければなりません。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/pcibex-error/07_replace.png&quot; alt=&quot;キャプション&quot; /&gt;&lt;/p&gt;

&lt;p&gt;これで所望の挙動を得られました。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/pcibex-error/08_success.png&quot; alt=&quot;キャプション&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;原因の考察&quot;&gt;原因の考察&lt;/h2&gt;

&lt;p&gt;JavaScriptは詳しくないのでわからないのですが、
上手く行くケースと上手く行かないケースのスクリプトを
ざっと眺めてみたところ、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_AddElementType(&quot;Controller&quot;, ...)&lt;/code&gt;
という記述の有無に差があることに気づきました。
上手く行かないケースでは、この記述が無いようです。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/pcibex-error/05_add.png&quot; alt=&quot;キャプション&quot; /&gt;&lt;/p&gt;

&lt;p&gt;恐らくですが、これで”Controller”にまつわる
機能を追加しているのでしょう。
時間ができたらよく調べてみます。&lt;/p&gt;</content><author><name></name></author><category term="chatgpt" /><summary type="html">大学の授業準備の際にPCIbexを使おうとしたところ、 エラーになって非常に困ったので エラーの概要と解決方法、原因の考察のログを残しておきます。 学期期間中かつToDoが鬼のように溜まっているので 推敲なしの書きなぐりですがご容赦ください。</summary></entry><entry><title type="html">ChatGPTを英語の授業で使う方法</title><link href="https://kishiyamat.work/2023/01/27/chatgpt-for-efl.html" rel="alternate" type="text/html" title="ChatGPTを英語の授業で使う方法" /><published>2023-01-27T03:00:00+00:00</published><updated>2023-01-27T03:00:00+00:00</updated><id>https://kishiyamat.work/2023/01/27/chatgpt-for-efl</id><content type="html" xml:base="https://kishiyamat.work/2023/01/27/chatgpt-for-efl.html">&lt;p&gt;テレビ東京のワールドビジネスサテライトで
ﾊﾟｿｺﾝﾆﾁｮｯﾄｸﾜｼｲ英語講師枠でChatGPTの使い方を説明しました。
知っていると時間が節約でき、また学生へのフィードバックの質を改善できたり、
研究の効率も上がるので、Grammarly や DeepL と同等のレベルで
必須のツールになるんじゃなかろうかと感じています。&lt;/p&gt;

&lt;p&gt;英語の教材作成に使えそうなプロンプト（つまり入力）を作るツールは
すでに&lt;a href=&quot;https://chatgpt-prompt-generator.streamlit.app/&quot;&gt;こちら&lt;/a&gt;で公開しています。&lt;/p&gt;

&lt;p&gt;このアプリでは、題材(下の方で入力できます)に対する
問題の種類(Vocab quizzes, Difficult words, Cloze tests, Comprehension tasks, Discussion topics)
を選択したり、対象の学習者を選択することで、
ChatGPTに投げるプロンプトの候補を作成してくれます。
&lt;u&gt;さらに、ChatGPTの前身であるInstructGPTに投げることも可能です。&lt;/u&gt;
結果を見ても、結構いい感じの問題を作成してくれることが多いです&lt;sup id=&quot;fnref:fee&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:fee&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;

&lt;p&gt;このアプリはまだ実験と評価の途中なので、
何か気づいた点があればフィードバック貰えるとうれしいです。
明らかなバグや機能のリクエストは&lt;a href=&quot;https://github.com/kishiyamat/chatgpt-prompt-generator/issues&quot;&gt;GitHubのIssues&lt;/a&gt;
にしていただくか、下のDisqussに直接コメントくださっても助かります。
他にも、アイデアレベルの「こんなことをしてみたい」のような話も歓迎です&lt;sup id=&quot;fnref:api&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:api&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;

&lt;p&gt;もちろん使用上の注意としては、GrammarlyやDeepLと全く同じで、
モデルによるアウトプットの正誤判断はユーザーができないとダメです。
ここらへんは入力に対する最尤の系列を出力するという共通点があって、
誰もが使う典型的にはタイピングの変換ツールがあるかと思います。
漢字の「薔薇」は書けずとも、「ばら」に対して「薔微」や「ヴァラ」が
おかしいかどうかの判定はできねばならんわけです。&lt;/p&gt;

&lt;p&gt;以下余談です。&lt;/p&gt;

&lt;p&gt;生成するプロンプトが全て英語ですが、
将来的にはプロンプトを日本語でも作れるようにしたいです。
なんとなくですが、英語の問題作成の場合は日本語のプロンプトのほうが
意図と合うものが作れるような気もしなくもないので。
仮に日本語の方が良い英語の問題が作れる場合、
単純にそういうデータ（出題日本語、題材英語）が多いからからかもしれません。&lt;/p&gt;

&lt;p&gt;インタビューの中で「学生に使われたらどうする？」という当然の質問がありました。
そこで「ChatGPTを使って問題を出す」と答えたのですが、
これは冗談じゃなくて現実的にアリなんじゃないかと思ってます。
特に英文のライティングなんてのは主張の展開をみたいので、
そこで使われたら学習者の能力を測れなくなって致命的です。
そうした自体を避けるためにも有効活用していきたいですね。&lt;/p&gt;

&lt;hr /&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:fee&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;その場合、1リクエストあたり5円くらい岸山に請求が飛ぶのでお手柔らかにおねがいします。 &lt;a href=&quot;#fnref:fee&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:api&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;これを気軽にAPIとして使えるようになる将来、
本当に色々なことができるようになると思います。というより、
ChatGPTに限らず Grammatical error correction や 
Automatic speech recognition が気軽にリクエストで処理できるの、
これまでの指導とは全く違う形成的な評価ができるようになるので
非常に楽しみです。 &lt;a href=&quot;#fnref:api&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name></name></author><category term="chatgpt" /><summary type="html">テレビ東京のワールドビジネスサテライトで ﾊﾟｿｺﾝﾆﾁｮｯﾄｸﾜｼｲ英語講師枠でChatGPTの使い方を説明しました。 知っていると時間が節約でき、また学生へのフィードバックの質を改善できたり、 研究の効率も上がるので、Grammarly や DeepL と同等のレベルで 必須のツールになるんじゃなかろうかと感じています。</summary></entry><entry><title type="html">オンライン音声実験マニュアルの公開</title><link href="https://kishiyamat.work/2022/11/08/website-for-audio-exp.html" rel="alternate" type="text/html" title="オンライン音声実験マニュアルの公開" /><published>2022-11-08T03:00:00+00:00</published><updated>2022-11-08T03:00:00+00:00</updated><id>https://kishiyamat.work/2022/11/08/website-for-audio-exp</id><content type="html" xml:base="https://kishiyamat.work/2022/11/08/website-for-audio-exp.html">&lt;p&gt;音声研究のためのオンライン実験（ブラウザ上で実施できる実験）の作成をより簡単にするため、
「オンライン音声実験マニュアル」というウェブサイトを作りました。リンクは以下になります。&lt;/p&gt;

&lt;p&gt;https://cool-atami.github.io/online-audio-experiment/&lt;/p&gt;

&lt;p&gt;コンテンツは以下の通りですが、&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;知覚実験の概要（AX実験、ABX＆AXB実験、自然度測定の説明）&lt;/li&gt;
  &lt;li&gt;ミニマルな知覚実験の作成（jsPsychの概要とミニマルな実験の作成）&lt;/li&gt;
  &lt;li&gt;jsPsych(基本)（jsPsychの基本機能であるtimelineやpreloadなど）&lt;/li&gt;
  &lt;li&gt;jsPsych(発展)（jsPsychの発展: リスト作成やランダマイズなど）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;近々、以下のものも加えようと思っています。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;産出実験の概要（作成中）&lt;/li&gt;
  &lt;li&gt;産出実験の作成（作成中）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;論文がアウトプットのメインになってしまい、最新の知見を独占してしまっているのってあるべき姿ではないよなぁという感じはしていて、
&lt;a href=&quot;https://researchmap.jp/chuyu_huang&quot;&gt;Chuyu HUANG&lt;/a&gt; と作成しました。&lt;/p&gt;

&lt;p&gt;若干スクリプトを書かねばならないので見た目ハードル高めかもしれませんが、
学部生の方も見ながら実験を無事に作れていたようです。
jsPsychのバージョンはウェブサイトだとver.6ですが、近々7に変えねば…。&lt;/p&gt;</content><author><name></name></author><category term="audio" /><category term="experiment" /><summary type="html">音声研究のためのオンライン実験（ブラウザ上で実施できる実験）の作成をより簡単にするため、 「オンライン音声実験マニュアル」というウェブサイトを作りました。リンクは以下になります。</summary></entry><entry><title type="html">一般化線形混合モデルのチュートリアル</title><link href="https://kishiyamat.work/2021/03/03/tutorial-lme-vwp.html" rel="alternate" type="text/html" title="一般化線形混合モデルのチュートリアル" /><published>2021-03-03T06:00:00+00:00</published><updated>2021-03-03T06:00:00+00:00</updated><id>https://kishiyamat.work/2021/03/03/tutorial-lme-vwp</id><content type="html" xml:base="https://kishiyamat.work/2021/03/03/tutorial-lme-vwp.html">&lt;p&gt;詳細はリンク先で説明しますが、
&lt;a href=&quot;https://kishiyamat.github.io/tutorial-lme-vwp/&quot;&gt;A Tutorial on LME and VWP&lt;/a&gt;
というサイトを作りました。
目的は (i) パンデミック収束後に視線計測実験をより多くの人が実施できるようにすること
と (ii) 実験実施前に基礎的な統計の知識を共有すること
です。
オンラインでも実施可能なように Zoom で実施し、
資料はチュートリアル終了後も使えるように
ハンズオンがオンラインでできるように工夫しました。
動画は以下になります。Adobe Rush が編集しやすくて助かりました。&lt;/p&gt;

&lt;center&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/cwS_SVy4ZaM&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/center&gt;

&lt;p&gt;GitHubによる静的サイトのホスティングや
datacampによる実行環境の提供がなければ実現できませんでしたし、
JekyllのプロジェクトやGitのバージョン管理、
そもそもRによるパッケージ開発や
統計学の進展がなければ実現できず、
もちろん、大学の支援プロジェクトと
プロジェクトを推進してくださった教員の皆様にも感謝していて、
何から感謝して良いのかわからない状態です。
本当に良い時代に生まれたなぁと感謝しつつ、
後世の方も同じ思いになれるように頑張りたいものです。&lt;/p&gt;

&lt;p&gt;チュートリアルの質問、改善点、リクエストなどがあれば、
岸山(kishiyama.t[於]gmail.com)までご連絡ください。
(過去の記事、加筆修正しようと思ってるのに
なかなか手が出せずにいますが
ちょくちょくリファクタリングしていきます。)&lt;/p&gt;</content><author><name></name></author><category term="lme" /><category term="r" /><category term="vwp" /><category term="poem" /><summary type="html">詳細はリンク先で説明しますが、 A Tutorial on LME and VWP というサイトを作りました。 目的は (i) パンデミック収束後に視線計測実験をより多くの人が実施できるようにすること と (ii) 実験実施前に基礎的な統計の知識を共有すること です。 オンラインでも実施可能なように Zoom で実施し、 資料はチュートリアル終了後も使えるように ハンズオンがオンラインでできるように工夫しました。 動画は以下になります。Adobe Rush が編集しやすくて助かりました。</summary></entry><entry><title type="html">ibex実験を自由に公開する</title><link href="https://kishiyamat.work/2020/08/21/deploy-local-ibex.html" rel="alternate" type="text/html" title="ibex実験を自由に公開する" /><published>2020-08-21T06:00:00+00:00</published><updated>2020-08-21T06:00:00+00:00</updated><id>https://kishiyamat.work/2020/08/21/deploy-local-ibex</id><content type="html" xml:base="https://kishiyamat.work/2020/08/21/deploy-local-ibex.html">&lt;p&gt;【2020-08-24更新】本文の随所に https はibex farm だと使えないという旨の
記述があらわれますが 2020/04/26 以降からhttps は使えます。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;April 26, 2020, Alex Drummond (the creator of IBEX) has secured the domain of the original Ibex Farm.&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;https://www.pcibex.net/2020/04/27/farms-performance-slowdowns-alternatives-future-plans/&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;【2021-08-30更新】ibex-farm によるサービス提供は終了しますが、PCIbexに移行すると良いと思います。
外部にデータを置くのが問題ある場合、今回紹介する方法は役に立つかもしれません。&lt;/p&gt;

&lt;h2 id=&quot;はじめに&quot;&gt;はじめに&lt;/h2&gt;

&lt;p&gt;研究室の先輩が「サーバー借りて
ibex (心理言語学とかで使われるオンライン実験) を公開したい」
と言っているのをゼミのslackで目にしました&lt;sup id=&quot;fnref:deploy&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:deploy&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;。
通常の公開手順は簡潔で便利ですが、
制限もあり場合によっては都合が良くないからです(なおこの手の「公開」をデプロイと呼びます)。&lt;/p&gt;

&lt;p&gt;以下の表の一行目には従来の手法 (PC/Ibex farm)
とサービスの安定性、Don’t repeat yourself(DRY)、
データの保存場所を記載してます。
DRYはデータをコピー、ダウンロードしないとならないという話で、
ストレージはファイルの保存場所の話です。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Method&lt;/th&gt;
      &lt;th&gt;Stability&lt;/th&gt;
      &lt;th&gt;DRY&lt;/th&gt;
      &lt;th&gt;Storage&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;ibex farm&lt;/td&gt;
      &lt;td&gt;depends on ibex farm&lt;/td&gt;
      &lt;td&gt;copy code and results&lt;/td&gt;
      &lt;td&gt;remote&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;local + ngrok&lt;/td&gt;
      &lt;td&gt;depends on local and ngrok&lt;/td&gt;
      &lt;td&gt;:)&lt;/td&gt;
      &lt;td&gt;local&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;従来の ibex farm を用いた手法だと
データやソースはコピーしないとならず、また実験はPCIbexの運営の安定性に依存します。
他方、今回紹介する手法(二行目)では
実験の安定性の依存先を変更できたり、
ソースコードのコピペや結果のダウンロードが不要になり、
データの保存場所も外部のサーバーではなくお手元のPCになります。&lt;/p&gt;

&lt;p&gt;以下、まずは心理言語学で使われている
オンライン実験をさくっと紹介し、
手元で実行しているibex実験をウェブを通じて公開する手順を紹介します。
なお ibex を挙げていますがあくまでも例なので、
ブラウザ経由で実行できる類の実験ならなんでも良いはずで、
そこそこ汎用性はあると思います。&lt;/p&gt;

&lt;h2 id=&quot;手法&quot;&gt;手法&lt;/h2&gt;

&lt;h3 id=&quot;ibex-実験webアプリケーション&quot;&gt;ibex: 実験webアプリケーション&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;概要&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;心理言語学の一界隈では ibex という実験用webアプリが利用する流行があり、
「オンライン実験ってオンサイトと変わらないくらい信頼性あるよね」
という雰囲気があるとおもいます。
なお、オンライン実験の背景や特徴、注意点などは以下の動画がまとめています。&lt;/p&gt;

&lt;center&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/zat7b3zfy9U&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/center&gt;

&lt;p&gt;本題に戻りますが、ibexは実験用webアプリなので、
実験をオンラインで実施できます&lt;sup id=&quot;fnref:ibex&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:ibex&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;。
この「webアプリ」というのは私達が普段使っている
YouTube や Gmail などのサービスと同じで、
&lt;u&gt;1. webアプリ&lt;/u&gt; と &lt;u&gt;2. サーバー&lt;/u&gt;、 &lt;u&gt;3. URL&lt;/u&gt; が必要です。
この内、ibex は実験の1の雛形を提供してくれていて、
後述する ibex farm は2と3を提供してくれます。
我々エンドユーザーはibexの &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/data_includes&lt;/code&gt; 等を変えることで
実験のwebアプリを作るわけです。&lt;/p&gt;

&lt;p&gt;例えば、
Google が YouTube というwebアプリのソースコードを全て公開しているとします。
もし YouTube のフォントをヒラギノに変えたければ、
その設定だけを変えて公開すればヒラギノYouTubeのwebアプリができるわけです。
同様に、ibexのソースコードは全て公開されており、
英語の Self-paced reading test の文を任意の日本語にしたければ、
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/data_includes&lt;/code&gt; 等を変えれば日本語SPRTの実験用webアプリになるわけです&lt;sup id=&quot;fnref:grep&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:grep&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ハンズオン&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;いやそもそも &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/data_includes&lt;/code&gt; って何ですか、
となる方もいると思うので、まずは ibex のソースコードのダウンロードから始めましょう。
&lt;a href=&quot;https://github.com/addrummond/ibex&quot;&gt;GitHubのページ&lt;/a&gt; に
緑のボタンがあるので、そこからソースコードを落とします。
あるいは&lt;a href=&quot;https://github.com/addrummond/ibex/archive/master.zip&quot;&gt;ここ&lt;/a&gt; をクリックでもいけると思います。
zipファイル&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ibex-master.zip&lt;/code&gt; のダウンロードを視認したら次に進みます。&lt;/p&gt;

&lt;p&gt;ダウンロードした &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ibex-master.zip&lt;/code&gt; は
人によって後から確認しづらい場所に置かれてしまうかもしれません。
このあと使うので見やすい場所に移動させましょう。
Mac や Windows ならデスクトップとかがオススメです。
Macならアーカイブユーティリティみたいなやつ、
Windowsなら右クリックで解凍できます。&lt;/p&gt;

&lt;p&gt;ここでようやく先ほどの &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/data_includes&lt;/code&gt; の説明ができます。
中を見ると細々としたものが書かれていますが、
&lt;strong&gt;&lt;u&gt;ここでは何も触れず、
「あぁ実験の設定や刺激はここらへんで指定するんだなぁ」
ということのみを確認しましょう。&lt;/u&gt;&lt;/strong&gt;
ibex自体の詳しい説明はマニュアルか、
日本語なら『ことばの実験研究の方法』という書籍を参照できます。&lt;/p&gt;

&lt;p&gt;以上がwebアプリとしてのibexです。
本当はすぐに変更して試したいのですがサーバーの話を先にするので、
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www/server.py&lt;/code&gt; の存在も確認しましょう。
(Windows だと &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.py&lt;/code&gt; が見えないかもしれません)&lt;/p&gt;

&lt;h3 id=&quot;python-2-サーバー&quot;&gt;Python 2: サーバー&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;概要&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;さて、webアプリを動かすためにはサーバーやURL&lt;sup id=&quot;fnref:site&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:site&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;が必要であると上で述べました。
サーバーというとわかりづらいですが、
要はwebアプリをユーザーに提供(serve)してくれるパソコンのことです。
ibexはサーバーの起動にPythonを使うので、
Pythonさえ使える環境ならばお手持ちのPCでもibexを起動できます。
(もしかしたら実験の動作チェックにしたことがある方もいるかもしれません。)&lt;/p&gt;

&lt;p&gt;Python には2系と3系があり、ここでは2系を使います。
将来的には3系に移行すると思うので、
ドキュメントを実施前に確認したほうが良いと思ってます。
&lt;strong&gt;&lt;u&gt;今回はPythonのコードを触ることはないので、
「実験のアプリをローカルで動かすサーバーの起動に必要なんだなぁ」
ということのみを確認しましょう。&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ハンズオン&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;実験をローカルで動かすために Python 2 のインストールから始めましょう。
まずは &lt;a href=&quot;https://wiki.python.org/moin/BeginnersGuide/Download&quot;&gt;BeginnersGuide/Download&lt;/a&gt;
を参照しながらOSにあった 2.7 をダウンロードします。
[Latest Python 2 Release - Python 2.7.x]をクリックすると、
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Version&lt;/code&gt; 列の末尾に &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;installer&lt;/code&gt; とついたファイルが見つかります。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Operating System&lt;/code&gt; 列のでOSと合うインストーラーを落とします。
なお、Windows x86-64 MSI installer は64bit用です。
自身のPCのバージョンを確認する方法は
&lt;a href=&quot;https://support.microsoft.com/ja-jp/help/15056/windows-32-64-bit-faq&quot;&gt;FAQ&lt;/a&gt;で確認してください。
落としたファイルをクリックしたら大抵の場合は
そのままインストールが始まります。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;チェック&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ここまでの確認として、
ibexをローカルのサーバーで動かしてみるチェックをしてみます。
手順は以下の通りです。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ibex-master/www/server.py&lt;/code&gt; を python で実行&lt;/li&gt;
  &lt;li&gt;localhost:3000 に接続&lt;/li&gt;
  &lt;li&gt;実験画面の確認&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;最初の &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ibex-master/www/server.py&lt;/code&gt; に関しては
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;server.py&lt;/code&gt; をクリックしても、
ターミナルを立ち上げて
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;python2.7 server.py&lt;/code&gt; を打ち込んでもいけます。
Windows で PowerShell などに詳しくなければ
クリックして実行で良いでしょう。
おそらく、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www/server.py&lt;/code&gt; がアイコン付きで表示されているはずです。&lt;/p&gt;

&lt;p&gt;起動した後、画面自体には特に変化はありませんが、
Chromeなどのブラウザから localhost:3000 に接続してみます(URLにベタ打ちでOKです)。
すると実験画面が現れ、これで ibexがローカルで動いていることになり、
実験の動作確認なんかでも使えるのでそこそこ便利だとは思います。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/ibex-localhost.png&quot; alt=&quot;ibex-localhost&quot; /&gt;&lt;/p&gt;

&lt;p&gt;なお、実験を表示させてみて変えたい場所があれば
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep -r 'query'&lt;/code&gt; のようにコマンドすることで編集したい場所を
スムーズに探せます。
もちろん、何がどこにあるかをドキュメントを読んで
確認するほうが大切ですが、
最初の取っ掛かりとしてはそれもありかと。&lt;/p&gt;

&lt;h3 id=&quot;ngrok-urlの発行と接続&quot;&gt;ngrok: URLの発行と接続&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;概要&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;これでローカルで実験のアプリを起動はできました。
ただ、これだと実験用のPCを被験者に送らない限り
被験者に参加してもらうことはできません。
このローカルで起動した実験にインターネット経由で
直接アクセスしてもらうことはできないのでしょうか。
そこで使えるのが ngrok です。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://ngrok.com/&quot;&gt;ngrok&lt;/a&gt; はざっくり言うと
ローカルで稼働しているネットワークサービスを外部公開できるサービスです。
OSによって作業が変わるのですが、基本的には
以下の手順を踏みます。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;ngrok の利用登録/鍵を取得&lt;/li&gt;
  &lt;li&gt;ngrok (アプリ) のインストール/鍵登録&lt;/li&gt;
  &lt;li&gt;URLとポートをトンネル&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;ハンズオン&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;まずは ngrok の利用登録を行い、通信に必要な鍵を取得します。
利用登録は 1. ngrok のホームページに行き 2. サインアップ、ログイン するだけです。
なお、この時 google か github のアカウントを持っているとスムーズです。
ログインする以下のような画面に移ると思います。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/ngrok.png&quot; alt=&quot;ibex-localhost&quot; /&gt;&lt;/p&gt;

&lt;p&gt;ログインすると &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ngrok.exe authtoken &amp;lt;token&amp;gt;&lt;/code&gt; のコマンドが表示され、
authtoken の後ろが「鍵」です。
なお、画像では「見せられないよ！」となっている部分が鍵になります。
ここまでが ngrok の利用登録/鍵を取得の話になります。&lt;/p&gt;

&lt;p&gt;次に ngrok (アプリ) のインストール/鍵登録でして、
インストールはzipを落として解凍するだけです。
つまり &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ibex-master.zip&lt;/code&gt; と同様の作業をします。
すると ngrok の実行ファイルが生成されます。
なお、次の「鍵登録」はちょっとややこしい+
セッションに8時間くらいの制限がつくだけなので、
雰囲気だけ掴みたい場合は飛ばしてOKです。&lt;/p&gt;

&lt;p&gt;Windowsの場合は解凍して生成された ngrok の実行ファイルをクリックします。
するとターミナルという黒い画面が起するので、
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ngrok.exe authtoken &amp;lt;token&amp;gt;&lt;/code&gt; とタイプします。
この &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;token&lt;/code&gt; の部分には先程の「見せられないよ！」をコピペしてください。
もしかすると ctrl + v が聞かないかもしれませんが、
右クリックとかでペーストできるかもしれません。&lt;/p&gt;

&lt;p&gt;Mac の場合は同様に ngrok の実行ファイルがあるディレクトリに移動して
ターミナルを起動し、
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./ngrok authtoken &amp;lt;token&amp;gt;&lt;/code&gt; とタイプします。
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;token&lt;/code&gt; の部分には先程の「見せられないよ！」をコピペしてください。&lt;/p&gt;

&lt;p&gt;そしてibexが起動している local port の3000番に
HTTP tunnel forwardingをするために、
ターミナル上でWindows は
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ngrok.exe http 3000&lt;/code&gt;、
Mac は
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./ngrok http 3000&lt;/code&gt;、
をコマンドします。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;チェック&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;するとターミナル上にトンネルの入り口のURLが表示されるので、
それ経由で別のPCからインターネット経由で
ご自身のPCの3000番で実行している実験にアクセスできます。
任意の端末で表示されているURLにアクセスしてみてください。
その端末でサンプルを行うと、
ローカルの &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ibex/results&lt;/code&gt; の下に結果が保存されるはずです。&lt;/p&gt;

&lt;h3 id=&quot;未検証&quot;&gt;未検証&lt;/h3&gt;

&lt;p&gt;ここまでで色々と詰めたわけですが、
いくつか未検証な部分や制限があります。
まず、安定性がローカルに依存することから、
ローカルのプロセスが落ちると実験は落ちます。
なので、実験中はローカルを起こしておかないといけません。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Method&lt;/th&gt;
      &lt;th&gt;Protocol&lt;/th&gt;
      &lt;th&gt;Stability&lt;/th&gt;
      &lt;th&gt;DRY&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;ibex farm&lt;/td&gt;
      &lt;td&gt;http&lt;/td&gt;
      &lt;td&gt;depends on ibex farm&lt;/td&gt;
      &lt;td&gt;copy code and results&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;local + ngrok&lt;/td&gt;
      &lt;td&gt;http/https&lt;/td&gt;
      &lt;td&gt;depends on local and ngrok&lt;/td&gt;
      &lt;td&gt;:)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;また、一度プロセスが落ちるとURLが無効になるため
URLの貼り直し(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./ngrok http port&lt;/code&gt;) を再度行わなければなりません。
実験の手順をHackMDやNotionなどのサービスで共有していれば
「当日確認してください」とできますが、
メールでURLを配信してしまっている場合は再配信が必要になります。
ただ、スリープならプロセスは死なないのでURLも使い続けられます。&lt;/p&gt;

&lt;p&gt;後はプロセスを落とした後、ラテンスクエアなどが機能するかは検証していません。
また、別のPCで立ち上げて実験をすれば別の実験扱いになります。&lt;/p&gt;

&lt;h2 id=&quot;まとめ&quot;&gt;まとめ&lt;/h2&gt;

&lt;p&gt;さて、ここまでの話は少し重かったので、
最後に三つのアプリの役割を下のテーブルを見ながら整理しましょう。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Application&lt;/th&gt;
      &lt;th&gt;Role&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;ibex&lt;/td&gt;
      &lt;td&gt;experiment design / web app.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Python 2&lt;/td&gt;
      &lt;td&gt;local server to run the app.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ngrok&lt;/td&gt;
      &lt;td&gt;provides URL to localhost&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;ibex は実験webアプリなので、これを変更して実験をデザインします。
そしてPython 2 はibexでデザインしたwebアプリを
ローカルで走らせるのに使い、
ngrok はローカルで走っている実験へのトンネル(URL発行、アクセス)を行います。&lt;/p&gt;

&lt;p&gt;つまり、
&lt;u&gt;1. ibexで実験を作成&lt;/u&gt;、
&lt;u&gt;2. Python2 で実験を localhost で実行&lt;/u&gt;、
&lt;u&gt;3. ngrok でlocalhostで走る実験へのURLを発行&lt;/u&gt;
の3ステップを実行することになります。&lt;/p&gt;

&lt;p&gt;ibex farm は
&lt;u&gt;1. アレンジされた ibex の実験ファイル(`/data_includes`)を受け取り&lt;/u&gt;、
&lt;u&gt;ibex用のサーバーとURLを提供してくれる&lt;/u&gt;のです&lt;sup id=&quot;fnref:more&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:more&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;。
ありがたいですね。&lt;/p&gt;

&lt;h2 id=&quot;おまけ&quot;&gt;おまけ&lt;/h2&gt;

&lt;h3 id=&quot;いただいたコメント&quot;&gt;いただいたコメント&lt;/h3&gt;

&lt;p&gt;PCIbexのフォーラムにIRB approvalを得るためサーバーの詳細を知る必要があるんだが、
どこで参照できるのかという&lt;a href=&quot;https://www.pcibex.net/forums/topic/server-details/&quot;&gt;コメント&lt;/a&gt;
があったそうです。
そういう時にも自分のサーバーです（キリッ）
と言えると面倒が少なくて嬉しいかもしれません。&lt;/p&gt;

&lt;p&gt;ただ、ユースケースがやや限定的で、
常にサーバー監視してないと落ちると気づけないのも事実です。
ここらへんはibex farmとかを使ってても同じことが言えるんですが、
これはibex farmにping飛ばし続けるbotを作って
こっちは落ちたらslackにアラート、とかでも解決できる気がします。&lt;/p&gt;

&lt;p&gt;あとibexの実験で手元にデータが溜まるのは結構新鮮な体験らしいです。&lt;/p&gt;

&lt;h3 id=&quot;gitgithubで管理&quot;&gt;Git/GitHubで管理&lt;/h3&gt;

&lt;p&gt;実験デザイン(コード)や結果の共有に GitとGitHubを使えば
コードや結果の等価性を保証できます。&lt;/p&gt;

&lt;h3 id=&quot;text-editor&quot;&gt;Text Editor&lt;/h3&gt;

&lt;p&gt;要はJavaScriptなわけですが、
カッコのマッチやインデントの整理などの
機能を持つテキストエディターがあると良いでしょう。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Visual Studio Code&lt;/li&gt;
  &lt;li&gt;Atom&lt;/li&gt;
  &lt;li&gt;Sublime&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;ssh&quot;&gt;SSH&lt;/h3&gt;

&lt;p&gt;ローカルを落としたい時、
SSHを使えばつけっぱなしにできます。
あとは audio を回収するときの
受け入れサーバーにするとか？&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;https://www.pcibex.net/wiki/recording-and-collecting-audio-samples/&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;nginx&quot;&gt;nginx&lt;/h3&gt;

&lt;p&gt;もうデプロイしたほうが早いと思った人向け: &lt;a href=&quot;https://qiita.com/KosukeJin/items/bb0daaed9f058439e225&quot;&gt;HackMDインストールとHTTPS化まで&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:deploy&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;サーバー借りるとこは良いとはいえ、
       わざわざ実験のためにドメイン取得して固定するのかとか、
       デプロイどうすんねんとか、
       そもそも既存のプラットフォームじゃだめなんかとか、
       いろんな懸念がありました。
       また仮に実行できても、
       手法の汎化性能が低過ぎで
       属人化する雰囲気がはんぱない。 &lt;a href=&quot;#fnref:deploy&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:ibex&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;開発者は Alex Drummondでアプリの
     &lt;a href=&quot;https://github.com/addrummond/ibex&quot;&gt;コード&lt;/a&gt;も
     &lt;a href=&quot;https://github.com/addrummond/ibex/blob/master/docs/manual.md&quot;&gt;マニュアル&lt;/a&gt;も
     オンラインで参照できます。 &lt;a href=&quot;#fnref:ibex&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:grep&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;変えたい場所があれば &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep -r 'query'&lt;/code&gt; のように
        コマンドすることで編集したい場所を
        スムーズに探せます。 &lt;a href=&quot;#fnref:grep&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:site&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;正確にはドメインですが簡単のため。 &lt;a href=&quot;#fnref:site&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:more&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;まだ ibex と ibex farm の違いはわかりづらいかもですが、
     イメージとしては
     「動画を公開したいけど自分のサーバー/URLがないからYouTubeに投稿しよう！」
     というのに似ています。
     動画を上げると、動画再生アプリを動かすサーバーとURLが同時に利用可能になりますよね。
     つまり 「実験を公開したいけど自分のサーバー/URLがないからibex farmに投稿しよう！」
     という感じです。
     実験のwebアプリを動かすサーバーとURLが与えられるわけです。 &lt;a href=&quot;#fnref:more&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name></name></author><category term="ibex" /><category term="ngrok" /><summary type="html">【2020-08-24更新】本文の随所に https はibex farm だと使えないという旨の 記述があらわれますが 2020/04/26 以降からhttps は使えます。</summary></entry></feed>