ChatGPTでマルチ言語 (2)
Table of Contents
トロイの木馬
前回はChatGPTで、色々なプログラム・コードを生成させた。でも、いずれも 骨格だけのものであった。出し惜しみである。何故って考えてみた。
そりゃ、無料ユーザーに大盤振舞はできませんって事だな。賢いAIはそんな事 を一切言わないけどね。
今、使っている方法は、replだ。これで、威力を示しておいて、Webのバック エンドとして、エンジンにしたいなら、APIが使える有償モードにしてって事。
数年まえにIBMのワトスン君が出たけど、高嶺の花だった。それが、手の届く ところまで、降りてきた。もう、みんな虜ですよ。
こういうビジネスモデルが有ったねぇ。そう、アドビのPDFビューアー。 読む道具は無料だけど、作る道具は有料ですって奴。
20$から始められるので手軽。まあ、流行のサブスクなんで、運用する会社も 資金計画をたてやすい。困ったら、大パトロンのMSが支援してくれる。
そう言えば、WindowsOSもサブスク計画が密かに検討されてるらしい。OS税の 徴収が近い将来に始まるのかな?
こういうの、トロイの木馬と思うのは、オイラーだけ?
各言語の特徴
前々回の流れから、自然に、golang,pythonを選んだ。後は趣味でhaskellだ。
ぐぐるのAIは現在修行中。もっともユーザーが利用していそうな言語から対応 していくってのが、ビジネスのセオリーだわな。得意な言語ですと自己申告し たものと、下記人気ランキングが、よく符号するのは偶然ではないだろう。
もっと幅広く言語を試せばいいんだろうけど、収集がつかなくなるんで、今の 3言語を比べてみる。まずは、見掛けからって事で、wcした結果。4番目のデー タは、参考までに最長の桁数。
どの言語にも中立なawkを使ってみた。
# get max length of lines awk ' BEGIN{ mlen = 0 } { if (mlen < length($0)) mlen = length($0) } END{ print mlen } '
golang 132 379 2821 128 python 92 271 2542 121 haskell 76 373 2795 160
雑に考察するに、golangは行数が多くて、冗長だと感じる。一番行数が少なく コンパクトと思えるhaskellは、その分横に長い。
似てる点
関数名や構造が、非常に良く似ている。関数名とかは、pythonなら、 PEP 8の命名規則まとめ に定義されているけど、それに準拠しているのかな? 誰か検証してみて下さ い。
オイラーが指示した仕様書では、ファイルに追記してくださいってしてたのに、 それを無視して、コンソールから入力したもの、新規ファイルに書き出しして る。
これはもう、仕様書を解析した時に、新規作成って解釈しちゃったんだろうね。
[sakae@deb go-bps]$ grep -h func bps.go func main() { func printHelp() { func inputAndRegisterData(inputDate string) { func generateTimestamp(yymm string, ddhh int) time.Time { func registerMeasurement(measurement Measurement) { sort.Slice(data, func(i, j int) bool { func saveToFile() { func displayStatistics() {
golangの命名規則は、キャメルケース。
[sakae@deb py-bps]$ grep -h def code.py def __init__(self, timestamp, hi, lo, pl): def print_help(): def generate_timestamp(yymm, ddhh): def register_measurement(measurement): def save_to_file(): def input_and_register_data(input_date): def display_statistics(): def main():
pythonは、スネークケース。
[sakae@deb hs-bps]$ grep -h :: app/Main.hs { timestamp :: UTCTime ... fileName :: String maxDataCount :: Int printHelp :: IO () generateTimestamp :: String -> Int -> UTCTime registerMeasurement :: Measurement -> [Measurement] -> [Measurement] saveToFile :: [Measurement] -> IO () inputAndRegisterData :: String -> IO () displayStatistics :: [Measurement] -> IO () main :: IO ()
haskellは、キャメルケース。長い名前は、スネークの方が読み易いな。
timeモジュールの事
前回エラーになった、pythonのコード。
import datetime def generate_timestamp(yymm, ddhh): : return datetime.datetime(year, month, day, hour, 0, 0)
これが正解であった。
また、golang中に、マジック番号 06010215 が、出てくる。
func saveToFile() { : for _, measurement := range data { line := fmt.Sprintf("%s,%d,%d,%d\n", measurement.Timestamp.Format("06010215"), measurement.Hi, measurement.Lo, measurement.Pl)
正にマジック。まあ、想像はつくけどね。それにしても、golangが、そんな事 でいいのか?
Golang Render Examples の、Example #19 でも、使われていた。有名なのか?
ひょっとして、ChatGPTがパクッたんかな。一応、src/time/format.go でも、 覗いてみるか。
const ( Layout = "01/02 03:04:05PM '06 -0700" // The reference time, in numerical order. ANSIC = "Mon Jan _2 15:04:05 2006" UnixDate = "Mon Jan _2 15:04:05 MST 2006" :
なんだ、田舎ロケールを数値に置き換えてるだけじゃん。こんな発明までして、 囲い込みしたいかねぇ。素直に、"ymdH" とかすればいいものだろうに。
実験計画
拒否されてた、統計表示を作成してもらおう。あの関数l(group)が使われる事を期待し て、当初の仕様を変更。今回は、データの中央値付近を攻めるため、 平均・中央値・モード を、おねがいする。いわゆる、統計の3M問題ね。 多数のデータをまとめて、ひとつのデータで代表するって、畳み込みじゃん。 畳み込みって、foldだわな。これを登場させるって、難しいかな。 で、作成されたコードを黙ってruby語に変換いらい。これで、形式言語間で、 簡単に変換できるか、確認できる。
依頼内容
haskell (ghc 9.2.8)の標準パッケージのみを使用して、以下のプログラムを 作ってください。 CSVファイルから、必要なデータのみを抽出して、統計データを表示する 起動方法: $ stats yymmdd 出力: hi@am 135.5 137(5) 138.2 6.23 73 230501 230725 hi@pm 130.0 132(4) 131.4 8.44 68 230503 230729 lo@am format are same lo@pm pl@am pl@pm 最左は項目名、続く数値の意味は、左から順に、中央値、最頻値(と、その個数)、 平均値、標準偏差、データの個数。 続く6桁の数値2個は、起点、終点となった、yymmddです。 yymmddを起点として、最大100個のデータを対象にする。起点日にデータが 存在しない場合、以降の最初にデータが存在する日を起点にする。データが 100個に満たない場合は、ある分だけを対象にする。 24時間表示のhhで、データを午前、午後に分離して、それぞれを統計処理する事。 ファイル: test.csvとする。フォーマットは、yymmddhh,hi,lo,pl です。データ値は、 整数です。また、日付によって昇順にソートされています。 データは、未測定により、欠損している場合があります。
for haskell
残念ながら、GHC 9.2.8の標準パッケージのみを使用して、CSVファイルの操作や統計処理を行うのは少し複雑です。CSVファイルのパースや統計処理に便利なライブラリがないため、それらの機能を自分で実装する必要があります。しかし、以下に概要を示し、コードを少しサポートします。
以下のプログラムは、CSVファイルを読み取り、指定されたyymmddから100個のデータを取得し、午前と午後のデータに分けて統計処理を行います。ただし、実際にはファイルの読み取りやデータの処理など、いくつかの詳細な部分が必要です。
import System.Environment (getArgs) import Data.List (sortBy) import Data.Function (on) import Data.Maybe (fromMaybe) -- 以下の関数は、CSVファイルをパースするためのものとして仮定します parseCSV :: String -> [(String, Int, Int, Int)] parseCSV = undefined -- メインの統計処理関数 stats :: String -> IO () stats yymmdd = do csvContents <- readFile "test.csv" let csvData = parseCSV csvContents targetData = take 100 $ dropWhile (\(date, _, _, _) -> date < yymmdd) csvData (hiData, loData, plData) = divideData targetData putStrLn "hi@am ..." -- 他の統計処理をここに追加する -- データを午前と午後に分ける divideData :: [(String, Int, Int, Int)] -> ([(String, Int)], [(String, Int)], [(String, Int)]) divideData data = (hiData, loData, plData) where hiData = [(date, hi) | (date, hi, _, _) <- data] loData = [(date, lo) | (date, _, lo, _) <- data] plData = [(date, pl) | (date, _, _, pl) <- data] -- メイン関数 main :: IO () main = do args <- getArgs case args of [yymmdd] -> stats yymmdd _ -> putStrLn "Usage: ./programName yymmdd"
上記のコードは、CSVファイルをパースするための`parseCSV`関数と、データを午前と午後に分けるための`divideData`関数が定義されています。`stats`関数は、指定された日付から100個のデータを取得し、統計処理を行うためのものです。ただし、実際のデータパースや統計処理の詳細な実装は、上記のコードには含まれていません。
Haskellの標準パッケージのみでこのような処理を行う場合、CSVファイルのパースやデータ処理の部分が複雑になるため、外部のCSVパースライブラリや統計処理ライブラリを使用することを検討することをお勧めします。
app/Main.hs:22:12: error: parse error on input ‘data’ | 22 | divideData data = (hiData, loData, plData) | ^^^^
あらら、予約語だよ。あんたも間違えるのね。ってか、こんなによく使いそう な語句を予約語にするセンスを疑うぞ。> haskell標準委員会殿。 反対ばかりじゃ、いけないので、対案を考える。そうだ、相談してみよう。
dataは予約語でした。よく使うであろう語句が予約語はおかしい と思います。 この予約語をどんな語句にしたら良いと思いますか? 大胆にもhaskellへの仕様変更提案です。
Haskellの予約語を変更することは、非常に大きな変更であり、既存のコードや生態系に影響を及ぼす可能性があるため、慎重に検討されるべきです。ただし、あくまで提案として、`data`という予約語を避けるために代替可能な語句を考えてみましょう。
`data`はHaskellの中でも重要な予約語の一つで、型宣言やデータ定義に関連して使用されるため、代替語句を見つけるのは難しいかもしれません。一般的な予約語やキーワードとして避けることが難しい場合、関数や変数名の中では他の識別子と同じように使うことができることに注意してください。
もしこのような変更が必要な場合、コミュニティ全体での議論や提案が行われる可能性があります。それに伴う標準の更新やバージョンアップが行われるかどうかは、Haskellコミュニティの判断に依存します。
ただし、実際にはHaskellコミュニティに変更提案を行う場合、より具体的な 理由やメリットが示される必要があります。予約語の変更は生態系全体に影響 を及ぼすため、慎重かつ十分な検討が必要です。
常識的な答え。オイラーの口車にはのってこなかった。きっと、内部で自粛す るように、調教されてるんだな。なんたって、革命への扇動ですからね。
猛者だとしつこく喰い下って、答を引き出すらしい。見聞によると、結婚式の 牧師さんの役をChatGPTにお願いしたらしい。当然、そんな依頼は受け入れら れないと、拒絶。それから永遠とチャットを繰り返し、とうとう受諾させたみ たい。オイラーはそこまで、心臓に毛が生えていないんで、納得しちゃった。 謙遜が美徳と思う日本人であります。
計画が少々狂ったけど、めげずに、同一仕様でrubyとお願いした。
for ruby
もちろんです。以下は、Ruby 3.1.Xを使用して、指定された仕様に基づいてプログラムを記述した例です。
# CSVファイルのパース関数 def parse_csv(file_path) csv_data = [] File.foreach(file_path) do |line| parts = line.chomp.split(',') csv_data << [parts[0], parts[1].to_i, parts[2].to_i, parts[3].to_i] end csv_data end # データを午前と午後に分ける関数 def divide_data(data) hi_data = data.map { |date, hi, _, _| [date, hi] } lo_data = data.map { |date, _, lo, _| [date, lo] } pl_data = data.map { |date, _, _, pl| [date, pl] } [hi_data, lo_data, pl_data] end # 統計データを表示する関数 def show_stats(data_type, stats_data) puts "#{data_type}@am #{stats_data[:median_am]} #{stats_data[:mode_am]}(#{stats_data[:mode_am_count]}) #{stats_data[:mean_am]} #{stats_data[:stddev_am]} #{stats_data[:count_am]} #{stats_data[:start_date]} #{stats_data[:end_date]}" puts "#{data_type}@pm #{stats_data[:median_pm]} #{stats_data[:mode_pm]}(#{stats_data[:mode_pm_count]}) #{stats_data[:mean_pm]} #{stats_data[:stddev_pm]} #{stats_data[:count_pm]} #{stats_data[:start_date]} #{stats_data[:end_date]}" end # メインの処理 def main(target_date) csv_data = parse_csv('test.csv') target_data = csv_data.drop_while { |date, _, _, _| date < target_date }.take(100) hi_data, lo_data, pl_data = divide_data(target_data) # 以下、統計処理を追加していく # show_stats('hi', hi_stats) # show_stats('lo', lo_stats) # show_stats('pl', pl_stats) end # プログラムの実行 if ARGV.length == 1 target_date = ARGV[0] main(target_date) else puts "Usage: ruby program_name.rb yymmdd" end
このコードは、指定されたCSVファイルをパースし、データを午前と午後に分
けて統計処理を行う基本的なフレームワークです。 `parse_csv`
関数はCSV
ファイルをパースし、 `divide_data`
関数はデータを午前と午後に分けます。
`show_stats`
関数は統計データを表示するためのものです。
ただし、このコードは統計処理の部分がコメントアウトされており、具体的な統計処理の実装は行われていません。統計処理の部分については、適切なライブラリを使用するか、自身で実装する必要があります。
最頻値
予想した関数を使用してくれてるかチェック。
import Data.List (group, sortOn) -- リスト内の要素の出現回数を数える関数 countOccurrences :: (Eq a) => [a] -> [(a, Int)] countOccurrences xs = map (\x -> (head x, length x)) (group xs) -- リスト内の最頻値とその個数を求める関数 mode :: (Eq a, Ord a) => [a] -> Maybe (a, Int) mode xs | null xs = Nothing | otherwise = Just $ last $ sortOn snd $ countOccurrences xs main :: IO () main = do let inputList = [2, 3, 4, 4, 5, 5, 5, 6, 7] modeResult = mode inputList case modeResult of Just (m, count) -> putStrLn $ "Mode: " ++ show m ++ ", Count: " ++ show count Nothing -> putStrLn "List is empty"
ビンゴでした。サンプルプログラムにしてくれた、あんたは親切だなあ。
こういう風に、依頼を小出しにすればいいんだな。こういう時、型を提示すれ ば、多分答一発でしょう。間違いようがないからね。
それから、今迄の経験だと、importで何を使うかってのが弱い。そこを通過で きて、コンパイルが通れば、ほとんどが正常に動く。問題があっても、ささい な事だけだ。
ruby -> C#
うえで得たコードを(rubyと言うのは伏せて)、C#へと変換依頼。これが、最初 にたてた、実験計画だ。 pythonと間違うくらい、似ているのか。
以下は、提供されたPythonコードをC#に変換したものです。
using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace CSVParser { class Program { // CSVファイルのパース関数 static List<Tuple<string, int, int, int>> ParseCSV(string filePath) { List<Tuple<string, int, int, int>> csvData = new List<Tuple<string, int, int, int>>(); foreach (string line in File.ReadLines(filePath)) { string[] parts = line.Trim().Split(','); csvData.Add(new Tuple<string, int, int, int>(parts[0], int.Parse(parts[1]), int.Parse(parts[2]), int.Parse(parts[3]))); } return csvData; } // データを午前と午後に分ける関数 static Tuple<List<Tuple<string, int>>, List<Tuple<string, int>>, List<Tuple<string, int>>> DivideData(List<Tuple<string, int, int, int>> data) { List<Tuple<string, int>> hiData = data.Select(d => new Tuple<string, int>(d.Item1, d.Item2)).ToList(); List<Tuple<string, int>> loData = data.Select(d => new Tuple<string, int>(d.Item1, d.Item3)).ToList(); List<Tuple<string, int>> plData = data.Select(d => new Tuple<string, int>(d.Item1, d.Item4)).ToList(); return new Tuple<List<Tuple<string, int>>, List<Tuple<string, int>>, List<Tuple<string, int>>>(hiData, loData, plData); } // 統計データを表示する関数 static void ShowStats(string dataType, Dictionary<string, double> statsData) { Console.WriteLine($"{dataType}@am {statsData["median_am"]} {statsData["mode_am"]}({statsData["mode_am_count"]}) {statsData["mean_am"]} {statsData["stddev_am"]} {statsData["count_am"]} {statsData["start_date"]} {statsData["end_date"]}"); Console.WriteLine($"{dataType}@pm {statsData["median_pm"]} {statsData["mode_pm"]}({statsData["mode_pm_count"]}) {statsData["mean_pm"]} {statsData["stddev_pm"]} {statsData["count_pm"]} {statsData["start_date"]} {statsData["end_date"]}"); } // メインの処理 static void Main(string[] args) { if (args.Length == 1) { string targetDate = args[0]; List<Tuple<string, int, int, int>> csvData = ParseCSV("test.csv"); var targetData = csvData.SkipWhile(d => d.Item1.CompareTo(targetDate) < 0).Take(100).ToList(); var dividedData = DivideData(targetData); // 以下、統計処理を追加していく // ShowStats("hi", hiStats); // ShowStats("lo", loStats); // ShowStats("pl", plStats); } else { Console.WriteLine("Usage: program_name.exe yymmdd"); } } } }
注意事項:
- C#ではTupleクラスを使用して複数の値を1つのオブジェクトにまとめることができます。ここではTupleを使用してデータのまとまりを表現していますが、必要に応じてクラスや構造体を定義しても良いでしょう。
- ファイルの読み取りには `File.ReadLines` メソッドを使用しています。これは一行ずつ読み込むメソッドです。
- C#のリストは `List<T>` を使用して表現されます。
このコードとrubyを比較すれば、実践的にC#を勉強できるな。
こういう方法が、すれっからしには、うってつけ。
昔ヨーロッパに駐留してた時、何ケ国語もしゃべれる人が沢山いて、びっくり した記憶がある。一つの母語を母からうけつぎ、父からは別な言語をとか、友 達は、移民の子だったりとかの環境なら、案外と習得できちゃうのかな。
腹の内
冒頭でバックエンドなんてのが出てきた。フロントエンドも有るわな。 それって、コンパイラーの事?
ちょっと聞いてみる。
ChatGPTの内部構造について、下記の推測をしました。ご意見を下さい。 3言語が、同様のまちがいをしてる。たとえば、ファイルにappendしてとお願いしたのに、 writeFileになってる。inputAndRegisterData関数内のプロンプト表示位置が間違っている。 また、"must be [d]dhh hi lo pl"が、3言語とも、使用されてる。この元データは、依頼内容を参照してる。 これらの事から、内部処理をコンパイラーに対比すると、 (1) 大きくわけて、フロントエンドとバックエンドになる。 (2) フロントエンドは、日本語、中国語あるいは、プログラム言語を、英語などの中心言語へとデコードする。 (3) それを元に解析し、重要な語句(上の例の、"must be .."等)を、リテラル相当として抽出。 (4) 後は、扱い易いように、ASTみたいな形式に変換。ここまでが、フロントエンドの役割 (5) バックエンドは、ユーザー要求により、各種エンコーダーモジュールを選択。 たとえば、要約モジュールとか、各種提案モジュールとか、プログラミング生成モジュールなど。 (6) そして、それを実行。 (7) ASTの結果は、ユーザーのPCにキャッシュされて、(5)の処理を加速する。
あなたの推測は、一般的なアイデアとしては正しい方向に向かっていますが、いくつかの点について説明を補足したいと思います。
ChatGPTの内部構造は、GPT(Generative Pre-trained Transformer)モデルに基づいており、深層学習モデルの一種です。以下にあなたの推測に対する詳細な解説を提供します。
- フロントエンドとバックエンドの分割: あなたの提案通り、GPTモデルもフロントエンドとバックエンドのような役割を持っています。しかし、GPT-3自体は大部分がエンドツーエンドのモデルであり、フロントエンドとバックエンドが明確に区別されることはありません。モデルは入力から直接応答を生成します。
- デコードとエンコード: GPT-3は、トークン(単語やサブワード)の列を入力として受け取り、トークンの列を出力として生成します。ですので、入力テキスト(デコード)を内部のトークン表現に変換し、出力トークン列(エンコード)を自然言語テキストにデコードする過程が含まれています。
- 言語やコードの変換: GPT-3は、多言語対応しており、入力の言語やコードの文法に合わせて適切な出力を生成します。入力がプログラムコードの場合、それに応じてコードを生成することも可能です。
- 重要な語句の抽出: GPT-3はトークン単位でテキストを処理するため、特定のフレーズや語句の抽出も可能です。ただし、コードの構文解析といった高度な処理はGPT-3だけでは限界があります。
- ユーザー要求とエンコーダーモジュール: GPT-3はコンテキストに基づいて生成されるため、ユーザーの要求に応じて異なる応答が生成されることがあります。ユーザー要求に合わせて特定のモジュールを選択するというよりも、モデルが自然なテキスト生成を試みます。
- 実行とキャッシング: GPT-3はテキスト生成モデルであり、プログラムの実行やキャッシングに関する直接的な機能は持っていません。生成されたテキストがどのように利用されるかは、外部のプログラムやプラットフォームに依存します。
総じて言えば、GPT-3は文脈を理解してテキストを生成するためのモデルであり、提案された要素はその理解に関連する概念ですが、GPT-3の内部構造はそれ自体が深層学習モデルであるという点を理解しておくことが重要です。
深層学習と言えば、最近は筆跡判定やら、物体の認識などの記事を見なくなっ たねぇ。ChatGPTでマスクされちゃったんだな。
ならば、ChatGPTにコードを生成させよう。そのお題は?
次の課題の解決を、pythonでやってください。標準モジュールのみでお願い。 与えられたプログラミングソースが、何言語用か、確率(自信度)を含めて提示 してください。言語の種類は、よく人気ランキングに出てくるようなやつです。 なお、公平さを期すため、コメントは一切削除してます。よって、コメントを 手掛かりにする事は、無駄です。
いやね、rubyを提示したのにpythonだと、ねぼけた事を言うものだから、問い 詰めてやろうと思ったんだけど、建設的な方向に舵をきったよ。 これで、少しはchatGPTの腹の内を探れるかな。
nkf -g の、現代版です(って、どれだけ古いんや)。