週刊アスキー

  • Facebookアイコン
  • Twitterアイコン
  • RSSフィード

ChatGPTプロンプトプログラミング講座(1)

生成AIはプログラミングに何をもたらすのか?

アルゴリズムよさようなら

 まずは、以下のビデオを見ていただきたい。

 私が、まだプログラマになりたての頃、IBMの大型機の端末にこんな具合で大きく時間を表示している人がいた。HP95LXという1991年に発売された超小型パソコンの表計算ソフトで、やっぱりこんな感じで時計を表示するサンプルマクロもあった。

 仕事の道具を、ちょっとだけ遊び心をもって違うことに使ってみるのは楽しい。そんな気分で、エクセルでもこんな時計表示をやってみたいと思っていたのだが、そのままになっていた。それが、ChatGPTに頼んでみるとスルスルとVBAのコードが出てきて、30分ほどでちゃんと動くものになっていた。

 VBAの達人の方々ならともかく、私のようにふだんVBAに親しんでいない人間としては画期的なことである。しかも、ふだんのプログラミングとは比べようもなくラクである。

 生成AIによって、プログラミングの世界が大きく変化することになると思う。前回の「ChatGPTでプログラミングのフラット化がはじまっている」に続いて、ChatGPTでコードを生成して動かす。今回は、その基本テクニックを紹介したいと思う。

どう動かすかではなく何を作りたいかを伝える

 私が、このVBAを書いてもらうためにChatGPTに与えたプロンプトは次のようなものである。

EXCELのVBAで時計を表示するコードを書いてください。

「時」と「分」と「秒」を表現する数字は、16行×3列のセルを黒で塗ることで表現します。

数字の「2」は、以下のパターンで「1」に相当するセルは塗る、空白は塗らないで表現します。

11
  1
  1
  1
  1
  1
  1
  1
11
1
1
1
1
1
1
111

時、分、秒の間に「:」を16行×1列で次のパターンで表現します。1が塗り、空白は塗らない。





1
1




1
1





各数字や「:」の間は1列空いているものとします。
1分に1回時間を更新してください。

 ChatGPTによるコードの生成は、まだ始まって数カ月の発展段階にある。プログラミングは、この業界にとっては飯のタネの根幹をなすものなので、みんなやっていたとしても黙ってノウハウを蓄積しているのかもしれない。あるいは、コード生成できるのは知ってはいても、いままでゴリゴリ書いてきたスタイルを維持するほうが安全と考えているのかもしれない。

 コード生成のための《プロンプトエンジニアリング》というものがあるわけだが、いわば、《プロンプトプログラミング》というような確立されたジャンルになると思う。それは、いま見えている範囲で書き出すと、次のような特徴がある。

1)手順やアルゴリズムで書くのではなく問題を表現する
2)人に伝えるように例示を使って説明する
3)参考になるコードがあればそれを与えて理解を促す
4)対話的(段階的)にすすめる

 もちろん、手順やプロセス、あるいはアルゴリズムをChatGPTに伝えることもできる。そのほうが有効な場合はそうするとよいだろう。しかし、ChatGPTプログラミングでは、「こんなものが作りたい」と問題を定義するように伝えるのがよい。

 理由ははっきりしていて、そのほうが人間もラクだし誤解がないからだ。3つの数字を並べ変えるとき「大きい順に並べる」と書くほうが、並べ変える手順を伝えるより間違いがないのは明らかだ。いわば《静的》なプログラミングができる。これが、プロンプトプログラミングの最大のメリットだと思う。

 IFやWHILEや変数の組み合わせによるアルゴリズムがこんがらがってバグになる。私ば、7年ほど「全国小中学生プログラミング大会」に関わってきたが、子どもたちの「プログラムが動かなくて苦労した」とか「何度も投げ出したいと思った」といった声を聴くたびに、心が痛むのだった。

 そのいまいましい世界から人々が救い出されるというだけでも、《静的》なプログラミングには意味がある。ちょうど、作りたいプログラムの概念をリングノートに鉛筆でスケッチするような、絵のような《静的》さかげんだ。

 すべてを《ことば》だけで説明するとしたらそのための《言語力》のほうがよほど大変では? という意見もありそうだが、次の《例示》と《コードを与える》で解決することも多い。

《例》を示してやる――まさにプロンプト的な

 次に、いかにもChatGPTプログラミング的といえるのが、《例示》を積極的に使うことだ。これは、OpenAIも言っていることでプロンプトエンジニアリングの基本の1つである。今回の例では、数字の「2」を表示する例をあげて、ほかもこんな調子でやってねと伝えている部分である。

 ちなみに、私は「2」の例をプロンプトで与えたのだが、ChatGPTが書きだしたコードでは「0」が生成されていた。ほかの数字については自分で書いてねときたのだが、頼めばすべての数字を生成してくれそうである(今回は、1、3~9のフォントは自分でコードに書き加えたのだが)。

ChatGPTはプログラムのコードもむしゃむしゃと食べているらしい

 ChatGPTには、人間の言葉だけでなくコードを与えてしまうことは、すでに裏技的に行われていたことだ。彼らは、プログラムのコードも学習していて与えたコードを理解することができる。その例として、さきほどのVBAにタイマー機能を追加することをこの手法でやってみることにする。

 パソコンの画面に時分秒が大きく出るならば、ハッカソンなどのイベントで、ハックタイムなどの終了までの時間を表示するやつを作ってみたくなる。同じセッションで続けて、タイマーを追加してほしいとプロンプトを与えてもよいのだが、新たなセッション(画面左上から「+ New Chat」を選ぶ)でもよい。私がChatGPTに与えたプロンプトは、次のようなものだった。

以下のコードでエクセルのVBAとして時計が動いています。
これを改造して時刻とタイマーが表示されるVBAにしてください。
A1のセルにタイマーのタイムアップ時刻を15:30(午後3時30分のとき)のように与えます。
タイマーは、タイムアップまでの残り時間を赤い色で塗って表示。
秒ごとに更新。
タイマーの時、分、秒は、時刻と同じデザインで時刻の下に1行の間隔をあけて表示。
--------------------------
Option Explicit

Dim StopFlag As Boolean

Sub DisplayClock()
      Dim CurrentTime As Date
      Dim CurrentHour As Integer


※前述の時計表示のVBAのコードを貼りつけてある。

 正直なところ、これは出てきたコードを実行してもすぐには動かなかった。タイマーの値が正しく表示されなかったり、24時をまたぐ場合の処理が適切でなかったり。何回かのやりとりのあと、最終的にコードを見直して指示を与えることもして、ようやく動かすことができた。「タイマーとは」と丁寧に説明すべきところだった(前回記事の「空き時間とは」参照)。

 このやり方の変形として、複雑なプログラムを作りたいときに、「このプログラムから呼び出されることを想定して関数のコードを書いてください」ということもできる。

 ChatGPTが、コードを理解しているように見えるというのは凄いことだ。プロンプトではすべてを言葉で表現しなければならないという限界をあっさりと超えてしまうからだ。これは、かつて第四世代言語などといわれた時代のコードジェネレーターとは隔世の感がある。

 こうしてできたエクセルのVBAで動く時計&タイマーを、以下に示す。A1のセルにタイムアップの時間を入力して実行。A1が空のときはただの時計となる。

プログラミングは《対話》によって行われることになった

 プログラムの複雑さが増すにしたがってプロンプトも長くややこしいものとなり、ChatGPTとの間にも誤解が生じがちだ。そこで、OpenAIのテクニカルレポートにも書かれていることだが《few-shot prompt》という技を使う。つまり、いちどに完成形のコードを求めるのではなく、段階的に理解をうながしながら作り上げる。

 同じくテクニカルレポートに出てくる「幻覚」(ハルシネーション=でっちあげ)は、コード生成でも出てくる。これは、単なるコーディングミス(関数と変数、型の定義の間違いなど)やゴールに対する誤解によって生ずるように見える。

 前回記事でも書いたとおり実行時に出たエラーをそのままChatGPTに返してやって修正してもらうことができる。もちろん、間違っている点が明確であれば「〇〇ではなく××です」と指摘すればよい。そうやって、段階ごとに動きコードを導いて、次の段階に進めていくわけだ。

 こうなると本当に、ChatGPTと一緒にプログラムを書いている気分になってくる。

エラーを伝えるとお詫びとともに修正内容とコードをすぐに返してくる。こんないい同僚のいるプログラマは幸せだと思う。

 なお、ここで「OpenAIのテクニカルレポート」と呼んでいるのは、3月27日に発表された"GPT-4 Technical Report"のことである。内容は多岐にわたっており、ChatGPTで何かをしようという人は一度は目を通しておくべき内容である。これについては、2017年頃から深層学習のセミナーをご一緒させていただいた丸山不二夫さんによる「GPT-4 Technical Report を読む」が参考になる。

プロンプトプログラミングの良い点、気になる点

 プロンプトプログラミングについて、以下のようにまとめてみた。

プロンプトプログラミングの良い点

1)時間がかからない(書いてもらえる)
2)解説付き、コメント付きでコードが出てくるので、学びになる
3)テストデータを生成もできる
4)コードの品質の属人的なバラつきをなくすことができるかもしれない

 一方、まだ課題も多いのでその点もまとめておく。

プロンプトプログラミングのダメなところ

1)プロンプトのノウハウが発展段階
2)大規模開発の手法が確立されていない
3)対話を続けると混乱に陥ることがある
4)検証しないとバグの温床になる(ただの生成ツールと考えよ)

 前回記事でも指摘したように、やりとりを繰り返しているとChatGPTが混乱状態に陥ることがある。その場合には、同じセッションでは使えるようなコードには到達できないので、新たにセッションを始めたほうがよい。

 最後の「検証しないとバグの温床になる」は、前向きにとらえれば、まだ人間のやることが残っているという意味でもある。ChatGPTは、あくまでプログラマがラクするための道具であって、その生成されたコードを人間が管理しながら使うというのが、まともなシステム開発では適切な付き合い方なのだろう。

 ということで、なにぶん発展段階にあるChatGPTによるコード生成のお話なので、決定的な誤解もあるかもしれない。最後に、今回生成したエクセルで時計を表示するVBAのコード、タイマー付きバージョンを以下に紹介しておく。

 VBAの実行方法については、すぐにできるので各自調べていただきたい。A1のセルに「15:20」などと入れて実行するとその時間までのタイマーも表示される。終了は、Ctrl-Breakやウィンドウを閉じる、タスクの終了などで行う。


Option Explicit

Dim StopFlag As Boolean
Dim TimerFlag As Boolean
Dim Pattern_num(0 To 9) As String
Dim Pattern As String
Dim TargetTime As Date
Dim i As Integer
Dim j As Integer

Sub DisplayClockAndTimer()
      Dim CurrentTime As Date
      Dim CurrentHour As Integer
      Dim CurrentMinute As Integer
      Dim CurrentSecond As Integer
      Dim RemainingTime As Date
      Dim RemainingHour As Integer
      Dim RemainingMinute As Integer
      Dim RemainingSecond As Integer

      ' Initialize StopFlag
      StopFlag = False

      ' Initialize TimerFlag
      TimerFlag = True

      If Range("A1").Value = "" Then
              TimerFlag = False
      Else
              ' Get target time from A1
              TargetTime = Range("A1").Value + Date

              If TargetTime <= Now Then
                      TargetTime = DateAdd("d", 1, TargetTime)
              End If
      End If


      ' Clear cells
      Range("A1:AA35").Interior.Color = RGB(255, 255, 255)

      ' Set Column Widths and Height
      Range("A:AA").EntireColumn.ColumnWidth = 6
      Range("1:35").EntireRow.RowHeight = 16

      Do While Not StopFlag
              ' Get current time
              CurrentTime = Now
              CurrentHour = Hour(CurrentTime)
              CurrentMinute = Minute(CurrentTime)
              CurrentSecond = Second(CurrentTime)

              ' Display current time (Black)
              DisplayNumber Range("A1"), CurrentHour \ 10, False
              DisplayNumber Range("E1"), CurrentHour Mod 10, False
              DisplayColon Range("I1"), False
              DisplayNumber Range("K1"), CurrentMinute \ 10, False
              DisplayNumber Range("O1"), CurrentMinute Mod 10, False
              DisplayColon Range("S1"), False
              DisplayNumber Range("U1"), CurrentSecond \ 10, False
              DisplayNumber Range("Y1"), CurrentSecond Mod 10, False

              If TimerFlag = True Then
                      ' Calculate remaining time
                      If TargetTime >= CurrentTime Then
                            RemainingTime = TargetTime - CurrentTime
                            RemainingHour = Hour(RemainingTime)
                            RemainingMinute = Minute(RemainingTime)
                            RemainingSecond = Second(RemainingTime)
                    Else
                            RemainingTime = 0
                            RemainingHour = 0
                            RemainingMinute = 0
                            RemainingSecond = 0
                    End If

                      ' Display remaining time (Red)
                      DisplayNumber Range("A19"), RemainingHour \ 10, True
                      DisplayNumber Range("E19"), RemainingHour Mod 10, True
                      DisplayColon Range("I19"), True
                      DisplayNumber Range("K19"), RemainingMinute \ 10, True
                      DisplayNumber Range("O19"), RemainingMinute Mod 10, True
                      DisplayColon Range("S19"), True
                      DisplayNumber Range("U19"), RemainingSecond \ 10, True
                      DisplayNumber Range("Y19"), RemainingSecond Mod 10, True
              End If

              ' Wait 1 second
              WaitSecond

              ' Allow other events
              DoEvents
      Loop
End Sub

Sub StopTimer()
      ' Set StopFlag to true to stop the timer
      StopFlag = True
End Sub

Sub DisplayNumber(TopLeftCell As Range, Number As Integer, IsTimer As Boolean)
      ' Define patterns for numbers
      Pattern_num(0) = "111" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "111"
      Pattern_num(1) = "11 "& _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        "111"
      Pattern_num(2) = "11 " & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        " 1 " & _
                        "1  " & _
                        "1  " & _
                        "1  " & _
                        "1  " & _
                        "1  " & _
                        "1  " & _
                        "1  " & _
                        "111"
      Pattern_num(3) = "111" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "11 " & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "111"
      Pattern_num(4) = "1  " & _
                        "1  " & _
                        "1  " & _
                        "1  " & _
                        "1  " & _
                        "1 1" & _
                        "1 1" & _
                        "111" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1"
      Pattern_num(5) = "111" & _
                        "1  " & _
                        "1  " & _
                        "1  " & _
                        "1  " & _
                        "1  " & _
                        "1  " & _
                        "111" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "11 "
      Pattern_num(6) = " 11" & _
                        "1  " & _
                        "1  " & _
                        "1  " & _
                        "1  " & _
                        "1  " & _
                        "1  " & _
                        "111" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "111"
      Pattern_num(7) = "111" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 " & _
                        " 1 "
      Pattern_num(8) = "111" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        " 1 " & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "111"
      Pattern_num(9) = "111" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "1 1" & _
                        "111" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "  1" & _
                        "11 "


      ' Display number
      For i = 1 To 16
              For j = 1 To 3
                      If Mid(Pattern_num(Number), (i - 1) * 3 + j, 1) = "1" Then
                              If IsTimer Then
                                      TopLeftCell.Offset(i - 1, j - 1).Interior.Color = RGB(255, 0, 0)
                              Else
                                      TopLeftCell.Offset(i - 1, j - 1).Interior.Color = RGB(0, 0, 0)
                              End If
                      Else
                              TopLeftCell.Offset(i - 1, j - 1).Interior.Color = RGB(255, 255, 255)
                      End If
              Next j
      Next i
End Sub

Sub DisplayColon(TopLeftCell As Range, IsTimer As Boolean)
      ' Define patterns for colon
      Pattern = " " & _
                          " " & _
                          " " & _
                          " " & _
                          "1" & _
                          "1" & _
                          " " & _
                          " " & _
                          " " & _
                          "1" & _
                          "1" & _
                          " " & _
                          " " & _
                          " " & _
                          " " & _
                          " "

      ' Display colon
      For i = 1 To 16
              For j = 1 To 1
                      If Mid(Pattern, (i - 1) * 1 + j, 1) = "1" Then
                              If IsTimer Then
                                      TopLeftCell.Offset(i - 1, j - 1).Interior.Color = RGB(255, 0, 0)
                              Else
                                      TopLeftCell.Offset(i - 1, j - 1).Interior.Color = RGB(0, 0, 0)
                              End If
                      Else
                              TopLeftCell.Offset(i - 1, j - 1).Interior.Color = RGB(255, 255, 255)
                      End If
              Next j
      Next i
End Sub


Sub WaitSecond()
      ' Wait for 1 second
      Application.Wait (Now + TimeValue("0:00:01"))
End Sub
 

遠藤諭(えんどうさとし)

 株式会社角川アスキー総合研究所 主席研究員。MITテクノロジーレビュー日本版 アドバイザー。プログラマを経て1985年に株式会社アスキー入社。月刊アスキー編集長、株式会社アスキー取締役などを経て、2013年より現職。人工知能は、アスキー入社前の1980年代中盤、COBOLのバグを見つけるエキスパートシステム開発に関わりそうになったが、Prologの研修を終えたところで別プロジェクトに異動。「AMSCLS」(LHAで全面的に使われている)や「親指ぴゅん」(親指シフトキーボードエミュレーター)などフリーソフトウェアの作者でもある。趣味は、カレーと錯視と文具作り。2018、2019年に日本基礎心理学会の「錯視・錯聴コンテスト」で2年連続入賞。その錯視を利用したアニメーションフローティングペンを作っている。著書に、『計算機屋かく戦えり』(アスキー)、『頭のいい人が変えた10の世界 NHK ITホワイトボックス』(共著、講談社)など。

Twitter:@hortense667

この記事をシェアしよう

週刊アスキーの最新情報を購読しよう

この連載の記事