DeepSeekとChatGPTを比較検証

DeepSeekとは

DeepSeekとは、中国のスタートアップ企業「DeepSeek」が開発したLLMです。
特に話題となっているのが、DeepSeek R1というモデルで、
今までの学習手法と異なる手法で学習され、最先端のLLM「GPT o1」に匹敵する精度を誇りつつ、学習に必要なマシンパワー、GPUの大幅な削減に成功し、大きな注目を集めています。
色々疑惑もありそうなモデルですが、今回はこのDeepSeekの性能について簡単に検証していたいと思います。

比較対象

以下のモデルに対して、同じプロンプトを与え出力結果が想定通りか検証します。

検証モデルモデルの説明
GPT-4o幅広いタスクに対応する汎用的な言語モデル
DeepSeek V3幅広いタスクに対応する汎用的な言語モデル
GPT-o1複雑な推論に特化しているモデル。出力の前に深い考察を行う。
DeepSeek R1複雑な推論に特化しているモデル。出力の前に深い考察を行う。

Cobolコードの変換で検証

今回は、キーブレイク処理を行っているCobolコードを変換してみます。
年単位の集計、年かつ月単位の集計を行うコードです。
以下のようなレコードがあったときに、

Year: 2023, month: 01, suji: 10
Year: 2023, month: 01, suji: 20
Year: 2023, month: 02, suji: 5
Year: 2023, month: 02, suji: 3
Year: 2024, month: 01, suji: 15

以下の様に、年かつ月単位に、月の合計、それまでの年の合計が出力されるのが正しいコードです。

Year: 2023-01, Monthly:  30, YearlyTotal:  30
Year: 2023-02, Monthly:   8, YearlyTotal:  38
Year: 2024-01, Monthly:  15, YearlyTotal:  15

プロンプト

以下のようなプロンプトでステップバイステップで順を追ってPythonコードに変換してもらいたいと思います。

以下のコボルコードをPythonに変換してください。
1.コードが何を行っているか理解してください。
2.Cobol文化を踏襲せず、pythonの最適なコードを生成してください。
3.構造体を定義してください。
4.必要な関数を定義してください。
5.ファイルの読み込みは不要です、コード内にJsonでサンプルを用意してください
6.ファイルの出力は不要です、コード内でPrintしてください。

~~~~Cobolコードは省略~~~~

GPT-4oの結果

生成されたPythonコードの実行結果がこちらです

OutputRecord(year='2023', month='01', suji=30, suji_all=30)
OutputRecord(year='2023', month='02', suji=8, suji_all=38) 
OutputRecord(year='2024', month='01', suji=15, suji_all=53)

惜しいですが不合格です。
月の合計(suji)までは仕様通りですが、年の合計(suji_all)が全ての合計になってしまっています。

DeepsSeek v3の結果

DeepSeek V3で生成されたPythonコードの実行結果がこちらです。

Year: 2023, Month: 01, Suji: 30, Suji All: 30
Year: 2023, Month: 02, Suji: 8, Suji All: 38
Year: 2024, Month: 01, Suji: 15, Suji All: 53

GPT-4oと同じですね。惜しいですが不合格です。

GPT-o1の結果

いよいよ本丸の検証です。まずは現状最高峰の「GPT-o1」です。
o1は「思考の連鎖」を行うLLMです。答えを出すまでに深く考察し、Pythonコードを生成します。
実行結果はこちらです。

Year: 2023, Month: 01, Sum(Month): 30, Sum(Year): 30
Year: 2023, Month: 02, Sum(Month): 8, Sum(Year): 38
Year: 2024, Month: 01, Sum(Month): 15, Sum(Year): 15

流石です。正解です。
きちんと年単位の合計ということを理解して、2024年になったらリセットしてくれています。

GPT-o1は出力の前に深い考察を行うことによって精度を高めています。
以下に考察してくれた内容を添付しておきます。英語です。

▼こちらがGPT-o1が深く考察した内容です(クリックで展開)▼

Piecing together dataCOBOL code reads a file “in01.txt”, picking out year, month, and suji from each line to process data, focusing on key breaks by year and month.Setting totals and checking consistencyI start by collecting the initial SUJI values. Then, I loop through records to ensure year and month match, aggregating SUJI values accordingly.Switching gearsI adjust the key and reset aggregators when year or month shifts. Finally, I output the old key’s sums and reset for the new year’s initial SUJI.Charting the courseI’m considering using Python with JSON data, defining data structures, and crafting functions to process records. The goal is clear: efficiently compute sums and provide outputs.Juxtaposing sumsI’m piecing together initial values for consistency in the Python version. This balance ensures the ongoing calculation of monthly and yearly sums, aligning precisely with COBOL logic.Streamlining the processI’m mapping out how to handle records in Python: setting initial sums, flushing monthly sums, updating monthly and yearly sums, and ensuring the record’s data is accurately stored.Pulling together recordsWrapping up by outputting the last monthly group, ensuring each month’s data is captured in separate lines, and keeping the yearly sum updated. Progress is evident in these final steps.Organizing resultsI’m mapping out how to store and structure results in a list, ensuring each entry includes year, month, sum_for_month, and sum_for_year_so_far.Forming a planI’m building a Python function to process records, using dataclasses to manage data structures, and detailing the steps for summing monthly and yearly totals.Ensuring alignmentOK, let me see—I’m checking that Python records the old key’s sums when key changes or EOF is reached, matching COBOL’s partial line output for year or month transitions.Piecing together final touchesI’m working on finalizing the solution, focusing on the critical logic for handling records and ensuring code structure. Each part clearly contributes to the overall solution.Mapping out the solutionI’m finalizing the Python solution, ensuring it intricately follows COBOL logic. The emphasis is on Python’s handling of data and fitting everything together seamlessly.

DeepSeek R1の結果

そしてこちらが、最高峰の「GPT-o1」並みの精度を誇るDeepSeek R1です。
ちなみに、「GPT-o1」はお金を払わないとアクセスすらできませんが、こちらは無料です。
実行結果はこちらです。

Year: 2023-01, Monthly:  30, YearlyTotal:  30
Year: 2023-02, Monthly:   8, YearlyTotal:  38
Year: 2024-01, Monthly:  15, YearlyTotal:  15

お見事です、「GPT-o1」と同じく、仕様通りに変換することが出来ました。

同じく、以下に考察してくれた内容を添付しておきます。
読んでみるとすごく真剣に考察してくれていることがわかると思います。
GPT-4oやDeepSeek V3が間違った点に着目し、何度も考察を繰り返したうえで、答えに辿り着いています。

▼こちらがDeepSeek R1が深く考察した内容です(クリックで展開)▼

まず、Cobolのコードを理解する必要があります。このプログラムは、入力ファイルから年(YEAR)と月(MONTH)ごとに数値(SUJI)を集計し、出力ファイルに書き出す処理を行っています。キーブレイク処理を使用して、同じ年月のデータを合計し、新しい年月になった時に出力しているようです。

Pythonに変換する際のポイントは、Cobolのファイル操作を模倣せず、Pythonらしい構造を使うことです。ファイルの代わりにJSONデータを使用し、データを読み込んで処理します。また、キーブレイク処理は、グループ化して集計する方法に置き換えると効率的です。

まず、入力データを表す構造体を定義します。Pythonではdataclassを使うと良いでしょう。次に、入力データをJSON形式でサンプルとして用意します。データを読み込んだ後、年と月でグループ化し、各グループの合計を計算します。さらに、累積合計(SUJI-ALL)も管理する必要があります。これは、前のレコードまでの累積を保持しながら処理する必要があるため、ループ内で逐次更新します。

具体的な手順としては、以下のようになります:

  1. 入力データを年と月でソートする(Cobolの処理は順次読み込みなので、ソートされている前提かも)。
  2. 各レコードを順に処理し、現在のキー(年、月)が前のキーと同じかどうかをチェック。
  3. 同じ場合は合計を加算し、異なる場合は結果を出力用リストに追加し、新しいキーで合計をリセット。
  4. 累積合計は全体の合計を保持するため、各レコードの値を加算し続ける。

ただし、元のCobolコードではキーブレイクが年と月の両方で行われているので、Pythonでも同じ年と月のグループで集計しつつ、累積合計は全データの合計ではなく、おそらく各グループの累積ではないかもしれません。コードを注意深く確認すると、WK-SUM-SUJI-ALLは各グループ内での合計ではなく、全体の累積のように見えます。例えば、最初のグループのSUJI-ALLはそのグループの合計、次のグループでは前のグループの合計に加算される? それとも全体の合計を保持する?

元のコードを見ると、キーブレイクの際にWK-SUM-SUJI-ALLにIN01-SUJIを加算している部分があります。例えば、最初のREAD後、WK-SUM-SUJIとWK-SUM-SUJI-ALLの両方にIN01-SUJIを代入しています。その後、同じキーの場合は両方に加算し、異なるキーの場合(年が同じで月が異なる場合)はOUT01-SUJI-ALLにWK-SUM-SUJI-ALLを代入し、新しい月のSUJIを加えます。さらに、年が異なる場合も同様です。つまり、SUJI-ALLは年ごとの累積ではなく、全体の累積かもしれません。しかし、コードをよく見ると、年が変わるときにSUJI-ALLをリセットしているかどうかがポイントです。

例えば、最初のREAD後、WK-SUM-SUJI-ALLにIN01-SUJIを代入。次のレコードで同じ年なら、SUJI-ALLに加算。年が変わった場合、出力時にSUJI-ALLを書き出し、新しい年の最初のSUJIを代入する。この場合、SUJI-ALLは年ごとの合計になるかもしれません。しかし、元のコードのロジックが少し複雑なので、正確に把握する必要があります。

おそらく、SUJIは月ごとの合計、SUJI-ALLは年ごとの累積合計ではないかと考えられます。例えば、同じ年で異なる月の場合、SUJIは月ごとの合計、SUJI-ALLは年全体の合計。年が変わるときにSUJI-ALLをリセットする。しかし、元のコードのロジックを詳細に確認します。

元のコードの処理フロー:

  • 初期処理で最初のレコードを読み込み、キー(年、月)をセット。SUJIとSUJI-ALLにその値を代入。
  • 次のレコードを読み込み、新しいキーと古いキーを比較。
  • 年が同じ場合:
  • 月が同じならSUJIとSUJI-ALLに加算。
  • 月が異なる場合、出力を行い、新しい月の値をSUJIにセット。SUJI-ALLには前の月の合計に加える?
  • 年が異なる場合、出力し、新しい年の値をセット。SUJI-ALLは新しい年の値のみ?

しかし、実際のコードを見ると、年が同じ場合のELSE節(月が異なる場合)では、WK-SUM-SUJI-ALLにIN01-SUJIを加算しています。同様に、年が異なる場合でもSUJI-ALLにIN01-SUJIを代入しています。このため、SUJI-ALLは全体の累積ではなく、各キーグループ(年)ごとの累積合計のようです。つまり、SUJIは月ごとの合計、SUJI-ALLは年ごとの合計となります。

しかし、元のコードのロジックを正確にトレースする必要があります。例えば、最初のレコードが年2023、月01、値10の場合、SUJI=10、SUJI-ALL=10。次に同じ年で月01のレコードが来たら、SUJI=20、SUJI-ALL=20。次に同じ年で月02のレコードが来たら、前の月のSUJI(20)を出力し、SUJIを新しい月の値(例えば5)、SUJI-ALL=20+5=25。その後、同じ年で月02のレコードが続くと、SUJI=5+…、SUJI-ALL=25+…。年が変わった場合、SUJI-ALLは新しい年の最初の値になる。

したがって、SUJI-ALLは年ごとの累積合計です。このロジックをPythonで再現する必要があります。

Pythonでのアプローチ:

  • データを年、月でソートする必要がある(元のCobolコードは入力ファイルがソート済みである前提)。
  • 年ごとの累積合計(suji_all)と月ごとの合計(suji)を保持。
  • 現在の年と月を追跡し、年が変わったら累積合計をリセット、月が変わったら月合計をリセット。

ただし、Cobolコードではキーブレイク処理で年と月の両方をチェックしているため、年または月が変わったときに出力を生成します。Pythonでは、入力データをグループ化する方法がありますが、ここではデータがソートされていることを前提として、ループ処理で前のレコードと比較しながら処理します。

具体的には、以下のステップ:

  1. サンプルデータをJSONで作成。
  2. データを年、月でソート(Cobolの入力ファイルがソート済みと仮定)。
  3. 現在の年、月、月合計、年累積合計を保持する変数を初期化。
  4. 各レコードを順に処理:

a. 年が変わった場合、前の年のデータを出力し、年累積合計をリセット。

b. 月が変わった場合、前の月のデータを出力し、月合計をリセット。

c. 現在の月合計と年累積合計に値を加算。

  1. 最後のデータを出力。

ただし、Cobolコードでは年と月の両方が変わる場合、年が優先されます。つまり、年が変わった場合、月が同じでも出力されます。

Pythonコードの構造:

  • データクラスを定義(InputData, OutputData)。
  • JSONサンプルデータをリストで作成。
  • データを年、月でソート。
  • 現在のキー(年、月)を追跡しながら集計。
  • 結果をリストに保存し、printする。

コード例:

from dataclasses import dataclass

import json

from typing import List

@dataclass

class InputData:

year: str

month: str

suji: int

@dataclass

class OutputData:

year: str

month: str

suji: int

suji_all: int

def main():

# サンプルデータをJSON形式で用意

sample_json = '''

[

{"year": "2023", "month": "01", "suji": 10},

{"year": "2023", "month": "01", "suji": 20},

{"year": "2023", "month": "02", "suji": 5},

{"year": "2024", "month": "01", "suji": 15},

{"year": "2024", "month": "01", "suji": 25}

]

'''

input_data = [InputData(**item) for item in json.loads(sample_json)]

# 年、月でソート(Cobolの入力がソート済みと仮定)

input_data.sort(key=lambda x: (x.year, x.month))

output = []

current_year = None

current_month = None

suji_sum = 0

suji_all_sum = 0

for record in input_data:

if record.year != current_year:

if current_year is not None:

# 年のキーブレイク:前の年月のデータを出力

output.append(OutputData(current_year, current_month, suji_sum, suji_all_sum))

suji_all_sum = 0  # 年が変わったのでリセット?

current_year = record.year

current_month = record.month

suji_sum = record.suji

suji_all_sum = record.suji

elif record.month != current_month:

# 月のキーブレイク:前の月を出力

output.append(OutputData(current_year, current_month, suji_sum, suji_all_sum))

current_month = record.month

suji_sum = record.suji

suji_all_sum += record.suji

else:

# 同じ年月:合計を加算

suji_sum += record.suji

suji_all_sum += record.suji

# 最後のレコードを出力

if current_year is not None:

output.append(OutputData(current_year, current_month, suji_sum, suji_all_sum))

for out in output:

print(f"Year: {out.year}, Month: {out.month}, Suji: {out.suji}, Suji All: {out.suji_all}")

if __name__ == "__main__":

main()

しかし、このコードは元のCobolのロジックを完全に再現していない可能性があります。特に、SUJI-ALLの扱いについて再考が必要です。元のCobolコードでは、SUJI-ALLは年が変わってもリセットされていないかもしれません。例えば、年が同じ場合の月キーブレイク時にはSUJI-ALLは加算され続け、年が変わった場合のみリセットされるかもしれません。

元のCobolコードを再確認します:

初期処理の後、主処理のループ内で、新しいレコードを読み込みます。

キーブレイク1(年)とキーブレイク2(月)があります。

年が同じ場合:

  • 月も同じなら、SUJIとSUJI-ALLに加算。
  • 月が異なる場合、出力し、SUJIを新しい月の値にセット。SUJI-ALLには前のSUJI-ALL + 新しいSUJIをセット。

年が異なる場合:

  • 出力し、SUJIとSUJI-ALLを新しい年の値にセット。

したがって、SUJI-ALLは年ごとの累積ではなく、全体の累積ではなく、年と月のグループに関わらず、各キーブレイクで加算され続けるようです。しかし、コードをよく見ると、年が変わったときには、SUJI-ALLを新しい年の最初の値にセットしています。例えば:

ELSE節(年が異なる場合):

MOVE IN01-SUJI TO WK-SUM-SUJI

MOVE IN01-SUJI TO WK-SUM-SUJI-ALL

つまり、年が変わった場合、SUJI-ALLはその年の最初の値になります。したがって、SUJI-ALLは年ごとの累積合計ではなく、各レコードのSUJIを単純に加算したものではなく、キーブレイク処理の際にどのように更新されるかによります。

元のCobolコードのロジックを再確認:

  • 初期処理で最初のレコードを読み込み、WK-SUM-SUJIとWK-SUM-SUJI-ALLにIN01-SUJIを代入。
  • 次のレコードが同じ年の場合:
  • 同じ月なら両方に加算。
  • 異なる月の場合、出力してWK-SUM-SUJIを新しい月の値に、WK-SUM-SUJI-ALLは前の値 + 新しい月の値。
  • 年が異なる場合、出力してWK-SUM-SUJIとWK-SUM-SUJI-ALLを新しい年の値にリセット。

つまり、SUJI-ALLは同じ年の中では、月が変わっても累積され続け、年が変わるとリセットされる。つまり、SUJI-ALLは年ごとの累積合計です。

したがって、Pythonコードでは、年ごとにSUJI-ALLを管理する必要があります。年が変わったときにSUJI-ALLをリセットします。

修正したコード:

  • current_yearとcurrent_monthを追跡。
  • 同じ年の中では、SUJI-ALLは加算され続ける。
  • 年が変わるとき、SUJI-ALLは新しい年の最初の値になる。

コード例:

from dataclasses import dataclass

import json

from typing import List

@dataclass

class InputData:

year: str

month: str

suji: int

@dataclass

class OutputData:

year: str

month: str

suji: int  # 月ごとの合計

suji_all: int  # 年ごとの累積

def process_records(input_data: List[InputData]) -> List[OutputData]:

if not input_data:

return []

# 年、月でソート

sorted_data = sorted(input_data, key=lambda x: (x.year, x.month))

output = []

current_year = sorted_data[0].year

current_month = sorted_data[0].month

suji_month = sorted_data[0].suji

suji_year = sorted_data[0].suji

for record in sorted_data[1:]:

if record.year == current_year:

if record.month == current_month:

suji_month += record.suji

suji_year += record.suji

else:

# 月のキーブレイク:出力

output.append(OutputData(current_year, current_month, suji_month, suji_year))

current_month = record.month

suji_month = record.suji

suji_year += record.suji

else:

# 年のキーブレイク:出力

output.append(OutputData(current_year, current_month, suji_month, suji_year))

current_year = record.year

current_month = record.month

suji_month = record.suji

suji_year = record.suji

# 最後のレコードを出力

output.append(OutputData(current_year, current_month, suji_month, suji_year))

return output

def main():

# サンプルデータ

sample_json = '''

[

{"year": "2023", "month": "01", "suji": 10},

{"year": "2023", "month": "01", "suji": 20},

{"year": "2023", "month": "02", "suji": 5},

{"year": "2023", "month": "02", "suji": 3},

{"year": "2024", "month": "01", "suji": 15}

]

'''

input_items = json.loads(sample_json)

input_data = [InputData(**item) for item in input_items]

results = process_records(input_data)

for result in results:

print(f"{result.year}-{result.month}: Suji={result.suji}, SujiAll={result.suji_all}")

if __name__ == "__main__":

main()

このコードでは、年が同じ場合、月が変わるときに前の月の合計を出力し、suji_year(年累積)には新しい月の値を加算します。年が変わると、前の年のデータを出力し、suji_yearを新しい年の最初の値でリセットします。

例えば、サンプルデータの場合:

2023-01の2レコード(10+20=30)→ suji_month=30、suji_year=30+5+3=38?

Wait, no. サンプルデータの処理フローを確認します。

最初のレコード(2023-01,10):

current_year=2023, current_month=01, suji_month=10, suji_year=10.

次のレコード(2023-01,20):

月が同じなので、suji_month=30, suji_year=30.

次のレコード(2023-02,5):

月が異なるので、出力を追加(2023-01,30,30)。suji_month=5, suji_year=30+5=35.

次のレコード(2023-02,3):

月が同じ→ suji_month=8, suji_year=35+3=38.

次のレコード(2024-01,15):

年が異なる→ 前のデータを出力(2023-02,8,38)。current_year=2024, suji_month=15, suji_year=15.

最後に出力(2024-01,15,15).

結果:

2023-01: Suji=30, SujiAll=30

2023-02: Suji=8, SujiAll=38

2024-01: Suji=15, SujiAll=15

しかし、元のCobolコードのロジックでは、SUJI-ALLは年ごとの累積ではなく、各キーブレイク時にどのように更新されるかを正確に再現する必要があります。

Cobolコードのロジックの再確認:

例えば、年が同じで月が異なる場合:

  • 前の月の合計を出力。
  • suji_monthを新しい月の値に。
  • suji_allには前のsuji_all + 新しい月の値。

年が異なる場合:

  • 前の年・月の合計を出力。
  • suji_monthとsuji_allを新しい年の値に。

この場合、suji_allは年ごとの累積ではなく、処理全体の累積のように見えますが、年が変わるとリセットされます。

元のCobolコードでのSUJI-ALLの動き:

例:

レコード1: 2023-01,10 → SUJI=10, SUJI-ALL=10

レコード2: 2023-01,20 → SUJI=30, SUJI-ALL=30

レコード3: 2023-02,5 → 月キーブレイク。出力: SUJI=30, SUJI-ALL=30。その後、SUJI=5、SUJI-ALL=30+5=35

レコード4: 2023-02,3 → SUJI=8, SUJI-ALL=38

レコード5: 2024-01,15 → 年キーブレイク。出力: SUJI=8, SUJI-ALL=38。その後、SUJI=15, SUJI-ALL=15

最後に、最後のレコードを出力: SUJI=15, SUJI-ALL=15

出力されるデータ:

2023-01: 30,30

2023-02:8,38

2024-01:15,15

これがCobolコードの挙動と考えられます。したがって、Pythonコードでは、suji_allは同じ年の中では累積され、年が変わるとリセットされます。そのため、年ごとにsuji_allを保持する必要があります。

上記のPythonコードは、この挙動を正確に再現しています。例えば、サンプルデータの場合、出力は上記のようになります。

よって、このコードで要件を満たしていると考えられます。

まとめ

学習手法を変更するというアプローチでここまでの精度を実現しつつ、
大幅なコスト削減が行えている点は技術者の執念を感じます。
まだまだAIの進歩は続きそうですね。

お問い合わせ

弊社ではLLMを活用し、業務改善、新たな価値をご提案しております。
ChatGPT活用環境の構築、Dify導入、エージェント開発、RAGシステム構築などに
興味がある方や、よくわからないからもっと知りたい方は是非ともご相談下さい。

株式会社システムコンサルタント 営業統括部