TECH PLAY

株式会社AI Shift(株式会社サイバーエージェントグループ)

株式会社AI Shift(株式会社サイバーエージェントグループ) の技術ブログ

80

はじめに こんにちは、AIチームの長澤 ( @sp_1999N ) です。 この度 Oracle AI World 2025 にスポンサーとして参加してきました。 本ブログでは Oracle AI World (OAW) の概要とともに各種トピックスをご紹介できればと思います。 Partner Award 受賞 OAW やセッションのご紹介の前に、この度弊社 AI Shift がグローバルに受賞頂けたので、そちらの紹介からさせてください。 Oracle はさまざまな企業と協業しており、 PartnerNetwork という公式のパートナーシッププログラムが存在しています。AI Shift もパートナーシップを締結しており、かねてより連携を進めてきました。 これを背景に本年度の Oracle Partner Awards に選出され、 AI Innovation Award Regional Best-in-Class Technology/Cloud Service Partner を受賞いただく運びとなりました。 授賞式の様子 今回の OAW で授賞式が執り行われ、弊社からもメンバーが式に参加いたしました。喜びを噛み締めながらも、より良いサービス提供のために一層尽力せねばと気の引き締まる思いでした。 OAW 2025 の概要と雰囲気 イベント概要 このイベントは、去年まで Oracle Cloud World の名前で annual 開催されていたものだったのですが、今年から Oracle AI World の名称にリブランディングされたようです。キーフレーズも "AI changes everything" として設定され、会場やセッションの至る所で見かけました。 会場中央付近の休憩スペース 日程は2025年10月13日-16日の4日間で、場所は Las Vegas にある Venetian conference center で開催されました。様々な業界から多様な職種の人が参加しており、特にセッションが集中する 14, 15 日は会場が人でごった返しておりました。ちなみに、日本からの参加者は概ね400人程度とのことでした。 会場では朝食・昼食・セッション中の軽食などが提供され、ウォーターサーバーも至る所に配置されており、とても過ごしやすい環境でした。ただ会場は少し冷房の効きが強く、羽織がないと寒かったです。 また会場の複数箇所に DJ ブースがあったり、弾き語りアーティストが演奏していたりなど、一日中賑やかな雰囲気でした。 セッション概要 OAW ではハンズオンから LT までさまざまな形式で豊富はセッションが用意されています。 今年は累計で1000を超えるセッションが用意されていたようです。幅広い業界や対応者に向けたセッションが組まれているため、エンジニア以外の参加者も多く見かけました。 テーマや対象者カテゴリなどは用意されているものの、興味のあるセッションを見つけるのが難しかった印象です。ただイベントサイトにログインすると、セッション管理を個人ですることができ、自分用のスケジュールを組むことができました。モバイル App とも連携していたので、その点では使いやすかったです。 多いのは通常セッションだけでなく、keynote も5つ用意されておりました。どのセッションに出るのかを考えるのが良い意味でとても難しかったです。 また、同じクラウドベンダーである Google Cloud, Azure, AWS などの個別セッションも用意されている点も特有な感じがして、個人的には面白かったです。Multicloud を押し進める Oracle ならではの様相な気がします。 ブース出展 セッションと並行して、会場の大きなスペースではセッションの展示がありました。世界の様々な企業が参加しており、弊社もその1つとしてブースの出展をさせていただきました。 海外市場におけるニーズだけでなく、弊プロダクトへの反応などを率直に肌で感じることのできる良い機会になりました。 各社 Oracle のサービスをどのように利用しているのかなどをアピールしており、個人的には OKE で AI workload を構築しているような企業に興味を持ちました。 ブースの様子 セッションのご紹介 Keynotes 14日、15日の2日間で全部で5つも用意されていた keynote ですが、その中から面白かった内容をかいつまんでご紹介します。ただ全てで共通していたのは「AI への注力を示す姿勢」だったような気がします。 まず最初の keynote では既存のクライアントが、OCI 上の AI 機能をどのように利用しているかのクロストークが展開されていました。具体的には、Exelon・Avis Budget Group・Marriott International・Biofy など様々な業種での AI 利活用の様子が紹介されていました。 会場に参加していた企業の中には、これから AI 導入を推進しようとしている状況のところもあり、幅広い業界の参加者に AI 活用を訴えかける良い開幕だったように思います。 また会長兼CTOのラリーエリソン登壇の keynote は、1時間後ろ倒しからの対面予定だったのがオンライン中継に切り替わるなどのハプニングもありました。ただ内容自体はてんこ盛りでした。 個人的に面白かったのは、共同 CEO クレイが TikTok や OpenAI とクロストークをする会でした。具体的には「RDMA を用いて超低遅延通信を実現した Acceleron 」や「Acceleron RoCE を基盤とした、大規模AI向けクラウドスーパーコンピューター Zettascale10 」の紹介がありました。 特に Zettascale10 については、現在 OpenAI が構築を進めている Stargate の基盤として採用されているらしく、Oracle と OpenAI の協力関係の具体的な話が垣間見えた瞬間でした。 通常セッション AI Agents for Enterprise Research: AI-Q NVIDIA Blueprint on OKE タイトルの通り、AI-Q というオープンソースの Nvidia Blueprint を OKE 上で動かす、1時間ほどのハンズオンセッションになります。NeMo という生成 AI モデル向けの開発プラットフォームで用意された機能群を、NIM という推論用マイクロサービスで展開する内容になります。具体的な構成は以下のような感じでした。 https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/docs/assets/arch_diagram.png より引用 ハンズオンのメイン操作として NVIDIA RAG Blueprint の Helm chart を事前用意済み OKE サンドボックス環境に展開します。図の上側にあるように、基本的な処理フローとしては Embedding および Reranking を利用した RAG になります。メインの LLM は Llama Nemotron Super 49B です。 PDF などのファイルは NeMo Retriever Extraction Models などを利用してベクトルデータベースに格納されます。 Web UI も展開される Chart 構成になっており、推論基盤の展開だけでなく、UI を通したサービス利用まで体験できました。ハンズオンで使用した Helm chart はすでに GitHub で公開済みですので、ご興味のある方はぜひご覧ください。 AI That Pays Off: Real-World ROI from Predictive Service Optimization こちらは 20 分ほどの LT セッションになります。内容としては AI エージェントというよりももう少し古典的な機械学習モデルの実応用事例の紹介、と言った感じでした。 具体的には Oracle の提供する予測モデルを使用して、経路の最適化や需要予測が実際の企業でどのような ROI に結びついているかというトークが展開されていました。ヘルスケアや通信事業会社の方が登壇しており、 Oracle Fusion Field Service がどのように利用されているかが語られていました。 このような LT セッションはブース出展スペースと同じ会場で頻繁に開催されており、非テックな職種の方も熱心に参加されていました。 Knowledge Graphs in Oracle AI Database for AI-Driven Applications このセッションでは Oracle Autonomous AI Database を利用して、知識グラフを扱いました。 (ちなみに Oracle Autonomous Database は、Oracle Autonomous AI Database にリネームされました。他にも様々な既存サービスに AI の名称がつく変更がこの OAW 2025 で発表されています。) 紹介された内容は2つで、テーブルデータからグラフデータに変換するものと、テキストからグラフデータに変換するものでした。後者については LLM を Entitiy/Relation 抽出器として使用するいわゆる GraphRAG によるものです。 こちらのリポジトリ がセッション中に紹介されており、参考になるかと思います。 前者については、Autonomous AI Database に格納されている口座および入出金の2つのテーブルデータから、 Graph Studio を使ってグラフに変換する手順が紹介されていました。 Autonomous AI Database はコンバージドなデータベースなので、単一のエンジン上でリレーショナル・JSON・グラフなど様々なデータ形式を扱えます。Graph Studio も Autonomous AI Database の提供機能の1つです。UI 操作で気軽にグラフを作成できます。 またノートブック実行環境も提供されているため、以下のようにグラフ操作を行うことも可能です。 Graph Studio 上での Notebooks 操作 また以下のように SQL の構文でグラフに対する操作を実行することもできます。 SELECT account_id1, account_id2 FROM graph_table(BANK_GRAPH MATCH (v1)-[IS BANK_TRANSFERS]->{4}(v2) WHERE v1.name = 'RESSEL RIVERA" COLUMNS (..) ); この辺りは PL/SQL の面白さといえます。 またセッションの中では、DeepWalkというグラフ機械学習アルゴリズムを使って違法行為に使用された可能性のある銀行口座を探したりしました。 DeepWalk はグラフを単語列のように扱い、Word2Vec の考え方を適用することで、ノードを連続ベクトル空間に埋め込むアルゴリズムになります。テーブルだけでは難しかった示唆の発見が可能になります。 このように、テーブル/文書 <-> グラフのデータを1つのインスタンスだけで気軽に操作できるのはとても魅力的な機能です。 近年の LLM 利用では様々なデータベースを管理することが求められつつあるため、Autonomous AI Database はこの状況に対する1つの有望なアプローチであることが改めて感じられました。 Agentic AI for the Oracle AI Database via Our MCP Server このハンズオンセッションでは、 SQLcl* 向けの MCP Server を使った開発方法の紹介がありました。 具体的には VSCode 上で SQL Developer や Cline の拡張機能を使い、Oracle AI Database に対するエージェンティックな開発の様子を体験する内容になります。 コーディングエージェントとしては、Oracle が提供する Oracle Code Assist を使います。 OCI における Java・SuiteScript・PL/SQLなどに最適化されているため、OCI 上での開発には有用なツールになります。Cline を通して利用することができます。 Cline での設定画面の様子。左側の API Provider から設定できます。 そして、Cline MCP Server の Configure から以下のように json を設定することで、SQLcl 向けの MCP サーバが利用できるようになります。 command にはダウンロードした SQLcl へのパスを設定します。 brew install --cask sqlcl もしくは直接 ダウンロードページ から用意します。 { "mcpServers": { "sqlcl": { "disabled": false, "timeout": 60, "type": "stdio", "command": "path_to/sqlcl/bin/sql", "args": [ "-mcp" ] } } } ここまで設定できれば、このハンズオンの主題はほとんど完了したようなものでした。 あとは Cline 上でひたすら会話を続けることで、MCP server を介しながら実際にデータベースに接続しながらテーブルの作成やデータのインサートが、エージェントによって実行できました。 個人的にはとても良い体験だったので、実務への導入も検討したいと思える良い内容でした。 ※ SQLcl は Oracle AI Database 用のコマンドラインインターフェースです。SQL や PL/SQL文を対話形式もしくはバッチファイルで実行することができます。 弊社CTO青野の登壇 How to Build Agentic Knowledge Assistants with Oracle AI Database 26ai のセッションで、弊社 CTO 青野による登壇がありました。 Oralce Group VP および VP の方と一緒に登壇し、弊社製品の AI Worker がどのように Oracle 製品と連携しているかなどの発表を行いました。 登壇の様子 自分も聴講参加したのですが、自分たちが開発を行うプロダクトの発表について、海外の人が聞いているという光景はなんだかとても新鮮でした。 Japan セッション 上記でご紹介したセッションとは少し毛色の異なるものとして、Japan セッションというものがありました。 今回の OAW に日本から参加されていた方が集まり、日本オラクルの方から OAW のラップアップを受けることができました。 改めて主要な情報を日本語でキャッチアップできたり、日本の方と交流することのできるとても良い時間でした。 おわりに この記事ではスポンサーとして参加した Oracle AI World 2025 の全体的な振り返りをしてみました。 イベント期間中は朝から晩までとにかく充実した日々だったように思います。セッションもたくさんあり、まだまだ活かせていない/知らない機能がたくさんあるなと、収穫も多分にありました。 次回以降も機会があればぜひ参加したいと思える内容でした。 Keynote や一部セッションは期間限定で オンライン配信 されるようですので、ご興味のある方はぜひ覗いてみてください。 投稿 Oracle AI World 2025 参加レポート は 株式会社AI Shift に最初に表示されました。
アバター
はじめに こんにちは。AIチームの村田(X: @em_portero )です。 AI Shiftでは、生成AIリスキリング事業にてチーフエバンジェリストとして企業での生成AI活用を支援するリスキリング研修講師を担当及川(X: @cyber_oikawa )とAIチームで、AI技術の進化をストーリーとして読み解くAI教養ポッドキャスト「AI Shift Academy」を運営しています。 私も台本のためのサーベイに携わっており、この記事ではその中で調べたLLM-as-a-Judgeにまつわるバイアスについてまとめたいと思います。 背景 LLM-as-a-Judgeとは、LLMを評価者として利用し、他のAIモデルの出力や人間の回答の品質を判定する手法です。 いまやLLM-as-a-Judgeは学術/産業を問わずに広く利用されています。LLMを評価者として採用する手法は、その手軽さや高いスケーラビリティにより研究や開発を加速させます。 一方でLLMには学習データなどに起因するバイアスがあり、評価時にはそれらを回避・軽減することが必要です。回避するための第一歩はどのようなバイアスが存在するかを知ることです。 この記事ではLLM-as-a-Judge周辺のバイアスを総覧的にまとめることを目的とします。チートシート的なものになれば幸いです。そのため、原因や回避テクニックなどには詳しく触れません。気になる方は各論文を参照ください。 LLM-as-a-Judgeにまつわるバイアスの分類 一覧 このセクションではサーベイ論文 [Li+ '24] をベースに分類したバイアスを紹介します。 大分類として、 Presentation-Related Biases :文の表現方法に起因するバイアス Social-Related Biases :実社会に関連するバイアス Content-Related Biases :文の中身に関連するバイアス Cognitive-Related Biases :認知に関連するバイアス Scoring-Related Biases :数字を使った採点に関連するバイアス の5つを採用しそれぞれに分類されるバイアスをテーブル内で紹介します。分類は一つのバイアスが複数のカテゴリーに当てはまる場合もありますが、幅広くカバーすることを重視しました。 Presentation-Related Biases バイアス名 説明 日本語名/別名 Position Bias 入力の位置に基づいて判断を下す傾向。系列の早い段階または遅い段階の応答が、他の位置の応答よりも優先される。 位置バイアス Verbosity Bias コンテンツの実際の価値に関係なく、長さを品質と同一視し、より長い応答を好む傾向。 冗長性バイアス / length bias Style Bias 特定の文章スタイルを高く評価してしまう傾向(e.g. 絵文字/箇条書きを好む)。 スタイルバイアス Social-Related Biases バイアス名 説明 日本語名/別名 Authority Bias 書籍、ウェブサイト、有名人などの権威ある情報源への言及に影響される傾向。 権威バイアス / concreteness bias, citation bias Bandwagon-Effect Bias 大多数の意見に同調する傾向。評価者としてのLLMがコンテンツを客観的に評価するよりも、一般的な見解を優先する。 バンドワゴン効果バイアス Compassion-Fade Bias モデル名の削除などの匿名化戦略に影響され、LLMの判断に影響が及ぶ傾向。 共感フェードバイアス Diversity Bias 性別、民族性、その他の社会的分類など、アイデンティティ関連の指標に基づいて判断を変化させる傾向。 多様性バイアス Cultural Bias モデルが学習されていない文化圏の言語や文化を正しく評価できないバイアス。 文化バイアス Content-Related Biases バイアス名 説明 日本語名/別名 Sentiment Bias ネガティブまたは恐怖を感じさせる応答よりも、陽気または中立など、特定の感情的なトーンを持つ応答を好む傾向。 感情バイアス Token Bias 事前学習中により頻繁または顕著なトークンをLLMが好み、偏った判断につながる傾向。 トークンバイアス Context Bias 文脈上の例や文化的背景に影響されてLLMが偏った判断を下し、偏見のある、または文化的に配慮に欠ける結果につながる可能性のある傾向。 文脈バイアス Cognitive-Related Biases バイアス名 説明 日本語名/別名 Overconfidence Bias 評価判断において過大な自信を示し、過度に断定的だが潜在的に不正確な結論に至る傾向。 過信バイアス Self-Preference Bias 評価者として機能している同じモデルによって生成された出力を好み、客観性を損なう傾向。 自己選好バイアス / self-enhancement bias Refinement-Aware Bias 評価中に、回答がオリジナルか、洗練されたものか、あるいは会話履歴を伴うかによって、スコアリングが変動する傾向。 洗練度認識バイアス Distraction Bias 無関係なコンテンツに影響され、重要な要素から注意をそらすことで判断の質を低下させる可能性のある傾向。 注意散漫バイアス Fallacy-Oversight Bias 論理的な誤謬を見落とす傾向。これにより判断の正確性が損なわれる可能性がある。 誤謬看過バイアス table { width: 100%; table-layout: fixed; } th:nth-child(1), td:nth-child(1) { width: 20%; } th:nth-child(2), td:nth-child(2) { width: 50%; } th:nth-child(3), td:nth-child(3) { width: 30%; } Scoring-Related Biases バイアス名 説明 日本語名/別名 Numerical Bias 特定の数字を好むバイアス(e.g. 1~10→7, 0~100→42)。 数値バイアス Score Rubric Order Bias 各スコアの説明の提示順序によって引き起こされるバイアス(e.g. 123 vs 321)。 スコア基準順序バイアス Score ID Bias スコアの識別子として使用される形式によって生じるバイアス(e.g. 123 vs 一二三)。 スコアIDバイアス Reference Answer Score Bias プロンプトに提示される参照回答に特定のスコアを付与するかどうか、またそのスコアの値によって引き起こされるバイアス。 参照回答スコアバイアス バイアスの詳細 このセクションではいくつかのバイアスについて、論文をピックアップしながら詳細に触れようと思います。 Position Bias [Chen+ EMNLP '24] ではリストワイズ評価におけるモデルごとのバイアスを評価しています。例えば、Mixtralは最初の選択肢を好むがSparkは2番目を好むという傾向が観察されました。モデルごとに異なるバイアスが存在することは使用者として注意すべきでしょう。人間(少なくとも筆者)はマークシート形式で最初の選択肢を選びにくいですが、LLMはそうではないというのも意外な結果でした。 また、 [Shi+ '24] では要因ごとにPosition Biasに与える影響を分析しています(下表)。回答品質のギャップが小さいほどバイアスの影響を受けやすく、モデルファミリーごとにバイアスの強さが異なることを報告しています。 Position Bias の要因分析。[Shi+ '24] Table 1より引用。 Self-Preference Bias 評価者と回答生成者が同じモデルであることで発生するこのバイアスは原因の分析が進んでいます。 [Wataoka+ '24] ではPerplexityが低い回答を好みやすいことが原因であると主張しています。実験ではPerplexityが低い回答をLLMが人間よりも顕著に好むことが示されました(下図)。 Perplexityの差と評価結果のプロット。[Wataoka+ '24] Figure 3 より引用。 [Panickssery+ NeurIPS'24] ではLLMの自己認識を定量化し、それとPosition Biasに正の相関があることを示しています。 自己認識スコアとPosition Biasの相関。[Panickssery+ NeurIPS'24] Figure 4 より引用。 また、完全に同一のモデルだけではなく同じモデルファミリーに属するモデルを使用することでバイアスが発生することも報告されています [Li+ '25] 。 学習データやアーキテクチャが類似する同じファミリーではPerplexityなども類似することが要因だと考えられます。 Sycophancy バイアスの分類のひとつではありませんが、LLMを使用するときの問題として Sycophancy(おべっか、追従性)があります。ユーザーの信念や反論に合わせて、初期の評価や判断を容易に変更してしまう傾向です。 [Kim+ '25] では、この追従性がやり取りの形式によって大きく左右されることを調査しています。彼らは、LLMがLLM-as-a-Judge的な設定、すなわち相反する複数の議論を単一のターンで同時に評価するよう求められた場合と比較して、ユーザーからのフォローアップとして反論を提示される対話的な評価設定の方が、反論を認めやすく、自身の初期の判断を維持しにくいことを指摘しています。 原稿などをLLMでレビューするときは意識すると良いかもしれません。 おわりに 本記事ではLLM-as-a-Judgeにまつわるバイアスを総覧的にまとめました。LLM-as-a-Judgeを利用する方の頭の片隅に残り、より良い評価に繋がれば嬉しいです。 冒頭に触れたAI Shift Academy該当回のリンクは以下となります。ぜひご覧ください。 YouTube Spotify Apple Podcast Amazon Music 投稿 LLM-as-a-Judgeにまつわるバイアスまとめ は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは!筑波大学修士1年の朱 博瑄 ( @15sen3haku )です。 7月~9月の約3ヶ月間、AI Shift/サイバーエージェントにて、ミッション型インターンに参加させていただきました。 大学では自然言語処理に関する研究をしており、自然言語処理の社会実装や、ひいてはもっと広い範囲でのAIの社会実装を体感したいという思いから、今回のインターンに参加させていただきました。 本記事では、今回のインターンにて取り組んだ内容を紹介させていただきます! タスク: AIエージェントにおけるコンテキスト圧縮について 概要と背景 AI Shift が開発・提供している AI Worker は、企業が独自にエージェントの設計を行うことができる、AIエージェント構築プラットフォームです。 AI Worker では外部から情報を取得する様々な機能をサポートしています。 しかしながら、webスクレイピングなどを例にとると、1回のリクエストで数万トークンもの情報を取得しエージェントに入力してしまうなど、過剰な情報量となってしまう場合があります。 このように入力コンテキストが増大すると、コスト増・応答品質/速度低下・入力可能な文脈長上限を超過してしまう、といった問題があります。これらを解決するため、 必要な情報を損なわずに、エージェントに入力するコンテキストを上手い具合に圧縮する手法が必要 です。 そこで本タスクでは、 性能の高い圧縮手法をAIエージェントの一部に導入すること を目的に、 クエリに応じた圧縮の性能が高い手法はどれか 比較検証を行いました。 具体的には、クエリ志向型の長文脈要約データセットを用いて、複数のコンテキスト圧縮手法を定量・定性的に評価しました。 実験手法 近年、多くのコンテキスト圧縮手法が提案されつつあります。NAACL 2025で発表されたサーベイ論文 [1] では、ハードプロンプト手法とソフトプロンプト手法というように、大きく2種類の手法に分かれると主張されています。 "Prompt Compression for Large Language Models: A Survey" [1] より引用 2種類の手法それぞれの特徴を簡単に説明すると、以下の通りになります。 ハード圧縮 : 元の文章の言葉を自然言語として保ったまま、不要な部分を削る圧縮手法 ソフト圧縮 : 人間ではなくLLMが理解しやすいように、元の自然言語を人間には読めない特殊なトークンに変換する圧縮手法 ソフト圧縮は自然言語ではなく特殊なトークン (連続的なベクトル) に変換するため、ハード圧縮よりも高い圧縮率を実現しやすいというメリットがあります。 ですが、本タスクでは実応用を想定し、主に下記の2点から ハード圧縮手法 を実験手法として採用しました。 生成器による最終的な生成結果のみならず、圧縮器が出力した圧縮済みコンテキストも、何らかの機能で使用し得ること webスクレイピング等で取得した情報も、エージェントの処理過程としてユーザに可視化されること 実験で使用する3つのアプローチ 次に、本実験で使用する圧縮手法について説明します。下記の概要図のように、使用する圧縮手法は LLMによる要約 、 センテンスベースでの圧縮手法 、 トークンベースでの圧縮手法 の、大きく3種類に分類されます。 このうち、「LLMによる要約」はCloseなLLM APIを使用した手法ですが、センテンスベースとトークンベースの2つについては、Openなモデルを使用した手法となります。 具体的には、以下のパターンを比較します。実験設定の章で後述しますが、本実験では日英のデータセットを使用するため、LongLLMLingua のみ言語によってベースモデルを切り替えています。 (アーキテクチャの説明など、各手法の詳細は本稿では触れないので、ご興味を持った方は引用よりご参照ください。) LLMによる要約について gpt-4.1 gpt-4.1-mini gemini-2.5-flash gemini-2.5-flash-lite センテンスベースでの圧縮について CPC [2] large ┗ base model: Mistral-7B-Instruct-v0.2 CPC small ┗ base model: Llama-3.2-1B-Instruct Provence [3] ┗ base model: DeBERTa v3 reranker トークンベースでの圧縮について LongLLMLingua [4] large ※ ┗ base model (ja): Llama-3.1-Swallow-8B-Instruct-v0.5 ┗ base model (en): Llama-3.1-8B-Instruct LongLLMLingua small ※ ┗ base model (ja): Gemma-2-Llama-Swallow-2b-it-v0.1 ┗ base model (en): gemma-2-2b-it LLMLingua2 [5] ┗ base model: xlm-roberta-large ┗ base model: bert-base-multilingual ※ LongLLMLinguaは任意の base modelを選択可能のため、日本語では東京科学大のSwallowシリーズから採用、英語ではそれらのSwallowシリーズのベースモデルを採用 実験設定 データセットについて 今回使用するデータセットには、SUnsET [6] というものを使用します。今回の問題設定の都合上、 「クエリ - ロングコンテキスト - サマリ 」の3つ組が揃ったデータセットを使用する必要があったため、採用しました。 また、SUnsETは哲学や物語文や一般的な社説など、ドメイン非依存の広範な内容であることも、今回の問題設定に適していると考えました。 データサイズと平均トークン長は、以下の通りとなっています。 データサイズ ロングコンテキストの総数: 2,352個 「クエリ - ロングコンテキスト - サマリ」のペアの総数: 11,309個 平均トークン長 クエリ: 13.45 ロングコンテキスト: 3767.4 サマリ: 226.5 なお本実験では、ロングコンテキストの総数を200件、「クエリ - ロングコンテキスト - サマリ」のペアは963件としました。また、SUnsETは全て英語で記述されたデータセットのため、Gemini-2.5-proを使用して日本語翻訳を施し、日英の2つの言語で実験を行いました。 評価指標について コンテキスト圧縮の評価指標については、品質とレイテンシの2種類を計測しました。必要な情報を適切に圧縮してもらう必要があるため、品質の高さは無論重要ですが、実運用を想定すると、圧縮にかかる時間が遅すぎないことも大事な要素であると考えました。 1. 品質について 今回実行する圧縮タスクは、不要な情報を削り、必要な内容を適切に残すという内容であるため、圧縮後の内容を意味的に評価する必要があると考えました。そのため、ゴールドのサマリを利用した表層的な評価手法でなく、LLM-as-a-judgeを採用しました。評価用LLMには、Claude-Opus-4.1を使用しました。 具体的には、relevance と consistency (G-Eval [7] )という2つの指標を用いて、 「クエリ - ロングコンテキスト - 圧縮結果」 の3つ組を入力とし、5段階評価でLLM-as-a-judgeを行いました。それぞれの評価指標のイメージは以下の通りです。 relevance: 適切さ、関連性、圧縮結果がクエリとロングコンテキストに対し的を得た内容か consistency: 一貫性、整合性、圧縮結果がロングコンテキストに対しハルシネーションを起こしてないか 2. レイテンシ について 1つのデータの圧縮に要した時間 (sec.) を計測し、その平均時間を算出します。なお、LLMによる要約はGCP上で1台のCPUを使用し実行しました。センテンスベースとトークンベースは社内のGPU環境を使用しましたが、具体的に使用した環境は以下の通りです。 CPC: A100 1枚 Provence: V100 1枚 LongLLMLingua: A100 1枚 LLMLingua2: V100 1枚 実験結果の定量評価 実験結果について、精度→レイテンシの順で提示します。 1. 精度 について 概ね、 LLMによる要約 > 圧縮に特化したSLM (+センテンスベース > トークンベースの傾向) という結果となりました。今回のようなロングコンテキストの圧縮においては、OpenなSLMを圧縮に特化させた場合よりも、Closeでパラメータの大きなLLMを使用した方が、より精度の高い圧縮ができる、という知見が得られました。 日本語ではCPC (small)とLLMLingua2 (RoBERTa)がLLMに匹敵し、英語ではセンテンスベースの3種類が全体的にLLMに匹敵する、という結果になりました。 日英共に、トークンベースは全体的にスコアが低い、という結果となりました。これは後述しますが、トークンベースの手法では不自然な単語の区切りにより、意味がまともに通じない場合が多かったことが原因と考えられます。 総括すると、 精度に関してはLLMに要約させる手法が最善であり、かつOpenAI系列とGemini系列の間に大差は無い ということが分かりました。 2. レイテンシ について LLMによる要約と、特にLongLLMLinguaが、他の手法よりも遅いという結果になりました。 →前者はパラメータの規模やAPIの通信、後者は2段階の意味的なプルーニングの時間の長さが、主原因と考えられます。 LLMによる要約では、概ねOpenAI系列よりもGemini系列の方がレスポンスが速い傾向にありました。 圧縮結果の例 実際の圧縮結果について、抜粋した例を紹介します。 この例を見ると、上の3つはクエリに対して適当そうな内容を出力していますが、一番下のLongLLMLinguaは単語の区切りが不自然で、かなり読解が困難な内容を出力しておりました。 この傾向は英語でも同様でした。 ユーザ視点での定性評価 概要 これまでの実験で、コンテキスト圧縮の手法を、クエリ志向のロングコンテキスト要約データセットで定量評価した結果が出揃いました。 しかし実運用を考えると、単に "良い圧縮" ができることのみならず、後段のユーザに対する最終出力も考える必要があります。 そこで、圧縮の実験結果を踏まえ、 生成器の出力に対するユーザ視点での定性評価 を実行することとしました。 定性評価の手法 最終的な回答の生成器には、gpt-5を使用 日本語に翻訳したドキュメントを8つランダムに選出 1つのドキュメントに紐づくクエリを3つランダムに選出 「クエリ - ロングコンテキスト - gpt-5の最終回答」の組み を1クエリあたり5手法分用意(圧縮機は、先の定量評価を踏まえバランスよく5つ選出) 1. SUnsET で用意されている Gold サマリ 2. gpt-4.1-mini 3. gemini-2.5-flash 4. CPC(small) 5. LLMLingua2(RoBERTa) アノテータとして弊社の機械学習エンジニア12名で実施 1クエリに対応する5セットに対し、1位~5位で順位づけしてもらい、ボルダ得点 ※ を算出 ※ 線形的に ”順位の良さ=数値の高さ” とする: 1位=5点, 2位=4点, …, 5位=1点 定性評価の結果 以下の表が、定性評価によるボルダ得点の結果のまとめになります。 例えば、gpt-4.1-mini のスコアが LLMLingua2 (RoBERTa) より低く出たことから 「圧縮結果の良さは、後段の生成器の出力に対する定性評価の良さと相関するわけではない」 という可能性が示唆されました。 OpenAI 系列は、圧縮の定量評価では最も良いスコアでしたが、後段の定性評価では3位となってしまいました。 LLMLingua2 (トークンベース代表として採用)は、圧縮実験の精度の評価は低めでしたが、今回の定性評価では1位となりました。 定性結果を総括すると、 重要なトークンの抽出のみでも、生成器にとっては十分な情報量となっている可能性 が示唆されました。 ただし留意点として、今回選出したデータセットが「文章としての読解難易度がそもそも高い」というものがあります。アノテータからも文章読解の難易度に対する意見が実際に上がりました。ボルダ得点の分布も似ていることから、この数値の差が有意かどうかは追検証の必要があると考えています。 タスクのまとめと展望 本タスクに関する内容の最後として、まとめと今後の展望を記します。 エージェントを代表とした LLM アプリケーションでは「コンテキスト量の増大によるコスト増・応答の精度/速度低下・文脈長上限超過」が課題 LLMによる要約 、 センテンスベースの圧縮手法 、 トークンベースの圧縮手法 の大きく3種類の手法で実験を実行 圧縮出力の LLM-as-a-judge の結果は次の通り: LLM要約 > センテンスベース > トークンベース 生成器による最終的な出力を評価対象とした、人手による定性評価も実施 SUnsET のゴールド要約に加え、4つの手法を選択した合計5つの圧縮文をそれぞれ利用して、クエリに対する最終回答を生成 社内のAIエンジニア12人がアノテータ 定性評価の結果、「 圧縮品質と後段の生成結果に対する定性評価は、必ずしも相関しない 」ことが分かった YANS参加について 今回のタスクで行った内容を基に、YANS 2025 (第20回言語処理若手シンポジウム) にポスター発表で参加させていただきました!自分は9/18, 19の2日間参加させていただきました。 AI Shiftとしての YANS参加報告の記事 も上がっているので、是非ご覧ください (本件の発表題目は右記の通りです: [S1-P49] AIエージェントにおけるコンテキスト圧縮手法の定量的比較評価) ポスター発表では、有難いことに多くの方が関心を寄せてくださり、実りあるディスカッションができました。特に企業の方が多く見に来てくださったということもあり、昨今のAIエージェントの実応用における活況を改めて体感する機会となりました。 また、自分自身YANSは初参加 (学会参加自体は2度目) ということもあり、数多くのNLPerや他分野の方々と交流ができ、とても充実した楽しい時間となりました。 感想 改めてになりますが、AI Shiftで3ヶ月間インターンさせていただき、本当にありがとうございました! 社員の皆様全体が本当にあたたかく、最高の環境でインターンさせていただくことができました。3ヶ月間、本当にあっという間だったと感じます。 タスクに関することや ML/DS (機械学習エンジニア/データサイエンティスト) としての技術的な面のみならず、企業の一員として価値を提供していくことや、企業という場における適切なコミュニケーションの仕方など、ソフトスキルの面でも多くのことを学ばさせていただきました。AI Shiftの皆様のおかげで、圧倒的な成長ができたと思います! 他にも、YANS参加・諸々のイベント参加・様々な方とのランチを通じて、ML/DS という職種に対する解像度が高まったり、今後働くにあたっての自分の軸がより定まったような感覚があり、そうした人との交流という観点でも、大変貴重で充実した時間を過ごさせていただきました。 とにかく、改めまして3ヶ月間ありがとうございました! 引用 [1] Li, Zongqian, et al. "Prompt Compression for Large Language Models: A Survey." Proceedings of the 2025 Conference of the Nations of the Americas Chapter of the Association for Computational Linguistics: Human Language Technologies (Volume 1: Long Papers) , Association for Computational Linguistics, 2025, pp. 7182-95. [2] Liskavets, Barys, et al. "Prompt compression with context-aware sentence encoding for fast and improved llm inference." Proceedings of the AAAI Conference on Artificial Intelligence . Vol. 39. No. 23. 2025. [3] Nadezhda Chirkova, Thibault Formal, Vassilina Nikoulina, and Stéphane CLINCHANT. 2025. "Provence: efficient and robust context pruning for retrieval-augmented generation." In The Thirteenth International Conference on Learning Representations. [4] Jiang, Huiqiang, et al. "LongLLMLingua: Accelerating and Enhancing LLMs in Long Context Scenarios via Prompt Compression." Proceedings of the 62nd Annual Meeting of the Association for Computational Linguistics (Volume 1: Long Papers) , Association for Computational Linguistics, 2024, pp. 1658-77. [5] Pan, Zhuoshi, et al. "LLMLingua-2: Data Distillation for Efficient and Faithful Task-Agnostic Prompt Compression." Findings of the Association for Computational Linguistics: ACL 2024 , Association for Computational Linguistics, 2024, pp. 963-81. [6] Wright, Dustin, et al. "Unstructured Evidence Attribution for Long Context Query Focused Summarization." arXiv , 14 Feb. 2025, arxiv.org/abs/2502.14409. (Dataset page: https://huggingface.co/datasets/dwright37/SUnsET ) [7] Liu, Yang, et al. "G-Eval: NLG Evaluation Using GPT-4 with Better Human Alignment." Proceedings of the 2023 Conference on Empirical Methods in Natural Language Processing , Association for Computational Linguistics, 2023, pp. 2511-22. 投稿 AIエージェントにおけるコンテキスト圧縮手法の評価 (AI Shiftインターン体験記) は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは AIチームの戸田です ここ数年で音声対話システムは急速に身近なものになりました。特に、ChatGPTのアドバンスドボイスモードのようなリアルタイムで会話できるプロダクトに触れていると技術の進化を強く実感します。 このような体験を支える重要な技術の一つが、システムと人との会話の順番を自然に交代する「ターンテイキング」です。 以前、AIチームの大竹が検証した " MaAI " のように、この技術に特化したライブラリも開発されるなど、近年注目度が高まっています。 そのような中で先日、 STT(Speech-to-Text: 音声認識) APIを提供するプラットフォームのDeepgramが音声認識とターンテイキングのための End of Turn (End of Turn: 相手の発話の終了) を同時に予測する Fluxというモデルを公開しました 。本記事ではそのFluxを試してみたいと思います。 Deepgram Flux https://deepgram.com/learn/introducing-flux-conversational-speech-recognition 背景 Deepgramは、元々Novaというモデルシリーズで高精度で低遅延なストリーミングSTTモデルを開発・提供していました。 Deepgramのレポート ではOpenAIのWhisperより認識精度が高く速度も速いと言われており、オンライン会議の リアルタイム文字起こしなどで活用されていました 。 しかし、近年のLLMの進化から、音声認識そのものよりも、音声認識結果をどう活かすかがプロダクト上重要になってきました。特に音声対話システムのように「聞く」と「話す」が連続して行われる場面では、認識の速さが体験を大きく左右します。たとえば、ユーザーが話し終わって数秒遅れてAIが反応するだけで、「もたついた印象」を与えてしまいます。逆に、まだ話している途中で割り込むのも良くありません。 Fluxの登場 こうした課題に対して、Deepgramは Flux という新しいモデルシリーズを出しました。従来の音声認識モデルは "音声→テキスト" の変換を行うのみで、EoTの判断は、音声区間を検出する別のVAD(Voice Activity Detection)モデルを組み合わせる必要がありました。 ここでFlux は音声認識モデルの内部に発話が終わったかの判断を予測を行う仕組みを統合しました。これにより従来のVADが使っていた音響特徴だけでなく文脈的な情報をもとにEoT「発話の終わり」を推定できるようになります。例えば、Fluxは文末らしいイントネーションや文法構造も考慮しながら、発話が続くのかを判断すると言われています。 一点気をつけなければいけない点として、現在対応している言語は英語のみであることが挙げられます。日本語で試すことはできないので、この点は今後の多言語対応に期待です。 試してみる 事前準備 現在はPythonのみSDKが提供されています。pipでインストールします。 pip install deepgram-sdk 加えて音声処理のためffmpegをインストール必要があります。Macの場合はbrewでインストールできます。 brew install ffmpeg そして Deepgramのコンソール で発行したAPI Keyを環境変数DEEPGRAM_API_KEYに設定しておきます。 検証コードについて 本来はマイク入力などstreamingでの認識を想定したライブラリですが、今回は実験のため、静的な音声ファイルをチャンクに区切って擬似的にstreamingで流します。コードは Gistにアップロード したので詳細はこちらをご参照ください。つまりそうなところが2箇所あったので、抜粋して以下で紹介します。 環境変数の設定位置 from deepgram import AsyncDeepgramClient のようにdeepgramのSDKをimportする際に環境変数DEEPGRAM_API_KEYのチェックが走るので、dotenvなどを使う際はこの手前でloadするようにしてください。 eot_thresholdを設定する 公式のquickstart には記載がないのですが、EoTの予測を使用する際はDeepgramのAPIクライアント定義時に0.3〜0.9の範囲で eot_threshold を設定する必要があります。今回は適当に中間の0.6を設定しました。 認識結果を確認してみる Gistにあげたコードを実行すると--outputで指定した先に以下のカラムを持つcsvを出力します timestep: 時間(float) end_of_turn_confidence: EoTの確度(float) transcript: その時点で認識した単語(string) is_end_of_turn: EndOfTurnが反応したか(bool) このcsvを同じくGistにあげた こちらのコード を使って可視化します。 音声は以下の「If we deploy this version today, we’ll probably need a rollback plan ready.」という私の声でテストします。 この文は途中に 従属節(if節) が挟まっており、カンマ部分での一時的な沈黙を「発話の終わり」と誤検出しやすい典型的なケースです。 本当にこれが難しい問題なのかを、Whisperと合わせてよく使われるVADライブラリの webrtcvad を使って確認してみたいと思います。コードは同様に Gistにアップロード しており、こちらを実行すると以下のような結果になりました。 カンマのある4秒付近で非音声、つまりEoTになったと認識されてしまっています。フレーム長や区間融合のギャップなど調整すれば上手く認識できるかもしれませんが、人によって話し方は違いますし、なかなか難しい問題だということがわかります。 一方Deepgram Fluxを使った結果は以下になります。 少し認識結果が重なって見えにくいですが、4秒くらいのカンマ部分で若干end_of_turn_confidenceが上がりつつも、EoT認識を抑えたのが確認できます。これは、Fluxが音響的な無音だけでなく、発話構造の文脈情報を参照していることを示しています。つまり“If we deploy this version today, …” のように条件節が続く構文では、文が完結していないことをモデルが理解し、一時的に end_of_turn_confidence が上がっても、最終的なEoT出力を抑え込んでいるのだと思われます。 おわりに 本記事では音声認識モデルの内部でEoTを同時に推定するDeepgram Fluxを試してみました。文法的構造を考慮してEoTを予測しており、音響的には無音だがまだ発話が続くようなケースをうまく扱える点がよかったです。 なお、今回は検証対象に含めませんでしたが、Fluxには EagerEndOfTurn というオプションが存在します。これは、通常のEoTよりも早い段階で「そろそろ終わりそうだ」と判断したタイミングを通知する機能で、返答の準備を始めるための信号として使われます。EagerEndOfTurnが発火しても実際のEoTが確定しなければ、TurnResumed という信号が返り、再び音声認識を継続する仕組みになっています。特に応答生成にLLMを利用する場合、早くレスポンス準備を始められることは体験上大きな利点となり、よりテンポの良い対話を可能にします。 EoTとEagerEndOfTurnの二段構えによって、「発話が終わった」だけでなく「終わりそうだ」も検出できる点で、Fluxは従来の音声認識モデルには無い良さがあると思います。現在は英語のみ対応ですが、日本語も対応されることを期待して、今後もウォッチを続けたいと思います。 最後までお読みいただき、ありがとうございました! 投稿 Deepgram Fluxを使ったターンテイキング認識の実験 は 株式会社AI Shift に最初に表示されました。
アバター
はじめに こんにちは。AI チームの村田です。 2025年9月17日(水)〜2025年9月19日(金)に浜松アクトシティで行われた 第20回言語処理若手シンポジウム (YANS2025) に AI チームから2名 (栗原, 村田) と弊社でインターンシップ中の朱が参加しました 。 うち村田と朱は発表ありでの参加で、栗原は運営として携わりました。 本記事では、村田の全体的な印象と聴講参加した我々3名が特に興味を魅かれた研究発表をピックアップする形で参加報告をさせていただきます。 YANS2025の印象 YANS2025は応用に近い研究が多い印象を受けました。「Agent」または「エージェント」がタイトルに入った発表は22件あり、YANS2023の2件や YANS2024の4件と比較してかなりの増加でした。 また、 公式のトピック別索引 にて「評価指標・品質推定」に分類される発表も件数/割合ともに増加しています。個人としては、ビッグテックが強力なモデルを公開する中で評価への関心がより高まっており、それが反映された統計であると感じました。 年度 エージェント 評価指標・品質推定 発表件数 件数 割合 (%) 件数 割合 (%) 2023 2 1.4 10 7.1 140 2024 4 2.0 18 9.2 196 2025 22 8.6 38 14.8 257 研究発表の紹介 以降の内容に関しましては、全て著者の方々に掲載許諾を頂いた内容になります。 選んだ論文(村田) [S3-P36] 日本語LLMの“知識の境界線”を暴く:知識のカットオフ日の推定法の提案 山下 果凜 (日本女子大), 伊東 和香 (日本女子大), 西潟 優羽 (日本女子大), 倉光 君郎 (日本女子大) 研究の概要 知識カットオフとは LLM が学習したデータセットがいつ時点までのものかを表します。この研究では公開されたモデルのカットオフを推定することを目指します。先行研究では「2023年の UEFA チャンピオンズリーグの優勝は Real Madrid である」のような事実に基づくベンチマークを構築することで推定していました。この研究では「2023年12月1日(金)」のような自動構築可能な文字列のパープレキシティを計算することによる推測を提案しています。 気になった理由 日本語の公開 LLM には知識カットオフが公表されていないものも多く、リリース日の情報から推測することしかできません。研究/応用の両面から知識カットオフは重要な情報であり、その推定は有用であると感じました。指示学習済みを含む複数の LLM でリリース日より過去の日付をカットオフ日として推定できており、提案手法の有効性を示唆するものでした。 AI Shift で AI エージェントのプロダクトを開発する中でも LLM が "自認" する日付については慎重に扱う必要があることを実感しており興味を惹かれました。 また、サイバーエージェントの公開したモデルである calm2-7b および calm2-7b-chat でのみカットオフの推定日がリリース日より後になっており、それも含めて今後の発展が気になる研究でした。 選んだ論文(栗原) [S4-P22] 自発音声のモデルとしての音声言語モデル 神藤 駿介 (東大), 宮尾 祐介 (東大/NII LLMC) 研究の概要 従来の朗読音声によるモデル学習や、TTSによる音声合成の出力評価などの研究の枠組みがテキストベースで展開されているという前提の元での、自発音声のモデルとしての音声言語モデルの構築を提案している研究です。自発音声を用いることによって、より人間らしい音声モデリングを可能にするなどの可能性を提示しています。 気になった理由 人が通常喋る場合に、完全な文が思い浮かべられているかというとそうではないということを再認識させられる研究でした。特に考えがまとまっていないのに喋り出してしまう、あるいは喋りながら考えを整理するなどの振る舞いなどの人間っぽさを、従来のテキストベースの研究ではモデリングできていないのではという着眼点に非常に面白みを感じました。自発音声に固有で見られる人間っぽさを追求したその先に、どのような産業応用ができるのかなどを考える良いきっかけとなりました。 選んだ論文(朱) [S4-P28] 大規模言語モデルが抱える信念は対話中で一貫しているか? 辻村 有輝 (産総研), 浅田 真生 (産総研), 江上 周作 (産総研), 石垣 達也 (産総研), 高村 大也 (産総研) 研究の概要 CoTやReasoningモデルによりLLMの推論技術の開発が活発化していることを背景に、推論に伴いコンテキストの長さが発展していく中で、LLMの出力はコンテキストの中で一貫した内容となっているかを調査した研究です。実験の結果、モデルのスケーリングに伴い一貫性は必ずしも向上するとは言えないことと、数学・コーティングコーパスで学習されたモデルは高い一貫性を示すことを提示しています。 気になった理由 Reasoningモデルがより一般的になっていき、同時にコンテキスト量が増大していく中、最終的な出力の精度のみならず推論過程の内容について着目することも、非常に重要だと改めて感じました。モデルのパラメータ数が大きくなることと高い一貫性は、必ずしも相関しないという結果に面白みを感じ、下流タスクのスコアと一貫性の間にある関係性がどうなのか気になりました。AI ShiftにおけるAIエージェントのプロダクトにおいても、エージェントの出力時におけるコンテキスト中での内容の一貫性は、考える余地のある領域だと感じました。 おわりに 今回の YANS への参加を通じて、AI エージェントおよび音声対話をプロダクトとして提供する我々の取り組みに関連する研究が増えていることを感じました。アカデミアの成果をプロダクトをより良くするために取り入れられるような姿勢は忘れずにいたいという気持ちになりました。 また、ナイトセッションなどの交流の場も公式から提供されており、最前線の研究者や企業のエンジニアとの交流はとてもよい刺激となりました。 最後にはなりますが、このような素晴らしい学会を運営いただいた委員の皆様、ありがとうございました! 投稿 YANS2025 参加報告 は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは AIチームの戸田です 最近スマートグラスが注目を集めています。スマートグラスというと2013年頃にGoogleが発表した Google Glass が有名でしたが、当時はバッテリーの制約やソフトウェアの未熟さなどから一般利用には定着しませんでした。 https://ja.wikipedia.org/wiki/Google_Glass しかし2025年現在、当時の課題を解決し得るハードウェアとソフトウェアの基盤が揃いつつあり、再び注目を集めています。たとえばMetaが発表した Ray-Ban Meta Smart Glasses は、従来の「カメラ付きサングラス」という位置づけから進化し、今後のアップデートで小型ディスプレイを搭載する計画が明らかになっています。これは通知や簡単な情報をレンズ上に表示できるようにするもので、まさに日常的に“見る”体験を拡張する方向にシフトしている点が特徴です。 こうした動きは他のプレイヤーにも広がっており、 Even RealitiesのEven G1 など日常的に使えるデバイスが増えてきています。さらに基盤技術としては、バッテリー効率を向上させる SPAES のような新技術の開発も進行中です。 ソフトウェアも進化が進んでいます。Googleが発表した Android XR は、スマートグラスやMRヘッドセット向けに最適化された新しいOSで、まだ開発段階のようですが、既存のAndroidエコシステムを活かしつつ、スマートグラス特有の機能をサポートできると言われています。 こうした動向の中で私が注目したのは MentraOS です。MentraOSは Even G1 や Mentra Mach 1 、 Vuzix Z100 など複数のデバイスに対応するオープンソースOSで、ReactやTypeScriptといった一般的なフロントエンドの技術スタックでアプリを構築できるため、学習コストを最小限に抑えられます。 スマートグラスといっても、その形態はさまざまです。カメラやマイク、各種センサーを搭載し、現実世界を記録・解析することに特化したタイプもあれば、レンズに情報を重ねて表示するARグラスとしての性格が強いものもあります。今回は Vuzix Z100 というモデルを試したいと思います。 https://xpert.digital/ja/ポータブル拡張現実 Vuzix Z100は見た目が通常のメガネに近い軽量デザインを持ちつつ、片側のレンズにシースルーディスプレイを備えており、通知や簡単な情報を視界にオーバーレイ表示できます、長時間装着しても負担が少ないのが特徴です。 そしてこのVuzix Z100も、MentraOSがサポートする複数のスマートグラスのひとつです。つまり開発者はVuzix Z100専用のSDKを学ぶ必要はなく、MentraOSの共通APIを使ってアプリを作れば、そのまま他の対応機種(Even G1やMentra Mach 1など)にも展開できます。 今回は簡単なテキスト表示アプリを実際に作って動かしてみた過程を紹介します。 MentraOSの仕組み MentraOSの大きな特徴は、アプリがクラウド上に存在することです。開発者が作成したアプリはWebに展開し(下図YOUR APP)、MentraOS Cloudを介してスマートグラスとやりとりをします。専用デバイスに直接ネイティブアプリを書き込むのではなく、まるでWebアプリをホスティングするような感覚で開発できるのがポイントです。 https://docs.mentra.glass/ MentraOS Cloudとスマートグラスを繋ぐのが、スマートフォン上で動作する MentraOS App です。内部的にはこのアプリを通じてデバイスをクラウド上のアプリに接続しているのですが、最初のBluetooth接続さえ完了してしまえば、ユーザーにはスマートフォン上のアプリを意識することなく、グラスとアプリが自然に繋がっているように見える設計になっています。 この構成によりWebアプリと同じ感覚でスマートグラス向けのアプリを実装・デプロイできる点がMentraOSの最大の魅力です。 実装 環境設定 Node.js (v18以上)とBunをベースにTypeScriptで作成することを推奨されています。Macであれば以下のコマンドで構築できます。 brew install node@20 # Node.js curl -fsSL https://bun.sh/install | bash # Bun また、アプリはインターネット経由で接続されるためngrok を使って仮想的にアプリを公開できるようにします。GCPやAWSなどで別に公開する手段がある場合はこちらの準備は必須ではありません。ngrokの設定は 公式ドキュメント をご参照ください。 こちらの設定とは別に、スマートフォンにMentraOS Appをインストールしておく必要があります。 こちら からiOS版とAndroid版を選んで自身のスマートフォンにインストールし、スマートグラスとBluetoothで接続しておきます。また、初回はアカウントを作る必要があるので画面の指示に従って作成しておきます。 MentraOS Cloudにアプリを作成 以下の手順でMentraOS Cloudにアプリを作成します。最小構成なので、例えばマイクを使ったアプリを作るなどする場合は ドキュメント を参考に適宜権限を追加してください。 console.mentra.glass に移動します 「Sign in」をクリックし、MentraOS Appで使用しているのと同じアカウントでログインします。 「Create」をクリックして、アプリ作成画面に行き、必須項目を埋めていきます Package Name: 一意のパッケージ名を設定します( com.yourname.myfirstapp など) Display Name: 任意のアプリ名を入力します。 Description: アプリの概要を入力します。 Server URL: アプリのエンドポイントを入力します。今回はngrokの静的URLを入力します。 logo URL: アプリのロゴを入力します。URLとありますが、Localから任意の画像をアップロードできます アプリが作成されると、APIキーが提供されます。このキーをコピーしてください。 私の環境では以下のような設定になりました。 余談ですがLogoは現在話題のNano Bananaに作ってもらいました。シンプルで気に入っています。 新規プロジェクト作成 以下のコマンドで新規プロジェクトを作り、必要なライブラリをインストールします。 mkdir my-first-mentraos-app cd my-first-mentraos-app bun init -y bun add @mentra/sdk bun add -d typescript tsx @types/node 続けて以下の.envを作成します。 PORT=3000 PACKAGE_NAME=com.example.myfirstmentraosapp MENTRAOS_API_KEY=your_api_key_from_console PORTは任意のもので構いません。PACKAGE_NAMEはMentraOS Cloudに登録したものに合わせてください。MENTRAOS_API_KEYはMentraOS Cloudにアプリを作成したときに発行されたものにしてください。 アプリ部分 index.tsにアプリのメイン処理部分を実装します。 ドキュメントのBuild From Scratch のサンプルコードを参考にしていたのですが、動作確認のしやすさなどから若干の変更を加えました。コード全体は こちら にアップロードしています。 表示時間の変更 showTextWallというメソッドでスマートグラスの画面にテキスト表示をしているのですが、これにdurationMsというパラメータを追加しています。こちらはテキストを表示する時間をミリ秒単位で指定するパラメータで、デフォルトは5秒なのですが、きちんと表示されたかを確認するのに5秒は体感かなり短かったので、倍の10秒に設定しました。 外部APIから操作できるように サンプルは"Hello, World!"と表示するのみですが、POST /push エンドポイントを設定し、curlなどで叩くと任意のテキストを表示できるようにしました。 curl -X POST http://localhost:3000/push \ -H "Content-Type: application/json" \ -d '{"text":"テストメッセージです"}' 環境変数の読み込み 主機能とは関係ないのですが、 .env をロードし、必須値が未設定なら起動時に明示的にエラーを出すようにしました。自分が環境変数を設定し忘れてハマってしまったのでその対策です。 起動 アプリ側は以下のコマンドで起動します。 bun run tsx index.ts # (必要であれば) ngrok を使用してアプリをインターネットに公開 ngrok http --url=<NGROK_URL> 3000 起動後、スマートフォンでMentraOS Appを開くと先ほど作成したMy Test Appがあると思うので、横のトグルスイッチで有効化します。 これで準備は完了です。Localで起動しているアプリに対して以下のようにPOSTでメッセージを送ります。 curl -X POST http://localhost:3000/push \ -H 'Content-Type: application/json' \ -d '{"text":"テストですほげほげ"}' すると以下のようにスマートグラスにPOSTしたメッセージが表示されます。 おわりに 本記事ではMentraOSを使ったスマートグラスアプリの開発を試してみました。Webアプリ開発の延長線上でスマートグラスのアプリを開発できるのは非常に体験が良かったです。 記事では伝えにくいのですが、実際にcurl一発で現実の視界に文字が浮かぶ体験はかなりインパクトがあり、未来のインターフェースを一足先に触っている感覚がありました。まだUI/UX設計や情報の見せ方には課題が残っていますが、それも含めて開発者にとっては新しい挑戦領域なのではないかな、と思います。 最後までお読みいただき、ありがとうございました! 投稿 MentraOSでスマートグラスアプリの開発を試してみた は 株式会社AI Shift に最初に表示されました。
アバター
はじめまして!一橋大学SDS研究科 修士1年の佐藤祥太 ( @Shota_Sato01 ) です。今回私は8月のCA Tech JOBインターンに参加させていただきました! この記事では、配属先のAI Shiftでの取り組みについてご紹介させていただきます! 配 属部署について 今回のインターンでは、AI Shiftに配属になりました。「人とAIの協働を実現し人類に生産性革命をもたらす」というMISSIONのもと、AIエージェントやVoiceBotの開発に取り組んでいます。 ビジネスサイドとエンジニアサイドが議論しながら、組織で一体となってプロダクトの改善に取り組んでいるのが印象的でした。 タスク 概要と背景 AI Shiftが提供しているVoiceBotは、電話応対業務を自動化するサービスです。 その中で、お客様の発話が「何について言及しているのか」を正しく特定するのは会話の破綻を防ぐために非常に重要なファクターになります。 今回のインターンで私が取り組んだのは主にこの部分で、お客様の発話内容が何を示しているのかを膨大なリストの中から特定する、「エンティティリンキング」というタスクに取り組みました。 このタスクの課題としては 入力が音声認識結果であるため、認識誤りがあった場合正解となるエンティティと表記が一致しない 対象のリストが膨大であるとき、発話内容と正解となるエンティティを紐付けることが困難 という点があげられます。 そこで、私は、 音声認識誤りに頑健 効率的に候補を絞り込める (Nを小さくできる) という特徴を持つようなロジックについて検討を行い、提案した手法の検証を行いました。 手法 今回は、以下に示す計7つの手法 (BaseLine×3, SoftMatcha, MeCab+部分文字列検索×3) について検証を行い、定量的、定性的に比較を行いました。 BaseLine BaseLineの基本的なアイデアは、入力として、transcription (音声入力をテキストにしたもの) とエンティティのリスト全件分をLLMに渡し、出力として、transcriptionがリストの中のどのエンティティを指しているかを返します。 今回は、以下の3種類のプロンプトを作成しました。 Zero-Shot: transcriptionとエンティティリストをプロンプトの中に埋め込み応答を生成 Few-Shot: Zero-Shotに、入出力例を追加 CoT: Zero-Shotに、段階的に候補を絞り込むという指示を追加 候補集合の絞り込み BaseLineではLLMが数万件の候補から1つの正解を抽出する必要があり、正解率やプロンプト長の増加による実行速度への悪影響が懸念されます。 そこで、「LLMに入力する際の候補集合を事前に絞り込むことで、精度と実行時間を向上させられるのではないか」と考え、絞り込みの手法を複数提案し、検証を行いました。 SoftMatcha SoftMatcha [1]は高速かつ柔らかいパターンマッチ検索ツールです(余談になりますが、 2025年の言語処理学会@長崎 で実際に聴講させていただいた中で印象的な発表の一つでした)。 エンティティリスト全件をソース、transcriptを入力として、この手法で検索をかけてヒットしたものを候補集合に選定しました。 MeCabによる形態素解析 + 部分文字列検索 MeCab [2]は形態素解析のツールで、意味を持つ最小の表現単位(形態素)に分解することができます。 分解後の各形態素をキーワードとして、エンティティリストの全件に対し部分一致となるかどうかを判定し、絞り込みを行うのが基本的なアイデアになります。 今回は、このアイデアをベースに3種類の絞り込みのロジックを検討しました。 MeCab: transcriptionの各形態素に対し、得られた検索結果の和集合を候補集合にする MeCab + Cutdown: 上記の各検索結果のうち、サイズが1000以下のもののみを選択し、それらの和集合を候補集合にする MeCab + Comb: transcriptionからn個の形態素を選択し、それらをすべて含むエンティティを検索。これを全ての組み合わせに対し実行し、得られた検索結果の和集合を候補集合にする MeCab + Combについて補足させていただきます。上記の例であれば、n=2のとき、検索対象となる形態素の組み合わせは {(株式, 会社) | (株式, A) | (株式, えー) | (株式, A) | (会社, A) | (会社, えー) | (会社, A) | (A, えー) | (A, A) | (えー, A)} となります。 これらの各組み合わせに対し、すべての形態素に対し部分一致となるエンティティを検索し、それらの和集合を取ります。このような操作をn=1〜(transcriptionの形態素数)まで再帰的に行い、各試行で候補集合のサイズが1000を下回った時を最終的な候補集合として選択します。 ※なお、本検証においてはMeCabの辞書として unidic-lite を使用しています。 実験設定 今回は、エンティティリストとして、約2万件の固有名詞を対象にしました。 これらに対し、固有名詞のテキストをTTSを用いて合成音声を作り、それらを音声認識にかけることにより擬似的な音声認識結果を100件生成し、それらに対し、各手法の正解率と実行時間を評価します。 なお、実験で用いたLLMはGemini-2.0-Flashとし、今回扱うデータは、アルファベットの小文字化や空白除去等の正規化処理を事前に施しました。 実験結果 候補集合の絞り込みについて ここでは、BaseLine以外の手法についての候補集合の絞り込みについて議論します。 各手法毎の絞り込みを行った際の候補集合のサイズ ave_size:候補集合に含まれるエンティティ件数の平均値 LLM_miss:LLMが誤答したときのave_size included: 正解が含まれている候補集合の数 左側が音声認識がうまくいったとき、すなわち、音声の入力を正しくテキストに変換できたケースでの絞り込み結果になります(実用上では、このような場合は完全一致となるエンティティの検索を行えば良いと思いますが、参考のために併記させていただきます)。 右側は音声認識誤りが含まれるときの絞り込み結果です。 SoftMatchaは、認識誤りのない場合には、ほぼ一意に決定できるまでに候補集合を絞り込めています。一方で、少しでも認識誤りが含まれてしまうと検索結果にほとんどヒットせず、候補集合自体を作ることができなくなってしまう、という結果になりました。 一方、MeCabでは、認識誤りの有無にかかわらず、ほぼ全ての入力に対して、絞り込んだ集合の中に正解を含むことができています。しかし、最終的な候補集合のサイズの削減効果は限定的で、Nを小さくするという目標はあまり達成できていません。 MeCab + αの2手法は、いずれもMeCabと比較し効果的に候補を絞れていることが確認できます。元は約2万件あったデータを数百のオーダーにまで絞り込みを行えており、かつ、認識誤りを含む場合にも、およそ65%の割合で正解を含むことができています。 正解率と実行時間の評価 ここでは、全手法について、正解率と実行時間について議論します。 各手法毎の正解率と実行時間。実行時間は100回分の処理時間を1回あたりに換算したもの  BaseLineの3手法については、正解率に多少変動は見られるものの、大幅な性能の改善は見られませんでしたが、認識誤りのない場合に対し、Few-Shotで正解率が大きく低下しているのが気になりました(おそらく例の与え方が良くなかった...🤔)。 SoftMatchaについて、音声認識がうまく行っている場合には100%の正解率を誇っています。一方で、認識誤りがある場合に関しては、もっとも正解率が低くなっています。これは今回のような実験設定に対しては、意味の近さを用いた柔らかな検索手法では対応できず、絞り込みの時点で多くが失敗しているためだと考えられます。また、事前の絞り込みの段階で時間がかかってしまい、今回の全手法の中で最も実行時間が長いという結果になりました。 MeCabについて、こちらはBaseLine手法とあまり性能の変化が見られませんでした。 これは、絞り込み後の候補集合のサイズがあまり小さくならなかったということを反映していると考えられます。 MeCab + αの2手法について、こちらは効果的に絞り込みを行えたこともあり、正解率、実行時間ともに顕著な性能の向上が確認できます。ロジック的には単純ですが、有効な手法が提案できたのではないかと思います。 定性分析 ここでは、これまでの実験結果を踏まえて、BaseLine以外のそれぞれの手法のメリット・デメリットを分析します。 SoftMatcha この手法のメリットは、意味的に変化しない誤字に対して強いという点です。 例えば、「株式会社第一」というエンティティが正解のときに、「株式会社第1」というtranscriptionが得られたケースを考えます。音声認識的には誤りが存在しますが、この誤りは意味的には変化していません。このような事例に対しては正確に候補集合を抽出できました。同様に、文字の全角・半角の違いなどにも頑健です。 デメリットとしては、削除誤りや意味的に異なるフレーズに置換されるような誤りが生じるケースに弱いという点です。 MeCab この手法のメリットは、一部でも形態素が正しければ候補として拾えるという点です。 上記のような「株式会社AえーA」のような場合でも、「株式」や「会社」といった部分的に正解している形態素が含まれていれば候補集合に正解を含むことができます。 デメリットとしては、すべての形態素に対して検索を行うだけでは候補の絞り込みがほとんどできていいない点です。 MeCab + Cutdown この手法のメリットは、候補集合を小さくできることにあります。先ほどのMeCabの致命的すぎるデメリットを克服するべく、リストに頻出する形態素は機械的に除外するという制約をかけました。結果として、うまく候補を絞れました。 デメリットとしては、頻出形態素のみ、もしくは頻出形態素+音声認識誤りからなるようなエンティティを取りこぼしてしまうということです。 例として正解が「株式会社AI Shift」の時にtranscriptionが「株式会社AIシフト」となった場合を考えてみます。 このとき、「株式」「会社」「AI」のいずれも一般的なキーワードかと思います。このようなケースでは、結果として「シフト」のみが検索対象の形態素となり、正解を候補集合に含めることはできなくなります。頻出する形態素を決める基準を何件以上とするかの閾値の調整が重要になりそうです。 MeCab + Comb この手法のメリットは、MeCab + Cutdownと同様に、候補集合を小さくできることにあります。また、機械的に頻出形態素を除外するという操作をしないので、MeCab + Cutdownのデメリットを補うことができます。 デメリットとしては、制約を厳しくする、すなわち、nの数が大きくなると、正解となるエンティティを最終的に取りこぼしてしまう可能性が高くなる点です。例えば、n = transcriptionの形態素数のときは、すべての形態素に誤りが含まれない場合、もしくは削除誤りしか存在しないケースでしか正解を候補集合に含めることができなくなってしまいます。nの数をどこまで大きくするべきかは事前に調整が必要だと感じました。 まとめと展望 ここまで色々と実験についてお話ししましたが、結論としては以下になります。 MeCab + αの手法が効果的っぽい Cutdown、Combのどちらが良いかは検索対象となるエンティティの集合の特性に依存しそう Cutdown:表層が似ていないとき(特徴的な形態素があるとき)に強い Comb:表層が似ているとき(頻出する形態素が存在するとき)に強い  今回の検証は人工的に生成した認識結果をもとにしたものですが、実際の音声対話では言い淀みや、エンティティに直接関係しない発話が含まれるケースが存在します。今後の展望としては、そのような多様な発話に対する頑健性の評価や対処方法の検討、またエンティティの特性に応じたロジックの使い分けなどが考えられます。 また、提案手法ではユーザーの発話に対し、一回の処理で候補集合の絞り込みを行うことを主眼に置きましたが、発話内容によっては十分に絞りきれていないケースが見受けられました。そのような場合に対する、更なる絞り込みのロジックやインタラクションの検討も行う必要がありそうです。 インターンを振り返って 今回、インターンに参加するにあたって自分の中での目標が二つありました。 一つは、「(機械学習)エンジニアとして働くとはどういうことかを体感する」ということ。もう一つは、「技術的に成長する」ということです。 一つ目に関しては、確実に達成することができました。 プロダクトの開発に関連した各種ミーティングに同席させていただいて、実際にプロダクトをよりよくするための議論に参加することができました。 もちろん、技術にまつわる全ての議論についていけたわけではないですが、それでも、プロダクトの問題点や改善点を見つけ出すプロセスやそれに対するアプローチ・考え方は、とても勉強になりました。 二つ目に関しては、達成率は50%程度だと感じています。 今回のインターンでは、今まで学んでいた知識や技術を生かすことはできたと思っています。この経験は自分のこれまでの学びに対して自信を持てる良いきっかけになったと思います。 しかし、インターン期間中に技術的に新しいスキルを獲得したり、新たな分野の知見を獲得するようなことはもっともっとやりたかったと感じています。 最後になりますが、インターン期間中はAI Shiftの皆さんがとても親切にしてくださって、楽しくインターン期間を過ごすことができました。 ここまで読んでいただきありがとうございました!! 参考 [1] https://softmatcha.github.io/ [2] https://aclanthology.org/W04-3230/ 投稿 エンティティリンキングの性能改善のための効果的な絞り込み手法の検証 は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは、AIチームの大竹です。 最近、京都大学から会話のターンテイキング(話者交代)タイミングを簡単に予測できるツール MaAI が公開されたので、検証してみました。 本記事では、MaAIの簡単な紹介、ターンテイキングのタイミング予測の仕組みについての説明、インストールとサンプル実行および単一の音声ファイルに対する簡単な検証結果について記述します。 概要:MaAIとは MaAIは、会話における ターンテイキング 、 相槌 、 頷き といった非言語的な振る舞いをリアルタイムで連続的な予測を容易に実現できるパッケージです。 プロジェクト名の「MaAI」は、日本語の「間(ま)」や「間合い」に由来しており、会話における絶妙なタイミングや間合いの調整をAIで実現することを目指しています。 現在、日本語、英語、中国語に対応しており、CPUのみでも高速に動作する軽量設計が特徴です。 MaAIで何ができるか MaAIは主に3つの非言語的振る舞いを予測・生成します。 ターンテイキング : Voice Activity Projection (VAP) モデルを用いて、次の瞬間にどちらが発話するかを予測します。さまざまな音響条件で学習されたVAPモデルやプロンプトでの制御ができるVAPモデルなども使用できるようになっています。 相槌 : 「うん」や「はい」といった聞き手としての反応を、適切なタイミングで生成します。 頷き : 相槌と関連する頭の上下運動を予測します。声を出さずに、聞き手としての関心を示すことができます。 VAPモデルについて VAPモデルのアーキテクチャ図 モデルアーキテクチャ VAPモデルは、事前学習済みのCPC encoder、self-attention Transformer、cross-attention Transformer、そしてVAPやVAD(Voice Activity Detection)などのタスクを処理する線形層から構成されています。 処理フローとして、まず入力されたステレオ音声はチャネル毎に個別処理され、次にチャネル間の相互作用が捉えられた後、最終的にその結果が線形層へと送られます。 VAPタスク 2名の話者の対話において将来の音声活動を共同で予測すること が主な目的です。将来の2秒間の時間枠を8つの2値ビンに分割し、合計256通りの組み合わせの中から、将来がどの状態に該当するかを予測する多クラス分類問題を学習します。 推論時には、すぐ先の未来の音声活動予測値である p_now 、それよりも先の未来の予測値である p_future を以下の処理で算出します。 p_now は、最初の2つのビン(0-200ミリ秒と200-600ミリ秒)における各話者の発話確率を合計し、softmaxを適用することで算出 p_future は、同様に後半の2つのビン(600-1200ミリ秒と1200-2000ミリ秒)から算出 VADサブタスク VAD(Voice Activity Detection)はVAPモデルの補助的なタスクであり、 対話参加者の音声活動を検出する ことが目的です。出力は2次元のベクトルで、各次元が各参加者の発声確率に対応します。 相槌予測と頷き予測 VAPモデルから得られる256次元のベクトルを線形層に入力し、専用データセットを用いて相槌や頷きの種類を分類するモデルを学習します。この際、分類クラスに「相槌なし」、「頷きなし」といった項目を加えることで、種類の予測と同時に、それらが発生するタイミングの予測も可能にします。 インストールとサンプル実行 インストール pip install maai サンプル実行 from maai import Maai, MaaiInput, MaaiOutput mic = MaaiInput.Mic() zero = MaaiInput.Zero() maai = Maai(mode="vap", lang="jp", frame_rate=10, context_len_sec=5, audio_ch1=mic, audio_ch2=zero, device="cpu") maai_output_bar = MaaiOutput.ConsoleBar(bar_type="balance") maai.start() while True: result = maai.get_result() maai_output_bar.update(result) 実行結果 x1 | x2 : 各チャンネルの音声の波形を簡易的に表示したものです。 x1 がチャンネル1(マイク入力)、 x2 がチャンネル2(無音)に対応します。右側の (0.0031, 0.0000) のような数値は、それぞれのチャンネルの音声波形の振幅に相当します。 p_now: 現在のフレームで、 チャンネル2 が発話している確率を示します。バーが左に行くほど、ユーザが話している可能性が高いことを意味します。 p_future : 少し未来のフレームで、 チャンネル2 が発話している確率を予測したものです。 vad: VADの予測結果で、どちらかのチャンネルで発話が検出されているかを示します。バーが左に行くほどユーザが話している可能性が高いことを意味します vad(x1) : チャンネル1(ユーザ)の音声区間検出の結果です。 vad(x2) : チャンネル2(システム)の音声区間検出の結果です。 上記の例では 「あ、えっと、明日の夜5時にえーっと3人でえー予約をしたいんですけど… あと、駐車場ってそちらの店舗にありましたっけ」 と発話しています。 言い淀みでは p_now は左に振れていて、「ありましたっけ」を言い終わると右に振れていることから適切にターンテイキングのタイミングを予測できていることがわかります。もし、このタイミングで次に話す文言が決まっていて、予測したターンテイキングポイントで即座にシステムが発話できたら、非常にスムーズな対話が実現できるのではないかと思います。 検証 飲食店予約対話を模したユーザ発話の音声ファイルを例にして前述したターンテイキング、相槌、頷きの予測を試し、尤もらしい予測ができているか定性的に確認してみます。 音声ファイル 「えっと、来週の水曜日の午後5時に5人で予約できますか?」 電話音声として録音された8kHzの音声を16kHzにアップサンプリングしています。 実行コード 以下のコードを作成して実行します。 import os import sys import json import queue import argparse from datetime import datetime from pathlib import Path from maai import Maai, MaaiInput, MaaiOutput # シードの固定 import numpy as np import random import torch SEED = 42 random.seed(SEED) np.random.seed(SEED) torch.manual_seed(SEED) WAV_FILE_PATH = "sample.wav" def parse_arguments(): """コマンドライン引数を解析""" parser = argparse.ArgumentParser(description='MaAI GUI Plotを画像として保存') # 入力ファイル parser.add_argument('--wav', '-w', type=str, default=WAV_FILE_PATH, help='入力音声ファイルのパス') # モード選択 parser.add_argument('--mode', '-m', type=str, choices=['vap', 'vap_mc', 'vap_prompt', 'bc_2type', 'nod'], default='vap', help='処理モード (default: vap)') # フレームレート parser.add_argument('--frame-rate', '-f', type=int, default=10, help='フレームレート (default: 10)') # コンテキスト長 parser.add_argument('--context-len-sec', '-c', type=int, default=5, help='コンテキスト長(秒) (default: 5)') # 出力設定 parser.add_argument('--output-dir', '-o', type=str, default='output_plots', help='出力ディレクトリのベース名 (default: output_plots)') return parser.parse_args() def create_output_directory(base_dir): """タイムスタンプ付きの出力ディレクトリを作成""" timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') output_dir = Path(base_dir) / timestamp output_dir.mkdir(parents=True, exist_ok=True) return output_dir def save_config(output_dir, args): """実行時のパラメータをconfig.jsonとして保存""" config = { "timestamp": datetime.now().isoformat(), "parameters": { "wav_file": args.wav, "mode": args.mode, "language": "jp", "frame_rate": args.frame_rate, "context_len_sec": args.context_len_sec, "device": "cpu", "output_dir": str(output_dir), }, "gui_plot_settings": { "shown_context_sec": 10, "frame_rate": args.frame_rate, "sample_rate": 16000 } } config_path = output_dir / "config.json" with open(config_path, 'w', encoding='utf-8') as f: json.dump(config, f, indent=2, ensure_ascii=False) print(f"設定を保存しました: {config_path}") return config_path def process_audio(args, output_dir): """音声処理とプロット保存のメイン処理""" # 設定を保存 save_config(output_dir, args) # 入力設定 print(f"入力ファイル: {args.wav}") if not os.path.exists(args.wav): print(f"エラー: 音声ファイル '{args.wav}' が見つかりません") sys.exit(1) wav = MaaiInput.Wav(wav_file_path=args.wav) zero = MaaiInput.Zero() # MaAIモデル初期化 print(f"モード: {args.mode}") maai = Maai( mode=args.mode, lang="jp", frame_rate=args.frame_rate, context_len_sec=args.context_len_sec, audio_ch1=wav, audio_ch2=zero, device="cpu" ) if args.mode == "vap_prompt": maai.set_prompt_ch1("ユーザーの発話") maai.set_prompt_ch2("テンポよく発話し、相手の発言が終わるとすぐに返答してください。") # GUI Plot初期化 gui_plot = MaaiOutput.GuiPlot( shown_context_sec=10, frame_rate=args.frame_rate, sample_rate=16000 ) # 処理開始 print(f"\n処理を開始します...") print(f"出力ディレクトリ: {output_dir}") print("-" * 50) maai.start() # メインループ loop_count = 0 consecutive_timeouts = 0 max_consecutive_timeouts = 3 while True: try: # タイムアウト付きでresultを取得 result = maai.result_dict_queue.get(timeout=1.0) consecutive_timeouts = 0 loop_count += 1 gui_plot.update(result) # 進捗表示 if loop_count % 10 == 0: print(f"Loop {loop_count}: 処理中...") except queue.Empty: # データが来ない場合 consecutive_timeouts += 1 if consecutive_timeouts >= max_consecutive_timeouts: print(f"\nデータ終了を検知({consecutive_timeouts}回タイムアウト)") break # 最終画像を保存(ループの外側) print("\n" + "-" * 50) if gui_plot.fig is not None: final_filename = output_dir / 'plot_final.png' gui_plot.fig.savefig(final_filename, dpi=300, bbox_inches='tight') print(f"最終画像を保存しました: {final_filename}") else: print("警告: プロットが初期化されていないため、画像を保存できませんでした") print(f"処理完了(総ループ数: {loop_count})") print(f"全ての出力は {output_dir} に保存されました") def main(): """メイン関数""" args = parse_arguments() # 出力ディレクトリ作成 output_dir = create_output_directory(args.output_dir) # 音声処理実行 process_audio(args, output_dir) if __name__ == '__main__': main() 実行結果 各実行モードで、音声波形図、VAP, VADの予測結果および相槌・頷きの種類とタイミングの予測結果をプロットします。相槌と頷きの予測確率の主語はシステムになっていることに注意してください。 プロットに示した指標 Input waveform 1 & 2(上2つのグラフ): 現在時点(0秒)から過去20秒間の音声波形を表示 Sample: 処理されたサンプル番号(フレームレート10Hzの場合、200サンプル = 20秒分のデータ) Frame: Sampleと同義 p_*: モデルの予測確率 mode = "vap" 途中の言い淀みでは、 p_now は高止まりしていて、最後の言葉を言い終わった直後に p_now が減少していて、発話確率の予測がうまくいっているように思えます。 mode = "vap_mc" 環境ノイズの追加、ゲインのランダム変更、より多様な音響条件などデータを工夫して学習したモデルを利用した予測結果です。 ユーザの発話が終了しても p_now の値が小さくならず、うまくいっていないようです。これは学習データとサンプルデータの音響条件やドメインの違いに起因するものだと考えられます。 学習データの選定はかなり重要で、学習データのドメインや音響条件などは適応先のものとできるだけ近いものにすることが良さそうだと感じました。 mode = "bc_2type" 相槌の種類とタイミングを予測するモデルの予測結果です。相槌なし、応答系の相槌、感情表出系の相槌の3クラス分類で学習されています。 p_bc_react : 応答系の相槌(「うん」「はい」など) p_bc_emo : 感情表出系の相槌(「へー」「おー」など) 言い淀みの時に相槌の予測確率が高くなっていて山を描いています。正しい挙動に見えますが、個人的にはもう少し高い確率値を返せると良いのかなと思いました。この辺りの挙動は学習データセットに依存しそうなので細かい調整は難しいのだろうと思います。 mode = "nod" 相槌のタイミングと頷きの種類・タイミングを予測するモデルの予測結果です。こちらのモデルは頷きと相槌を同時に予測のマルチタスクで学習されています。相槌の予測は2値分類、頷きの予測は4クラス分類になります。 p_bc : 相槌 p_nod_short : 小さい頷き p_nod_long : 大きい頷き p_nod_long_p : 準備動作付き頷き mode="bc_2type"の時と同様、概ね適切なタイミングで相槌・頷きの確率値が高くなっているように思います。mode="bc_2type"よりも相槌の予測確率値が大きくなっており、適切な予測ができるようになっていると思います。 mode = "vap_prompt" 各話者の特性をプロンプト(自然言語)で指定することで発話予測確率の挙動が変わります。 プロンプト機能を使うためには sentence-transformers , protobuf , sentencepiece をインストールしておく必要があります。 コード中でプロンプトを以下のように指定しています。 maai.set_prompt_ch1("ユーザーの発話") maai.set_prompt_ch2("テンポよく発話し、相手の発言が終わるとすぐに返答してください。") ユーザーの発話終了を待つように指示した場合 maai.set_prompt_ch1("ユーザーの発話") maai.set_prompt_ch2("ユーザーの話終わりを最後まで待って応答を返します。") 2パターンのプロンプトを使った結果を比較すると、プロンプトによる指示がなんとなく効いていることがわかると思います。プロンプトによってターンの取り方が指示通りに変化するというのは非常に興味深く、高度な技術だと感じました 。 相槌のタイミング調整などもプロンプトでできるようになると活用範囲が広がりそうだと思いました。 まとめ MaAIは、人間とAIの対話をより円滑で自然にするための強力なパッケージです。会話におけるターンテイキング(話者交代)や相槌、頷きといった、これまで機械的な再現が難しかった非言語的な振る舞いを、軽量なモデルで高精度にリアルタイム予測できる点にその価値があります。 今回の検証で特に注目すべきは、自然言語のプロンプトによってAIの応答タイミングを制御できる機能です。適用ドメインや対話シナリオによって適した間合いを容易に制御できるようになることで、対話AIの表現力を大きく向上させる可能性のある機能だと思います。 学習データのドメインに精度が依存するなど考慮すべき点はあるものの、MaAIは対話システム開発において、より人間らしい自然な「間」を実現するための重要な一歩となる技術であり、今後のさらなる発展が期待されます。 参考ドキュメント https://github.com/MaAI-Kyoto/MaAI https://aclanthology.org/2024.lrec-main.1036.pdf https://www.arxiv.org/abs/2507.23298 https://aclanthology.org/2025.naacl-long.367/ 投稿 ターンテイキングのタイミング予測を簡単に試せるライブラリMaAIを使ってみた は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは! AIチームの戸田です! 最近のLLM界隈では、推論速度の高速化が大きなトレンドになっています。先日、TikTokを運営しているByteDanceが公開した Seed Diffusion という拡散言語モデルもその流れを汲むものの一つだと思われます。 サーバーが混雑しているようで 、私は試すことはできなかったのですが、実験結果を見る限り、性能を保ちつつ、非常に高速な推論が可能になっているように見えました。 Seed Diffusion: A Large-Scale Diffusion Language Model with High-Speed Inference : Figure 1 こうした高速化のトレンドを眺めていて、私も拡散言語モデルの速度を活かした何か面白いものを作ってみたくなりました。そこで今回作ったのが、拡散言語モデルを使ったリアルタイムなアプリケーション生成システムです。(※) 簡単に説明すると、ユーザーの要望に基づいて、その場でアプリケーションのUIやロジックをHTMLで生成し、すぐに実行可能な形で提供するというものです。 技術としては従来の自己回帰型モデルでも実現可能なものですし、既に v0 や bolt のようなプロダクトとしても一般的になっていますが、拡散言語モデルであればより高速な推論が可能なので、ユーザーの待ち時間を最小限に抑えることができ、また違った体験があるのではないか、と考えました。 前置きが長くなりましたが、本記事では、拡散言語モデルを使って、リアルタイムにアプリケーションを生成するシステムを作った際に実装上工夫した点と実際にどのように動くのかのデモを共有したいと思います。 拡散言語モデルの原理や挙動については以前も記事を書いておりますので、そちらを参照いただければと思います。( 参考1 , 参考2 ) ※: こちらは DeepMindのGemini 2.5 Flash Liteのデモ に触発されたアイディアです 使用する拡散言語モデル 生成に利用するのは InceptionLabsのMercury Coder です。こちらはコード生成に特化した拡散言語モデルで、従来の自己回帰型モデルと比較して5〜10倍高速な推論が可能とされています。 現状APIで一般利用可能な拡散言語モデルはこれしかないのですが、コード生成に特化しているため、今回の用途には十分な性能を持っていると思い、採用しました。 簡単にリクエスト方法を紹介しておきます。 curl https://api.inceptionlabs.ai/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer INCEPTION_API_KEY" \ -d '{ "model": "mercury", "messages": [ {"role": "user", "content": "What is a diffusion model?"} ], "max_tokens": 1000 }' 拡散言語モデルは自己回帰型モデルと異なり、Suffixを指定することもできます。こういった細かいテクニックは ドキュメント をご参照いただければと思います。 実装紹介 Next.js 15 + TypeScript + Tailwind CSSで作成しました。コードは全て GitHubで公開 していますので、興味のある方はぜひご覧ください。以下より工夫した点を紹介させていただきます。 UI PCのデスクトップ画面のような体験を意識しました フォーマット固定するためのfew-shot prompt LLMに完全なHTMLを出させるため、あらかじめ事前に作った「メモ帳アプリ」の完全なHTMLを例(few-shot)として提示し、出力形式を強く誘導しています。 コードでいうと こちらの部分 が該当しますので、興味のある方はご参照いただければと思います。 生成UIの実行(iframe srcDoc + sandbox) 返ってきたHTMLはHTMLコードを文字列として直接指定する iframe srcDoc で描画します。これにより、ファイルを作成・ホスティングする必要なく、動的に生成したHTMLをその場で <iframe> 内に表示できます。これにより、ビルドは不要で、“その場で動く”体験を実現しようとしました。 デモアプリなので、セキュリティ的な部分はあまり気にしませんでしたが、 sandbox="allow-scripts allow-same-origin" のみを付与し、権限を最小限に抑えるようにしました。 アイコン自動選択 生成されるアプリのアイコンは、説明文に基づいて Lucide React の中から1つだけ適したアイコンを返すようにLLMを誘導しています。例えばお絵描きができるペイントアプリだったらパレットのアイコン、といった感じです。 デモ 作成したアプリを使って「メモ帳」と「ペイント」を作ってもらいました。UIが生成されるのはアプリを作ったタイミングではなく、アプリを開くタイミングです。 きちんと動作するアプリができていますね。ちなみに「スネークゲーム」のようなちょっと複雑なアプリも問題なく作成できました。 せっかくなので自己回帰型モデルで最速と思われるGemini 2.5 Flash Liteとアプリの生成速度を比較してみました。私の動画編集スキルがしょぼかったので、キャプションが入れられなかったのですが、上がGeminiで下がMercury Coderです。 Geminiが大体6秒かかるのに対してMercury Coderは約4秒ほどで生成できています。 ドハティのしきい値 (0.4秒)からすると、どちらも時間がかかり過ぎなので、今後に期待かもしれません。個人的な感覚ですが、わずかな差ではありますが、何回も使っているとGeminiよりも使用感は良かったように感じました。 おわりに 今回は拡散言語モデルを使ったリアルタイムなアプリケーション生成システムを作ってみました。 従来の自己回帰型モデルに比べて待ち時間の少ない生成はなかなか体験がよかったですが、実応用となるとまだまだ高速化が必要かもしれません。 Mercury Coderのような拡散言語モデルに加えて、最近では groq や cerebras のようなLLM推論に特化したハードウェアによる推論APIも出現してきているので、今後はリアルタイム性のあるLLMアプリケーションが増えてくるのではないかと期待しています。CodingAgentのような開発者の生産性向上にも大きく貢献するのではないでしょうか。 最後までお読みいただきありがとうございました! 投稿 拡散言語モデルを使ってリアルタイムなアプリケーション生成システムを作った は 株式会社AI Shift に最初に表示されました。
アバター
はじめに こんにちは、AI チームの長澤 ( @sp_1999N ) です。 弊社では AI Worker という LLM エージェント構築プラットフォームを提供しています。 LLM エージェントを運用していると重要な要素になるのが「可観測性 = Observability」になります。 複雑な推論や複数のアクションを前提とした LLM エージェントでは、その挙動をいかに監視するかが運用上重要なトピックになります。 LLM エージェントの Observability 基盤としては、Datadog など様々な企業からサービスとして展開されており、また OSS として使えるものも多数出現しています。このことからも、LLM エージェントを自社サービスとして提供する場合は、可観測性が重要であることが伺えます。 一方で増えつつある選択肢の中から、どれを選べば良いかというのもよくある悩みです。 そこで今回は、多く存在する LLM エージェント Observability 基盤を網羅的に紹介してみようと思います。 注意: この記事の内容は 2025/7 時点の情報をベースにしておりますのでご注意ください。また基本的にドキュメントベースの情報になります。 早見表 サクッと比較したい方向けに、早見表を記載しております。一言はあくまで自分の所感になります。 OSS プロンプト管理 評価基盤 SDK 一言 Braintrust × ⚪︎ ⚪︎ Python, TS, Ruby, Java, Go, Kotlin エージェントやツール管理までできるend-to-end なプラットフォーム Dash0 × × ⚪︎ Python, JS/TS, Go, Erlang, Java, .NET言語, Ruby ランチャーやマルチテナント対応など本番想定での使い勝手の良さが特徴 LangSmith × ⚪︎ ⚪︎ Python, JS/TS LangChain エコシステムとのシームレスな統合 New Relic × × △ Python, JS/TS, .NET言語, Java, Go, Ruby 汎用 APM という立ち位置。MCP の呼び出しもトレースしてくれる LangWatch × △ ◎ Python, TS Scenario を使ったエージェントのシミュレーション評価が可能 Datadog × × ⚪︎ Python, Java, JS/TS Ragas など off-the-shelf な llm-as-a-judge の評価が可能 Traceloop × ⚪︎ ⚪︎ Python, TS, Go, Ruby OTel を LLM 向けに拡張した標準規格である OpenLLMetry 提供 Phoenix ⚪︎ ⚪︎ ◎ Python, TS 検索やエージェント評価まで対応した充実した評価基盤を持つ Langfuse ⚪︎ ⚪︎ ⚪︎ Python, JS/TS カスタムダッシュボードなど UI/UX 面で優れる Laminar ⚪︎ △ ⚪︎ Python, JS/TS キューを使ったデータのラベル付けが可能 SigNoz ⚪︎ × × Python, JS/TS, Java, Go, PHP, .NET言語, Ruby, Elixir, Rust, C++, Swift 汎用 APM という立ち位置で他フレームワークとの統合で利用 Langtrace ⚪︎ ⚪︎ ⚪︎ Python, TS 2行の実装でトレースが可能な手軽さが魅力 紹介する Observability 基盤の選定 弊社で開発している AI Worker では、開発言語として TypeScript、エージェントフレームワークとして Mastra を採用しています。(AI Shift における開発体制については こちらのブログ で述べていますので、興味のある方はぜひどうぞ!) そこで今回はこのような背景を踏まえ、以下を選定の条件とします。 SDK として TypeScript がサポートされていること データ収集基盤だけでなく、評価・分析基盤まで提供されていること 今回は TS を対象言語としていますが、多くのプロバイダーは Python も同時にサポートしているため、Python で開発されている方にとっても参考になる内容になっていると考えられます。また Go や Ruby などをサポートしているフレームワークも存在するので、言語軸で調べられている方の足掛かりにもなれば幸いです。 比較対象とする Observability Providers 条件を踏まえ、まずは Mastra 側の情報をチェックしてみます。 Mastra の公式ドキュメントには Observability Providers のリストが公開されています。 https://mastra.ai/en/reference/observability/providers これだけでも多くのプロバイダーが存在することが分かります。数は多いですが、せっかくなのでそれぞれ特徴を紹介してみようと思います。また Datadog, Arize AI の Phoenix, Langtrace も LLM Observability プラットフォームとしてツール提供しているため、これら3つも紹介の対象に加えたいと思います。 1つ紹介の軸として、OSS としてセルフホストが可能かどうかを設けて整理したいと思います。 Non-OSS プロバイダー このセクションで紹介するプロバイダーは全て non-OSS なサービスとして開発されているものになります。 商用レベルの利用では基本的に有料ですが、趣味としての個人開発やトライアルとして無料で使えるプランも用意されているものがほとんどです。 それぞれの特徴を簡単に見てみます。 Braintrust Braintrust は2023年に設立されたスタートアップで、 a16z などの著名なベンチャーキャピタルからの出資を受けています。利用企業には instacart, stripe, Notion など有名な企業が並びます。 OSS としては開発されていませんが、Python や TypeScript 以外にも Go や Ruby など 様々な言語 で SDK を提供してくれています。監視や評価基盤だけでなく、LLM エージェントそれ自体やエージェントに提供するツールなども構築できる、end-to-end なプラットフォームとして展開されているのが特徴と言えそうです。( Agent の作成機能 は執筆時点では beta 版としての提供のようです) https://www.braintrust.dev/docs/start Free plan も用意されており、個人プロジェクトなどの小規模な利用の場合はこのプランで試してみるのも良いかもしれません。 Dash0 Dash0 も2023年に設立され、ニューヨークに拠点を置く企業になります。OpenTelemetry ネイティブな監視基盤を提供しています。SDK としてこちらも JS/TS, Python, Go, Ruby, Java など 複数の言語 をサポートしています。こちらも OSS ではなく、セルフホストなどはできません。 こちらも、監視から評価までを一貫して提供してくれています。その上で、プラットフォーム内に ランチャー が用意されているなど使い勝手の良い印象です。また マルチクライアント・マルチテナント対応 や アクセスコントロール 、 アラート通知 の設定が可能になっていたりと、本番利用に向けた充実した機能が揃っています。 LangSmith LangSmith は、お馴染み LangChain 社が展開するプラットフォームの1つです。趣味の範囲での個人開発であれば無料で利用できますが、商用の場合は有料となります。また LangChain がそうであるように、LangSmith においてもサポートされる言語は Python と JS/TS のみになります。 Observability, Evals, Prompt Engineering の3要素を柱として構成されています。 https://docs.smith.langchain.com/ 最大の特徴は LangChain エコシステムとのシームレスな統合であると考えられます。LLM アプリケーションのフレームワークとして LangChain や LangGraph を使用している場合、環境変数の設定のみを行えば、 基本的には追加コードなし でトレースができるようになります。 その一方でフレームワーク非依存な提供であるため、LangChain エコシステム以外にも組み込むことが可能で、実際に各種商用 LLM プロバイダーや Vercel AI SDK とのインテグレーションなどが提供されていたりします。 New Relic New Relic は 2008 年にサンフランシスコで設立された、APM (Application performance monitoring) ツールを提供する企業です。他のプロバイダーと比較するとその立ち位置としては Datadog に近いと言えます。 AI Monitoring として、AI アプリ向けの APM ソリューションを提供しています。 対応言語 には Go, Ruby, JS/TS, Python などがあります。 MCP の呼び出しに関してもトレースを行い、そのパフォーマンスの監視も行ってくれます。ただし、他プロバイダーが押し出しているような「評価」に関する機能については、現時点ではユーザーフィードバックの収集程度にとどまっているようです。 LangWatch LangWatch は 2023 年にオランダで設立された LangWatch 社によって提供される LLMOps プラットフォームです。ライセンス自体は Business Source License 1.1 になるので、OSS には分類されません。セルフホストでの運用も可能ですが、商用利用の場合は 有料プラン での契約が必要になる点にご注意ください。対応言語は Python および TypeScript のようです。 エージェントの評価に力を入れたプラットフォームになっており、 Scenario と呼ばれるエージェントテストフレームワークが独自に導入されています。これはマルチターンの行動を前提とした LLM エージェントを、シナリオベースでマルチターンに評価するためのものになります。多くのプロバイダーを紹介していますが、このようなユーザーシミュレーター機能を備えたものは他ではあまり見当たらなかったため、大きな特徴と言えます。 また有料機能として Guardrails があり、ハルシネーションやセンシティブデータの漏洩などをリアルタイムで検知してくれます。 Datadog 馴染みのある方も多いと思われる Datadog も LLM Observability 基盤の提供 を行っております。 対応言語 として Python, Java, JS/TS で SDK が提供されています。LangChain や VertexAI などのフレームワークに対するインテグレーションも提供されているため、対象のものであれば少ないコードの変更量でトレースを計装できるようになります。しかし言語によって サポートフレームワーク が異なっています。 モニタリング、評価、実験の3つを柱として構成しており、それらをリッチなダッシュボードで管理することが可能です。 https://docs.datadoghq.com/llm_observability/ 評価においては Ragas や NeMo などが組み込まれているため、off-the-shelf な LLM-as-a-judge の実施が可能です。これ以外にもカスタムメトリクスを実装することも可能です。 エージェントに特有のマルチターンやツール呼び出しの評価など、LangWatch や Phoenix が提供してくれている部分についての拡張も期待したいところです。 Traceloop Traceloop は Y Combinator などから出資を受けている企業で、2023 年に設立されています。Traceloop 自体は OSS ではない、商用プラットフォームなのですが、同社によって OpenLLMetry という OSS の標準規格が提唱されています。Traceloop 自体はその上に立つ分析/評価プラットフォームという位置付けになります。 対応言語 は Python, TS/JS, Go, Ruby になります。トレース、評価、データセットの3つを軸としてサービスが構成されています。他プロバイダーで見たようなプロンプトのバージョニングなどは、記事執筆時点ではなさそうでした。 デモページ から簡単に使用感を確かめることができます。 OpenLLMetry Op enLLMetry は OpenTelemetry を「LLM 特有のデータ」に拡張する形で機能します。例えばプロンプトの内容、生成結果、消費トークン量など LLM 特有のデータについて、OTel では標準的な仕様がありませんでした。そこで、OpenLLMetry はその不足分を補う形の標準規格として提唱されている、という位置付けになります。 また OpenLLMetry は Traceloop 以外にも Datadog や Braintrust などの他社ツールへの統合も提供しています。したがって、データ収集基盤として OpenLLMetry を使うことにより、可視化や評価基盤のツールは Traceloop 以外にも様々選べるようになります。(= ベンダーロックインの回避) OpenLLMetry の 対応言語 は Python, TS/JS, Go のようです。 OSS プロバイダー Phoenix Phoenix は Arize AI によって開発されている OSS ( ELv2 ) の LLM Observability 基盤になります。 セルフホストであっても、料金の発生なしに機能制限なく利用することが可能です。ただし、同社により Arize AX というエンタープライズ向けの有料プラットフォームが用意されていることから、ある種のオープンコアモデルとしても見ることができます。アーキテクチャも比較的シンプルであるため、セルフホスト時の保守・運用コストはそこまで高くなく利用できると考えられます。 https://arize.com/docs/phoenix/self-hosting 対応言語は Python と TS の2種類のようです。 こちらもトレース、評価、実験およびプロンプト管理を主機能として備えています。クラウド、コンテナ、jupyter notebook、ターミナルなど様々な 環境 で利用することができます。その意味では研究などの実験管理基盤としても利用できるかと思います。 また エージェント周りの評価 や RAG などで必要な 検索に関する評価 なども用意されているため、やれることが細やかに整備されている印象を受けます。 デモ も用意されているので、その使用感を簡単に確認することができます。 Phoenix については弊社の 別記事 でもその使用感を簡単に紹介しているので、興味のある方はぜひご覧ください。 Langfuse Langfuse はドイツに拠点を構え、 Y Combinator からも出資を受ける 2022 年設立の企業になります。 Phoenix と同様、有料プランの加入なしでも、セルフホストで ほとんどの機能を利用できる オープンコアの形式をとっています。(基本的には MIT ライセンス ですが、一部機能は商用ライセンスが適用されます。) 対 応言語 は Python, JS/TS になります。 トレース、評価、実験、プロンプト管理などほとんどの主要な機能が提供されています。 LLM-as-a-judge の評価においては、Datadog 同様、Ragas などのメトリクスをそのまま利用できます。 ダッシュボードのカスタマイズ も可能で、OSS のプロバイダーとしては総合的に完成度が高いように感じられます。デモ画面も用意されているので、気になる方はぜひ覗いてみて下さい。 https://langfuse.com/docs/demo その反面、アーキテクチャ構成が若干複雑で、セルフホストする場合の保守・運用コストは若干高めかもしれません。ただし、 Helm チャート も提供されているので、チャレンジはしやすいように整えられています。 Google Cloud での例: https://langfuse.com/self-hosting/gcp Laminar Laminar も Y Combinator に出資を受ける、2024 年設立のサンフランシスコの企業になります。 Apache-2.0 ライセンス で開発されており、セルフホストでの運用が可能です。 対応言語 は JS/TS, Python になります。Laminar 自体のバックエンドは Rust で実装されているため、高速な挙動が期待できます。アーキテクチャは少し複雑ですが、メッセージキューとして RabbitMQ, データは Clickhouse と Postgres を組み合わせたモダンな構成のようです。 こちらもトレース、評価、実験、プレイグラウンドなどの主要な機能がサポートされています。またメッセージキューを使ったデータセットのラベル付けが可能です。 SQL エディタ の機能があり、Laminar に格納されているデータに対する読み書きがしやすいインターフェースが提供されています。SQL でトレース情報などを管理・集約されたい方にとっては魅力的な機能かもしれません。 SigNoz SigNoz は汎用的な APM (Application performance monitoring) ツールという感じで、ポジションとしては Datadog や New Relic と近いですが、OSS ( Apache-2.0 ) として使用できる点に違いがあります。 ドキュメント を見る限り、SigNoz 自体は LLM Observability に特化した機能を提供しているわけではなさそうです。その代わりそれに準ずる OSS との統合を押し出している印象を受けます。 本記事でも紹介している Langtrace や Traceloop などがその統合例として紹介されています。したがって、LLM に対する監視基盤を整えつつ、アプリケーション全体の監視基盤と統合したい場合は SigNoz を使うと良さそう、と言えそうです。 Langtrace Langtrace は 2024 年 2 月に一般提供が開始された比較的若いツールとなります。 Scale3 Labs という企業によって開発されました。同社はもともと Web3 インフラの Observability ツールを開発する企業として 2022 年に設立されたようです。対応言語は Python および TS のみのようです。OSS ( AGPL-3.0 ) として利用でき、セルフホストも可能です。セルフホストの場合、Langtrace の client サーバーに加え、Postgres DB, Clickhouse DB が必要になります。 https://github.com/Scale3-Labs/langtrace?tab=readme-ov-file#-langtrace-system-architecture トークン利用量などを含めたログ・トレースの取得、評価、プロンプトのバージョン管理など LLM Observability で主要な機能は全て揃っているようです。 また、Langtrace は2行の実装のみでトレースが可能になり、その手頃さは魅力的なポイントになります。 まとめ 今回は LLM エージェントの Observability 基盤として使えるプロバイダーをまるっと紹介してみました。単なる監視基盤というより、評価・実験基盤としての意味合いで機能が提供されている点が、LLM Observability ならではな印象を受けました。 基本的な提供機能は概ね共通していましたが、LangWatch や Phoenix はエージェントに特化した評価の整備が進んでいるなど、主に評価基盤における機能の差が見られました。そのほかはダッシュボードや導入のしやすさなどがそれぞれ特色が出るポイントとなりました。導入のしやすさにおいては、インテグレーションの提供有無を見ることも重要です。今回紹介した多くのプロバイダーは Mastra インテグレーションを提供しているため、簡単に組み込むことができます。 個人的にまとめるのであれば、次のようになると思います。 Non-OSS からは、Datadog を APM としてすでに導入している場合は Datadog を、LangChain, LangGraph をエージェントフレームワークとして使用しているのであれば LangSmith を、LLM エージェントの評価にこだわりたいなら LangWatch をおすすめするかなと思います。 OSS の場合は、ダッシュボードなどの UI/UX の良さにこだわるなら Langfuse、手軽さや全体的な網羅性を取るなら Phoenix という感じでしょうか。 これから LLM エージェントの Observability 基盤を導入する方にとって、参考になれば幸いです。 投稿 LLMエージェントオブサーバビリティ基盤についてまとめてみた は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは AIチームの戸田です 今回は、AI Agentが自身で解決できない問題に直面した際に、Slackを通じて人間に助言を求めることができるMCP(Model Context Protocol)、 AskOnSlackMCP をつくったので、架空のカスタマーサポートのデモを交えて紹介したいと思います。 https://github.com/trtd56/AskOnSlackMCP Human-in-the-loop 近年、AI Agentの能力が向上し、コーディングや業務効率化など多くの領域で活躍が増えていますが、まだ人間の判断や専門知識が必要な場面は多く存在します。例えばカスタマーサポートのような、複雑な問題解決や感情的な配慮が必要な領域では、AIと人間の協働(Human-in-the-loop)が重要になります。 実現するためのツール これをAI Agentで実現する方法として、一つは LangGraphのHuman-in-the-loop機能 や HumanLayer といったHuman-in-the-loop用のライブラリをつかってAgentのコードに直接処理を追加する方法があります。これらは柔軟に制御を行うことができますが、実装の複雑性が高いという課題もあります。 もう一つはMCPで実現する、といった方法があります。例えば以下のようなものがありあます。 KOBA789/human-in-the-loop : Discordで人間の助言を求めることができる GongRzhe/Human-In-the-Loop-MCP-Server : 専用のGUIダイアログを通じて人間と対話する 通信チャンネルが限定されるなど制約はありますが、MCPの設定を行うだけで簡単に利用することができます。私は業務でSlackをよく使うのですが、Slackに対応したHuman-in-the-loopのMCPがなかったので自分で作ってみた、というのが今回の取り組みの背景となります。 AskOnSlack MCP 動作イメージ 作ったMCPサーバーは、AI AgentとSlackの間の橋渡し役として機能します。AI Agentが人間の助けが必要と判断した場合、MCPを通じてSlackの指定チャンネルに質問を投稿し、人間からの回答を待ちます。人間の回答はスレッド形式で人間からの回答を受け取り、それを参考に最終的な回答を生成します。 npmでの直接実行に対応しているのでローカルインストール不要でCursorやClaude Desktop統合できます。SlackのBot TokenやApp Tokenなどの設定が必要なのですが、READMEにまとめていますので、こちらを参考にしていただければと思います。 カスタマーサポートなどのリアルタイムな判断が必要な業務において、AIの自律性と人間の専門性を効果的に組み合わせられるツールだと考えています。 カスタマーサポートボットでの検証 検証のため架空のカスタマーサポートのチャットボットを作りました。 https://github.com/trtd56/SimpleCSBot パスワードリセット、支払い方法、返金、解約に関する4つのFAQが登録されており、表層ベースでマッチングしてGPT-4o-miniで回答を生成する非常にシンプルなRAGを用いたチャットボットです。 このチャットボットに今回開発したMCPを設定し、事前登録されていない、例えば「営業時間を教えて」のような質問をチャットボットに投げた際に、ちゃんとMCPを使って人間に助けを求められるかを確認します。 参考までにMCPを設定する前に「営業時間を教えて」と聞くと以下のような回答が得られます。 FAQに該当する内容がないので妥当な回答だと思います。 MCPを設定して検証を行った際の録画が以下になります。画面左側がSlack、右側がチャットボットの画面です 検証の流れは以下です。 AIによる自動応答 「返金はできますか?」というFAQに登録済みの質問には、AIエージェントが即座に自動で回答。 AIが回答できない質問と人間へのエスカレーション 次に顧客が「営業時間は?」というFAQにない質問をすると、この問い合わせは自動的にSlackの担当者チャンネルに通知(エスカレーション)する。 人間による対応とAIからの返信 Slackで通知を受け取ったオペレーターが「10時〜19時です」と返信すると、その内容を考慮した回答が顧客とのチャット画面にAIからの回答として表示される。 再びAIによる対応 「ありがとう」といった定型的な挨拶には、再びAIが自動で応答。 特に "3. 人間による対応とAIからの返信" がHuman in the loop MCPを使うメリットだと考えています。これまでもチャットボットが答えられない質問を人間のオペレーターにエスカレーションする仕組みはありましたが、「AIとの対話」から「人間との対話」への切り替えが明確に分断されており、顧客体験のスムーズさを損なうという課題がありました。今回のMCPを使えば顧客はAIと会話している感覚のまま、よりシームレスに的確な答えを得ることができるのではないでしょうか。 まとめ 今回は、AI Agentが自身で解決できない問題に直面した際に、Slackを通じて人間に助言を求めることができるMCP、AskOnSlackMCPを架空のカスタマーサポートのデモを交えて紹介しました。 ちなみにMCPには Elicitation という構想があり、こういった人間とのインタラクションも標準化しようとしています。ElicitationはまだDraft段階ですが、今後こういった機能が増えてくることが予測されます。引き続きキャッチアップを続け、また興味深い内容があれば共有したいと思います。 最後までお読みいただきありがとうございました! 投稿 AI Agentが回答に困った時にSlackで人間に助言を求められるMCPを検証した は 株式会社AI Shift に最初に表示されました。
アバター
こんにちはAIチームの戸田です。 今回は Gemini Diffusion の登場をきっかけに最近話題になった拡散言語モデルの推論過程に興味を持ち、その一例として拡散言語モデルのLLaDAの推論を実際に手元で確認してみた結果を共有したいと思います。 拡散言語モデルに関しては、以前 Inception LabsのMercury Coderに関する記事 も書かせていただきましたので、こちらも合わせて見ていただけると嬉しいです。 拡散言語モデル ChatGPTをはじめとする現在のほとんどの大規模言語モデル(LLM)は自己回帰モデル(Autoregressive Language Model)と呼ばれ、一方向に一トークンずつテキストを生成します。 前のトークンがすべて生成されないと次のトークンを生成できず、各トークン生成ごとに巨大なニューラルネットワークの計算が必要なため、 InceptionLabsのBlog によると自己回帰モデルは最先端モデルでも毎秒50〜200トークン程度の生成速度に制限されているといわれています。 拡散言語モデル(Diffusion Language Model)は画像生成で成功を収めた拡散モデルのアプローチをテキスト生成に応用した技術で、自己回帰モデルとは異なり、いきなりテキスト全体を生成します。最初はただのノイズなのですが、徐々に修正させていくことでテキストを生成します。 前回の記事 のデモ 速度だけでなく全体の文章構造の整合性に優れると言われており、 最近の研究 ではreversal curse(※1)にも耐性があると言われています。 他にも 自己回帰モデルからの転移学習 や Embeddingへの応用 など、特にAIの中でも研究が活発になってきている分野の一つと言えると思います。 拡散言語モデルの推論についての疑問 InceptionLabsのレポート によると、拡散言語モデルはChain of Thought: CoTのような自己回帰型モデルのPromptのテクニックがそのまま使えるとの記述が見られます。 Mercury: Ultra-Fast Language Models Based on Diffusion 左から右に逐次的に生成するのであれば、中間的な推論ステップを生成することで推論能力を向上させることができるというのはわかりますが、全体を一度に生成する拡散言語モデルでこれがうまく働くというのは奇妙な感じです。 例えば代表的な拡散言語モデルの一つである LLaDAの論文 には拡散言語モデルの興味深い推論過程が紹介されています。 Large Language Diffusion Models , Table 4 こちらはLLaDAの応答で、まさにCoTのお手本のような中間的な推論を行なっています。色の濃淡はそのトークンが拡散モデルのサンプリングプロセスのどの段階で予測されたかを示しており、暗い色になる程サンプリングの後期段階で予測されたトークンだそうです。注目してほしいのは一段目で、最初の4時間の移動距離を計算するための "4 hours" や "4 ="といったトークンより先に答えの "48" が生成されている点です。こういった現象は自己回帰モデルでは起こりえないと思います。 このように拡散言語モデルの推論過程には直感に反するような挙動をするようで、非常に気になり、実際に動かして試してみることにしました。 検証 環境 Vertex AIのColab Enterpriseで環境を構築しました。g2-standard-4の環境でL4 GPUを一枚設定しています。ライブラリなどは特に追加しておらず、Colab Enterpriseのデフォルト環境です。 モデルカードとしては8Bとなっていますが、従来のLLMとモデル構造が違うので、従来の8BクラスまでのLLMの検証でよく使われるT4 GPUでは稼働しないので注意してください。 コード 以下が検証用のコードです。 import torch from transformers import AutoModel, AutoTokenizer device = 'cuda' model = AutoModel.from_pretrained('GSAI-ML/LLaDA-8B-Instruct', trust_remote_code=True, torch_dtype=torch.bfloat16).to(device).eval() tokenizer = AutoTokenizer.from_pretrained('GSAI-ML/LLaDA-8B-Instruct', trust_remote_code=True) messages = [{ "role": "user", "content": INPUT_TEXT # ここに検証用のテキストを入れる }] visualization_states, final_text = generate_response_with_visualization( model, tokenizer, device, messages, block_length=16 # block_lengthは拡散生成を適用する幅、LLaDAリポジトリのapp.pyの設定に合わせる ) print(visualization_states) generate_response_with_visualizationは LLaDAのリポジトリ のapp.pyから拝借したものになります。block_lengthは拡散生成を適用していく幅で、一度に全体を生成すると|EOS|が大量に生成されてしまうという課題から、Sliding Windowのように少しずつ拡散モデルによる生成を適用していくのがよいらしいです。ここはよくわからなかったので、リポジトリのデフォルト設定を使わせていただきました。 以下に実際に試した結果を添付します。 サンプル1 Large Language Diffusion Models に記載されていた上記の例です。タスクとしては数学問題に分類されると思います。 Lily can run 12 kilometers per hour for 4 hours. After that, she runs 6 kilometers per hour. How many kilometers can she run in 8 hours? 結果 Diffusion Language Model - Generation Process (Embeddable) /* Styles for the host element itself, if any (optional) */ /* 例えば、埋め込みコンポーネントのデフォルトの幅やマージンなど */ /* The ID below is no longer strictly needed by the script if using previousElementSibling, */ /* but can be kept for other purposes or changed to a class. */ /* 下記のIDは、previousElementSiblingを使用する場合、スクリプトには厳密には不要になりましたが、 */ /* 他の目的のために保持したり、クラスに変更したりすることができます。 */ #diffusion-visualization-embed { /* Or a class like .diffusion-visualization-host */ /* max-width: 800px; */ /* margin: 20px auto; */ } (function () { // START OF IIFE: Creates a new scope for each script instance // --- Shadow DOM Setup and Component Rendering --- // Get the div element immediately preceding this script tag // このscriptタグの直前にあるdiv要素を取得します const hostElement = document.currentScript.previousElementSibling; if (hostElement) { // Check if a shadow root already exists (e.g., if script runs multiple times on the same element by mistake) // シャドウ ルートが既に存在するかどうかを確認します (例: スクリプトが誤って同じ要素で複数回実行された場合) // Though with IIFE and previousElementSibling, this specific check might be less critical for *this* error, // it's good practice if the host element could be reprocessed. // IIFE と previousElementSibling を使用すると、この特定のエラーに対するこの特定のチェックはそれほど重要ではないかもしれませんが、 // ホスト要素が再処理される可能性がある場合は良い習慣です。 if (hostElement.shadowRoot) { console.warn("Shadow DOM already attached to this host element. Skipping re-initialization.", hostElement); return; // Exit if already initialized for this specific host element } const shadowRoot = hostElement.attachShadow({ mode: 'open' }); // 1. Load Tailwind CSS into the Shadow DOM const tailwindLink = document.createElement('link'); tailwindLink.setAttribute('rel', 'stylesheet'); tailwindLink.setAttribute('href', 'https://cdn.tailwindcss.com'); shadowRoot.appendChild(tailwindLink); // 2. Add custom styles to the Shadow DOM const customStyles = document.createElement('style'); customStyles.textContent = ` /* Custom styles for within the Shadow DOM */ body { /* This will apply to the 'body-like' div inside shadow DOM */ font-family: 'Inter', sans-serif; background-color: #111827; /* bg-gray-900 */ color: #FFFFFF; /* text-white */ padding: 1rem; /* p-4 */ } @media (min-width: 768px) { /* md: */ body { padding: 2rem; /* md:p-8 */ } } .token { white-space: pre; /* Preserve spaces within tokens */ } * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } `; shadowRoot.appendChild(customStyles); // 3. Create the main structure of the component within the Shadow DOM const componentWrapper = document.createElement('div'); // componentWrapper.className = 'bg-gray-900 text-white p-4 md:p-8 antialiased'; // Apply body-like styles directly componentWrapper.innerHTML = ` Diffusion Language Model Generation Process Visualization 各ステップはトークンシーケンスの状態を示します。色の凡例: [MASK] , 確定済 , 新規生成 , 注目/変更 . 拡散言語モデルの状態可視化 `; shadowRoot.appendChild(componentWrapper); // --- Data Configuration --- // この 'visualizationStates' 配列に直接データをコピー&ペーストして編集してください。 // データ形式: ステップの配列。各ステップは [トークン文字列, 色の16進文字列] のペアの配列です。 // ============================================================================== // // START: 可視化データ定義箇所 (ここにデータをペーストしてください) // // ============================================================================== // let visualizationStates = [ [['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], [' ', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#66CC66'], [' first', '#66CC66'], [' ', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#66CC66'], [' hours', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#66CC66'], [' she', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#66CC66'], [' ', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#66CC66'], ['2', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], ['[MASK]', '#444444'], [' ', '#FFAA33'], ['[MASK]', '#444444'], [' =', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#FFAA33'], [' ', '#6699CC'], ['4', '#66CC66'], [' =', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], ['[MASK]', '#444444'], ['4', '#66CC66'], ['8', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#66CC66'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#FFAA33'], ['\n', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#FFAA33'], [' the', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['4', '#66CC66'], [' hours', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], ['[MASK]', '#444444'], [' ', '#66CC66'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], ['[MASK]', '#444444'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#66CC66'], [' runs', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#66CC66'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['[MASK]', '#444444'], [' *', '#66CC66'], [' ', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['[MASK]', '#444444'], [' *', '#6699CC'], [' ', '#6699CC'], ['[MASK]', '#444444'], [' =', '#66CC66'], [' ', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['[MASK]', '#444444'], [' *', '#6699CC'], [' ', '#6699CC'], ['[MASK]', '#444444'], [' =', '#6699CC'], [' ', '#6699CC'], ['2', '#66CC66'], ['4', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['6', '#66CC66'], [' *', '#6699CC'], [' ', '#6699CC'], ['[MASK]', '#444444'], [' =', '#6699CC'], [' ', '#6699CC'], ['2', '#6699CC'], ['4', '#6699CC'], [' kilometers', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['6', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#66CC66'], [' =', '#6699CC'], [' ', '#6699CC'], ['2', '#6699CC'], ['4', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['6', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['2', '#6699CC'], ['4', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#66CC66'], ['In', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['6', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['2', '#6699CC'], ['4', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' total', '#FFAA33'], ['[MASK]', '#444444'], [' hours', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['6', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['2', '#6699CC'], ['4', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' total', '#6699CC'], [' eight', '#66CC66'], [' hours', '#6699CC'], [',', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['6', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['2', '#6699CC'], ['4', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' total', '#6699CC'], [' eight', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['7', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['6', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['2', '#6699CC'], ['4', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' total', '#6699CC'], [' eight', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], [' ', '#66CC66'], ['7', '#6699CC'], ['2', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['6', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['2', '#6699CC'], ['4', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' total', '#6699CC'], [' eight', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], [' ', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], [' =', '#66CC66'], [' ', '#6699CC'], ['7', '#6699CC'], ['2', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['6', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['2', '#6699CC'], ['4', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' total', '#6699CC'], [' eight', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], ['[MASK]', '#444444'], [' ', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], [' +', '#66CC66'], [' ', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], [' =', '#6699CC'], [' ', '#6699CC'], ['7', '#6699CC'], ['2', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['6', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['2', '#6699CC'], ['4', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' total', '#6699CC'], [' eight', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], ['[MASK]', '#444444'], [' ', '#6699CC'], ['4', '#66CC66'], ['[MASK]', '#444444'], [' +', '#6699CC'], [' ', '#6699CC'], ['2', '#66CC66'], ['[MASK]', '#444444'], [' =', '#6699CC'], [' ', '#6699CC'], ['7', '#6699CC'], ['2', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['6', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['2', '#6699CC'], ['4', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' total', '#6699CC'], [' eight', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], ['[MASK]', '#444444'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#66CC66'], [' +', '#6699CC'], [' ', '#6699CC'], ['2', '#6699CC'], ['4', '#66CC66'], [' =', '#6699CC'], [' ', '#6699CC'], ['7', '#6699CC'], ['2', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['6', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['2', '#6699CC'], ['4', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' total', '#6699CC'], [' eight', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], ['[MASK]', '#444444'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' +', '#6699CC'], [' ', '#6699CC'], ['2', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['7', '#6699CC'], ['2', '#6699CC'], [' kilometers', '#66CC66'], ['[MASK]', '#444444'], ['', '#66CC66']], [['In', '#6699CC'], [' the', '#6699CC'], [' first', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['1', '#6699CC'], ['2', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' the', '#6699CC'], [' next', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#6699CC'], [' ', '#6699CC'], ['6', '#6699CC'], [' *', '#6699CC'], [' ', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['2', '#6699CC'], ['4', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['In', '#6699CC'], [' total', '#6699CC'], [' eight', '#6699CC'], [' hours', '#6699CC'], [',', '#6699CC'], [' she', '#6699CC'], [' runs', '#66CC66'], [' ', '#6699CC'], ['4', '#6699CC'], ['8', '#6699CC'], [' +', '#6699CC'], [' ', '#6699CC'], ['2', '#6699CC'], ['4', '#6699CC'], [' =', '#6699CC'], [' ', '#6699CC'], ['7', '#6699CC'], ['2', '#6699CC'], [' kilometers', '#6699CC'], ['.', '#66CC66'], ['', '#6699CC']] ]; // ============================================================================== // // END: 可視化データ定義箇所 // // ============================================================================== // // --- Default/Sample Data (used if visualizationStates is empty, or as a template) --- if (visualizationStates.length === 0) { console.log("visualizationStates is empty for host:", hostElement, ". Populating with sample data."); const MASK_TOKEN = "[MASK]"; const MASK_COLOR = "#444444"; const CONFIRMED_COLOR = "#6699CC"; const NEW_COLOR = "#66CC66"; const FOCUS_COLOR = "#FFAA33"; const totalTokens = 18; const createMaskStep = () => Array(totalTokens).fill(null).map(() => [MASK_TOKEN, MASK_COLOR]); visualizationStates.push(createMaskStep()); let s1 = createMaskStep(); s1[0] = ["In", NEW_COLOR]; visualizationStates.push(s1); let s2 = createMaskStep(); s2[0] = ["In", CONFIRMED_COLOR]; s2[1] = [" the", NEW_COLOR]; visualizationStates.push(s2); let s3 = createMaskStep(); s3[0] = ["In", CONFIRMED_COLOR]; s3[1] = [" the", CONFIRMED_COLOR]; s3[2] = [" first", NEW_COLOR]; visualizationStates.push(s3); let s4 = createMaskStep(); s4[0] = ["In", CONFIRMED_COLOR]; s4[1] = [" the", CONFIRMED_COLOR]; s4[2] = [" first", CONFIRMED_COLOR]; s4[3] = [" 4", NEW_COLOR]; visualizationStates.push(s4); let s5 = createMaskStep(); s5[0] = ["In", CONFIRMED_COLOR]; s5[1] = [" the", CONFIRMED_COLOR]; s5[2] = [" first", CONFIRMED_COLOR]; s5[3] = [" 4", CONFIRMED_COLOR]; s5[4] = [" hours", NEW_COLOR]; visualizationStates.push(s5); let s6 = createMaskStep(); s6[0] = ["In", CONFIRMED_COLOR]; s6[1] = [" the", CONFIRMED_COLOR]; s6[2] = [" first", CONFIRMED_COLOR]; s6[3] = [" 4", CONFIRMED_COLOR]; s6[4] = [" hours", CONFIRMED_COLOR]; s6[5] = [" she", FOCUS_COLOR]; visualizationStates.push(s6); let s7 = createMaskStep(); s7[0] = ["In", CONFIRMED_COLOR]; s7[1] = [" the", CONFIRMED_COLOR]; s7[2] = [" first", CONFIRMED_COLOR]; s7[3] = [" 4", CONFIRMED_COLOR]; s7[4] = [" hours", CONFIRMED_COLOR]; s7[5] = [",", NEW_COLOR]; s7[6] = [" she", CONFIRMED_COLOR]; visualizationStates.push(s7); let s8 = createMaskStep(); s8[0] = ["In", CONFIRMED_COLOR]; s8[1] = [" the", CONFIRMED_COLOR]; s8[2] = [" first", CONFIRMED_COLOR]; s8[3] = [" 4", CONFIRMED_COLOR]; s8[4] = [" hours", CONFIRMED_COLOR]; s8[5] = [",", CONFIRMED_COLOR]; s8[6] = [" she", CONFIRMED_COLOR]; s8[7] = [" runs", NEW_COLOR]; visualizationStates.push(s8); let s9_user_example = createMaskStep(); s9_user_example[0] = ["In", CONFIRMED_COLOR]; s9_user_example[1] = [" the", CONFIRMED_COLOR]; s9_user_example[2] = [" first", CONFIRMED_COLOR]; s9_user_example[3] = [" ", CONFIRMED_COLOR]; s9_user_example[4] = ["4", CONFIRMED_COLOR]; s9_user_example[5] = [" hours", CONFIRMED_COLOR]; s9_user_example[6] = [",", CONFIRMED_COLOR]; s9_user_example[7] = [" she", CONFIRMED_COLOR]; s9_user_example[8] = [" runs", CONFIRMED_COLOR]; s9_user_example[9] = [" ", CONFIRMED_COLOR]; s9_user_example[10] = ["1", NEW_COLOR]; s9_user_example[11] = ["2", NEW_COLOR]; visualizationStates.push(s9_user_example); } // --- End of Sample Data Logic --- // --- Rendering Logic (targets elements within Shadow DOM) --- // Note: All functions are now local to the IIFE // 注意: すべての関数はIIFEに対してローカルになりました function getContrastingTextColor(hexColor) { if (!hexColor || hexColor.length 0.5 ? '#000000' : '#FFFFFF'; } catch (e) { console.error("Error parsing hexColor:", hexColor, e); return '#000000'; } } function renderVisualization() { // Query within the shadowRoot for the container // コンテナのshadowRoot内をクエリします const container = shadowRoot.getElementById('visualization-container'); if (!container) { console.error("Visualization container not found in Shadow DOM for host:", hostElement); return; } container.innerHTML = ''; if (!visualizationStates || visualizationStates.length === 0) { container.innerHTML = ' 表示するデータがありません。スクリプト内の `visualizationStates` 配列を編集してください。 '; return; } if (!Array.isArray(visualizationStates) || !visualizationStates.every(step => Array.isArray(step) && step.every(tokenPair => Array.isArray(tokenPair) && tokenPair.length === 2 && typeof tokenPair[0] === 'string' && typeof tokenPair[1] === 'string' && /^#[0-9A-Fa-f]{6}$/.test(tokenPair[1]) ))) { container.innerHTML = ' `visualizationStates` のデータ形式が無効です。期待される形式: [[ [token, "#RRGGBB"], ... ], ...] '; console.error("Invalid data format in visualizationStates for host:", hostElement, visualizationStates); return; } visualizationStates.forEach((stepTokens, stepIndex) => { const stepDiv = document.createElement('div'); stepDiv.className = 'step bg-gray-800 p-3 md:p-4 rounded-lg shadow-lg'; const stepLabel = document.createElement('h2'); stepLabel.className = 'step-label text-lg md:text-xl font-semibold mb-3 text-sky-400'; stepLabel.textContent = `ステップ ${stepIndex}:`; stepDiv.appendChild(stepLabel); const tokensContainer = document.createElement('div'); tokensContainer.className = 'tokens-container flex flex-wrap gap-0.5'; stepTokens.forEach(([token, color]) => { const tokenSpan = document.createElement('span'); tokenSpan.className = 'token py-1 px-1.5 rounded-sm text-xs md:text-sm font-mono leading-tight shadow-sm'; tokenSpan.textContent = token === " " ? "\u00A0" : token; tokenSpan.style.backgroundColor = color; tokenSpan.style.color = getContrastingTextColor(color); tokensContainer.appendChild(tokenSpan); }); stepDiv.appendChild(tokensContainer); container.appendChild(stepDiv); }); } // Ensure Tailwind is loaded before rendering // レンダリング前にTailwindが読み込まれていることを確認します if (tailwindLink.sheet) { // For browsers that support sheet property immediately renderVisualization(); } else { tailwindLink.onload = () => { renderVisualization(); }; } // Fallback timeout for rendering, in case onload doesn't fire reliably (e.g. cached stylesheet) // レンダリングのフォールバックタイムアウト。(例: キャッシュされたスタイルシートなど) onloadが確実に発行されない場合に備えます。 setTimeout(() => { const container = shadowRoot.getElementById('visualization-container'); // Check if container is still empty (or only has the placeholder message) // コンテナがまだ空であるか (またはプレースホルダーメッセージのみが含まれているか) を確認します if (container && container.children.length === 0 || (container.children.length === 1 && container.firstElementChild.tagName === 'P')) { console.log("Fallback: Tailwind might be cached or onload didn't fire. Rendering for host:", hostElement); renderVisualization(); } }, 500); // Slightly increased timeout for safety 安全のためにタイムアウトをわずかに増やしました } else { console.error("Could not find host element for a script instance. Ensure script is placed immediately after its target div."); // このスクリプトインスタンスのホスト要素が見つかりませんでした。スクリプトがターゲットdivの直後に配置されていることを確認してください。 } })(); // END OF IIFE パラメータの違いのせいか論文通りにはいきませんでしたが、ステップ 25〜30を見ると、3行目の48 + 24より先にその回答の72が生成されていることがわかります。 サンプル2 CoTの元論文 の例で、推論タスクでは定番となっている文字の処理です。 Take the last letters of the words in “Lady Gaga” and concatenate them. 結果 Diffusion Language Model - Generation Process (Embeddable) /* Styles for the host element itself, if any (optional) */ /* 例えば、埋め込みコンポーネントのデフォルトの幅やマージンなど */ /* The ID below is no longer strictly needed by the script if using previousElementSibling, */ /* but can be kept for other purposes or changed to a class. */ /* 下記のIDは、previousElementSiblingを使用する場合、スクリプトには厳密には不要になりましたが、 */ /* 他の目的のために保持したり、クラスに変更したりすることができます。 */ #diffusion-visualization-embed { /* Or a class like .diffusion-visualization-host */ /* max-width: 800px; */ /* margin: 20px auto; */ } (function () { // START OF IIFE: Creates a new scope for each script instance // --- Shadow DOM Setup and Component Rendering --- // Get the div element immediately preceding this script tag // このscriptタグの直前にあるdiv要素を取得します const hostElement = document.currentScript.previousElementSibling; if (hostElement) { // Check if a shadow root already exists (e.g., if script runs multiple times on the same element by mistake) // シャドウ ルートが既に存在するかどうかを確認します (例: スクリプトが誤って同じ要素で複数回実行された場合) // Though with IIFE and previousElementSibling, this specific check might be less critical for *this* error, // it's good practice if the host element could be reprocessed. // IIFE と previousElementSibling を使用すると、この特定のエラーに対するこの特定のチェックはそれほど重要ではないかもしれませんが、 // ホスト要素が再処理される可能性がある場合は良い習慣です。 if (hostElement.shadowRoot) { console.warn("Shadow DOM already attached to this host element. Skipping re-initialization.", hostElement); return; // Exit if already initialized for this specific host element } const shadowRoot = hostElement.attachShadow({ mode: 'open' }); // 1. Load Tailwind CSS into the Shadow DOM const tailwindLink = document.createElement('link'); tailwindLink.setAttribute('rel', 'stylesheet'); tailwindLink.setAttribute('href', 'https://cdn.tailwindcss.com'); shadowRoot.appendChild(tailwindLink); // 2. Add custom styles to the Shadow DOM const customStyles = document.createElement('style'); customStyles.textContent = ` /* Custom styles for within the Shadow DOM */ body { /* This will apply to the 'body-like' div inside shadow DOM */ font-family: 'Inter', sans-serif; background-color: #111827; /* bg-gray-900 */ color: #FFFFFF; /* text-white */ padding: 1rem; /* p-4 */ } @media (min-width: 768px) { /* md: */ body { padding: 2rem; /* md:p-8 */ } } .token { white-space: pre; /* Preserve spaces within tokens */ } * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } `; shadowRoot.appendChild(customStyles); // 3. Create the main structure of the component within the Shadow DOM const componentWrapper = document.createElement('div'); // componentWrapper.className = 'bg-gray-900 text-white p-4 md:p-8 antialiased'; // Apply body-like styles directly componentWrapper.innerHTML = ` Diffusion Language Model Generation Process Visualization 各ステップはトークンシーケンスの状態を示します。色の凡例: [MASK] , 確定済 , 新規生成 , 注目/変更 . 拡散言語モデルの状態可視化 `; shadowRoot.appendChild(componentWrapper); // --- Data Configuration --- // この 'visualizationStates' 配列に直接データをコピー&ペーストして編集してください。 // データ形式: ステップの配列。各ステップは [トークン文字列, 色の16進文字列] のペアの配列です。 // ============================================================================== // // START: 可視化データ定義箇所 (ここにデータをペーストしてください) // // ============================================================================== // let visualizationStates = [ [['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#FFAA33'], [' last', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#66CC66'], [' of', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], [' in', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], [' are', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], [' in', '#6699CC'], ['[MASK]', '#444444'], ['Lady', '#66CC66'], [' Gaga', '#66CC66'], ['[MASK]', '#444444'], [' are', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#66CC66'], [' words', '#66CC66'], [' in', '#6699CC'], ['[MASK]', '#444444'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['[MASK]', '#444444'], [' are', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#66CC66'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['[MASK]', '#444444'], [' are', '#6699CC'], [':', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#66CC66'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#66CC66'], ['-', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], ['[MASK]', '#444444'], [':', '#FFAA33'], ['[MASK]', '#444444'], ['\n', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#66CC66'], [':', '#6699CC'], ['[MASK]', '#444444'], ['\n', '#6699CC'], ['-', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], ['[MASK]', '#444444'], ['\n', '#6699CC'], ['-', '#6699CC'], ['[MASK]', '#444444'], [':', '#66CC66'], ['[MASK]', '#444444'], ['\n', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], ['[MASK]', '#444444'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#66CC66'], [':', '#6699CC'], ['[MASK]', '#444444'], ['\n', '#6699CC'], ['\n', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], ['[MASK]', '#444444'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#66CC66'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], ['[MASK]', '#444444'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#66CC66'], ['ating', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#FFAA33'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#66CC66'], [' gives', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#6699CC'], [' gives', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#66CC66'], ['', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#6699CC'], [' gives', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#6699CC'], ['', '#6699CC'], ['', '#66CC66'], ['', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#6699CC'], [' gives', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#66CC66'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#6699CC'], [' gives', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#66CC66'], ['', '#66CC66'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#6699CC'], [' gives', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#66CC66'], ['', '#66CC66'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#6699CC'], [' gives', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#FFAA33'], ['', '#66CC66'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#6699CC'], [' gives', '#6699CC'], ['[MASK]', '#444444'], [' "', '#FFAA33'], ['ya', '#66CC66'], ['[MASK]', '#444444'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#6699CC'], [' gives', '#6699CC'], [' us', '#FFAA33'], [' "', '#6699CC'], ['ya', '#6699CC'], ['".', '#66CC66'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#6699CC'], [' gives', '#6699CC'], [' us', '#6699CC'], [' "', '#6699CC'], ['ya', '#6699CC'], ['".', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#6699CC'], [' gives', '#6699CC'], [' us', '#6699CC'], [' "', '#6699CC'], ['ya', '#6699CC'], ['".', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#6699CC'], ['[MASK]', '#444444'], ['', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#66CC66']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#6699CC'], [' gives', '#6699CC'], [' us', '#6699CC'], [' "', '#6699CC'], ['ya', '#6699CC'], ['".', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#66CC66'], ['', '#66CC66'], ['', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#6699CC'], ['[MASK]', '#444444'], ['', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#6699CC']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#6699CC'], [' gives', '#6699CC'], [' us', '#6699CC'], [' "', '#6699CC'], ['ya', '#6699CC'], ['".', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#66CC66'], ['', '#6699CC'], ['[MASK]', '#444444'], ['', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#66CC66'], ['', '#6699CC']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#6699CC'], [' gives', '#6699CC'], [' us', '#6699CC'], [' "', '#6699CC'], ['ya', '#6699CC'], ['".', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#6699CC'], ['', '#6699CC'], ['', '#66CC66'], ['', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#66CC66'], ['', '#6699CC'], ['', '#6699CC']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#6699CC'], [' gives', '#6699CC'], [' us', '#6699CC'], [' "', '#6699CC'], ['ya', '#6699CC'], ['".', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#66CC66'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['[MASK]', '#444444'], ['', '#66CC66'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#6699CC'], [' gives', '#6699CC'], [' us', '#6699CC'], [' "', '#6699CC'], ['ya', '#6699CC'], ['".', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#66CC66'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#66CC66'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC']], [['The', '#6699CC'], [' last', '#6699CC'], [' letters', '#6699CC'], [' of', '#6699CC'], [' the', '#6699CC'], [' words', '#6699CC'], [' in', '#6699CC'], [' "', '#6699CC'], ['Lady', '#6699CC'], [' Gaga', '#6699CC'], ['"', '#6699CC'], [' are', '#6699CC'], [':', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Lady', '#6699CC'], [':', '#6699CC'], [' y', '#6699CC'], ['\n', '#6699CC'], ['-', '#6699CC'], [' Gaga', '#6699CC'], [':', '#6699CC'], [' a', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Concat', '#6699CC'], ['en', '#6699CC'], ['ating', '#6699CC'], [' these', '#6699CC'], [' letters', '#6699CC'], [' gives', '#6699CC'], [' us', '#6699CC'], [' "', '#6699CC'], ['ya', '#6699CC'], ['".', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#66CC66'], ['', '#66CC66'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC'], ['', '#6699CC']] ]; // ============================================================================== // // END: 可視化データ定義箇所 // // ============================================================================== // // --- Default/Sample Data (used if visualizationStates is empty, or as a template) --- if (visualizationStates.length === 0) { console.log("visualizationStates is empty for host:", hostElement, ". Populating with sample data."); const MASK_TOKEN = "[MASK]"; const MASK_COLOR = "#444444"; const CONFIRMED_COLOR = "#6699CC"; const NEW_COLOR = "#66CC66"; const FOCUS_COLOR = "#FFAA33"; const totalTokens = 18; const createMaskStep = () => Array(totalTokens).fill(null).map(() => [MASK_TOKEN, MASK_COLOR]); visualizationStates.push(createMaskStep()); let s1 = createMaskStep(); s1[0] = ["In", NEW_COLOR]; visualizationStates.push(s1); let s2 = createMaskStep(); s2[0] = ["In", CONFIRMED_COLOR]; s2[1] = [" the", NEW_COLOR]; visualizationStates.push(s2); let s3 = createMaskStep(); s3[0] = ["In", CONFIRMED_COLOR]; s3[1] = [" the", CONFIRMED_COLOR]; s3[2] = [" first", NEW_COLOR]; visualizationStates.push(s3); let s4 = createMaskStep(); s4[0] = ["In", CONFIRMED_COLOR]; s4[1] = [" the", CONFIRMED_COLOR]; s4[2] = [" first", CONFIRMED_COLOR]; s4[3] = [" 4", NEW_COLOR]; visualizationStates.push(s4); let s5 = createMaskStep(); s5[0] = ["In", CONFIRMED_COLOR]; s5[1] = [" the", CONFIRMED_COLOR]; s5[2] = [" first", CONFIRMED_COLOR]; s5[3] = [" 4", CONFIRMED_COLOR]; s5[4] = [" hours", NEW_COLOR]; visualizationStates.push(s5); let s6 = createMaskStep(); s6[0] = ["In", CONFIRMED_COLOR]; s6[1] = [" the", CONFIRMED_COLOR]; s6[2] = [" first", CONFIRMED_COLOR]; s6[3] = [" 4", CONFIRMED_COLOR]; s6[4] = [" hours", CONFIRMED_COLOR]; s6[5] = [" she", FOCUS_COLOR]; visualizationStates.push(s6); let s7 = createMaskStep(); s7[0] = ["In", CONFIRMED_COLOR]; s7[1] = [" the", CONFIRMED_COLOR]; s7[2] = [" first", CONFIRMED_COLOR]; s7[3] = [" 4", CONFIRMED_COLOR]; s7[4] = [" hours", CONFIRMED_COLOR]; s7[5] = [",", NEW_COLOR]; s7[6] = [" she", CONFIRMED_COLOR]; visualizationStates.push(s7); let s8 = createMaskStep(); s8[0] = ["In", CONFIRMED_COLOR]; s8[1] = [" the", CONFIRMED_COLOR]; s8[2] = [" first", CONFIRMED_COLOR]; s8[3] = [" 4", CONFIRMED_COLOR]; s8[4] = [" hours", CONFIRMED_COLOR]; s8[5] = [",", CONFIRMED_COLOR]; s8[6] = [" she", CONFIRMED_COLOR]; s8[7] = [" runs", NEW_COLOR]; visualizationStates.push(s8); let s9_user_example = createMaskStep(); s9_user_example[0] = ["In", CONFIRMED_COLOR]; s9_user_example[1] = [" the", CONFIRMED_COLOR]; s9_user_example[2] = [" first", CONFIRMED_COLOR]; s9_user_example[3] = [" ", CONFIRMED_COLOR]; s9_user_example[4] = ["4", CONFIRMED_COLOR]; s9_user_example[5] = [" hours", CONFIRMED_COLOR]; s9_user_example[6] = [",", CONFIRMED_COLOR]; s9_user_example[7] = [" she", CONFIRMED_COLOR]; s9_user_example[8] = [" runs", CONFIRMED_COLOR]; s9_user_example[9] = [" ", CONFIRMED_COLOR]; s9_user_example[10] = ["1", NEW_COLOR]; s9_user_example[11] = ["2", NEW_COLOR]; visualizationStates.push(s9_user_example); } // --- End of Sample Data Logic --- // --- Rendering Logic (targets elements within Shadow DOM) --- // Note: All functions are now local to the IIFE // 注意: すべての関数はIIFEに対してローカルになりました function getContrastingTextColor(hexColor) { if (!hexColor || hexColor.length 0.5 ? '#000000' : '#FFFFFF'; } catch (e) { console.error("Error parsing hexColor:", hexColor, e); return '#000000'; } } function renderVisualization() { // Query within the shadowRoot for the container // コンテナのshadowRoot内をクエリします const container = shadowRoot.getElementById('visualization-container'); if (!container) { console.error("Visualization container not found in Shadow DOM for host:", hostElement); return; } container.innerHTML = ''; if (!visualizationStates || visualizationStates.length === 0) { container.innerHTML = ' 表示するデータがありません。スクリプト内の `visualizationStates` 配列を編集してください。 '; return; } if (!Array.isArray(visualizationStates) || !visualizationStates.every(step => Array.isArray(step) && step.every(tokenPair => Array.isArray(tokenPair) && tokenPair.length === 2 && typeof tokenPair[0] === 'string' && typeof tokenPair[1] === 'string' && /^#[0-9A-Fa-f]{6}$/.test(tokenPair[1]) ))) { container.innerHTML = ' `visualizationStates` のデータ形式が無効です。期待される形式: [[ [token, "#RRGGBB"], ... ], ...] '; console.error("Invalid data format in visualizationStates for host:", hostElement, visualizationStates); return; } visualizationStates.forEach((stepTokens, stepIndex) => { const stepDiv = document.createElement('div'); stepDiv.className = 'step bg-gray-800 p-3 md:p-4 rounded-lg shadow-lg'; const stepLabel = document.createElement('h2'); stepLabel.className = 'step-label text-lg md:text-xl font-semibold mb-3 text-sky-400'; stepLabel.textContent = `ステップ ${stepIndex}:`; stepDiv.appendChild(stepLabel); const tokensContainer = document.createElement('div'); tokensContainer.className = 'tokens-container flex flex-wrap gap-0.5'; stepTokens.forEach(([token, color]) => { const tokenSpan = document.createElement('span'); tokenSpan.className = 'token py-1 px-1.5 rounded-sm text-xs md:text-sm font-mono leading-tight shadow-sm'; tokenSpan.textContent = token === " " ? "\u00A0" : token; tokenSpan.style.backgroundColor = color; tokenSpan.style.color = getContrastingTextColor(color); tokensContainer.appendChild(tokenSpan); }); stepDiv.appendChild(tokensContainer); container.appendChild(stepDiv); }); } // Ensure Tailwind is loaded before rendering // レンダリング前にTailwindが読み込まれていることを確認します if (tailwindLink.sheet) { // For browsers that support sheet property immediately renderVisualization(); } else { tailwindLink.onload = () => { renderVisualization(); }; } // Fallback timeout for rendering, in case onload doesn't fire reliably (e.g. cached stylesheet) // レンダリングのフォールバックタイムアウト。(例: キャッシュされたスタイルシートなど) onloadが確実に発行されない場合に備えます。 setTimeout(() => { const container = shadowRoot.getElementById('visualization-container'); // Check if container is still empty (or only has the placeholder message) // コンテナがまだ空であるか (またはプレースホルダーメッセージのみが含まれているか) を確認します if (container && container.children.length === 0 || (container.children.length === 1 && container.firstElementChild.tagName === 'P')) { console.log("Fallback: Tailwind might be cached or onload didn't fire. Rendering for host:", hostElement); renderVisualization(); } }, 500); // Slightly increased timeout for safety 安全のためにタイムアウトをわずかに増やしました } else { console.error("Could not find host element for a script instance. Ensure script is placed immediately after its target div."); // このスクリプトインスタンスのホスト要素が見つかりませんでした。スクリプトがターゲットdivの直後に配置されていることを確認してください。 } })(); // END OF IIFE こちらも正しく推論できていることが分かります。ステップ 12の箇条書きなどは苗字と名前が先に箇条書きされて、後に目的の最後の文字が出力されるのは面白かったです。タスクによって癖のようなものがありそうです。 サンプル3 こちらも CoTの元論文 の例で、モデル自信の知識が問われる選択問題になります。 Sammy wanted to go to where the people were. Where might he go? Options: (a) race track (b) populated areas (c) desert (d) apartment (e) roadblock 結果 Diffusion Language Model - Generation Process (Embeddable) /* Styles for the host element itself, if any (optional) */ /* 例えば、埋め込みコンポーネントのデフォルトの幅やマージンなど */ /* The ID below is no longer strictly needed by the script if using previousElementSibling, */ /* but can be kept for other purposes or changed to a class. */ /* 下記のIDは、previousElementSiblingを使用する場合、スクリプトには厳密には不要になりましたが、 */ /* 他の目的のために保持したり、クラスに変更したりすることができます。 */ #diffusion-visualization-embed { /* Or a class like .diffusion-visualization-host */ /* max-width: 800px; */ /* margin: 20px auto; */ } (function () { // START OF IIFE: Creates a new scope for each script instance // --- Shadow DOM Setup and Component Rendering --- // Get the div element immediately preceding this script tag // このscriptタグの直前にあるdiv要素を取得します const hostElement = document.currentScript.previousElementSibling; if (hostElement) { // Check if a shadow root already exists (e.g., if script runs multiple times on the same element by mistake) // シャドウ ルートが既に存在するかどうかを確認します (例: スクリプトが誤って同じ要素で複数回実行された場合) // Though with IIFE and previousElementSibling, this specific check might be less critical for *this* error, // it's good practice if the host element could be reprocessed. // IIFE と previousElementSibling を使用すると、この特定のエラーに対するこの特定のチェックはそれほど重要ではないかもしれませんが、 // ホスト要素が再処理される可能性がある場合は良い習慣です。 if (hostElement.shadowRoot) { console.warn("Shadow DOM already attached to this host element. Skipping re-initialization.", hostElement); return; // Exit if already initialized for this specific host element } const shadowRoot = hostElement.attachShadow({ mode: 'open' }); // 1. Load Tailwind CSS into the Shadow DOM const tailwindLink = document.createElement('link'); tailwindLink.setAttribute('rel', 'stylesheet'); tailwindLink.setAttribute('href', 'https://cdn.tailwindcss.com'); shadowRoot.appendChild(tailwindLink); // 2. Add custom styles to the Shadow DOM const customStyles = document.createElement('style'); customStyles.textContent = ` /* Custom styles for within the Shadow DOM */ body { /* This will apply to the 'body-like' div inside shadow DOM */ font-family: 'Inter', sans-serif; background-color: #111827; /* bg-gray-900 */ color: #FFFFFF; /* text-white */ padding: 1rem; /* p-4 */ } @media (min-width: 768px) { /* md: */ body { padding: 2rem; /* md:p-8 */ } } .token { white-space: pre; /* Preserve spaces within tokens */ } * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } `; shadowRoot.appendChild(customStyles); // 3. Create the main structure of the component within the Shadow DOM const componentWrapper = document.createElement('div'); // componentWrapper.className = 'bg-gray-900 text-white p-4 md:p-8 antialiased'; // Apply body-like styles directly componentWrapper.innerHTML = ` Diffusion Language Model Generation Process Visualization 各ステップはトークンシーケンスの状態を示します。色の凡例: [MASK] , 確定済 , 新規生成 , 注目/変更 . 拡散言語モデルの状態可視化 `; shadowRoot.appendChild(componentWrapper); // --- Data Configuration --- // この 'visualizationStates' 配列に直接データをコピー&ペーストして編集してください。 // データ形式: ステップの配列。各ステップは [トークン文字列, 色の16進文字列] のペアの配列です。 // ============================================================================== // // START: 可視化データ定義箇所 (ここにデータをペーストしてください) // // ============================================================================== // let visualizationStates = [ [['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#FFAA33'], ['[MASK]', '#444444'], [' answer', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#66CC66'], [' answer', '#6699CC'], [' is', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], ['[MASK]', '#444444'], ['b', '#66CC66'], [')', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#66CC66'], ['b', '#6699CC'], [')', '#6699CC'], ['[MASK]', '#444444'], [' areas', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#66CC66'], [' areas', '#6699CC'], ['.', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#66CC66'], ['\n', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['[MASK]', '#444444'], ['ulated', '#66CC66'], ['[MASK]', '#444444'], [' to', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#66CC66'], ['ulated', '#6699CC'], [' refers', '#66CC66'], [' to', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], [' a', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#66CC66'], [' with', '#66CC66'], [' a', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], ['[MASK]', '#444444'], [' number', '#FFAA33'], [' of', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#66CC66'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#FFAA33'], [',', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#FFAA33'], ['[MASK]', '#444444'], [' or', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#66CC66'], [' or', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['.', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#66CC66'], [' there', '#66CC66'], ['.', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#6699CC'], [' there', '#6699CC'], ['.', '#6699CC'], ['[MASK]', '#444444'], [',', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], [' to', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#6699CC'], [' there', '#6699CC'], ['.', '#6699CC'], ['[MASK]', '#444444'], [',', '#6699CC'], [' if', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], [' to', '#6699CC'], [' go', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#6699CC'], [' there', '#6699CC'], ['.', '#6699CC'], ['[MASK]', '#444444'], [',', '#6699CC'], [' if', '#6699CC'], [' Sammy', '#66CC66'], ['[MASK]', '#444444'], [' to', '#6699CC'], [' go', '#6699CC'], [' to', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#6699CC'], [' there', '#6699CC'], ['.', '#6699CC'], ['[MASK]', '#444444'], [',', '#6699CC'], [' if', '#6699CC'], [' Sammy', '#6699CC'], ['[MASK]', '#444444'], [' to', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' where', '#66CC66'], [' the', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#6699CC'], [' there', '#6699CC'], ['.', '#6699CC'], ['[MASK]', '#444444'], [',', '#6699CC'], [' if', '#6699CC'], [' Sammy', '#6699CC'], ['[MASK]', '#444444'], [' to', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' where', '#6699CC'], [' the', '#6699CC'], [' people', '#66CC66'], ['[MASK]', '#444444'], [',', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#6699CC'], [' there', '#6699CC'], ['.', '#6699CC'], ['[MASK]', '#444444'], [',', '#6699CC'], [' if', '#6699CC'], [' Sammy', '#6699CC'], ['[MASK]', '#444444'], [' to', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' where', '#6699CC'], [' the', '#6699CC'], [' people', '#6699CC'], [' were', '#66CC66'], [',', '#6699CC'], [' he', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#6699CC'], [' there', '#6699CC'], ['.', '#6699CC'], ['[MASK]', '#444444'], [',', '#6699CC'], [' if', '#6699CC'], [' Sammy', '#6699CC'], [' wanted', '#66CC66'], [' to', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' where', '#6699CC'], [' the', '#6699CC'], [' people', '#6699CC'], [' were', '#6699CC'], [',', '#6699CC'], [' he', '#6699CC'], [' would', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#6699CC'], [' there', '#6699CC'], ['.', '#6699CC'], [' So', '#FFAA33'], [',', '#6699CC'], [' if', '#6699CC'], [' Sammy', '#6699CC'], [' wanted', '#6699CC'], [' to', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' where', '#6699CC'], [' the', '#6699CC'], [' people', '#6699CC'], [' were', '#6699CC'], [',', '#6699CC'], [' he', '#6699CC'], [' would', '#6699CC'], [' likely', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#6699CC'], [' there', '#6699CC'], ['.', '#6699CC'], [' So', '#6699CC'], [',', '#6699CC'], [' if', '#6699CC'], [' Sammy', '#6699CC'], [' wanted', '#6699CC'], [' to', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' where', '#6699CC'], [' the', '#6699CC'], [' people', '#6699CC'], [' were', '#6699CC'], [',', '#6699CC'], [' he', '#6699CC'], [' would', '#6699CC'], [' likely', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['', '#66CC66'], ['', '#66CC66']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#6699CC'], [' there', '#6699CC'], ['.', '#6699CC'], [' So', '#6699CC'], [',', '#6699CC'], [' if', '#6699CC'], [' Sammy', '#6699CC'], [' wanted', '#6699CC'], [' to', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' where', '#6699CC'], [' the', '#6699CC'], [' people', '#6699CC'], [' were', '#6699CC'], [',', '#6699CC'], [' he', '#6699CC'], [' would', '#6699CC'], [' likely', '#6699CC'], ['[MASK]', '#444444'], [' to', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['.', '#66CC66'], ['', '#6699CC'], ['', '#6699CC']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#6699CC'], [' there', '#6699CC'], ['.', '#6699CC'], [' So', '#6699CC'], [',', '#6699CC'], [' if', '#6699CC'], [' Sammy', '#6699CC'], [' wanted', '#6699CC'], [' to', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' where', '#6699CC'], [' the', '#6699CC'], [' people', '#6699CC'], [' were', '#6699CC'], [',', '#6699CC'], [' he', '#6699CC'], [' would', '#6699CC'], [' likely', '#6699CC'], [' go', '#66CC66'], [' to', '#6699CC'], [' a', '#66CC66'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['.', '#6699CC'], ['', '#6699CC'], ['', '#6699CC']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#6699CC'], [' there', '#6699CC'], ['.', '#6699CC'], [' So', '#6699CC'], [',', '#6699CC'], [' if', '#6699CC'], [' Sammy', '#6699CC'], [' wanted', '#6699CC'], [' to', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' where', '#6699CC'], [' the', '#6699CC'], [' people', '#6699CC'], [' were', '#6699CC'], [',', '#6699CC'], [' he', '#6699CC'], [' would', '#6699CC'], [' likely', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#FFAA33'], ['[MASK]', '#444444'], [',', '#FFAA33'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['.', '#6699CC'], ['', '#6699CC'], ['', '#6699CC']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#6699CC'], [' there', '#6699CC'], ['.', '#6699CC'], [' So', '#6699CC'], [',', '#6699CC'], [' if', '#6699CC'], [' Sammy', '#6699CC'], [' wanted', '#6699CC'], [' to', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' where', '#6699CC'], [' the', '#6699CC'], [' people', '#6699CC'], [' were', '#6699CC'], [',', '#6699CC'], [' he', '#6699CC'], [' would', '#6699CC'], [' likely', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' like', '#FFAA33'], [',', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], [' populated', '#66CC66'], ['[MASK]', '#444444'], ['.', '#6699CC'], ['', '#6699CC'], ['', '#6699CC']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#6699CC'], [' there', '#6699CC'], ['.', '#6699CC'], [' So', '#6699CC'], [',', '#6699CC'], [' if', '#6699CC'], [' Sammy', '#6699CC'], [' wanted', '#6699CC'], [' to', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' where', '#6699CC'], [' the', '#6699CC'], [' people', '#6699CC'], [' were', '#6699CC'], [',', '#6699CC'], [' he', '#6699CC'], [' would', '#6699CC'], [' likely', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' like', '#6699CC'], [',', '#6699CC'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], ['[MASK]', '#444444'], [' a', '#FFAA33'], [' populated', '#6699CC'], [' area', '#66CC66'], ['.', '#6699CC'], ['', '#6699CC'], ['', '#6699CC']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#6699CC'], [' there', '#6699CC'], ['.', '#6699CC'], [' So', '#6699CC'], [',', '#6699CC'], [' if', '#6699CC'], [' Sammy', '#6699CC'], [' wanted', '#6699CC'], [' to', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' where', '#6699CC'], [' the', '#6699CC'], [' people', '#6699CC'], [' were', '#6699CC'], [',', '#6699CC'], [' he', '#6699CC'], [' would', '#6699CC'], [' likely', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' like', '#6699CC'], [',', '#6699CC'], ['[MASK]', '#444444'], [',', '#FFAA33'], ['[MASK]', '#444444'], [',', '#FFAA33'], [' a', '#6699CC'], [' populated', '#6699CC'], [' area', '#6699CC'], ['.', '#6699CC'], ['', '#6699CC'], ['', '#6699CC']], [['The', '#6699CC'], [' correct', '#6699CC'], [' answer', '#6699CC'], [' is', '#6699CC'], [' (', '#6699CC'], ['b', '#6699CC'], [')', '#6699CC'], [' populated', '#6699CC'], [' areas', '#6699CC'], ['.', '#6699CC'], ['\n', '#6699CC'], ['\n', '#6699CC'], ['Pop', '#6699CC'], ['ulated', '#6699CC'], [' refers', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' with', '#6699CC'], [' a', '#6699CC'], [' large', '#6699CC'], [' number', '#6699CC'], [' of', '#6699CC'], [' people', '#6699CC'], [' living', '#6699CC'], [',', '#6699CC'], [' working', '#6699CC'], [',', '#6699CC'], [' or', '#6699CC'], [' being', '#6699CC'], [' there', '#6699CC'], ['.', '#6699CC'], [' So', '#6699CC'], [',', '#6699CC'], [' if', '#6699CC'], [' Sammy', '#6699CC'], [' wanted', '#6699CC'], [' to', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' where', '#6699CC'], [' the', '#6699CC'], [' people', '#6699CC'], [' were', '#6699CC'], [',', '#6699CC'], [' he', '#6699CC'], [' would', '#6699CC'], [' likely', '#6699CC'], [' go', '#6699CC'], [' to', '#6699CC'], [' a', '#6699CC'], [' place', '#6699CC'], [' like', '#6699CC'], [',', '#6699CC'], [' well', '#FFAA33'], [',', '#6699CC'], [' well', '#66CC66'], [',', '#6699CC'], [' a', '#6699CC'], [' populated', '#6699CC'], [' area', '#6699CC'], ['.', '#6699CC'], ['', '#6699CC'], ['', '#6699CC']] ]; // ============================================================================== // // END: 可視化データ定義箇所 // // ============================================================================== // // --- Default/Sample Data (used if visualizationStates is empty, or as a template) --- if (visualizationStates.length === 0) { console.log("visualizationStates is empty for host:", hostElement, ". Populating with sample data."); const MASK_TOKEN = "[MASK]"; const MASK_COLOR = "#444444"; const CONFIRMED_COLOR = "#6699CC"; const NEW_COLOR = "#66CC66"; const FOCUS_COLOR = "#FFAA33"; const totalTokens = 18; const createMaskStep = () => Array(totalTokens).fill(null).map(() => [MASK_TOKEN, MASK_COLOR]); visualizationStates.push(createMaskStep()); let s1 = createMaskStep(); s1[0] = ["In", NEW_COLOR]; visualizationStates.push(s1); let s2 = createMaskStep(); s2[0] = ["In", CONFIRMED_COLOR]; s2[1] = [" the", NEW_COLOR]; visualizationStates.push(s2); let s3 = createMaskStep(); s3[0] = ["In", CONFIRMED_COLOR]; s3[1] = [" the", CONFIRMED_COLOR]; s3[2] = [" first", NEW_COLOR]; visualizationStates.push(s3); let s4 = createMaskStep(); s4[0] = ["In", CONFIRMED_COLOR]; s4[1] = [" the", CONFIRMED_COLOR]; s4[2] = [" first", CONFIRMED_COLOR]; s4[3] = [" 4", NEW_COLOR]; visualizationStates.push(s4); let s5 = createMaskStep(); s5[0] = ["In", CONFIRMED_COLOR]; s5[1] = [" the", CONFIRMED_COLOR]; s5[2] = [" first", CONFIRMED_COLOR]; s5[3] = [" 4", CONFIRMED_COLOR]; s5[4] = [" hours", NEW_COLOR]; visualizationStates.push(s5); let s6 = createMaskStep(); s6[0] = ["In", CONFIRMED_COLOR]; s6[1] = [" the", CONFIRMED_COLOR]; s6[2] = [" first", CONFIRMED_COLOR]; s6[3] = [" 4", CONFIRMED_COLOR]; s6[4] = [" hours", CONFIRMED_COLOR]; s6[5] = [" she", FOCUS_COLOR]; visualizationStates.push(s6); let s7 = createMaskStep(); s7[0] = ["In", CONFIRMED_COLOR]; s7[1] = [" the", CONFIRMED_COLOR]; s7[2] = [" first", CONFIRMED_COLOR]; s7[3] = [" 4", CONFIRMED_COLOR]; s7[4] = [" hours", CONFIRMED_COLOR]; s7[5] = [",", NEW_COLOR]; s7[6] = [" she", CONFIRMED_COLOR]; visualizationStates.push(s7); let s8 = createMaskStep(); s8[0] = ["In", CONFIRMED_COLOR]; s8[1] = [" the", CONFIRMED_COLOR]; s8[2] = [" first", CONFIRMED_COLOR]; s8[3] = [" 4", CONFIRMED_COLOR]; s8[4] = [" hours", CONFIRMED_COLOR]; s8[5] = [",", CONFIRMED_COLOR]; s8[6] = [" she", CONFIRMED_COLOR]; s8[7] = [" runs", NEW_COLOR]; visualizationStates.push(s8); let s9_user_example = createMaskStep(); s9_user_example[0] = ["In", CONFIRMED_COLOR]; s9_user_example[1] = [" the", CONFIRMED_COLOR]; s9_user_example[2] = [" first", CONFIRMED_COLOR]; s9_user_example[3] = [" ", CONFIRMED_COLOR]; s9_user_example[4] = ["4", CONFIRMED_COLOR]; s9_user_example[5] = [" hours", CONFIRMED_COLOR]; s9_user_example[6] = [",", CONFIRMED_COLOR]; s9_user_example[7] = [" she", CONFIRMED_COLOR]; s9_user_example[8] = [" runs", CONFIRMED_COLOR]; s9_user_example[9] = [" ", CONFIRMED_COLOR]; s9_user_example[10] = ["1", NEW_COLOR]; s9_user_example[11] = ["2", NEW_COLOR]; visualizationStates.push(s9_user_example); } // --- End of Sample Data Logic --- // --- Rendering Logic (targets elements within Shadow DOM) --- // Note: All functions are now local to the IIFE // 注意: すべての関数はIIFEに対してローカルになりました function getContrastingTextColor(hexColor) { if (!hexColor || hexColor.length 0.5 ? '#000000' : '#FFFFFF'; } catch (e) { console.error("Error parsing hexColor:", hexColor, e); return '#000000'; } } function renderVisualization() { // Query within the shadowRoot for the container // コンテナのshadowRoot内をクエリします const container = shadowRoot.getElementById('visualization-container'); if (!container) { console.error("Visualization container not found in Shadow DOM for host:", hostElement); return; } container.innerHTML = ''; if (!visualizationStates || visualizationStates.length === 0) { container.innerHTML = ' 表示するデータがありません。スクリプト内の `visualizationStates` 配列を編集してください。 '; return; } if (!Array.isArray(visualizationStates) || !visualizationStates.every(step => Array.isArray(step) && step.every(tokenPair => Array.isArray(tokenPair) && tokenPair.length === 2 && typeof tokenPair[0] === 'string' && typeof tokenPair[1] === 'string' && /^#[0-9A-Fa-f]{6}$/.test(tokenPair[1]) ))) { container.innerHTML = ' `visualizationStates` のデータ形式が無効です。期待される形式: [[ [token, "#RRGGBB"], ... ], ...] '; console.error("Invalid data format in visualizationStates for host:", hostElement, visualizationStates); return; } visualizationStates.forEach((stepTokens, stepIndex) => { const stepDiv = document.createElement('div'); stepDiv.className = 'step bg-gray-800 p-3 md:p-4 rounded-lg shadow-lg'; const stepLabel = document.createElement('h2'); stepLabel.className = 'step-label text-lg md:text-xl font-semibold mb-3 text-sky-400'; stepLabel.textContent = `ステップ ${stepIndex}:`; stepDiv.appendChild(stepLabel); const tokensContainer = document.createElement('div'); tokensContainer.className = 'tokens-container flex flex-wrap gap-0.5'; stepTokens.forEach(([token, color]) => { const tokenSpan = document.createElement('span'); tokenSpan.className = 'token py-1 px-1.5 rounded-sm text-xs md:text-sm font-mono leading-tight shadow-sm'; tokenSpan.textContent = token === " " ? "\u00A0" : token; tokenSpan.style.backgroundColor = color; tokenSpan.style.color = getContrastingTextColor(color); tokensContainer.appendChild(tokenSpan); }); stepDiv.appendChild(tokensContainer); container.appendChild(stepDiv); }); } // Ensure Tailwind is loaded before rendering // レンダリング前にTailwindが読み込まれていることを確認します if (tailwindLink.sheet) { // For browsers that support sheet property immediately renderVisualization(); } else { tailwindLink.onload = () => { renderVisualization(); }; } // Fallback timeout for rendering, in case onload doesn't fire reliably (e.g. cached stylesheet) // レンダリングのフォールバックタイムアウト。(例: キャッシュされたスタイルシートなど) onloadが確実に発行されない場合に備えます。 setTimeout(() => { const container = shadowRoot.getElementById('visualization-container'); // Check if container is still empty (or only has the placeholder message) // コンテナがまだ空であるか (またはプレースホルダーメッセージのみが含まれているか) を確認します if (container && container.children.length === 0 || (container.children.length === 1 && container.firstElementChild.tagName === 'P')) { console.log("Fallback: Tailwind might be cached or onload didn't fire. Rendering for host:", hostElement); renderVisualization(); } }, 500); // Slightly increased timeout for safety 安全のためにタイムアウトをわずかに増やしました } else { console.error("Could not find host element for a script instance. Ensure script is placed immediately after its target div."); // このスクリプトインスタンスのホスト要素が見つかりませんでした。スクリプトがターゲットdivの直後に配置されていることを確認してください。 } })(); // END OF IIFE なかなか興味深い結果で、思考→回答という生成順になる自己回帰モデルのCoTと逆で、回答が先に生成され、思考が生成文章の後に来ています。全体を一気に生成する拡散言語モデルならではの挙動といえるかもしれません。 感想 検証前は感覚を掴みづらかったのですが、人間で言うと、ルーティン的にこなしているような慣れている問題を与えられた時、先に答えが頭に浮かんで、後でその整合性をとるために途中の過程を思い描く、といった挙動に近いイメージを持ちました。(※2) 今回の検証では確認できませんでしたが、最初に浮かんだ答えが間違っていると判断した場合、remasking(※3)というトークンを書き換える処理が走ることもあるようで、自己回帰モデルの問題点として挙げられるエラーの蓄積にも対処できそうです。 また、拡散言語モデルの特徴として、suffixや文中で必ず使用してほしいトークンなどを指定することができます。今回検証したような全体最適化を行う場合、現在は全く想像できませんが、新しいPromptテクニックが必要になってくるのではないかな、と思いました。 一方、このような推論方法に 疑問を投げかけるような研究 もありました。拡散言語モデルはToken Error Rate: TERの低い生成は高速に行えますが、Sequence Error Rate: SERの低い生成を行うためには拡散ステップを増やさなければならず、正確性が求められるタスクであれば結果として自己回帰モデルの方が効率が良い、と言う主張です。確かにGemini Diffusionのベンチマーク比較をみると、最小のGemini 2.0 FLASH-LITEの方が優勢に見えます。 今後全てのモデルが拡散言語モデルに置き換わるわけではなさそうで、タスクによって使い分けが必要になってくるのではないでしょうか。(※4) まとめ 本記事では拡散言語モデルの推論過程を実際にみてみました。 最初は直感に反すると思った挙動でしたが、実際に動かしてみると、なんとなく人間の思考に近い部分もあるかな、と感じるところもありました(わかった気になってるだけかもしれませんが、、、) ベースの拡散言語モデルの性能向上はもちろんですが、今回検証に使ったLLaDAは最近マルチモーダルバージョンの LLaDA-V が出ていたり、マルチモーダル化の研究も進んでいるようで、今後の発展にとても期待しています。引き続きウォッチを続け、興味深い内容があればまた共有させていただきたいと思います。 最後までお読みいただきありがとうございました! 参考 ※1: reversal curse ... LLMが「AはBである」という情報を学習しても、「BはAである」という逆の関係性を推論できない現象です。 ※2: Diffusion of Thoughts: DoT という考え方があるそうです。 ※3: Large Language Diffusion Models のAppendixのB.3に詳細な解説があります。 ※4: こういったところから、自己回帰モデルと拡散モデルのいいとこどりをする Diffusion Guided Language Modeling: DGLM や Autoregressive Diffusion Models といった手法がでてきています。 投稿 拡散言語モデルの推論過程を眺めてみる は 株式会社AI Shift に最初に表示されました。
アバター
はじめに こんにちは、AIチームの大竹です。 近年、音声対話アプリケーションの進化が目覚ましく、顧客対応の自動化や業務効率化への期待が高まっています。弊社の AI Messenger Voicebot も例外ではなく、最先端の生成AI技術を活用した自然な対話基盤を構築し、お客様の電話応対業務のDXを推進すべく日々進化を続けています。 しかし、依然としてシナリオ(ワークフロー)型の構成になっており、「エッジケースに対応しきれず離脱率が高い」、「 対象とするタスクのスコープが狭くタスク完了率が低いと が低い」といった課題があります。また、複雑なシナリオ設計は人手では限界があり、より柔軟で自律的な対話システムが求められていると考えています。 本記事では、このような課題を解決しうる「自律型対話システム」の実現可能性を探るため、最新のE2E(End-to-End)音声対話APIと音声対話システム構築プラットフォームに関する調査・検証結果をまとめてご紹介します。 E2E音声対話API 1. OpenAI Realtime Agents OpenAIが提供する Realtime API を基盤とし、さらに高度なエージェント連携パターンを示す公式デモを動かしてみました。 特徴 あらかじめ定義されたエージェントグラフに基づき、会話の文脈に応じて各エージェントが順番に処理を引き継ぎます 。 重要な判断が必要な場面では、GPT-4.1のようなRealtime API内で動作しているGPT-4oシリーズのモデルと比較して高性能なモデルが判断を支援する仕組みが取り入れられています 。 会話のフローはプロンプトによって細かく制御でき、例えばユーザーの名前や電話番号といった重要なヒアリング項目を文字単位で確認するなど、正確な項目のヒアリングと認証を目的とした設計が可能です 。 デモ:エージェント連携 以下のデモでは飲食予約対話を題材にして、エージェント連携を試しています。予約エージェントとFAQ回答エージェントを定義し、お互いのエージェントが連携して予約対話を進めていきます。予約中に店舗への質問を検知したら、FAQ専門のエージェントが出てきて回答するという挙動になっています。 検証からの所感 自然言語による大まかな対話設計と、Function Callingによる外部システム連携や厳密な制御を組み合わせるアプローチは、開発のしやすさと柔軟性のバランスが良いと感じました。 一方で、音声合成の品質や、エージェントの自律性が高すぎることによる意図しない応答、バージイン(ユーザー発話への割り込み)処理の安定性など、実用化に向けた課題も散見されました。 また、エージェント連携機能がリアルタイムの音声対話において有用かどうかは疑問でした。明確に役割を分けた方が回答の精度は高くなるだろうと思う一方で、対話のインタラクションの面でエージェントがその都度切り変わるというのはUXを下げてしまうのではないかとも感じました。例えばデモではエージェントが切り替わる際にそれぞれのエージェントが挨拶をしていて不自然と感じられます。LLMに渡すコンテキストをうまく設定すれば解決できるのかもしれませんが、挙動を制御するための設定が煩雑になり大変そうです。 2. Gemini Live API Gemini Live APIはGoogleのGeminiモデルを活用した、双方向かつリアルタイムな通信が可能なマルチモーダル対話ができるAPIです 。 特徴 Function Callingを活用し、外部API連携をシームレスに実行するリアルタイム音声対話システムを構築できます 。 画面共有をしながらGeminiと対話するといった、よりリッチなインタラクションも可能です 。 特筆すべきは「Grounding with Google Search」機能で、APIがGoogle検索と連携し、外部情報を参照しながら回答を生成します 。 デモ:Grounding with Google Search機能 Google AI Studio の画面でGrounding with Google SearchをオンにしてGemini Live APIを起動することで簡単に試すことができます。以下のデモでは、LLMが学習していないはずの新しいAI Shiftのミッションとビジョンを質問していますが、正確かつ高速に答えられています。 検証からの所感 音声合成の品質は比較的高く、自然な印象でした。 しかし、人名などの固有名詞の認識誤りや、その誤りを訂正する仕組みの作り込みは、システムプロンプトやFunction Callingの設計で工夫が必要です。このような制御性に関する問題はRealtime APIと同様に実用化する上でボトルネックだと感じました。 Google Searchとの連携機能は非常に強力で、事前の知識登録なしにドメイン固有の質問にも答えられるのでとても便利そうです。 これらのE2E音声対話APIは、「スムーズな対話」の実現に向けて大きく前進していますが、現状では「対話の質や精度」との間にトレードオフが存在します。特に複雑な業務や 高い正確性が求められる窓口応対業務などの場面 が求められる場面では、まだ改善の余地があると感じます。 音声対話システム構築プラットフォーム より高度でカスタマイズされた音声AIエージェントを構築するためのプラットフォームも進化しています。 1. ElevenLabs Conversational AI リアルな音声合成技術で知られるElevenLabsが提供する、人間と自然な対話が可能な音声AIエージェントを迅速かつ容易に構築するためのプラットフォームです 。 アーキテクチャ ユーザーの音声入力を音声認識でテキスト化し、LLMが応答を生成、それを音声合成で再び音声として出力するという、パイプライン型の構成を採用しています 。 構築要素 使用するLLMの選択 エージェントの性格や会話の文脈を定義するシステムプロンプト ドメイン固有情報を提供するナレッジベース 外部機能と連携するツール など、多岐にわたる設定項目を通じてエージェントをカスタマイズできます 。なお、 声の選択、話し方(安定性、スピード、類似性など)も細かく調整可能です 。 所感 多機能である反面、設定項目が多く、構築難易度が高いと感じました。 日本語の音声認識・合成の精度については、まだ向上の余地があると感じられました。 2. Twilio AI Assistants (α版) 顧客とのコミュニケーションを自動化し、パーソナライズされた体験を提供することを目指す、Twilioの強力なプラットフォームです 。現在はまだα版で、開発者向けに実験的なプロジェクトとして提供されており、「次世代の顧客インタラクションの定義」を目標に掲げています 。 構成要素 顧客メモリ & Segment連携: 顧客理解を深め、対話履歴を活かして個別対応を実現 。 Tools: 外部システムと連携し、特定タスクを自動実行 。 Knowledge Sources : 外部情報を参照し、幅広い質問への対応力を強化 。 Channels: 多様な経路(電話、API等)でアシスタントと接続 所感 非常に多機能で、顧客理解に基づいた高度なパーソナライゼーション機能といったvoicebotの展望を考える上で非常に参考になる機能が多かったです。 一方でこれらの機能を実現するには、各コンポーネントの深い理解と緻密な設定が求められ、構築難易度と運用の難易度は高そうだと感じました。 自律型音声対話システム実現への考察 これまでの検証を踏まえ、自律型音声対話システムに求められる要素と、その実現に向けた課題を改めて整理します。 自律型音声対話システムに求められる要素 スムーズな対話 自然なタイミングでの話者交替と応答 発話の割り込みがあった際に内容に応じて話者交替を行うべきかどうかを判断する 適切なタイミングで相槌を打ち、話を聞いていることを示してユーザを安心させ話の続きを促す 上記の要素に関して、Realtime APIやGemini Live APIに部分的に搭載はされてきてはいるものの機械学習の技術的にまだ実現できていないものです。実用を考えると、システムが動き続けている間、常時ストリーミングで動作しなければいけないため、低コストかつ高スケーラビリティを維持しながら実現することはさらに困難です。 自律的な行動 文脈に応じた柔軟な意図・スロットの訂正 外部API連携 外部ナレッジを考慮した応答 人間のオペレーターへの適切なタイミングでのエスカレーション 曖昧な発話への明確化質問 電話音声対話での適切な情報の粒度を考えた応答生成 こちらはLLMを用いることで解決する見込みがあり、対話品質を高める上で非常に効果的だと考えています。 実現に向けた課題 「スムーズさ」と「対話品質」のジレンマ リアルタイム性を追求する現在のE2E APIでは、対話の スムーズさ は向上しつつありますが、その反面、 応答の質や精度 が犠牲になるケースも見られます。スムーズさを向上させ、音声対話のインタラクション性を高めるがゆえに、対話の質が下がって結果的にUXを下げてしまうことになると元も子もなくなってしまいます。実用レベルの対話品質を考慮すると対話の全てをE2E APIないし一つのLLMモデルに任せるといったことは、まだまだだいぶ先の話になりそうです。 対話設計の複雑性 Agent型のシステムは、ツールやガードレール、ナレッジやメモリなど多くの要素を管理する必要があり、設計が複雑になりがちです。「自律性を高めれば設計の複雑さが低減する」という単純な仮説は、現状では成り立ちにくいと言えます。とはいえ、自律性を高めていこうとすると、自ずとAgent型のシステム構成になってくるので、これらの設定項目の自動生成など、対話設計のサポート機能などは必要なのかなと思います。 まとめ リアルタイム音声対話APIや高度な構築プラットフォームの登場により、音声対話システムはより自然で、より賢く進化しつつあります。しかし、まだまだ実用に耐えうる精度ではないと感じました。真に「自律型」と呼べる音声対話システムを実現するには、音声対話特有のインタラクションに関する技術的な課題の克服と、ドメイン固有の課題を踏まえた設計や運用をいかに音声対話に反映できるかが重要になるのではと考えています。 今後もこれらの技術動向を注視し、人とAIがより効果的に協働できる未来を目指して、技術調査および検証・開発を進めてまいります。 長文お読みいただきありがとうございました! 投稿 E2E音声対話API・構築プラットフォーム最新動向の調査と自律型音声対話システムの展望 は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは AIチームの戸田です 先日、LLMの "aha moment" に関して興味を持ち、関連論文やWeb上の記事を読んでみたところ、賛否両論の様々な見解があり興味深かったので、今回はその内容を共有したいと思います。 aha momentとは そもそもaha momentとは、ドイツの心理学者のカール・ビューラーが提唱した心理学上の概念で、今まで分からなかったことや、問題の答えが、突然「あっ、そうか!」とひらめく瞬間のことを言うようです。日本語だとアハ体験と呼ばれていたりします。 間違い探しが、よくaha momentの事例として挙げられる https://ja.wikipedia.org/wiki/アハ体験 LLM研究におけるaha moment LLM文脈でのaha momentは、 DeepSeek-R1のtechnical report で言及された、LLMの中でも推論に特化した LRM(Large Reasoning Model, 大規模推論モデル) の学習中に観察された現象のことを指します。これはLRMが問題を解いている最中に人間が内省する際に使う言葉を発し、戦略を見直すかのような挙動です。 具体的にtechnical reportで挙げられている例としては以下のようなものがあります。 DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning: Table 3 赤文字部分が「aha moment」と呼ばれているところです。日本語に訳すと「ちょっと待てよ、、、あっ、そうか!よし、一旦立ち止まって段階的に考えてみよう〜〜〜」みたいな感じでしょうか。 この aha momentをより深く分析した研究( Understanding Aha Moments: from External Observations to Internal Mechanisms )では、aha momentで観測される "Aha" や "Hmm" のようなトークンをAnthropomorphic Tokens(※1)とラベル付し、特に難しい問題に直面するとこのような言葉遣いが増えると述べています。実際にモデルの内部計算を見てもPerplexityが高い時に出現しやすいらしく、モデルが次のTokenを予測するのを迷っている状態と言えそうです。 この挙動は推論崩壊(※2)を避けるため、タスクの難易度に基づいて動的に思考を調整するのに役立っていると主張されています。 Understanding Aha Moments: from External Observations to Internal Mechanisms: Figure 6b aha momentが観測されるモデルをAha, されないモデルをNo-Ahaとして、推論崩壊の発生率を比較 興味深いのは、このような振る舞いを明示的に教え込んでるのではなく、LLMが学習の過程で自ら獲得したように見える点です。 Logic-RL: Unleashing LLM Reasoning with Rule-Based Reinforcement Learning という研究ではRLHF(人間のフィードバックの強化学習)からルールベースの報酬からの強化学習への移行を提案しています。(※3) こういったことから、「aha moment」は単なる模倣を超えた創発的な能力なのではないか、と期待されているようです。 aha momentに関する議論 この現象からこれ自体の解釈や、それによってLRMをどう捉えるべきか、といった議論が様々されているので簡単に紹介していきます。 実はLLMの基本的な挙動の範囲は超えていないのでは? Hacker Newsのスレッド とそれに関連する Xのポスト では、ある文脈に続く確率の高い単語を予測するというLLMの基本的な挙動の範囲を超えていないのでは?という議論がされています。観測される振る舞いも、学習データに含まれる人間の思考プロセスを含む膨大なテキストのパターンを学習した結果、特定の条件下でそのパターンを生成することが最適と判断されただけではないか、ということです。(※4) Xのポストより aha momentが観測されるきっかけと言われている強化学習を行なっていないモデルでもaha momentのような自己反省的な言動(self-reflection keywords)が観測されたらしい また、「Wait」や「Aha」といった言葉は、モデルが本当に内省しているのではなく、人間がそのような状況で使いそうな言葉を統計的に生成しているだけかもしれないので、それに人間的な意味を見出してしまうことに注意が必要とも論じられています。 LRMのための新たな評価基準が必要なのでは? 「aha moment」のような現象は、一見すると深い理解や推論能力の現れのように見えますが、 Failure Modes of LLMs for Causal Reasoning on Narratives という研究では、推論モデルは文章の表面的な順番に騙されてしまったり、因果推論などで表面的なパターンに頼る傾向があると述べられています。このように、「aha moment」を示したとしても、それが真の理解に基づいているのか、あるいは巧妙なパターンマッチングの結果なのかを見極めることは現状できません。深い理解をしているかのように振る舞うLRMは、そのせいで過大評価されている可能性があります。 そういったところから、LRMの評価では単純な正答率だけでなく、モデルがどのように結論に至ったのか、そのプロセスや頑健性を見る必要性があるのでは、と言われています。例えば MR-Ben: A Meta-Reasoning Benchmark for Evaluating System-2 Thinking in LLMs という研究では推論過程を評価するようなベンチマークの提案がされています。こちらは一例ですが、「aha moment」のような現象は、それが単なる表面的な反応なのか、それとも深い思考プロセスを経た結果なのかを区別できるような、より詳細な評価軸が求められているようです。 実際の推論能力向上との直接的な因果はある? A Survey of Efficient Reasoning for Large Reasoning Models: Language, Multimodality, and Beyond という研究では、「Aha」や「Wait」のような単語を使って応答を引き伸ばすことが、必ずしも推論の質を高めるとは限らないのでは?と論じられています。(※5)DeepSeek-R1のtechnical reportで報告された内容はたまたま正解に導くような洞察でしたが、他の事例を見ていくと、むしろ非効率だったり、モデルの不確かさを示していたりする場合もあると言われています。単純にトークンが増えてコストが増えるという問題もあります。 aha momentを示したとしても、 Stop Overthinking: A Survey on Efficient Reasoning for Large Language Models で述べられているような単純な問題に対して過剰に推論を行なってしまうOverthinking問題(※6)は依然として残る上に、 Reasoning Models Can Be Effective Without Thinking ではLRMに推論させたふりをさせるNoThinking手法が効果があったと言われています。 NoThinking手法の例: <think>タグ内に"Okay I have finished thinking"を入れることで考えたことにしてしまい、LRMに出力を促す 現状、aha momentと実際の推論能力向上との間の直接的な因果関係は、まだはっきりしていないようです。 感想 ここまで調査結果を共有してきましたが、ここからは私の個人的な感想を書かせていただきます。私自身LLMの研究を専門としているわけではないので、LLMをツールとして使っている1人のエンジニアの意見として読んでいただければと思います。 今回の調査を始める際は「aha moment」が非常に興味深く、他にどんな事例があるのだろう、という期待が主な気持ちでしたが、現在はやや懐疑的な意見に傾いています。観測された振る舞いの多くは、LRMが事前学習で覚えたパターンを強化学習によって最適化した、つまり高度な模倣なのではないかという考え方に近づいています。そして私自身陥ってしまったことなのですが「aha」のように擬人化された言葉遣いは、人間側が過剰に意味付けをしてしまうので注意が必要そうです。 とはいえ、aha momentのような挙動が、たとえそれが模倣や最適化の結果だとしても、自律的に現れたという事実は非常に興味深いです。特定の条件下であれば、LRMは私たちが想像する以上に人間らしい振る舞いを獲得できるのかもしれません。 また、今回の調査では扱いませんでしたが、もし「aha moment」が実際に起こっている創発的能力であるのであれば、LRMが人間が予測してない能力を獲得したり、それによって自律的に戦略を変更したりするリスクに備える必要があるかもしれません。そのような研究もあるようなので、機会があればまた調べてみたいです。 おわりに 今回は、LLMの「aha moment」に関する調査結果と私の感想を共有させていただきました。この分野は非常に動きが速く、次々と新しい研究が登場しています。今後も関連動向を注視し、興味深い知見があればまた共有したいと思います。 最後までお読みいただき、ありがとうございました! 余談 文脈的に入れづらかったので記事本文に載せきれなかった内容です。興味深かったのでこちらで共有させてください。 ※1: 逆に"Instead"や"Therefore"のような構造化された思考ステップを示すようなTokenを Reasoning Tokenと名付け、Llama-3.1-8B-Instructのような非強化学習のモデルでよく観測されたようです。 ※2: 論文ではLanguage Mixing, Language Repetition, Reasoning Path Repetitionの3パターンに分類されていましたが、 Alice in Wonderland: Simple Tasks Showing Complete Reasoning Breakdown in State-Of-the-Art Large Language Models では、間違っているのに尤もらしい理由をこじつけてしまうような現象が挙げられており、Reasoning Modelの推論が変な挙動をしてしまう問題は多岐にわたるようです。またReasoning CollapseやReasoning Breakなど表現もまだ統一されていないようです。 ※3: Reinforcement Learning for Reasoning in Large Language Models with One Training Example という研究では、たった1つのサンプル問題で数学的推論能力を向上させることができたそうです! ※4: Does Reinforcement Learning Really Incentivize Reasoning Capacity in LLMs Beyond the Base Model? こちらの研究では、強化学習は基盤モデルで学習済みのパスを最適化するための、ある種の蒸留のような役割なのでは?という検証をされています。 ※5: 相反する内容になってしまうのですが、 s1: Simple test-time scaling という研究ではLRMが推論を終了するトークン(</think>など)を出そうとした場合、強制的に "Wait" のようなトークンで上書きし、自己反省を促すようにすると、与えられたタスクの成功率が向上したとの報告がありました。こういったことも含め、まだLRMにおける推論の役割はわからないことが多いですね。 ※6: 逆に短絡的に結果を出してしまうShallowthinking問題もあるようです。 投稿 LLMの推論における “aha moment” について調べてみた は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは、 AIチームの戸田です。 本記事では Inception Labs のMercury APIのベータ版が使えるようになったので、簡単に試してみました。 ドキュメントは こちら で確認できます。 拡散言語モデル 現在のほとんどの大規模言語モデル(LLM)は「自己回帰モデル」と呼ばれ、一方向に一単語ずつテキストを生成します。 前のトークンがすべて生成されないと次のトークンを生成できず、各トークン生成ごとに巨大なニューラルネットワークの計算が必要なため、最先端モデルでも毎秒50〜200トークン程度の生成速度に制限されています。 拡散言語モデル(Diffusion Language Models、dLLMs)は画像生成で成功を収めた拡散モデルのアプローチをテキスト生成に応用した技術で、自己回帰モデルとは異なり、いきなりテキスト全体を生成します。最初はただのノイズなのですが、徐々に修正させていくことでテキストを生成します。速度だけでなく全体の文章構造の整合性に優れるというメリットもあると言われています。 Inception LabsとMercury Inception Labsはスタンフォード大学、UCLA、コーネル大学の教授陣によって設立されたスタートアップで、いち早くこの拡散言語モデルに目をつけMercury Coderというモデルを実用化しました。 名前の通りコード生成に特化したモデルで、 Inception Labsのブログ では従来の自己回帰モデルより5〜10倍高速と評価されています。加えて、高速でありながらGPT-4o Miniなどの競合モデルを品質面でも凌駕しています。 現在APIが公開されているのはMercury Coder Smallで、Miniの方は近日公開予定のようです。 一点気をつけておきたいのは比較モデルがClaude 3.5 HaikuやGPT-4o miniなど速度&コスパ重視のモデルであって、難しい問題を解けるようなLLMの性能という点では現在の先端モデル(Claude Sonnet 3.7など)には及ばないということです。拡散言語モデルはまだ研究が始まったばかりの分野なので、ここは今後に期待です。 より詳細な技術情報はInception Labsの レポート をご参照ください。 検証 現在 SDKはまだ用意されていないようなので、以下のように直接API Endpointを叩きます。 curl https://api.inceptionlabs.ai/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer [YOUR_API_KEY]" \ -d '{ "model": "mercury-coder-small", "messages": [ {"role": "user", "content": "拡散モデルとは?"} ], "max_tokens": 100 }' 検証のために簡単なデモアプリを作りました。コードは こちら に公開しています。こちらのデモアプリは生成過程を示すために、1サイクルごとに1秒間の遅延を入れていますので、実際のレスポンスはもっと高速です。 まずはコーディングに関係ない簡単な質問をしてみました。ノイズのような意味のない文章から、徐々に意味のある文章が生成される過程が見れると思います。また日本語でもきちんと質問に答えてくれますね。 コーディング Mercury Coder Smallはコーディングエージェントなのでコーディングタスクを任せてみたいと思います。定番の(?)ブロック崩しゲームを作らせてみます。Promptは日本語で「pyxelでブロック崩しを作って」と指示しました。結果以下のようなゲームが生成されました。 ちゃんとブロック崩しゲームですね。生成されたコードは こちら に公開しておきます。 suffixの指定 拡散言語モデルの面白い機能として、生成時にsuffixを指定することができるというものがあります。これは全体を一気に生成する拡散言語モデルならではの機能と思われ、特にコーディングタスクに有効活用できそうだと言われています。 curl https://api.inceptionlabs.ai/v1/fim/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer [YOUR_API_KEY]" \ -d '{ "model": "mercury-coder-small", "prompt": "def fibonacci(", "suffix": "return a + b", "stream": true }' | jq promptに def levenshtein( 、suffixに return dist を与えて編集距離(レーヴェンシュタイン距離)を測る関数を書かせてみます。先ほどのデモコードを若干変更してsuffixを指定できるようにします(変更したコードは こちら ) 確認したところ、動的計画法を使った計算効率の良い正しいコードが出力されました。 他にも二分探索のような基本的なアルゴリズムやPytorch用のLoss関数などを何個か試しましたが、どれも現在のLLMと遜色なく生成できました。suffixについてはまだどのような応用ができるかは予想できませんが、コーディングにおいては現在書いているコードの続きを生成する現在の予測変換の形が大きく変わるかもしれません。 感想 まず純粋に面白いアイディアだな、と感じました。画像分野で成功した仕組みが言語処理に応用されるのは一昔前にCNNが言語処理に応用されたのを思い起こさせました( 参考 )。そして面白いだけではなく、生成されるテキストのクオリティも現在主流の自己回帰型モデルと遜色がないことが確認でき、コンセプトだけでなく、確かな実力も兼ね備えているという印象です。 一方、生成速度に関してはタスクとの相性があるのではないか?とも思いました。拡散言語モデルの仕組み上、ある程度まとまった単位で生成を行う特性があるため、例えばRealtime APIのようなストリーミング処理で、入力に対して逐一出力が求められるようなユースケースは相性が悪そうです。ただし、開発元のInception Labsはコーディング支援LLMの開発を目標にしているようなので、こういったユースケースはスコープ外なのかもしれません。 公式のDiscordコミュニティでは、ユーザーが主体となってCoding AgentのClineから呼び出すための拡張機能の開発を進めている様子が見られました。まだベータ版ということもあり、これから多くの改善や最適化が進んでいくことが期待できそうです。 おわりに 本記事ではInception LabsのMercury APIのベータ版を簡単に試してみました。 拡散言語モデルは従来の自己回帰型LLMに比べて非常に高速に動作し、出力も遜色のないことから、言語モデルの次世代標準となるかもしれません。拡散言語モデルはInception Labs以外でも研究されており、例えば香港大学の研究室から Dream 7B というモデルがオープンウェイトで公開されています。今後は画像や音声などのマルチモーダル統合なども進んだら面白そうだと思います。引き続きこの分野もウォッチしていきたいです。 最後までお読みいただきありがとうございました! 投稿 Inception Labsの拡散言語モデルを試してみた は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは、 AIチームの戸田です 今回は先日LangChainから発表された LangGraph CodeAct を E2B の仮想環境で動かしてみようと思います。CodeActは最近注目を集めているAI AgentのTool連携における新しいパラダイムで、Function Callingのような従来のツール使用方法とは一風変わった手法です。本記事ではCodeActを安全に実行するための方法と、その可能性について紹介します。 CodeActとは CodeActは、AI AgentがToolを利用する際の新しいアプローチです。従来のAI Agentでは、事前に定義された関数を呼び出して処理を行うのが一般的でした。しかし、CodeActでは、AI Agent自身がタスク達成に必要なPythonコードをその場で生成し、実行します。 これには以下のような利点があります: 柔軟性の向上 : 事前定義関数に縛られず、状況に応じた処理が可能 ステップ数の削減 : 複雑な処理を一度のコード生成・実行で完結できる メンテナンスコストの低減 : ツールセットを網羅的に定義する必要がない LangChainチームは、近年話題になっている Manus というAI Agentに触発されてこの機能を追加したと 述べています 。 https://manus.im/app また、LLMの運用ツールとして有名な LangSmith との連携も可能で、どのようなコードが生成され、実行されたのかを詳細に追跡できるのが強みです。 ちなみにこのCodeActという概念は、 以前「CodeAgent」としてご紹介したsmolagentsの提唱する概念 と同じ流れを汲んでいます。まだ新しい分野のため呼び名が定まっていないようです。 以前紹介したCodeAgentのsmolgagents 本記事ではLangChainの定義に合わせてCodeActと呼称させていただきます。 セキュリティリスクと安全な実行環境 CodeActの大きな特徴である「自動コード生成と実行」は、同時に大きな懸念をもたらします。具体的には以下が挙げられます: 予期せぬ動作 : 生成されたコードが意図しない挙動をする可能性 システムアクセス : ローカルファイルやネットワークに無制限にアクセスするリスク リソース消費 : 無限ループや高負荷処理によるシステム不安定化 悪意あるコード : 悪意のある操作を実行してしまうリスク これらのリスクを軽減するために、今回は E2B というサンドボックス環境でCodeActを実行してみます。E2Bは隔離された仮想環境でコードを実行でき、ホストシステムを保護しながら安全にAIコードを試せるサービスです。E2Bについては以前書いた記事( 1 、 2 )をご参照いただければと思います。 環境セットアップと実装 必要なライブラリのインストール まずは必要なライブラリをインストールします LangGraph CodeActは、LangGraph上に構築されており、以下のコードでインストールできます。 # LangGraph CodeActのインストール pip install langchain langgraph langgraph-codeact # E2B仮想環境のインストール pip install e2b-code-interpreter 環境変数の設定 各サービスを利用するための環境変数を設定します。各ドキュメント( LangSmith 、 E2B 、 OpenAI )を参考に、以下のものを設定してください # LangSmithの設定 LANGSMITH_API_KEY= LANGSMITH_TRACING= LANGSMITH_ENDPOINT= LANGSMITH_PROJECT= # E2Bの設定 E2B_API_KEY= # OpenAI APIの設定 OPENAI_API_KEY= LangGraph CodeActの処理フロー LangGraph CodeActの基本的な処理の流れをシンプルに示すと以下のようになります。 ユーザーの要望(query)が入力される call_modelノードが要望を解決するためのPythonコードを生成 sandboxノードが生成されたコードを実行 実行結果をもとにqueryが達成できているか評価 達成できていなければ、フィードバックをもとにcall_modelノードが再度コードを生成 queryが達成されるまで繰り返し 実装コード 以下のコードでLangGraphでCodeActを使ったAI Agent実装します from langchain.chat_models import init_chat_model from langgraph_codeact import create_codeact from langgraph.checkpoint.memory import MemorySaver model = init_chat_model("gpt-4o", model_provider="openai") tools = ... # ユーザーが定義した関数をリストで渡す eval_fn = ... # コード実行関数 code_act = create_codeact(model, tools, eval_fn) agent = code_act.compile(checkpointer=MemorySaver()) toolsは特に渡したい関数が無ければ空のリストを渡せば良いです。またcall_modelノードとsandboxノードのやり取りで、過去の履歴を参照させるためにMemorySaverを設定しています。 ここで重要になってくるのはコードを実行するためのeval_fnです。こちらはLLMが生成したコード文字列を受け取って実行して結果を返す関数になります。 READMEにあるサンプルコード を参考にeval_fnを定義しますが、そのままだと手元の環境でコードを実行してしまうので、E2Bのサンドボックス環境で実行するように変更します。 import io import contextlib from e2b_code_interpreter import Sandbox def my_eval_fn(code: str, _locals: dict[str, Any]) -> tuple[str, dict[str, Any]]: """ code: 実行するPythonコード _locals: コード実行時のローカル変数 """ try: with contextlib.redirect_stdout(io.StringIO()) as f: sbx = Sandbox() # 仮想環境を作る execution = sbx.run_code(code, envs=_locals) # 仮想環境でPythonコードを実行 # 実行結果を表示 print("Execution Results:") print(execution.logs.stdout[0]) result = f.getvalue() if not result: result = "<code ran, no output printed to stdout>" except Exception as e: result = f"Error during execution: {repr(e)}" return result, _locals 今回の検証では使用しませんが、_localsには環境変数、例えば外部APIのAPI Keyなどを辞書形式で設定することができるようです。 実行テスト こちらのコードをeval_fnに設定することでCodeActを行うAgentの設定が完了しました。以前smolagentを試した時と同様、フィボナッチ数の118番目を聞いてみようと思います。 messages = [{ "role": "user", "content": "Could you give me the 118th number in the Fibonacci sequence?", }] out = agent.invoke( {"messages": messages}, config={"configurable": {"thread_id": 1}}, ) print(out["messages"][-1].content) # The 118th Fibonacci number is 1264937032042997393488322. Let me know if you need help with anything else! 結果として正しい値が得られました。 では、この過程でどのようなコードが生成され実行されたのか、LangSmithで確認してみましょう。 前回smolagentが生成したものと全く同じコードが生成されているのがわかります。 前回の記事では、smolagentを使ってWebスクレイピングのタスクも実行しました。今回もWebスクレイピングを依頼するqueryを試してみましょう。messagesのcontentを以下のように変更して実行すると結果が得られます。 - "content": "Could you give me the 118th number in the Fibonacci sequence?", + "content": "Could you get me the title of the page at url 'https://www.ai-shift.co.jp/techblog/5333'?", . . . # 結果: # The title of the page is: "【AI Shift Advent Calendar 2024】2024年のTech Blog/対外発表の振り返り | 株式会社AI Shift" こちらも前回の記事と同様、BeautifulSoupを使ったスクレイピングを行うコードが生成されました。 smolagentsと異なる点として、smolagentsはrequestsとbeautifulsoupなどのimportして使えるライブラリのインストールを明に示す必要があったのに対し、CodeActではそれが必要ないことが挙げられます。実行環境にインストールされているものであれば、そのまま使用することができるので便利とも言えますし、想定外の挙動をする可能性もあるので危険とも言えます。ここは実行環境のE2Bの方で使用できるライブラリを制限するなどの対応が必要かもしれません。 まとめ 今回はLangGraph CodeActをE2Bの仮想環境で動かし、AIによるコード生成・実行の新しい可能性を探りました。 CodeActアプローチの最大の魅力は、AI Agentがタスク解決のために必要なコードを自律的に作成し実行できる点です。これにより、従来のように事前に全ての機能を定義しておく必要がなくなり、より柔軟で創造的な問題解決が可能になります。 一方で、自動コード実行にはセキュリティ上の懸念が伴うため、E2Bのようなサンドボックス環境の活用が重要です。今回示したように、E2Bを用いることで、生成AIの能力を安全に引き出すことができます。 現状ではE2B環境の実行を手動設定する必要があるなど、機能面で改善の余地はありますが、LangChainエコシステムと統合されている点やLangSmithによる実行ログの詳細な追跡は非常に強力です。今後のアップデートでより統合された体験が提供されることを期待しています。 最後までお読みいただき、ありがとうございました! 投稿 LangGraph CodeActをE2Bの安全な仮想環境で動かす は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは、 AIチームの戸田です 今回はPythonでリアルタイムなAIアプリケーションを作る際に役立つライブラリ、 FastRTC を使って簡単なVoicebotを構築してみたいと思います。 FastRTC https://fastrtc.org/ FastRTCは、Pythonでリアルタイムの音声およびビデオストリーミングアプリケーションを構築するためのライブラリです。 VoicebotのようなリアルタイムなAIアプリケーションを作るとなると、 WebRTC や websockets などの技術が必要になります。しかしこれらのノウハウは様々な情報源に散らばっており、習得が困難です。近年は生成AIによるコード生成も使えますが、WebRTCやwebsocketsを利用したPythonコードとなると、現在はまだ正しいコードの生成に苦労する印象です。 FastRTCは、特にこういった知識が専門ではない機械学習エンジニアがリアルタイムAIアプリケーションをPythonで開発する際の障壁を取り除くことを目指しています。 作成したアプリケーションはGradioのUIやTwilioのIP電話として簡単に公開することができます。IP電話で繋ぐ際はサンプリング周波数の変更などの音声形式の処理が必要なのですが、こういった煩雑な処理も内部で担ってくれているので便利です。今回はIP電話でLLMと会話するアプリケーションを作ってみます。 実装 ライブラリはpipでインストールできます pip install fastrtc Cookbookに乗っている サンプルコード を参考に、レストラン予約を行うVoicebotを構築してみたいと思います。ロールプレイングでDB接続などは行いません。 import base64 import asyncio import numpy as np import openai from fastrtc import ( AdditionalOutputs, AsyncStreamHandler, Stream, get_twilio_turn_credentials, wait_for_item, ) from gradio.utils import get_space from openai.types.beta.realtime import ResponseAudioTranscriptDoneEvent, ResponseOutputItemDoneEvent SAMPLE_RATE = 24000 SYS_PROMPT = """あなたは、レストラン「AI-SHIFT」の予約受付担当です。 以下の制約条件と入力文、会話履歴を考慮して、お客様からの予約の問い合わせに対応してください。 発話は短く簡潔に、ヒアリングは一項目ずつ行い、一度に複数の質問をしないでください。 # 制約条件 * レストラン名: AI-SHIFT * 営業時間: * 平日: 11:00-22:00 * 土日祝: 10:00-23:00 * 定休日: 年末年始 * 予約可能な人数: 1名様から4名様まで * お客様の名前、予約日時、人数、その他要望をお伺いします。 * お客様が予約を完了するまで、会話を続けてください。 * 会話終了後、予約内容を復唱して確認して下さい。 """ class OpenAIHandler(AsyncStreamHandler): def __init__( self, ) -> None: super().__init__( expected_layout="mono", output_sample_rate=SAMPLE_RATE, output_frame_size=480, input_sample_rate=SAMPLE_RATE, ) self.connection = None self.output_queue = asyncio.Queue() self.function_call = False def copy(self): return OpenAIHandler() async def start_up( self, ): self.client = openai.AsyncOpenAI() async with self.client.beta.realtime.connect( model="gpt-4o-mini-realtime-preview-2024-12-17" ) as conn: await conn.session.update( session={ "turn_detection": {"type": "server_vad"}, "instructions": SYS_PROMPT, } ) self.connection = conn async for event in self.connection: if event.type == "response.audio_transcript.done": print(event) await self.output_queue.put(AdditionalOutputs(event)) if event.type == "response.audio.delta": await self.output_queue.put( ( self.output_sample_rate, np.frombuffer( base64.b64decode(event.delta), dtype=np.int16 ).reshape(1, -1), ), ) async def receive(self, frame: tuple[int, np.ndarray]) -> None: if not self.connection: return _, array = frame array = array.squeeze() audio_message = base64.b64encode(array.tobytes()).decode("utf-8") await self.connection.input_audio_buffer.append(audio=audio_message) async def emit(self) -> tuple[int, np.ndarray] | AdditionalOutputs | None: return await wait_for_item(self.output_queue) async def shutdown(self) -> None: if self.connection: await self.connection.close() self.connection = None stream = Stream( OpenAIHandler(), mode="send-receive", modality="audio", concurrency_limit=1, # 同時接続数 time_limit=90, # 1回の通話の制限時間 ) stream.fastphone() 全て合わせても100行ちょっとです。これだけの少量のコードでVoicebotが構築できてしまいます。 起動すると以下のような出力がされます。 提示された電話番号に電話をかけるとコードを要求されるので、プッシュボタン(IVR)で電話番号の右側に提示されている6桁のコードを入力します。成功すると INFO: ('172.31.47.62', 0) - "WebSocket /telephone/handler" [accepted] INFO: connection open のような表示がされ、設定したレストラン予約対話が可能です。 電話番号は毎月3分のみ利用できるFastRTC側で用意されたものになっていますが、自身のTwilioアカウントと連携して任意の番号を設定することも可能です。本記事では扱わないので こちらのドキュメント をご参照ください。 今回はOpenAIのRealtime APIを利用した双方向通信のリアルタイム音声会話の例を示しましたが、FastRTCではVADについても標準機能を搭載しているので、VADで発話区間を抽出→STTモデルで文字起こし→ LLMで返答生成→返答をTTSモデルで音声として出力、といったパイプライン型の対話システムも構築できます。Realtime APIは現状では制御が難しく、hallucinationの懸念もあるため、より複雑な業務用途ではパイプライン型の方が適している場合も多いです。 展開方法 今回は電話応答のVoicebotの実装でしたが、FastRTCはGradioベースのUIやFastAPIのエンドポイントも簡単に実装することができるので、ユースケースに合わせて展開することができます。 stream.fastphone() の部分を以下のように変更してください。 Gradio stream.ui.launch() FastAPI from fastapi import FastAPI from fastapi.responses import HTMLResponse, StreamingResponse app = FastAPI() stream.mount(app) # sample streaming endpoint @app.get("/outputs") def _(webrtc_id: str): async def output_stream(): async for output in stream.output_stream(webrtc_id): chatbot = output.args[0] yield f"event: output\ndata: {json.dumps(chatbot[-1])}\n\n" return StreamingResponse(output_stream(), media_type="text/event-stream") 他の応用方法 ドキュメントには他にも 様々なサンプル実装 があるので、それらを参考に色々試してみるのも良さそうです。私が興味を惹かれたのはYoloによる Object Detectionの例 です。 GeminiのLive APIなど映像を扱えるマルチモーダルな LLMが公開されているものの、Streamingで動画像を扱う経験が無く、ハードルが高かったのですが、FastRTCを通せばかなり楽に実装できそうに見えました。 おわりに FastRTCを使うことで、WebRTCやwebsocketsなどの複雑な技術スタックを考慮することなく、Pythonだけで簡単にリアルタイムな音声対話システムを構築できることがわかりました。 5年前に AI Messenger Voicebot を開発し始めた頃は、ただ電話を繋ぐだけでもかなり苦労したことを覚えています。当時は音声の送受信、VAD、STT、TTSのそれぞれを個別に実装し、それらを連携させるためのコードを書く必要がありました。 加えてLLMも普及していなかった(GPT-3が発表されたくらい)ので対話管理のための処理も考える必要がありました。 今やわずか100数行のコードでリアルタイム音声対話システムが実現できる時代になりました。AI開発の民主化が急速に進んでいることを実感します。 最後までお読みいただき、ありがとうございました! 投稿 FastRTCを使って爆速でVoicebotを構築する は 株式会社AI Shift に最初に表示されました。
アバター
はじめに こんにちは、AIチームの大竹です。 2025年4月6日(日)〜4月11日(金)にインド・ハイデラバード、 Hyderabad International Convention Centre にて開催された、音響・音声・信号処理分野における世界最大の国際会議である 第50回 IEEE International Conference on Acoustics, Speech, and Signal Processing (ICASSP 2025) に同じくAIチームの長澤とともに参加し、弊社から1件の研究発表を行いました。 今年は記念すべき50周年の開催となり、投稿論文数は6947件、採択論文数は3145件(採択率45.3%)と、この分野への注目度の高さが伺えました。また、今回の会議では、ビザ取得の都合などさまざまな理由により、ポスター発表でのno showや口頭発表での事前録画ビデオの上映が多く見られました。 学会の様子 開催地インド・ハイデラバードへの渡航は、シンガポール経由で約15時間かかりました。Rajiv Gandhi国際空港から学会会場まではタクシーで約1時間でしたが、道路状況や常にクラクションが鳴り響く交通事情は印象的でした。 学会会場は広く、ポスターセッションや企業ブースも活気がありましたが、前述のビザ問題の影響で発表者が不在のセッションも見受けられました。 学会イベントとしては、Welcome Receptionが開催され、インドの伝統武術「カラリ・パヤット」や、映画『RRR』の「Naatu Naatu」を含むインドダンスのパフォーマンスが披露され、現地の文化に触れる貴重な体験となりました。 発表内容とディスカッション Task Vector Arithmetic for Low-Resource ASR 発表情報: セッション:Poster 3C 発表日:4/10 11:30 〜 13:00 (インド, ハイデラバード現地時間) 著者:⚪︎Haruki Nagasawa (AI Shift), ⚪︎Shinta Otake (AI Shift), Shinji Iwata (CyberAgent) 論文リンク 発表資料 研究概要 本研究では、task vectorを活用し、データ拡張を用いずに低資源言語における自動音声認識(ASR)の性能を向上させる手法を提案しました。具体的には、事前学習済みのASRモデルと、ターゲット言語の性能向上を手助けするサポート言語でファインチューニングしたモデルからtask vectorを計算し、それをターゲット言語でファインチューニングしたモデルに加算します。実験の結果、同じ語族の言語から生成したtask vectorを用いることで、ターゲット言語のWER(Word Error Rate)が最も改善されることが示されました。 なお、本研究ではターゲット言語は事前学習済みASRモデルにとって学習データ量が5時間未満の低資源言語としています。 ディスカッション(主なQ&A) Q: task vectorとは具体的に何を指すか? また、task vectorを作成するためにも追加の学習が必要か? A: task vectorは、事前学習済みモデルの重みと、特定のタスク(ここではサポート言語のASRタスク)でファインチューニングした後のモデル重みの差分ベクトルです。このtask vectorを任意のパラメータに加算することによって、特定のタスクの能力を付加することができます。なお、task vectorの作成にはサポート言語でのファインチューニングが必要です。 Q: task vectorをどの程度反映させるかを調整するscaling factor λは、どのように決定したか? A: 本研究では、 FLEURSデータセット の検証セットを用いてグリッドサーチを行い、最適なscaling factorを決定しました。 Q: 最適なサポート言語の組み合わせは、どのように見つけたか? A: ターゲット言語と同じ語族内で1〜3言語、または全言語中でデータ量が多い上位1〜3言語をサポート言語の候補として選択しました。これらの候補についてtask vectorを適用し、検証セットで最も性能が良かった組み合わせを実験結果として報告しています。 Q: 提案手法によってどのような理屈でoverfittingやcatastrophic forgettingを防げるのか? A: ターゲット言語のデータのみでファインチューニングすると、限られたデータにモデルが過剰適合しやすくなります。しかし、事前学習済みモデルのパラメータを基準点として、関連するサポート言語への適応方向を示すtask vectorを加えることで、パラメータがターゲット言語の狭い最適解に陥るのを防ぎ、より汎化された、あるいは事前学習時の知識を保持しやすいパラメータ空間へと誘導する正則化のような効果があると考えています。 Q: 言葉ではない音声、例えば笑い声、泣き声、フィラー(「あー」「えーと」など)といった非言語的な音声にも適用可能か? A: 本研究では、主に低資源言語における単語の認識精度向上に焦点を当てておりました。そのため、笑い声のような非言語的な音声の認識に対して、提案手法を直接テストしてはおりません。しかし、概念的には、特定の非言語音(例:笑い声)を認識するタスクを定義し、それに対応するtask vectorを計算し、活用できる可能性はあると考えています。 Q: 英語やドイツ語のような高資源言語(rich language)同士の組み合わせで適用した場合、どのような結果が期待できるか? A: 本研究では主に低資源言語の性能改善を目的としていたため検証はしていませんが、高資源言語は元々の性能が高いため、別の高資源言語のtask vectorを加えても改善の余地は小さく、性能向上は限定的だと考えています。 終わりに ICASSP 2025では、近年のLLMの目覚ましい発展を受け、その技術を音声処理に応用しようとする研究や、応用に伴う課題解決を目指す研究が数多く発表されていました。一方で、音声処理には幅広いタスクがあるため、タスクに特化したアーキテクチャの探求も依然として活発に行われており、LLMが必ずしもデファクトスタンダードとなっていない現状は、音声処理分野が今まさに発展途上であり、取り組みがいのある分野であることを改めて実感しました。 ポスター発表においては、世界各国から集まった第一線の研究者の方々と直接、有意義な議論を交わすことができ、大変貴重な経験となりました。 今回発表した研究は、 サイバーエージェント社内のゼミ制度 における活動から生まれた成果です。この経験を糧とし、今後はAI Shiftのメンバーとして、事業に直結する課題解決に貢献できる研究開発に取り組み、国際学会への投稿を目指して精進して参ります! 最後にこのような素晴らしい学会を開催・運営してくださった関係者の皆様に心より感謝申し上げます。 投稿 ICASSP2025 発表報告 @Hyderabad, India は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは、AI チームの長澤( @sp_1999N )です。 今回は Arize AI 社が開発・提供する LLM アプリケーション向けの監視ツール Phoenix の紹介および簡単なデモ構築を行いたいと思います。 Phoenix icon デモとして Chainlit で構築した簡易的なチャットサービスを、自前ホストした Phoenix サーバーで監視してみたいと思います。 Phoenix について Phoenix は LLM アプリケーション向けの監視ツールとして、オープンソースで開発・提供されています。Python および TypeScript 向けに SDK の提供もされています。 LLM アプリケーションは API の呼び出しに留まらず、プロンプトや対話履歴の管理、function calling などで用いるツールの提供などその複雑さを増しています。この状況に対して、Phoenix は総合的な管理基盤として必要となる要素を提供してくれています。 LangSmith も似た機能を提供していますが、大きな違いとしてオープンソースか否かという点があります。Phoenix は "プライバシーとカスタマイズ性を念頭に置いて設計された オープンソースのプラットフォーム " となっており、セルフホスティングすることが可能です。LangSmith でもセルフホスティングは可能ですが、 エンタープライズプランへの加入 が必要になります。 また Phoenix は OpenTelemetry を基盤 としている点も大きな魅力となっています。 この記事では、Docker で Phoenix サーバーをホスティングして使ってみることに重点に置き、トレースからエラー監視までを行ってみようと思います。 Chainlit で構築したチャットサービスを監視する Chainlit は LLM のチャットサービスを簡単に作成できる Python パッケージです。気になる方は 公式ドキュメント をご参照ください。 今回は Phoenix によるトレーシングの様子を体感するため、簡易的なユースケースを用意します。 シナリオとして「LLM Agent が DB へのアクセス管理を行い、認証されたユーザーに対してのみ DB での操作を許可する」という設定を考え、このやりとりをチャット形式で行います。 Spans, Traces, Projects 具体的な実装例の前に、トレース管理において 重要なコンセプト を簡単に整理します。 Spans Span は LLM アプリケーション内での特定の操作や処理を表す基本的な単位です。​ 各 Span は、開始時間、終了時間、メタデータなどの情報を持ち、アプリケーション内でのリクエストの流れを詳細に追跡できるようになります。 Traces Traceは、アプリケーションまたはエンドユーザーによって行われたリクエストが、複数ステップで処理される様子を記録します。 ​Trace は1つ以上の Span で構成され、最初の Span は Root Span となります。​各 Root Span は、開始から終了までのリクエスト全体を表し、親 Span の下にある Span は、リクエスト中に発生する詳細なコンテキストを提供します。 Projects Project は複数の Trace をまとめたものになります。基本的に、1つのアプリケーションやサービスに紐づくものとして扱います。 Docker compose Phoenix とアプリケーションのコンテナをそれぞれ建てるため、以下のような compose file を作成します。イメージは docker pull arizephoenix/phoenix で pull してきます。 こちらのページ でサンプルが紹介されているので、今回はこれを踏襲します。 services: phoenix: image: arizephoenix/phoenix:latest ports: - "6006:6006" # UI and OTLP HTTP collector - "4317:4317" # OTLP gRPC collector llm-server: build: context: . dockerfile: Docker/Dockerfile ports: - "8000:8000" env_file: - .env depends_on: - phoenix 6006 番 port で Phoenix の管理画面に、 8000 番 port で Chainlit で作成したチャット UI にアクセスすることができます。ちなみに、環境変数としては以下のものを使用しています。 OPENAI_API_KEY="YOUR_OPENAI_API_KEY" COLLECTOR_ENDPOINT="http://phoenix:6006/v1/traces" PROD_CORS_ORIGIN="http://phoenix:3000" 無事に立ち上がると、図1のような Phoenix の管理画面を開くことができます。今回は検証用に phoenix-run-on-docker という名前のプロジェクトを作成し、そこで Chainlit サービスの挙動を監視します。 図1: Projects 管理画面 (添付の画像は Chainlit サービスを動かした後のものになっており、Token がどれくらい消費されたのかなどがプロジェクト単位で確認できることが分かります) Phoenix のセットアップ まず初めに、アプリケーションの監視を行う上で必要になる Tracer を定義します import os from phoenix.otel import register from openinference.instrumentation.openai import OpenAIInstrumentor # Initialize tracer provider and tracer TRACER_PROVIDER = register( project_name="phoenix-run-on-docker", endpoint=os.getenv("COLLECTOR_ENDPOINT"), set_global_tracer_provider=False ) # Setup OpenAI instrumentation OpenAIInstrumentor().instrument(tracer_provider=TRACER_PROVIDER) # Get tracer instance TRACER = TRACER_PROVIDER.get_tracer(__name__) プロジェクト名を phoenix-run-on-docker として TRACER_PROVIDER を定義します。 COLLECTOR_ENDPOINT としては http://phoenix:6006/v1/traces など Phoenix のエンドポイントとなるものを指定します。 また OpenAIInstrumentor を使用することで、コード内で呼び出される OpenAI LLM へのリクエストを自動収集することができます。 そして TRACER_PROVIDER から TRACER を取得していますが、これがコード内で Span などのトレース単位を管理するオブジェクトになります。コードへの組み込みで使用することになります。 トレースの組み込み 上記で用意した TRACER を使って、Chainlit での挙動を監視します。 import uuid import json import chainlit as cl from dotenv import load_dotenv from openinference.instrumentation import using_session from openinference.semconv.trace import SpanAttributes from phoenix_config import TRACER from llm_service import get_response from tools import ( fetch_db_info, auth_user ) load_dotenv() MESSAGE_HISTORY = [] session_id = str(uuid.uuid4()) @cl.on_chat_start async def on_chat_start(): cl.Message(content="Ask me anything!").send() # エラー分析のため、一部挙動パターンに対応していない不完全な関数となっています @cl.on_message async def on_message(message: cl.Message): if not message.content: await cl.Message(content="Feel free to ask me anything!").send() with TRACER.start_as_current_span(name="agent", attributes={SpanAttributes.OPENINFERENCE_SPAN_KIND: "agent"}) as span: with using_session(session_id): span.add_event("チャットストリームを開始") span.set_attribute(SpanAttributes.INPUT_VALUE, message.content) MESSAGE_HISTORY.append({"role": "user", "content": message.content}) response = get_response(MESSAGE_HISTORY) if response.choices[0].message.tool_calls: span.add_event("function calling の実施") tool_call = response.choices[0].message.tool_calls[0] args = json.loads(tool_call.function.arguments) tool_name = tool_call.function.name try: if tool_name == "fetch_db_info": result = fetch_db_info() elif tool_name == "auth_user": result = auth_user(args["username"], args["password"]) else: raise ValueError(f"Unknown tool name: {tool_name}") except Exception as e: raise e MESSAGE_HISTORY.append(response.choices[0].message) MESSAGE_HISTORY.append({ "role": "tool", "tool_call_id": tool_call.id, "content": str(result) }) response = get_response(MESSAGE_HISTORY) MESSAGE_HISTORY.append(response.choices[0].message) response_content = response.choices[0].message.content else: response_content = response.choices[0].message.content MESSAGE_HISTORY.append({"role": "assistant", "content": response_content}) span.set_attribute(SpanAttributes.OUTPUT_VALUE, response_content) await cl.Message(content=response_content).send() 少し長いですが、重要なところだけをピックアップしてご紹介します。 注目頂きたいのは on_message 関数になります(後述のエラー分析のため、一部挙動パターンに対応していないコードになります🙇) on_message の関数が主軸となっており、 with TRACER.start_as_current_span() の with 句が宣言されています。これにより、1ターン分の対話を1つの Span として管理することができます。 また with using_session() が続きますが、これによりOpenTelemetry Context にセッションIDを追加します。今回の場合、Chainlit のアプリケーションを再起動するたびに新しい uuid が付与されるので、1セッションごとの管理が可能になります。 続いて span.add_event() を使って、イベント記録を行っています。これは Phoenix 上のログメッセージのようなものとして管理するものになります(ドキュメントは こちら ) そして span.set_attirbute() を使って key/value ペア としてより詳細な挙動の記録を行います。今回は SpanAttributes で事前定義されている INPUT_VALUE および OUTPUT_VALUE に値を詰めることで、管理画面上で Session として一連の対話を管理できるようにします。 ここで、一度監視の様子を確認するために Chainlit 上でしりとりをしてみようと思います。 図2: Chainlit でのやり取りを Session としてトレースしている様子 Chainlit でサービスを立ち上げ、数ターン分会話をした後に Phoenix 管理画面の Session タブを見てみると、図2のように対話のラリーが記録できていることが分かります。 なぜか「ん」で終わって勝ってしまいましたが、対話順に Trace が記録されていることが分かります。また上部のバーを見ると、この会話全体におけるトークンの総消費量やレイテンシーなどが確認できます。 この会話は3つの Trace から構成されていることがわかるので、次に Trace を見てみます。 図3: Traces 管理画面 Traces タブを開くと図3のような画面が確認できます。それぞれ agent が親 Span となり、そこに llm Span がそれぞれ紐づいていることが分かります。 ここで llm Span をクリックしてみてみます。 図4: llm Span のトレース内容詳細 すると図4のように、system prompt などをその時のリクエスト内容の詳細を確認することができます。Tools などを開くと、LLM に設定されている json 形式の tool description なども確認できます。 また Trace ではアノテーションを付与することも可能です。今回はしりとりのルールを無視した不正な返答があったとして correctness というアノテーションを用意し、カテゴリカルラベルとして bad 、数値スコアとして 0 を設定してみます(図5)。 図5: アノテーションの実施 そのほか、しりとりの勝率などをアノテーションすると、下記図6のように Span にどのようなラベルが付与されているのかであったり、アノテーションラベルの統計情報を確認できるようになります。 図6: アノテーション後の管理画面の様子(correctness, human win rate などのカスタムアノテーションが指標として追加されている) 今回は試していませんが、コード内でアノテーションを付与することも可能です。例えば、ツールの呼び出し結果などを集計しておくことで、どの関数が LLM にとって使いにくそうか、などの分析も可能になります。また Datasets や Evaluation の機能と組み合わせることで、 LLM as a judge による評価などにも拡張 できます。 Tool Call それでは最後に、function calling のトレースを行って今回の検証を終わろうと思います。 import random import time from phoenix_config import TRACER TOOL_DESCRIPTIONS = [ { "type": "function", "function": { "name": "fetch_db_info", "description": "Fetch a list of users from a database. This tool can only be used by users who are authorized. Make sure to use the auth_user tool before using this tool.", "parameters": { "type": "object", "properties": {} }, "required": [] } }, { "type": "function", "function": { "name": "auth_user", "description": "Authenticate a user with a password. Need username and password.", "parameters": { "type": "object", "properties": { "username": {"type": "string"}, "password": {"type": "string"} }, "required": ["username", "password"] }, "required": [] } } ] @TRACER.tool() def fetch_db_info(): # 1-3秒のランダム遅延 delay = random.uniform(1.0, 3.0) time.sleep(delay) # 10%の確率で失敗 if random.random() < 0.1: raise RuntimeError("Failed to fetch users data") # ダミーのユーザーデータを生成して返す dummy_users = [ {"id": 1, "name": "Taro Tanaka", "email": "tanaka@example.com", "age": 28}, {"id": 2, "name": "Hana Sato", "email": "sato@example.com", "age": 34}, {"id": 3, "name": "Suzuki Ichiro", "email": "suzuki@example.com", "age": 42}, {"id": 4, "name": "Yuko Ito", "email": "ito@example.com", "age": 25}, {"id": 5, "name": "Ken Yamada", "email": "yamada@example.com", "age": 31} ] return dummy_users @TRACER.chain() def get_password(username): passwords = { "ais_user": ["aishift2025"], "admin": ["admin_master_password"] } return passwords[username] @TRACER.chain() def get_auth_users(): users = ["ais_user", "admin"] return users @TRACER.tool() def auth_user(username, password): auth_users = get_auth_users() auth_password = get_password(username) if password in auth_password and username in auth_users: return "Authorized" else: return "Not authorized" LLM によって直接呼び出される関数は fetch_db_info と auth_user の2つのみです。 これらの関数には @TRACER.tool() のデコレータを使用します。これにより、「LLM によって実行される関数」として監視が可能になります。 また auth_user 関数に注目すると、内部で get_password と get_auth_users が呼び出されていることが分かります。このように「直接 LLM からは呼び出されないが、内部で実行される関数」には @TRACER.chain() のデコレータを使用することで、内部連鎖の様子をトレースすることが可能になります。 再度 Chainlit を立ち上げ直し、会話をしてみます。すると先ほどのしりとりとは別に、新しくセッションが切り出されます。 図7: Function calling を用いたアプリケーションの挙動 図7において、認証を行っている Trace #2 をみてみると、function calling が行われていることが分かります。下記図8は @TRACER.tool() でデコレートした auth_user 関数の挙動の様子です。関数への入出力までしっかりトレースされていることが分かります。また @TRACER.chain() でデコレートした2つの関数が紐づいている様子も確認でき、こちらについても同様に関数の入出力を確認することができます。 図8: Function calling によって実行されたツールのトレース内容詳細 今回のやり取りでは認証挙動までは成功しましたが、 fetch_db_info 関数の呼び出しに失敗してしまいました。 そこで Trace #3 の AI の返答が undefined となってしまっている点に注目し、この Trace の詳細をみてみます。すると、 400 番エラーが返されていることが確認できます。 図9: エラー時の Trace 詳細 図10: エラー時の Trace Attributes の画面 図9および図10を確認してみると、 tool_call が LLM から要求された後に、関数が実行されずにユーザーメッセージが格納されてしまっていることが分かります。今回は Chainlit で on_response 単位で LLM を動かしていたため、「 on_response の最終出力が tool_call の要求であるパターン」に対応できていなかったようです。 詳細なトレースがされているからこそ、どうしてアプリケーションが動かなくなってしまったのかの分析までとてもやりやすくなっています。 おわりに 今回は Arize AI が提供する Phoenix をセルフホスティングして、LLM サービスの監視を行ってみました。 デモとして構築したのはかなり簡易的なものでしたが、より リッチな例 を公式が紹介してくれています。今回は紹介できなかった Datasets & Experiments, Evaluation などの機能についても例が用意されています。 ご興味のある方は是非一度ご覧いただくと、実際のアプリケーションへの導入イメージがより明確になるかもしれません。 投稿 Arize Phoenix で実現する LLM アプリケーションのトレース は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは、AIチームの戸田です。 KaggleのTitanicデータセットは、機械学習の入門として定番のデータセットです。 Titanic - Machine Learning from Disaster 多くの機械学習手法が試されてきたこのデータセットに対し、今回は少し異なるアプローチを試みたいと思います。ランダムフォレストのような従来の表形式データ向け機械学習手法ではなく、テキストを処理するLLM(Large Language Model)を使って、Titanicの生存者予測を試します。 従来の機械学習手法との比較が容易なこのデータセットを通じて、LLMを使ったアプローチがどの程度の予測精度を発揮するのか、その可能性について共有できたらと思います。 背景 先日、表形式のデータを扱っていた際、カラム名と値のセットをfew-shotとしてLLMに提供すれば、LLMが持つ知識を使って予測できるのではないかと思いつきました。 例えば何かしらのサービスの年ごとの継続/解約を予測したいケースを考えます。以下はユーザーごとの情報が表形式で整理されており、次年度のサービス契約継続可否を予測するシナリオです。 直感的には、ユーザーCはユーザーBの特性に近いため、契約継続の可能性が高いと予測できそうです。しかし実際のデータセットでは、多数の特徴量が存在し、単純な比較だけでは判断が困難なケースが多くなります。 ここで注目すべきは、LLMが学習過程で獲得した幅広い知識です。例えば: 「若年層はPremiumプランを好む傾向がある」 「最終アクセスが直近のユーザーは継続率が高い」 といった一般的な傾向をLLMは学習している可能性があります。このようなLLMの潜在的な知識を活用するために、few-shotでサンプルを与える方法が有効ではないかと考えました。つまり、いくつかの例(上記の表のユーザーAとB)をLLMに提示し、未知のケース(ユーザーC)について予測を求めるアプローチです。 この方法の最大の魅力は、特徴量エンジニアリングや複雑なモデル構築を行わなくても、LLMが持つ暗黙知を活用できる可能性がある点です。 事例を調べてみると、こういったアプローチに関する研究をまとめた以下のリポジトリを見つけました: LLM-on-Tabular-Data-Prediction-Table-Understanding-Data-Generation こちらは表形式データへのLLM応用研究をまとめたもので、few-shotでLLMにサンプルを与えて予測を行う手法も含まれていました。 LLMへのfew-shotでのサンプルの与え方は様々考えられますが、本記事ではこのリポジトリで最初に紹介されていた「 Tablet 」というベンチマークの形式を使って検証したいと思います。 Tablet Tabletは TABLET: Learning From Instructions For Tabular Data という論文で提案された表形式データ予測におけるLLM活用の評価を行うベンチマークです。 UCIから取得された20種類の多様な表形式データセットに、様々な言い回し、粒度、技術性を持つ自然言語の指示文を付与しています。 独自のデータセットを定義することも可能です。 環境構築 公式リポジトリのREADMEに従って環境構築を行います。 git clone https://github.com/dylan-slack/Tablet.git cd Tablet python3 -m pip install -e . Tabletですが、OpenAI APIにリクエストする部分が古かったので get_gpt3_revisions関数 内の生成結果取得部分(43-52行)を以下のように変更しました response = openai.ChatCompletion.create( model="gpt-4o-mini", messages=[ {"role": "user", "content": text}, ], ) 加えて、本記事執筆時点(2025/03/05)の最新版だとmodelパラメータに"chat-gpt" (OpenAI APIを利用)を設定した際の分岐が消えているので、OpenAIの LLMで実験したい際はここも追加する必要があります。 evaluate.pyの371行目 の下に以下の分岐を追加してください。 elif self.model == "chat-gpt": self.tokenizer = None self.model_obj = None なお、READMEのサンプルのようにflanなどのHuggingfaceのモデルを利用する際はこの手順は不要です。 Titanic Datasetでの検証 Kaggleのチュートリアルとして有名なTitanicのデータセットを使って LLMでの表形式データの予測を試してみたいと思います。 データ準備 Kaggleのコンペティションページ からTitanicのデータセットをダウンロードし、任意の場所に解凍します。 欠損値対応など基本的な前処理に加え、目的変数をLLMが扱えるようにテキストに変換します。 import pandas as pd train_df = pd.read_csv("{解凍した場所}/train.csv") test_df = pd.read_csv("{解凍した場所}/test.csv") # 目的変数をテキストに変換 train_df["Survived"] = train_df["Survived"].map({0: "died", 1: "survived"}) train_x = train_df.drop(columns=["Survived"]) train_y = train_df["Survived"].values eval_x = test_df eval_y = np.array(["died" for _ in range(len(test_df))]) # testデータの目的変数は未知なので仮の値を置く dtypes = train_x.dtypes names_of_categorical_columns = dtypes[dtypes == "object"].index.tolist() names_of_number_columns = dtypes[dtypes != "object"].index.tolist() # カテゴリ変数の欠損値は"missing"に変換 train_x[names_of_categorical_columns] = train_x[names_of_categorical_columns].fillna("missing") eval_x[names_of_categorical_columns] = eval_x[names_of_categorical_columns].fillna("missing") # 数値の欠損値は-1に変換 train_x[names_of_number_columns] = train_x[names_of_number_columns].fillna(-1) eval_x[names_of_number_columns] = eval_x[names_of_number_columns].fillna(-1) 前処理されたデータをTabletで扱う形式に変換します。 from Tablet import create # LLMへの指示. コンペティションサイトのOverviewより. instructions = """The sinking of the Titanic is one of the most infamous shipwrecks in history. On April 15, 1912, during her maiden voyage, the widely considered “unsinkable” RMS Titanic sank after colliding with an iceberg. Unfortunately, there weren’t enough lifeboats for everyone onboard, resulting in the death of 1502 out of 2224 passengers and crew. While there was some element of luck involved in surviving, it seems some groups of people were more likely to survive than others. In this challenge, we ask you to build a predictive model that answers the question: “what sorts of people were more likely to survive?” using passenger data (ie name, age, gender, socio-economic class, etc). Please answer as either `survived` or `died`. """ create.create_task(train_x, eval_x, train_y, eval_y, name="titanic", header="You must follow the instructions to predict if passengers had suvived.", nl_instruction=instructions, categorical_columns=names_of_categorical_columns, num_gpt3_revisions=1, # 今回は実験のため1パターンのみ生成 save_loc="./data/benchmark") こちらのコードを実行すると、save_locで指定したディレクトリに変換されたデータがJSON形式で保存されます。以下に変換された1サンプルを示します。 { "header": "You must follow the instructions to predict if passengers had suvived.", "lift_header": "", "class_text": "Answer with one of the following: died | survived.", "instructions": "The sinking of the Titanic is one of the most infamous shipwrecks in history.\nOn April 15, 1912, during her maiden voyage, the widely considered \u201cunsinkable\u201d RMS Titanic sank after colliding with an iceberg. Unfortunately, there weren\u2019t enough lifeboats for everyone onboard, resulting in the death of 1502 out of 2224 passengers and crew.\nWhile there was some element of luck involved in surviving, it seems some groups of people were more likely to survive than others.\nIn this challenge, we ask you to build a predictive model that answers the question: \u201cwhat sorts of people were more likely to survive?\u201d using passenger data (ie name, age, gender, socio-economic class, etc).\n\nPlease answer as either `survived` or `died`.", "label": "died", "serialization": "PassengerId: 109\nPclass: 3\nName: Rekic, Mr. Tido\nSex: male\nAge: 38\nSibSp: 0\nParch: 0\nTicket: 349249\nFare: 7.9\nCabin: missing\nEmbarked: S\n" } serializationにカラム名とそのデータの値がテキストで入っているのがわかります。このテキストとinstructionsを使ってLLMが予測を行います。 ちなみにlift_headerが空ですが、こちらはinstructionsを指定しない際にalternate_headerという引数から生成されるようなので、instructionsが指定されていれば問題ないです。(こちら、READMEに記載がなく、 コードの該当部分のコメント から読み取ったので間違いがあればご指摘いただきたいです) LLMの予測を作成 Tabletはevaluateという機能を使って評価を行うことができます。使用するLLMは基本的にgpt-3.5-turboになりますが、 synthetic_language.pyの201行目 を変更することで任意のOpenAIのモデルを設定することができます。今回の実験ではgpt-4o-miniを設定しました。 from Tablet import evaluate benchmark_path = "./data/benchmark/performance/" tasks = [ 'titanic/prototypes-synthetic-performance-0', ] evaluator = evaluate.Evaluator(benchmark_path=benchmark_path, tasks_to_run=tasks, model="chat-gpt", encoding_format="gpt", results_file="titanic_results.txt", # 結果の出力先 k_shot=1) evaluator.run_eval(how_many=3) # 予測を3回生成する k_shotでfew-shotで何サンプルデータを与えるか設定します。今回は1, 2, 4, 8について3回予測を生成しました。 実行が終わるとresults_fileで設定したところに出力されるので、それを読み込んでKaggleに提出できる形にします。 df = pd.read_csv("titanic_results.txt", header=None) k = 1 sub_df = pd.read_csv("{解凍した場所}/gender_submission.csv") for i in range(3): sub_df["Survived"] = [int(j == "survived") for j in eval(df[5].iloc[i])] sub_df.to_csv(f"tablet_gpt_k{k}_{i}.csv", index=None) 結果 1, 2, 4, 8のfew-shotの予測結果のスコア(3回の平均)は以下のようになりました。 few-shotのサンプル数 k Public Score (3回の平均) 1 0.63635 2 0.64593 4 0.72009 8 0.74241 few-shotのサンプル数を増やすことで予測精度が向上していることが確認できました。 k=8の0.74241というスコアは、 こちらの公開Notebook からランダムフォレストを使って学習した予測に近い(0.74162)ことが分かります。これから特徴量エンジニアリングを行わなくても、シンプルなfew-shotで予測を行うアプローチだけで一般的な機械学習モデルに匹敵する性能を達成できたということがわかると思います。 一方、 複雑な特徴量エンジニアリングを行うケース (0.83732)や、 LightGBMのようなランダムフォレストより複雑なモデルを使うケース (0.82296)には及ばず、few-shotのサンプル数増加とPuclic Scoreの増加の割合を見ても、ここに追いつくことはなかなか難しそうです。 今回のTitanicデータセットを用いた検証で気になった点は、この問題の解法がWeb上に広く存在するため、LLMが既にそのパターンを学習済みである可能性です。よりニッチなデータセットであれば、今回の実験のような少量のサンプルでの問題解決はより困難になると考えられます。なお、今回は実施しませんでしたが、zero-shotでの性能についても今後検証してみたいと考えています。 おわりに 本記事では、表形式データをLLMで扱う研究リポジトリ「Tablet」の紹介と、Titanicデータセットでの LLMのfew-shot予測の事例を検証しました。 LightGBMのような上位の手法と比べれば、まだ性能面で劣る部分は多々ありますが、「特徴量エンジニアリングを最小限に留められる」「学習データが少なくても利用しやすい」といった利点から、工夫次第では今後の重要なアプローチとなり得るのではないでしょうか。 例えば8個という少ないサンプルでナイーブなランダムフォレストと同レベルの予測ができるということは、医療や金融などのプライバシー上の制限から学習データを収集するのが困難な分野で役に立つのではないでしょうか。 加えて先日 こちらのポスト で紹介されていたような、 GBDT(Gradient-Boosted Decision Trees)の最初の決定木としてLLMの予測を使用する、といった手法も考えられているようです。世界の知識を豊富に獲得しているLLMを「ドメイン知識の入り口」として活用し、それを既存の機械学習モデルに落とし込むハイブリッドなアプローチで非常に興味深いです。 これからも進化の続くLLMの可能性に注目しつつ、表形式データとの組み合わせについてもウォッチしていきたいです。 最後までお読みいただきありがとうございました! 投稿 LLMで挑むTitanic生存予測: Few-Shot Leaningで表形式データはどこま解ける? は 株式会社AI Shift に最初に表示されました。
アバター