見出し画像

【生成AI】コード自動生成における本当の問いは何か

生成AIの登場により、プログラミングのあり方が大きく変わろうとしている。AIが自動でソースコードを書くという一昔前では見られなかった光景が一般的になり、AIの書いたコードを利用して開発する新たなコーディングスタイルが生まれた。AIがコードを自動生成してくれるおかげで、開発効率が飛躍的に向上したなどの報告もあり、その経済的影響は計り知れない[1][2]。また、コード自動生成は進歩が非常に早いのも特徴である。OpenAIのGPT-4やGoogle DeepMindのGeminiを筆頭に、Code Llama[7]やStarCoder[8]などのオープンソースのモデルも続々と登場し、コード自動生成の技術は文字通り日進月歩である。この流れはどこへ向かっているのか、プログラミングはもはや人の立ち入れる領域ではなくなってしまうのか、そして本質は何なのか。そのような問いを大局的な視点で出来るだけ深く考察してみたい。

テーゼ:プログラムは正しくなければならない

この記事の中では、プログラムの正しさについてとりわけ重要な概念として扱いたい。なぜなら、何かを実現しようとするのであれば、そのための道筋は常に正しい方向に向くように律されるべきだからである。巨大な建造物/システムを構築するならば、それを織り成す全ての部品/コンポーネントが、あるべき形で妥協なく作られなければならない。

プログラムの正しさとは本来、それがあらかじめ定めた仕様(Specification)を満たしているかどうかで判断される。例えば、自然数Nの階乗を計算するプログラムであれば、その出力は数学表記のN!と等しくなければならない。つまり、入力Nが自然数であるという条件が満たされた時、出力はN!と等しい一つの数であるという条件を満たすというのが、このプログラムの仕様である。そして、実装されたプログラムがこれらの条件を満たして初めて、このプログラムは正しいと言える。

プログラムの正しさを考えることは重要だ。例えば仮に、AIによって生成されたプログラムが、このような明確な仕様なしに生み出されたとする。つまり、ChatGPTに雑に「階乗を計算するプログラムを作って」と指示した場合、その指示に従ってそれらしいプログラムが出来上がる。しかし、もしそれが単に10の階乗を計算して出力するものだったとしても、何の文句も言えない。なぜなら、この指示では「任意の自然数N」の階乗を計算するように特定(Specify)していないからである。もちろん、今日のChatGPTは非常に賢いので、「階乗を計算するプログラム」という指示の意図を汲み取り、「入力値として与えられる任意の自然数Nの階乗を計算するプログラム」と正しく理解するかもしれない。ただ、指示によって意図する内容が複雑になるにつれ、ChatGPTによしなに解釈してもらうことを期待するのは難しくなる。不適切な指示により、意図したものでないものが出来上がる可能性があるのだ。

自動化をするということは、いずれは人手では作れないような大きなものを作ることにその意義がある。ただ、砂上の楼閣という言葉があるように、不安定な土台の上に高い建物を建てることはできない。関数の入出力が連鎖するようなプログラムがあった時、最初の関数が正しくなければ、途中の関数が全て正しくても、最終的なプログラムの出力は正しくならない。巨大で複雑なシステムを構築するのであれば、正しさが保証されたプログラムのみを積み重ねなければならない

問題解決のためのプログラム合成、そして正しさの保証

コード自動生成が現在のように爆発的に普及する前、元々この分野にはプログラム合成(Program Synthesis)という概念があり、古くは1950年代頃から自動的にプログラムを生成するという研究から始まっている[3]。初期の頃のプログラム合成の考え方は、現在のコード自動生成とは大きく違う。その頃の重要な論文の中には、Problem-Solving(問題解決)やTheorem-Proving(定理証明)などの言葉が並んでおり[4][5]、その時代のプログラム合成とは、現在の状態から目的の状態に至るための操作を得る手段と考えられていた。より正確にいえば、ある条件を満たす入力データがあって、それをある条件を満たす出力データに変換するという目的が与えられた時、その具体的な手続きを生成することがプログラム合成だという認識であった。

プログラム合成において欠かせない概念が仕様(Specification)である。プログラム合成における仕様とは、現在の状態と目標の状態の関係が満たすべき条件のことだ[3]。より実践的な言い方をすれば、仕様はプログラムにある入力が与えられた時、どのような出力をすべきかという、入出力の条件を定義するものである。つまり、仕様はこのプログラムは何(What)をすべきかを示しているのだ。そしてこの仕様の完全な形は、入出力に関する論理的制約として記述することで与えられる。

それに対し、どのように(How)プログラムを実装するかを任されるのがプログラム合成器(Program Synthesizer)である。プログラム合成器は何を生成しても良いわけではなく、仕様を満たすように生成する必要がある。なぜならば、仕様を満たすことがこのプログラムの目的だからだ。仕様を満たさないものは、正しいプログラムとは言えないのだ。

そして、合成器により生成されたプログラムの候補が、仕様を満たすかを検証するのが検証器(Verifier)である。仕様を満たすかどうかわからないプログラム候補は、この検証プロセスを通るされることで正しいプログラムとして認定される。この一連の流れにより、複数あるプログラム候補の中から、問題を正しく解決するものだけを選別し、正しいプログラムを生成することができる。

図1: 古典的なプログラム合成

かつてのプログラム合成の枠組みにおいては、正しさが保証されることが必要であると考えられていた。なぜなら、プログラムは問題解決のために存在し、その問題を解くことという役割の下で完結していたからだ。そしてそもそも、コンピュータ自体が汎用性に乏しく性能も低かったため、今のようにUI上から自由自在にインタラクションする術が無かったことも理由だろう。

LLMによるコード生成とコーディング補助という使い方

ところが、時代は大きく変わりコンピュータの性能は飛躍的に進歩し、大量のデータを扱えるようになり、インターフェースも発達した。その環境下において、現在話題になっている大規模言語モデル(LLM)によるコード生成は、以前とはまるで別のアプローチをとり、全く別の使われ方をしている。注目すべきは、LLMによるコード生成においては、仕様に対して正しく動作するプログラムを生成するような使われ方をあまりしないという点だ。それよりも、人のコーディング作業を補助するような使われ方をしている。

図2: LLMによるコーディング補助

古典的なプログラム合成とは異なり、自動での問題解決という強い目的をおかず、厳密な仕様や検証を要求しない。従って、コード生成の方法に関しても大きく異なる。古典的なプログラム合成のように、論理的制約で記述された仕様とそれ基づく検証により正しさが保証されたプログラムを生成するのではなく、LLMのコード生成は、短い自然言語での説明といくつかの例から、確率的な観点で尤もらしいトークンの列としてソースコードを生成する[8]。それも完全なソースコードが生成されるではなく、それ自体では動作しない部分的なソースコードが生成されることもある。そして、その生成されたコードが、果たして本当にユーザーの要求に応えているかはわからない。

プログラムとソースコードの違いは、それを読む主体が機械か人間かという点なのだが、LLMはあくまでも人のコーディング作業を補助する使い方が主流であり、生成器が生成するのは人が読み書きするソースコードだ。それは、コード生成のプロセスの中に暗に人の手が介在し、正当性の保証の責務がLLM側にはないことを意味する。ソースコードを書く主体は人間であり、コード自動生成はそれを補助するツールだという考えが背景にあるようにも見える。従って、LLMベースのコード生成器自体に検証機能は備わっていなく、正当性を保証するのは人間の側なのだ。

このアプローチでは、正しさが保証されたプログラムを出力することを放棄した代わりに、とっつきやすさや使いやすさを得た。正しさを保証するには仕様が必要で、そしてその生成されたコードを仕様にも基づいて検証する必要がある。しかし、それには大変な労力がかかる。確かに、完全な仕様の記述や厳密な検証は非常にハードルが高く、それがプログラム合成があまり普及しなかった一つの原因でもあった。なので、現在のコード自動生成の普及の仕方を考えると、極めて実用的な方向性だと考えられる。

LLMのコード生成器も正しさによって評価される

LLMによるコード生成は、正当性の厳密な検証に依拠しないという点で従来のプログラム合成よりもより実践的で柔軟な考え方をする。それは、プログラミングは人とAIの共同作業だという発想と相性が良い。ソースコードは、AIが生成したものを人が手直しすることで完成し、完成したものが要求を満たして正しく動作するかは人が判断する。その意味で、仕様通りに動く正しいプログラムを出力する装置というよりは、便利なコード生成ツールという路線を重視していると言えるだろう。実際、Facebookのオープンソースのコード生成モデルであるCode Llamaは、コーディングをより便利にするツールという方針を鮮明に打ち出している[7]。

もちろん、実際に利便性重視か正当性重視かという鮮明な二項対立があるわけではなく、両立は可能である上に何がどっちに属するかもはっきりとはしない。しかし、LLMベースのコード生成は現実的にコーディング補助ツールとしての使い方をされ、生成されたコードの正当性の追求とは少し距離を置いていることも事実だろう。

ただ、方向性や使われ方はどうであれ、結局のところLLMベースのコード生成器の性能評価も、プログラムの正当性の観点から行われている。ほとんどのコード生成器は、ユニットテストに通るかどうかをその性能の評価基準にしているのだ。つまり、あらかじめ問題と正解が与えられ、生成したコードを実際に動作させてそれが正解と同じ値を出力するかを確認することで生成器を評価している[9]。入出力の論理的条件に基づく厳密な検証ではないにせよ、満たして欲しいものを満たすという意味で、コードの正しさの検証が行われているのだ。ここに、使われ方と評価手法の間の捻れが見て取れる。

LLMベースのコード生成器は、従来のプログラム合成のようにあらかじめ定められた仕様に対し、生成時にそれを満たすことを検証して正当なプログラムだけを出力するわけではない。しかし、結局のところLLMベースのコード生成においても、正しいソースコードを出力することを目指して生成器は進歩しているのである。

正しさの基準こそが本当の問い

そもそもLLMにはHallucinations(幻覚)を引き起こすという特徴があり、それ単体では決定的に正しいソースコードを出力することはできない。従って、LLMのみを用いて生成されたコードを正しいと保証することはできない。しかし、その外部に検証器が存在し、仕様に基づいて生成されたコードを検証する機構が備わっていたらどうだろうか。

上述のように生成器は正しいコードを出力することを目指して作られている。そうであれば、LLMの出力が確率的なものであり制御できなかったとしても、何回も繰り返せばそのうち一つは検証を通る正しいコードを生成しうる。つまり、Hallucinationsの原因である内在的な創造性を上手く利用すれば、確率的に正しいコードを生成でき、試行回数を増やすことで確度高く正しいコードに到達できるはずだ[10][11]。

図3: 仕様と検証に基づくLLMベースのコード生成

このような総当たり的な手法を許すのであれば、効率性の良し悪しはあるにせよ、コードをどのように生成するかという問題は解決可能であることを意味する。そう考えると、本質的に着目すべき点は、実は「仕様に基づいた検証」であることがわかる。つまり、生成すべきものに対する正しさの基準をいかにして与えるかが本当の問いなのである。

それは第一に、仕様をどのように与えるのかである。完全なものとして入出力が満たすべき全ての論理的制約を与えるのか、不完全なものとして有限個の入出力の例を与えるのか、あるいは自然言語による定性的な指示として与えるのか。また、適用範囲を可用性・拡張性・保守性やセキュリティなどの観点にも拡張していくのか。そして第二に、検証手段をどう構築するかである。それぞれの仕様の表現はどこまで検証可能なのか、どのように検証するのか。これらを整理していく必要がある。

コード自動生成は奥深い問題だ。開発現場での実用性を考慮に入れれば、小さな関数の自動生成だけでなく、いずれはシステム全体の自動生成へと発展することが期待される。また、生成器への指示もより自然に、難なく行えるようにすることが求められる。しかし、そうなれば「正しさ」として定義する範囲が広くなり、基準はより規定しづらく曖昧になっていく。この問題をどう乗り越えていくか。そこに解を見出していく必要がある。実用的なシステムの自動生成は大いなるチャレンジなのだ。

参考文献

[1] Manning, S., Mishkin, P., Hadfield, G., Eloundou, T., and Eisner, E. A research agenda for assessing the economic impacts of code generation models. 2022.

[2] Peng, S., Kalliamvakou, E., Cihon, P., Demirer, M. The Impact of AI on Developer Productivity: Evidence from GitHub Copilot. ArXiv, abs/2302.06590, 2023.

[3] Sumit Gulwani, Oleksandr Polozov, Rishabh Singh, et al. Program synthesis. 2017.

[4] Manna, Z. and Waldinger, R. J. Toward automatic program synthesis. March 1971.

[5] Simon, H. A. Experiments with a heuristic compiler. October 1963. 

[6]  Austin, J., Odena, A., Nye, M., et al. Program Synthesis with Large Language Models. ArXiv, abs/2108.07732, 2021.

[7] Rozière, B., Gehring, J., Gloeckle, F., et al. Code Llama: Open Foundation Models for Code. ArXiv, abs/2308.12950, 2023.

[8] Li, R., Ben Allal, L., Zi, Y., et al. StarCoder: may the source be with you! ArXiv, abs/2305.06161, 2023.

[9] Chen, M., Tworek, J., Jun, H., et al. Evaluating Large Language Models Trained on Code. ArXiv, abs/2107.03374, 2021.

[10] Li, Y., Choi, D., Chung, J., et al. Competition-level code generation with AlphaCode. Science, 2022.

[11] Romera-Paredes, B., Barekatain, M., Novikov, A., et al. Mathematical discoveries from program search with large language models. Nature, 2023.


この記事が気に入ったらサポートをしてみませんか?