15
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

独自のgpt AIチャットボットを訓練と作成(自分の88本のQiita記事を使って、チャットAIを訓練してみた)

Last updated at Posted at 2023-04-15

この記事では、独自のGPTチャットAIをゼロからトレーニングするプロセスについて説明します。

  • 注:この記事の焦点は、既存のAIモジュールを微調整(Fine-Tuning)することではなく、あなたの入力テキストから会話スキルを学習するAIを訓練することです。
  • OpenAIは多くのリソースと新しい技術を投入しているため、ChatGPTに近いものを訓練することを期待すべきではありません。(笑

十分なデータ(例:Wikipediaのすべてのコンテンツ)があれば、GPT-2に似たモデルをトレーニングすることが可能です。

本記事では、日本語で書かれた 自分の88本のQiita記事 をデータソースとしてAIをトレーニングします(笑
このデータセットは非常に限られており、結果は最適ではありませんのでご注意ください。

この記事では、AIに関する経験がなくても手順に従って進められるように、段階的に説明します。

最終結果

88のQiita記事だけを使って 1万回(Mac miniで約1時間) のトレーニングを繰り返した後:

スクリーンショット 2023-04-15 13.04.59.png

200回の繰り返し学習しかしていない最初のうちは、正しい文章を作ることはできません:

しかし、使っている言葉は、私がQiitaで公開した88本の記事の中から選んだもので、それしか知識がありません。

スクリーンショット 2023-04-15 13.06.57.png

これが2000回のトレーニング後の結果です:

スクリーンショット 2023-04-15 13.07.07.png

約8000回のトレーニングの後

スクリーンショット 2023-04-15 13.05.56.png

より多くの回数を訓練することで、より正確な結果を得ることができます。
ただし、Qiitaの記事88件しか提供していないので(笑
(GPT-2はWebTextという約45テラバイトのテキストデータを含む大規模データセットで学習させた)

スクリーンショット 2023-04-15 13.04.59.png

この記事では、そんなAIを一からトレーニングする方法を紹介します。

環境設定

nanoGPT を使用して、AIチャットモジュールの学習を行うことになります。

リポジトリには、シェイクスピアのようなトレーニング用英語モジュールのコードも含まれています。
https://github.com/karpathy/nanoGPT/tree/master/data/shakespeare

日本語のテキスト入力を受けて日本語を出力するようにカスタマイズし(トークナイザーを置き換える)、
サンプリング機能(質問して回答を得る機能)も変更して、いつでも回答を入力できるようにします(上のスクリーンショットにあるような)。

以下のコマンドはMacOSで実行されていますが、他のシステムでも動作するはずです。

nanoGPTのリポジトリをクローン

git clone https://github.com/karpathy/nanoGPT

MacOSをお使いの場合は、HomeBrewをインストールして:

この記事で使用するすべての必要なライブラリをインストールするコードは次のとおりです:

python3 -m ensurepip

pip3 install transformers datasets tiktoken tqdm wandb numpy fugashi ipadic

pip3 install \   
  --pre torch torchvision torchaudio \
  --extra-index-url https://download.pytorch.org/whl/nightly/cpu

brew install mecab mecab-ipadic

トークナイザ

トークナイザとは何ですか?

AIモジュールは、トークンによって言語を理解します。テキストを入力すると、トークナイザを使用してこれらをトークンに変換します。AIが出力を生成するとき、トークンが生成されます。

そのトークンを、(モジュールのトレーニング時に使用した)同じトークナイザを使ってテキストに戻す必要があります。

スクリーンショット 2023-04-15 15.42.58-2.png

デフォルトで nanoGPT が使用するトークナイザは gpt-2 で、日本語についての理解はそれほど高くありません。そこで、ここでは日本語用のオープンソースのトークナイザを使用します:

Pythonで上記のトークナイザを使う

Pythonで上記のトークナイザをすぐに使うことができます。例えば、以下のコードを.pyファイルに入れます。

def read_markdown_file(file_path):
    if file_path.endswith(".DS_Store"):
        return ""
    with open(file_path, mode="r", encoding="utf-8") as md_file:
        print("Reading", file_path)
        content = md_file.read()
    return content

datasetFolderPath = "/Users/office/Desktop/MyOwnGPT/dataset/"
dataSetFileNames = os.listdir(datasetFolderPath)

entries = ""
for filename in dataSetFileNames:
    file_path = datasetFolderPath + filename
    print("Processing ", file_path)
    markdown_content = read_markdown_file(file_path)
    for line in markdown_content.split("\n"):
        if len(line) > 5:
            entries += line + "\n\n"

n = len(entries)
print("Total input entry length", n) 

これで、実行してトークナイズされた日本語テキストを確認できます。トークナイザモジュールは自動的にHugging faceからダウンロードされます。

スクリーンショット 2023-04-15 15.49.04-2.png

また、トークナイズされたテキストを元の文字列に戻すこともできます:

trainEntries = input("Enter the text to de-tokenize\n")

token_ids = [int(x) for x in trainEntries.strip("[]").split(", ")]
decoded_text = tokenizer.decode(token_ids)

print(decoded_text)

スクリーンショット 2023-04-15 15.59.13-2.png

トークナイザはテキストをいくつかのコンポーネントに分割しています。例えば、「個人開発者」は「個人」、「開発」、「者」となります。

トレーニングデータを準備する

私のトレーニングデータセット(笑)

スクリーンショット 2023-04-15 17.12.52.png

次に、すべてのトレーニングデータを1つのテキストファイルにまとめます。ここでは、私のQiitaの記事(88件)から.mdファイルをすべて変換するためにこのコードを使っています:

def read_markdown_file(file_path):
    if file_path.endswith(".DS_Store"):
        return ""
    with open(file_path, mode="r", encoding="utf-8") as md_file:
        print("Reading", file_path)
        content = md_file.read()
    return content

datasetFolderPath = "/Users/office/Desktop/MyOwnGPT/dataset/"
dataSetFileNames = os.listdir(datasetFolderPath)

entries = ""
for filename in dataSetFileNames:
    file_path = datasetFolderPath + filename
    print("Processing ", file_path)
    markdown_content = read_markdown_file(file_path)
    for line in markdown_content.split("\n"):
        if len(line) > 5:
            entries += line + "\n\n"

n = len(entries)
print("Total input entry length", n) 

さて、そのデータファイルの中身は、次のようなものになります:

また、App Clipのプログラムのファイルサイズは10MB未満でなければなりません。

## ステップ 1. App Clipのターゲットを作成する

トップにあるファイル (File) メニューをクリック、次にニュー (New) オプション、ターゲット (Target) をクリックして下さい。

App Clipを作成するには、ただ新しい  のターゲットをアプリに追加するだけです。

...

トレーニングセットとテスト(試験)セットを用意する

ここで、上記のデータを2つのセットに分け、
トレーニングセットはAIモジュールの学習に使用し、
テストセットはモジュールがどのように動作するかをテストするためのトレーニングプログラムに使用することにします:

trainEntries = entries[:int(n*0.9)]
evalEntries = entries[int(n*0.9):]

print("Train entries:", trainEntries, " eval entries ", evalEntries)

ここでは、上記のテキストの90%をトレーニングに、10%をテストに使っています。

入力テキストをトークンに変換

ここで、AIモジュールの学習プログラムが理解できるように、すべての入力をトークンに変換します。上記の日本語トークナイザーを利用することができます:

max_length = 512  # Maximum sequence length for BERT
tokenizer = BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking', max_len=max_length)

trainTokens = []
train_text_segments = [trainEntries[i:i+max_length] for i in range(0, len(trainEntries), max_length)]
for segment in train_text_segments:
    train_segment_tokens = tokenizer.encode(segment, add_special_tokens=True)
    trainTokens.extend(train_segment_tokens)

evalTokens = []
eval_text_segments = [evalEntries[i:i+max_length] for i in range(0, len(evalEntries), max_length)]
for segment in eval_text_segments:
    eval_segment_tokens = tokenizer.encode(segment, add_special_tokens=True)
    evalTokens.extend(eval_segment_tokens)

print(len(trainTokens), "used for training;", len(evalTokens), "used for eval")

これで、学習用のトークンがすべて入った trainTokens 変数と、
テストに使うトークンが入った evalTokens 変数が用意されました。

英語の入力しかない場合は、gpt-2トークナイザーを使用することができます:

enc = tiktoken.get_encoding("gpt2")
train_ids = enc.encode_ordinary(train_data)
eval_ids = enc.encode_ordinary(val_data)

トークンをファイルに保存

さて、トークンをファイルに書き込んでいきます:

train_ids = np.array(trainTokens)
val_ids = np.array(evalTokens)
newFolderPath = os.path.join(os.path.dirname(__file__), 'TrainingSet')
if not os.path.exists(newFolderPath):
    os.makedirs(newFolderPath)
train_ids.tofile(os.path.join(newFolderPath, 'train.bin'))
val_ids.tofile(os.path.join(newFolderPath, 'val.bin'))

これにより、学習データ用の train.bin ファイルとテスト用の val.bin ファイルが作成されます。これらのファイルは、ディレクトリ内の TrainingSet フォルダに作成されます。

トレーニングの開始

ここで、新しく生成された TrainingSet
クローンした nanoGPT リポジトリフォルダのdataフォルダに
コピーします。

スクリーンショット 2023-04-15 16.09.33-2.png

次に、nanoGPT リポジトリにある train.py 関数を使って、モジュールをトレーニングします。
eval_intervalを100に設定していることに注意してください。つまり、100回繰り返すごとに、outフォルダに学習済みモジュールが取得され、すぐにテスト(入力からの応答取得)に使用することができます。

python3 train.py \
  --dataset=TrainingSet \
  --n_layer=4 \
  --n_head=4 \
  --n_embd=64 \
  --compile=False \
  --eval_iters=1 \
  --block_size=64 \
  --batch_size=8 \
  --device=mps \
  --eval_interval=100

では、学習処理を開始します。

繰り返し回数が100回に達するたびに、out フォルダに出力モジュールファイルが表示されます:

スクリーンショット 2023-04-15 16.10.24-2.png

このファイルをテストするために、トレーニングを停止する必要はありません。トレーニングのプロセスを停止したい場合は、キーボードのcontrol-cキーで終了してください。

注意:正しい文章を出力できるモジュールを作るには、20000回以上の繰り返しが必要です。

sample.pyを日本語トークナイザーを使うように

今回はカスタムのトークナイザーを使用したので、クローンしたnanoGPTフォルダ内のデフォルトのsample.pyファイルを更新する必要があります:

import os
from contextlib import nullcontext
import torch
from transformers import BertJapaneseTokenizer
from model import GPTConfig, GPT
import random

# -----------------------------------------------------------------------------
init_from = 'resume' # either 'resume' (from an out_dir) or a gpt2 variant (e.g. 'gpt2-xl')
out_dir = 'out' # ignored if init_from is not 'resume'
num_samples = 1 # number of samples to draw
max_new_tokens = 500 # number of tokens generated in each sample
temperature = 0.05 # 1.0 = no change, < 1.0 = less random, > 1.0 = more random, in predictions
top_k = 100 # retain only the top_k most likely tokens, clamp others to have 0 probability
seed = random.randint(0, 2**32 - 1)
device = 'cpu' # examples: 'cpu', 'cuda', 'cuda:0', 'cuda:1', etc.
dtype = 'bfloat16' # 'float32' or 'bfloat16' or 'float16'
compile = False # use PyTorch 2.0 to compile the model to be faster
exec(open('configurator.py').read()) # overrides from command line or config file
# -----------------------------------------------------------------------------

torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
torch.backends.cuda.matmul.allow_tf32 = True # allow tf32 on matmul
torch.backends.cudnn.allow_tf32 = True # allow tf32 on cudnn
device_type = 'cuda' if 'cuda' in device else 'cpu' # for later use in torch.autocast
ptdtype = {'float32': torch.float32, 'bfloat16': torch.bfloat16, 'float16': torch.float16}[dtype]
ctx = nullcontext() if device_type == 'cpu' else torch.amp.autocast(device_type=device_type, dtype=ptdtype)

# model
if init_from == 'resume':
    # init from a model saved in a specific directory
    ckpt_path = os.path.join(out_dir, 'ckpt.pt')
    checkpoint = torch.load(ckpt_path, map_location=device)
    gptconf = GPTConfig(**checkpoint['model_args'])
    model = GPT(gptconf)
    state_dict = checkpoint['model']
    unwanted_prefix = '_orig_mod.'
    for k,v in list(state_dict.items()):
        if k.startswith(unwanted_prefix):
            state_dict[k[len(unwanted_prefix):]] = state_dict.pop(k)
    model.load_state_dict(state_dict)
elif init_from.startswith('gpt2'):
    # init from a given GPT-2 model
    model = GPT.from_pretrained(init_from, dict(dropout=0.0))

model.eval()
model.to(device)
if compile:
    model = torch.compile(model) # requires PyTorch 2.0 (optional)

# load custom tokenizer
tokenizer = BertJapaneseTokenizer.from_pretrained("cl-tohoku/bert-base-japanese-whole-word-masking")

# encode the beginning of the prompt
while(True):
    start = input("What do you want to ask?")
    start_ids = tokenizer.encode(start)
    x = (torch.tensor(start_ids, dtype=torch.long, device=device)[None, ...])
# run generation
    with torch.no_grad():
        with ctx:
            y = model.generate(x, max_new_tokens, temperature=temperature, top_k=top_k)
            decoded_text = tokenizer.decode(y[0].tolist(), skip_special_tokens=True)
            print(decoded_text)
            print('---------------')  

ここでは、BertJapaneseTokenizer を使って、日本語の出力をデトークしています。
また、次の入力を求め続けるループを追加しています。

さて、テストしてみましょう:

スクリーンショット 2023-04-15 13.04.59.png

質問したい文章(日本語)を入力し、キーボードのエンターキーを押す。

終了したいときは、control-cキーの組み合わせで終了します。

より多くのデータ、より多くの反復練習

より多くのデータ入力(例えば、https://github.com/karpathy/nanoGPT/tree/master/data/openwebtext)でAIを訓練すれば、gpt-2論文に記載されたようなチャットロボットを得ることができます。

もちろん、より多くの反復のためのモジュールの訓練も必要です。

私の論文では、私のQiitaの記事88個と1時間程度のトレーニングを使いました(なので、とてもとても弱いです、笑)。

Githubリポジトリ

私は、質問をしたり、ただランダムにチャットしたりするのにChatGPTを使うのが好きです。Siriを使ってGPT-3に質問をしたり、音声チャットを使えるiOSアプリを作りました。是非、以下のリンクをチェックしてください:
https://twitter.com/MszPro/status/1640874358301409281


お読みいただき、ありがとうございます。

MastodonやMissKeyでフォローしてください:🐘 マストドン @me@mszpro.com

Twitterでフォローしてください:☺️ Twitter @MszPro

☺️ サイト https://MszPro.com

writing-quickly_emoji_400.png

Written by MszPro~

15
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?