💊

chatGPT先生に教わる機械学習 ~SIGNATE competition「医学論文の自動仕分けチャレンジ」編~

2023/08/22に公開

1.はじめに

少し前のことですが、とても興味深い記事を読みました。

https://zenn.dev/seiyakitazume/articles/bc11bbd020cdfe

タイトルにある通り、データサイエンス初心者がchatGPTのサポートを受けながらモデル構築を行い、SIGNATEコンペティションの上位にランクインした、というのです。

筆者も本業でchatGPTを使うことはありますが、主な用途は「英語⇔日本語の翻訳」や「暇つぶしのお遊びレベル」でした。そのため本記事の内容は

そうか、、こういう使い方があったか

と目からウロコモノでした。

前回記事に書いたように、筆者はプログラミングや機械学習の勉強を独学で始めて以来、挫折→充電→再燃→挫折→・・・の無限ループに陥っていました。

プログラミング初学者あるあるかと思いますが、挫折の主な原因は以下です。

  • 複雑なコードがいつまでたっても書けるようにならない
  • わからないコードの意味を調べても初学者にわかりやすい解説がなかなか見つからない

しかしながら、上の記事をみて

「chatGPTにサポートしてもらいながらであればコードもかけるし、わからないところは何度でも教えてもらえる」

ということに気づきました。

何より、わからないところを気兼ねなく何度でも質問できるというのはかなり頼りになる存在ですよね。

そもそも筆者も少し勘違いしていたのですが、以下のツイートにもある通りプログラミング熟練者でもゼロから自力でコードを書き上げることは多くないようです。

https://twitter.com/OmathinB/status/1693191397623640137

そこで筆者も上記記事を参考に、chatGPT先生にサポートをしてもらいながらのプログラミング/機械学習モデルの構築を行っていきたいと思います。

今回のチャレンジにおける具体的な手段や目的は以下になります。

① ある具体的なテーマのもとchatGPT先生に聞きながらコーディング/機械学習モデルを構築
② 構築段階でわからないところはchatGPT先生に逐一質問
完成したコードやモデルについてchatGPT先生にわかりやすく解説してもらう

今回、特に③が最も重要であると考えております。というのも、これまでも書籍に記載されている、もしくは誰かが構築したモデルを見ても、結局は理解が及ばずに挫折モードに進んでいたからです…。

というわけで、初回からだいぶ長くなってしまいましたが以下まとめていきたいと思います。

2.題材

今回、題材としてSIGNATESOTA Challengeのひとつである「医学論文の自動仕分けチャレンジ」を選択しました。

https://signate.jp/competitions/595

選んだ理由は、本職で医学・科学論文を読むことが多々あり、比較的とっつきやすいテーマだったからです。

コンペティションの概要説明にもある通りですが、本コンペティションの目標タスクは端的に言うと

「システマティックレビュー作成の効率化・省力化を目指し、網羅的に収集された論文の中から目的に合致した論文を選別するための機械学習アルゴリズムを構築する」

であり、より具体的には

「収集された論文のタイトルおよび抄録のテキストデータを用いて、システマティックレビューの対象となる文献か否か(2値)を判定するアルゴリズムを作成する」

になります。

3.事前準備

以下の手順で各種事前準備を行います。

3-1. 使用環境

前回同様、以下の環境下で行いました。

  • Google Colaboratory
  • Google Drive

3-2.使用するデータセットのダウンロード

SIGNATEのコンペティションでは、通常使用するデータセットが該当ページからダウンロード可能です。

本コンペティションでは、以下の3つのデータセットが提供されています。

  • 学習用データ (train.csv)
  • 評価用データ (test.csv)
  • サンプル投稿ファイル (sample_submit.csv)

上記3つのファイルをダウンロードしたのち、Google Drive内に以下のような階層で作成した「data」フォルダ内に格納しました。

<Google Drive内のフォルダ構成>

My Drive
-----Colab Notebooks
----------SRWS-PSG 
---------------data
-----------------train.csv
-----------------test.csv
-----------------sample_submit.csv

3-3.GPU環境の確認とドライブの準備

こちらも前回記事同様、以下のコードを実行することで、GPU環境の確認、Google Driveの紐づけ、カレントディレクトリの指定をします。

#GPU環境の確認
import tensorflow as tf
tf.test.gpu_device_name()
#Google Driveと紐づける
from google.colab import drive
drive.mount('/content/drive')
#カレントディレクトリに移動
%cd "/content/drive/My Drive/Colab Notebooks/SRWS-PSG"

4.基礎的なモデルの実装

事前準備が整いましたので、ここからいよいよchatGPT先生の門下に入りご指導いただくことにします。

4-1.chatGPT先生に詳細な説明をする

chatGPT先生に師事するにあたり、今回実装したいモデルの詳細、条件、環境等を事細かに説明し、お手伝いをしてほしい旨も伝えます。

すると、chatGPT先生からさっそく以下のように具体的なコード付きで回答を頂けました。
(コードの簡単な補足説明もありましたが、その部分は今は省略して記載します)

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# データの読み込み
train_data = pd.read_csv('train.csv')
test_data = pd.read_csv('test.csv')

# データの前処理
# 文字化けや欠損値の処理、タイトルと抄録の結合などを行う

# 訓練データと検証データに分割
train_set, val_set = train_test_split(train_data, test_size=0.2, random_state=42)

# TF-IDFベクトル化
tfidf_vectorizer = TfidfVectorizer(max_features=5000)  # 適切なパラメータを設定
X_train_tfidf = tfidf_vectorizer.fit_transform(train_set['title_abstract_combined'])
X_val_tfidf = tfidf_vectorizer.transform(val_set['title_abstract_combined'])

# ロジスティック回帰モデルの構築と学習
model = LogisticRegression()
model.fit(X_train_tfidf, train_set['judgement'])

# 検証データで評価
val_predictions = model.predict(X_val_tfidf)
accuracy = accuracy_score(val_set['judgement'], val_predictions)
print(f'Validation Accuracy: {accuracy:.4f}')

# テストデータの予測
X_test_tfidf = tfidf_vectorizer.transform(test_data['title_abstract_combined'])
test_predictions = model.predict(X_test_tfidf)

# 提出用ファイルの作成
submission_df = pd.DataFrame({'id': test_data['id'], 'judgement': test_predictions})
submission_df.to_csv('submission.csv', index=False)

初心者が見ても、「おお、それっぽい!」というコードがいきなり得られてちょっぴり感激しました。

ただし、示したもらったコードはあくまでもテンプレートに近い状態なので、アレンジが必要です。

4-2.コードの編集

示されたコードをよく見ると、「データの前処理」において「文字化けや欠損値の処理、タイトルと抄録の結合などを行うと」のコメントアウトがあり、具体的なコードが省略されていました。

そのため、追加の質問をします。

すると、嫌がることなく省略された部分のコードを提案した形で再度コード示してくれました。

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# データの読み込み
train_data = pd.read_csv('train.csv')
test_data = pd.read_csv('test.csv')

# データの前処理
def preprocess_text(text):
    # テキスト内の改行や余分なスペースを削除
    text = text.replace('\n', ' ').replace('\r', ' ').strip()
    return text

# 文字化けや欠損値の処理
train_data['title'] = train_data['title'].apply(preprocess_text)
train_data['abstract'] = train_data['abstract'].apply(preprocess_text)
test_data['title'] = test_data['title'].apply(preprocess_text)
test_data['abstract'] = test_data['abstract'].apply(preprocess_text)

# タイトルと抄録の結合
train_data['title_abstract_combined'] = train_data['title'] + ' ' + train_data['abstract']
test_data['title_abstract_combined'] = test_data['title'] + ' ' + test_data['abstract']

# 訓練データと検証データに分割
train_set, val_set = train_test_split(train_data, test_size=0.2, random_state=42)

# TF-IDFベクトル化
tfidf_vectorizer = TfidfVectorizer(max_features=5000)  # 適切なパラメータを設定
X_train_tfidf = tfidf_vectorizer.fit_transform(train_set['title_abstract_combined'])
X_val_tfidf = tfidf_vectorizer.transform(val_set['title_abstract_combined'])

# ロジスティック回帰モデルの構築と学習
model = LogisticRegression()
model.fit(X_train_tfidf, train_set['judgement'])

# 検証データで評価
val_predictions = model.predict(X_val_tfidf)
accuracy = accuracy_score(val_set['judgement'], val_predictions)
print(f'Validation Accuracy: {accuracy:.4f}')

# テストデータの予測
X_test_tfidf = tfidf_vectorizer.transform(test_data['title_abstract_combined'])
test_predictions = model.predict(X_test_tfidf)

# 提出用ファイルの作成
submission_df = pd.DataFrame({'id': test_data['id'], 'judgement': test_predictions})
submission_df.to_csv('submission.csv', index=False)

このコードの「データの読み込み」の部分を、格納してある学習用データ (train.csv)と評価用データ (test.csv)を指定した形へと修正します。

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# データの読み込み
train_data = pd.read_csv("/content/drive/My Drive/Colab Notebooks/SRWS-PSG/data/train.csv")
test_data = pd.read_csv("/content/drive/My Drive/Colab Notebooks/SRWS-PSG/data/test.csv")

# データの前処理
def preprocess_text(text):
    # テキスト内の改行や余分なスペースを削除
    text = text.replace('\n', ' ').replace('\r', ' ').strip()
    return text

# 文字化けや欠損値の処理
train_data['title'] = train_data['title'].apply(preprocess_text)
train_data['abstract'] = train_data['abstract'].apply(preprocess_text)
test_data['title'] = test_data['title'].apply(preprocess_text)
test_data['abstract'] = test_data['abstract'].apply(preprocess_text)

# タイトルと抄録の結合
train_data['title_abstract_combined'] = train_data['title'] + ' ' + train_data['abstract']
test_data['title_abstract_combined'] = test_data['title'] + ' ' + test_data['abstract']

# 訓練データと検証データに分割
train_set, val_set = train_test_split(train_data, test_size=0.2, random_state=42)

# TF-IDFベクトル化
tfidf_vectorizer = TfidfVectorizer(max_features=5000)  # 適切なパラメータを設定
X_train_tfidf = tfidf_vectorizer.fit_transform(train_set['title_abstract_combined'])
X_val_tfidf = tfidf_vectorizer.transform(val_set['title_abstract_combined'])

# ロジスティック回帰モデルの構築と学習
model = LogisticRegression()
model.fit(X_train_tfidf, train_set['judgement'])

# 検証データで評価
val_predictions = model.predict(X_val_tfidf)
accuracy = accuracy_score(val_set['judgement'], val_predictions)
print(f'Validation Accuracy: {accuracy:.4f}')

# テストデータの予測
X_test_tfidf = tfidf_vectorizer.transform(test_data['title_abstract_combined'])
test_predictions = model.predict(X_test_tfidf)

# 提出用ファイルの作成
submission_df = pd.DataFrame({'id': test_data['id'], 'judgement': test_predictions})
submission_df.to_csv('submission.csv', index=False)

作業開始からものの数分でそれっぽいコードが完成しました。
わくわくしながら上記コードを実行してみると、、、

そうは問屋が卸さない...といったところでしょうか。あえなくエラーメッセージが出力されました。

エラーメッセージの内容を見ても初学者には何をどうすればよいかわからないので、それをそのままコピペしてchatGPT先生に修正方法を質問します。

すると得られた回答が以下です。

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# データの読み込み
train_data = pd.read_csv("/content/drive/My Drive/Colab Notebooks/SRWS-PSG/data/train.csv", encoding='utf-8')  # エンコーディングを指定
test_data = pd.read_csv("/content/drive/My Drive/Colab Notebooks/SRWS-PSG/data/test.csv", encoding='utf-8')  # エンコーディングを指定

# 以下同じコード
# ...

具体的なエラーの内容を教えてくれました。

どうやら「エンコードの指定」を適切に行わないと、今回のケースではエラーが発生するようです。

言われた通りに「encoding='utf-8'」を挿入し、再度実行をしましたが、依然として同様のエラーが出力されました。

その後、エラーが出てはそれをコピペしchatGPT先生の指導をあおぐ、といったことを繰り返し様々なエンコーディング(encoding='shift-jis'、encoding='auto'などなど)を順番に試していきました。

その結果、最終的に以下のコードでエンコーディングに関するエラーは解決しました。

# データの読み込み
train_data = pd.read_csv("/content/drive/My Drive/Colab Notebooks/SRWS-PSG/data/train.csv", encoding='latin1')
test_data = pd.read_csv("/content/drive/My Drive/Colab Notebooks/SRWS-PSG/data/test.csv", encoding='latin1')

一方で、今度は以下のような別のエラーが出力されましたので、これについても引き続きchatGPT先生に助けを求めます。

すると、データの前処理部分に関し修正案を提示してくれました。

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# データの読み込み
train_data = pd.read_csv("/content/drive/My Drive/Colab Notebooks/SRWS-PSG/data/train.csv", encoding='latin1')
test_data = pd.read_csv("/content/drive/My Drive/Colab Notebooks/SRWS-PSG/data/test.csv", encoding='latin1')

# データの前処理
def preprocess_text(text):
    if isinstance(text, str):
        # テキスト内の改行や余分なスペースを削除
        text = text.replace('\n', ' ').replace('\r', ' ').strip()
    return text

# 文字化けや欠損値の処理
train_data['title'] = train_data['title'].apply(preprocess_text)
train_data['abstract'] = train_data['abstract'].apply(preprocess_text)
test_data['title'] = test_data['title'].apply(preprocess_text)
test_data['abstract'] = test_data['abstract'].apply(preprocess_text)

# タイトルと抄録の結合
train_data['title_abstract_combined'] = train_data['title'] + ' ' + train_data['abstract']
test_data['title_abstract_combined'] = test_data['title'] + ' ' + test_data['abstract']

# 訓練データと検証データに分割
train_set, val_set = train_test_split(train_data, test_size=0.2, random_state=42)

# TF-IDFベクトル化
tfidf_vectorizer = TfidfVectorizer(max_features=5000)  # 適切なパラメータを設定
X_train_tfidf = tfidf_vectorizer.fit_transform(train_set['title_abstract_combined'])
X_val_tfidf = tfidf_vectorizer.transform(val_set['title_abstract_combined'])

# ロジスティック回帰モデルの構築と学習
model = LogisticRegression()
model.fit(X_train_tfidf, train_set['judgement'])

# 検証データで評価
val_predictions = model.predict(X_val_tfidf)
accuracy = accuracy_score(val_set['judgement'], val_predictions)
print(f'Validation Accuracy: {accuracy:.4f}')

# テストデータの予測
X_test_tfidf = tfidf_vectorizer.transform(test_data['title_abstract_combined'])
test_predictions = model.predict(X_test_tfidf)

# 提出用ファイルの作成
submission_df = pd.DataFrame({'id': test_data['id'], 'judgement': test_predictions})
submission_df.to_csv('submission.csv', index=False)

教えてもらった修正案のそのままコピペし実行したところ、まだ別のエラーメッセージが出力されたので、くじけずに何度でも先生に聞きます。

# データの前処理
def preprocess_text(text):
    if isinstance(text, str):
        # テキスト内の改行や余分なスペースを削除
        text = text.replace('\n', ' ').replace('\r', ' ').strip()
    else:
        text = ""  # 欠損値を空文字列に置き換え
    return text

ここまでの作業で、だいぶ「それっぽい」コードに仕上がってきました。

4-3.完成したコードの実行と検証結果の出力

上記までで仕上げた修正案を反映させたコードの全体像は以下になりました。

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# データの読み込み
train_data = pd.read_csv("/content/drive/My Drive/Colab Notebooks/SRWS-PSG/data/train.csv", encoding='latin1')
test_data = pd.read_csv("/content/drive/My Drive/Colab Notebooks/SRWS-PSG/data/test.csv", encoding='latin1')

# データの前処理
def preprocess_text(text):
    if isinstance(text, str):
        # テキスト内の改行や余分なスペースを削除
        text = text.replace('\n', ' ').replace('\r', ' ').strip()
    else:
        text = ""  # 欠損値を空文字列に置き換え
    return text

# 文字化けや欠損値の処理
train_data['title'] = train_data['title'].apply(preprocess_text)
train_data['abstract'] = train_data['abstract'].apply(preprocess_text)
test_data['title'] = test_data['title'].apply(preprocess_text)
test_data['abstract'] = test_data['abstract'].apply(preprocess_text)

# タイトルと抄録の結合
train_data['title_abstract_combined'] = train_data['title'] + ' ' + train_data['abstract']
test_data['title_abstract_combined'] = test_data['title'] + ' ' + test_data['abstract']

# 訓練データと検証データに分割
train_set, val_set = train_test_split(train_data, test_size=0.2, random_state=42)

# TF-IDFベクトル化
tfidf_vectorizer = TfidfVectorizer(max_features=5000)  # 適切なパラメータを設定
X_train_tfidf = tfidf_vectorizer.fit_transform(train_set['title_abstract_combined'])
X_val_tfidf = tfidf_vectorizer.transform(val_set['title_abstract_combined'])

# ロジスティック回帰モデルの構築と学習
model = LogisticRegression()
model.fit(X_train_tfidf, train_set['judgement'])

# 検証データで評価
val_predictions = model.predict(X_val_tfidf)
accuracy = accuracy_score(val_set['judgement'], val_predictions)
print(f'Validation Accuracy: {accuracy:.4f}')

# テストデータの予測
X_test_tfidf = tfidf_vectorizer.transform(test_data['title_abstract_combined'])
test_predictions = model.predict(X_test_tfidf)

# 提出用ファイルの作成
submission_df = pd.DataFrame({'id': test_data['id'], 'judgement': test_predictions})
submission_df.to_csv('submission.csv', index=False)

このコードを実行すると、、、

おお、エラーがでなくなりました。無事モデルが構築できたようです。

ここで出力されたValidation Accuracy(検証正答率)は、

「構築したモデルが検証データ(新しいデータ)に対して、どれくらい正確に予測できるかを示しており、今回は約98.29%のデータが正しく分類された」

ということを意味します(ドシロートがぎりぎりわかる範囲です)。


以上、一筋縄ではいかないものの独力では考えられないはやさでここまで到達することができました。

この時点でドシロートの筆者が気づいていない穴も当然あるかと思いますが、改善すべき点等も後程chatGPT先生に聞くとして、まずは構築したモデルの性能を確かめていきます。

5.SIGNATEにてスコアの確認

「なんか結構良い結果がいきなり得られたんちゃうんか」

と喜び勇みながら、構築したモデルの性能をSIGNATEに投稿してスコアで確認します。

カレントディレクトリ内に保存されているsubmission.csvをSIGNATEの「投稿」にアップすることで、すぐさまスコアが確認できます(submission.csvは提出用テンプレートと同じ書式に手動で修正しました)。

すると結果は、、、

、、、スコアひっくぅ、、、

リーダーボードに載っている上位者の暫定評価は軒並み0.9以上なので、今回筆者がはじき出したスコアはだいぶ低いことがわかります。

6.考察

Validation Accuracyが高いのにスコアが低いことから、どこかしらに落とし穴があると考えられます。

落ち着いて今回構築したモデルや結果について振り返りつつドシロートながらに考察したところ、いくつか気づいた点、気になった点がありました。

  1. Validation Accuracyだけだと漠然としている。各クラス(judgement = 0と1)それぞれに対応した正答率を確認する必要がある。題材の性質上、judgement = 0と1の間に不均衡があってもおかしくない。
  2. judgementが0か1のどちらに分類されるかは、titleとabstract中のキーワード(英単語)に基づいているはず。今回のモデルは構築に重要となるキーワードの重み付けがうまくされていないのではないか?

1については、これまたchtaGPT先生に質問し、詳細なクラスレポートを表示できるようにしてみました。

# クラスごとの正答率と詳細なレポートを表示
class_report = classification_report(val_set['judgement'], val_predictions)
print(class_report)

出力結果は以下になりました。

Validation Accuracy: 0.9829

precision recall f1-score support
0 0.9839 0.9989 0.9913 5312
1 0.8333 0.2564 0.3922 117
accuracy 0.9829 5429
macro avg 0.9086 0.6276 0.6917 5429
weighted avg 0.9806 0.9829 0.9784 5429

各パラメータの説明は長くなるのでここでは割愛しますが、クラス 1の Recall (再現率)が0.2564と非常に低いことがわかりました。

これは

「診断精度研究に該当する論文のうち、本モデルにおいて正しく診断精度研究に該当する(judgment = 1)と予測できた割合が約25.64%だった」

ということを示しています。

クラス 0の結果と合わせると

「本モデルにおいては、診断精度研究に該当しない論文(judgment = 1)の正答率は高かったが、その逆は否」

ということだったため、全体の正答率は低くSIGNATEのスコアも低くなったと考えることができます。

まとめ

しょっぱなからかなり長くなってしまったので、今回はここまでにしようと思います。

とりあえず今回の取り組みで分かったことは以下になります。

  1. chatGPT先生に教えてもらいながら基礎的なコーディングは十分可能
  2. 出力がエラーになった場合でもchatGPT先生に聞けばだいたい解決してくれる
  3. コードの意味、アルゴリズム、結果の解釈についてもわかりやすく教えてもらえる(これについては今回の記事では省略しました)

今回は結局高性能なモデルを構築できるには至っていないものの、少なくとも独力では到底たどり着けない完成度にまで割と短時間で到達することができました。

次回以降、引き続きchatGPT先生のサポートを得ながら、モデルの精度の向上とコードおよび構築したモデルの理解を深めていきたいと思います。

ではまた次回。

Discussion