最近、ChatGPTを含むLLM(Large Language Model)が注目を集めていますね。
これらのモデルは、人間に近い精度を実現し、自然言語によるコミュニケーションの可能性を広げており、 個人的にはこれら言語モデルを利用した対話システムや情報インターフェースなどへの応用に期待を感じています。
LangChainとは
一方でLLMが本当のことのように嘘を述べてしまう、いわゆるHallunicationが問題になっています。
LLMはあくまでも大量のデータをもとに自動的に学習された言語モデルであるため、専門知識や文脈に関する認識が限定されることがあります。 私は研究者ではないので詳しいことはわかりませんが、使う側からすれば汎用言語モデルに特定の専門知識そのものを求めることに違和感を感じており、 そもそもとしてLLMにそういったアウトプットを求めていません。
それよりは、LLMをツールとして特定の文献や学習コンテンツと連携することで、学習効率を高めるようなアプリを開発できる可能性に注目しています。
例えば、BingやGithub Copilot、ChatGPT Pluginなどは、LLMを活用した特定の領域にフォーカスしたアプリケーションの一部だと思っています。
LangChain は、こういったLLMを活用したアプリケーションの開発を支援するライブラリです。
LLMをツールとして特定の文献や学習コンテンツと連携することで、学習効率を高めるようなアプリについては以下の「Data Augumented Generation」にユースケースとして記載されています。
Data Augmented Generation — 🦜🔗 LangChain 0.0.123
人間の教師と学習していても、別の文献やソースでファクトチェックや考察視点の補完をするものです。
(2024/5/4更新)
上記のData Augumented Generationというページはリンク切れしており、現在は広くRAG(Retrieval-Augumented Generation)として認知されているようです。
また実用的なRAGを構築するためのTipsをまとめた記事もありますので、参考にしてみてください。
PDF文献をソースとして学習するユースケースを考えてみる
上記のような「LLMをツールとして特定の文献や学習コンテンツと連携することで、学習効率を高めるようなアプリ」として、このようなアプリを考えてみます。
ここでは以下の記事で紹介されている、大きなテキストをvector index化して自然言語で検索する例を応用して、PDFドキュメントを検索してみます。
環境の準備
Google Colabで必要なパッケージをインストールします。
|
|
続いてOpenAIのAPI keyをセットします。
|
|
OpenAIのAPIは有料です。ChatGPT plusとは別料金体系で、従量課金になっています。
2023年3月現在では登録時に3ヶ月間利用できる$5分のクレジットがもらえますので、試しに利用してみる分にはそれで十分かと思います。
ドキュメントの準備
今回PDFドキュメントには、azu (azu) · GitHubさんがCC BY NNで公開されているJavascript Promiseの薄い本を利用させていただきます。
ちなみに私がこの本に出会ったのは2014年くらいだったと思います。 現在でもAsync Functionが追記されているなど精力的にメンテされているようで、今でも初学者の方におすすめできる良書かと思います。
検索アプリの用意とテスト
必要なライブラリをインポートします。
|
|
LangChainのドキュメントには様々なドキュメントの利用方法が記載されているので参考になります。
ここでのPDFの読み込みには、以下のPDFセクションで紹介 pypdf
を利用する方法を使います。
|
|
これで、1ページ単位でPDFを分割できました。これをFAISSに渡して自然言語による検索を実施します。
|
|
「Promise.then?の使い方は?」 と質問した場合の検索結果を見てみます。
118
2
ページ分割と結果の提示は意図通りにできているようです。
17: JavaScript Promiseの本 18ThenableについてはPromiseを使ったライブラリを書くとき等には知っておくべきで すが、 通常の利用だとそこまで使う機会がないものかもしれません。 ThenableとPromise.resolveの具体的な例を交えたものは 第4章 のPromise.resolveとThenable にて詳しく解説しています。 Promise.resolve を簡単にまとめると、「渡した値でFulfilledされるpromiseオブ ジェクトを返すメソッド」と考えるのがいいでしょう。 また、Promiseの多くの処理は内部的に Promise.resolve のアルゴリズムを使って 値をpromiseオブジェクトに変換しています。 Promise.reject Promise.reject(error) は Promise.resolve(value) と同じ静的メソッドで new Promise() のショートカットとなるメソッドです。 例えば、 Promise.reject(new Error(“エラー”)) というのは下記のコードのシン タックスシュガーです。 new Promise( function (resolve,reject){ reject( new Error(“エラー” )); }); 返り値のpromiseオブジェクトに対して、thenの onRejected に設定された関数に エラーオブジェクトが渡ります。 Promise.reject( new Error(“BOOM!”)).catch(function (error){ console.error(error); }); Promise.resolve(value) との違いは resolveではなくrejectが呼ばれるという点 で、 テストコードやデバッグ、一貫性を保つために利用する機会などがあるかもし れません。 コラム: Promiseは常に非同期? Promise.resolve(value) 等を使った場合、 promiseオブジェクトがすぐにresolve されるので、 .then に登録した関数も同期的に処理が行われるように錯覚してしま います。
これは「Promise.resolve」に関する説明の節で Thenable
を説明しているところのちょうど最後にあたるページをポイントしているので悪くない結果だと思います。
29: JavaScript Promiseの本 30つまり、 Promise#then は単にコールバックとなる関数を登録するだけではなく、 受け取った値を変化させて別のpromiseオブジェクトを生成する という機能も持っ ていることを覚えておくといいでしょう。 Promise#catch 先ほどのPromise#then についてでも Promise#catch は既に使っていましたね。 改めて説明すると Promise#catch は promise.then(undefined, onRejected); のエ イリアスとなるメソッドです。 つまり、promiseオブジェクトがRejectedとなった 時に呼ばれる関数を登録するためのメソッドです。 Promise#then とPromise#catch の使い分けについては、 then or catch?で紹介しています。 IE8以下での問題 このバッジは以下のコードが、 polyfill27 を用いた状態でそれぞれのブラウザで 正しく実行できているかを示したものです。 polyfillとはその機能が実装されていないブラウザでも、その 機能が使えるようにするライブラリのことです。 この例では jakearchibald/es6-promise28 を利用しています。 Promise#catchの実行結果 var promise = Promise.reject( new Error(“message” )); promise. catch(function (error) { 27 https://github.com/jakearchibald/es6-promise 28 https://github.com/jakearchibald/es6-promise
こちらは「Promise.catch」を説明する節ですので、質問の意図からすると外れている気がします。
k=4
として結果を増やしてみると、以下も追加で出力されました。
111: JavaScript Promiseの本 112•thenをその場に並べた書き方 •forループを使った書き方 •reduceを使った書き方 •逐次処理する関数を分けた書き方 しかし、これはJavaScriptで配列を扱うのにforループや forEach 等、色々やり方 があるのと本質的には違いはありません。 そのため、Promiseを扱う場合も処理を まとめられるところは小さく関数に分けて、実装していくのがいいと言えるでしょ う。 まとめ このセクションでは、 Promise.all とは違い、 一つづつ順番に処理したい場合 に、Promiseでどのように実装していくかについて学びました。 手続き的な書き方から、逐次処理を行う関数を定義するところまで見ていき、 Promiseであっても関数に処理を分けるという基本的な事は変わらないことを示しま した。 Promiseで書くとPromise chainを繋げすぎて縦に長い処理を書いてしまうことがあ ります。 そんな時は基本に振り返り、処理を関数に分けることで全体の見通しを良くするこ とは大切です。 また、Promiseのコンストラクタ関数や then 等は高階関数なので、 処理を関数に 分けておくと組み合わせが行い易いという副次的な効果もあるため、意識してみる といいかもしれません。 高階関数とは引数に関数オブジェクトを受け取る関数のこと Promises API Reference Promise#then promise.then(onFulfilled, onRejected); thenコード例
API Reference章の「Promise.then」の解説が出力されました。これも妥当と思われます。
73: JavaScript Promiseの本 74この時、returnしたものがpromiseオブジェクトである場合、そのpromiseオブジェ クトの状態によって、 次の then に登録されたonFulfilledとonRejectedのうち、 どちらが呼ばれるかを決めることが出来ます。 var promise = Promise.resolve(); promise.then( function () { var retPromise = new Promise( function (resolve, reject) { // resolve or reject で onFulfilled or onRejected どちらを呼ぶか決まる }); return retPromise; }).then(onFulfilled, onRejected); 次に呼び出されるthenのコールバックはpromiseオブジェクトの状態によって決 定される つまり、この retPromise がRejectedになった場合は、 onRejected が呼び出され るので、 throw を使わなくても then の中でrejectすることが出来ます。 var onRejected = console.error.bind(console); var promise = Promise.resolve(); promise.then( function () { var retPromise = new Promise( function (resolve, reject) { reject( new Error(“this promise is rejected” )); }); return retPromise; }).catch(onRejected); これは、 the section called “Promise.reject” を使うことでもっと簡潔に書く ことが出来ます。 var onRejected = console.error.bind(console); var promise = Promise.resolve(); promise.then( function () { return Promise.reject( new Error(“this promise is rejected” )); }).catch(onRejected); まとめ このセクションでは、以下のことについて学びました。 •throw ではなくて reject した方が安全 •then の中でもrejectする方法
これは「throwしないでrejectしよう」の説明です。 Advanced章にはひとつ前に「Prmose.resolveとThenable」があるので、どちらかというとこちらを出力してほしかったところですね。
まとめ
一例しか示せていませんが、PDFドキュメントをvector index化して自然言語で検索するアプリの可能性を模索してみました。
まず、このようなアプリケーションをたった数行で構築できるだけでもLangChainの可能性の高さが伺えます。
結果を見てみると、そこそこ利用できそうな印象を受けます。やはり入力が自然言語であるためか不安定な面はありそうです。 質問の仕方によっても結果に感じる精度は変わってくるので、同じことを言い方を変えて質問してみるとよいかもしれません。 また検索結果も絞り込まず、なるべく多めに出力しておく方がよいでしょう。
LangChainにはPDF以外にもソースにできる例が沢山用意されており、WebページやRoam、Evernoteなどもありました。 デジタルガーデンなど自身のノートの検索に利用するしたり、ソースを変えたりマッシュアップすることで様々な応用ができそうです。
ちなみにLangChainにはPythonの他に、JavaScriptによる実装も提供されています。
今回はドキュメント検索の結果を出すだけの利用でしたが、より対話的に回答を得られるように次は「RetrievalQA」を使ってみたいと思います。