2024年6月26日水曜日

mlx-examples の LoRA を使って kakakikikeke っぽいツイートを自動生成してみた

mlx-examples の LoRA を使って kakakikikeke っぽいツイートを自動生成してみた

画像生成は stable-diffusion を使いました
音声生成は RVC を使いました
今回は文章生成をします

環境

  • macOS 14.5
  • Python 3.10.12
  • mlx-examples 69700d84

学習データの作成

Twitter のアーカイブデータを使います
アーカイブデータ内に tweets.js というツイートを管理しているファイルがあるのでそこから学習データとテストデータと評価データを作成します

学習させるツイートの元では 3 万件ほどありますが全部は学習させません
本当は全部学習させたいのですがマシンのスペックも考慮して 1300 件にしています
テストと評価データはそれぞれ 100 件ずつです

wc -l my_tweet/*
     100 my_tweet/test.jsonl
    1100 my_tweet/train.jsonl
     100 my_tweet/valid.jsonl
    1300 total

このデータセットの量でだいたい 1 時間ほどで LoRA を生成できるので 3 万件学習させても 4 日ほどあれば終わるかもしれません

学習データ作成スクリプト

以下 tweets.js を解析して学習データを作成するための Python スクリプトです
pandas と scikit-learn が必要になります
json ファイルを解析するだけなので pandas も scikit-learn もなしで解析することもできます

import json
from io import StringIO

import pandas as pd
from sklearn.model_selection import train_test_split

# tweets.js を読み込んでツイート情報を辞書に変換
tweets = []
with open("./tweets.js", mode="r") as f:
    for tweet in json.loads(f.read().replace("window.YTD.tweets.part0 = ", "")):
        t = {
            "full_text": tweet["tweet"]["full_text"],
            "created_at": tweet["tweet"]["created_at"],
        }
        tweets.append(t)

# JSONデータをPandas DataFrameに読み込む
df = pd.read_json(StringIO(json.dumps(tweets)))
pd.set_option("display.max_colwidth", 1000)
pd.set_option("display.max_rows", 1000)

# リンクを含むツイートは削除
df = df[~df["full_text"].str.contains("https://")]

print(df.head(100))

# テキスト長が短い学習データTOP1300をデータセットとして扱う
# 学習に時間がかかる場合もしくはメモリを使いすぎる場合は ascending=True にして短い順にするか 1300 の数を小さくすること
df["length"] = df.full_text.str.len()
df = df.sort_values(by="length", ascending=False)
df = df.head(1300)
print(len(df.index))

# データフレームをシャッフル
df = df.sample(frac=1).reset_index(drop=True)

# validとtest用のデータを100件ずつ取り出し、残りをtrainに分割
valid_df, remaining_df = train_test_split(df, test_size=len(df) - 100, random_state=42)
test_df, train_df = train_test_split(
    remaining_df, test_size=len(remaining_df) - 100, random_state=42
)


# ヘルパー関数:データフレームを新しい形式でJSON Linesファイルに変換
def df_to_jsonl(df, file_name):
    with open(file_name, "w", encoding="utf-8") as file:
        for _, row in df.iterrows():
            formatted_data = {"text": f"USER:ツイートして ASSISTANT:{row['full_text']}"}
            file.write(json.dumps(formatted_data, ensure_ascii=False) + "\n")


# 各データセットを対応するJSON Linesファイルに変換
df_to_jsonl(train_df, "./train.jsonl")
df_to_jsonl(valid_df, "./valid.jsonl")
df_to_jsonl(test_df, "./test.jsonl")

最終的には3つのファイルが作成できればいいので解析方法はおまかせします

ベースモデル

学習させるデータが日本語なのでベースモデルも日本語に特化したものを使ったほうがいいです
今回は https://huggingface.co/4bit/ELYZA-japanese-Llama-2-7b-instruct を使いました
また mlx は専用のフォーマットにモデルをコンバートする必要があるのでモデルをダウンロードしたら mlx-examples に含まれる convert.py を使って変換します

  • cd mlx-examples/lora
  • python convert.py --hf-path 4bit/ELYZA-japanese-Llama-2-7b-instruct -q --q-bits 4

コンバートしたモデルは mlx_model 配下に生成されます

学習

生成した学習データと変換した ELYZA のベースモデルを使って LoRA アダプタを生成します

  • python lora.py --model mlx_model --data my_tweet --train --iters 600

反復数も学習時間に影響するためあまり大きくすると学習時間が増えるので注意してください
600 回と 1300 件の学習データで約 1 時間で終了しました

推論

あとはテストします

  • python lora.py --model mlx_model --adapter-file adapters.npz --prompt "USER:ツイートして ASSISTANT:"
Loading pretrained model
Total parameters 1055.199M
Trainable parameters 2.097M
Loading datasets
Generating
USER:ツイートして ASSISTANT:iot_device には iot_device って何だよ、デバイス�も実装したいというときはどうすればいんだろうか。ま�、ros bond とかと同じ�じかな、デバイスに�する
  定を管理するクラスを作るだけだよな

何度か生成してみます

Loading pretrained model
Total parameters 1055.199M
Trainable parameters 2.097M
Loading datasets
Generating
USER:ツイートして ASSISTANT:10.5 10.6 10.7 、、、、10.9 10.10 10.11 を使うために 10 にしてるのか。ios の場合は 10.12 とかだからちゃんとやってるな、という�じ
==========
Loading pretrained model
Total parameters 1055.199M
Trainable parameters 2.097M
Loading datasets
Generating
USER:ツイートして ASSISTANT:airplay の機�ってどこの�品があるんだろうか。これから発表していくためにはコンテナに入れておかないとダメなのだろうか。����も����も結果は同じという
  とを�り返していく
==========
Loading pretrained model
Total parameters 1055.199M
Trainable parameters 2.097M
Loading datasets
Generating
USER:ツイートして ASSISTANT:その google colaboratory だとあるスクリプトをコードで����すか、、、コードの�分ってやつですが、、、そのためにコードの変更�所を����のがいいんだけ
  そのためにもしくは使えるのが
==========

この文字化けは何とかならないのだろうか、、

おまけ: 文字化け対策

てっとり早く対応する場合は生成されたいろいろ弄る前のトークンを最後に print すれば OK です

git diff
diff --git a/lora/lora.py b/lora/lora.py
index a90eda7..411f558 100644
--- a/lora/lora.py
+++ b/lora/lora.py
@@ -312,6 +312,7 @@ def generate(model, prompt, tokenizer, args):
             skip = len(s) - 1
     print(tokenizer.decode(tokens)[skip:], flush=True)
     print("=" * 10)
+    print(f"{s} \n\n")
     if len(tokens) == 0:
         print("No tokens generated for this prompt")
         return

文字化け対策して10個生成してみました

結局その一緒に遊ぶやつとセットにしないとダメなんですか。通信量かかりますが gps ロガーつけている状態じゃないとダメなんですか。そういうわけだからバックグラウンドでおいておいておけばいくらか 

customerservice ルートにログインしたときに dns コマンドによってクラスタの dns サービスを停止していたため、ルートからクラスタにリクエストが行けなくなっていたようだな。対応はわかっているけど now  

docker-compose 使うときは pod とか swarm とかクラスタとか pod とか svc とか nil じゃないという感じのものを指定しないといけないんだけどなー、、、どうすればいいかなー、、、なんかいなくてないのだろうか、、、 

docker-compose の場合、ローカルの machine 名の前に 0 をつけてくればどこでもインストールできるようになったのかな、、docker の proxyswarm とかはプロキシの指定が必要とか面倒だからなー。salt の場合のマシン名でコ 

docker-compose とか YouTube とかで配布するのに役立っているのがコンテナってやつとかパブリッシュなのにコンテナが必要だとか結構あるのかな、コンテナのためのコンテナってのがあるけどどういうことなんだろう 

自前の処理って製品の説明書?移行前のデータをどこまでコピーするのかとかは援助しないかな。そういう時には「自前の処理」という概念がないから。そもそも仕様がわからんわけ 

ブログから個人サイトのための micro-flutter を作るってなると github を使ってできない理由はないだろうけど、blog を用意するとなってみんなが悩むのは同じで、その問題を解決するためにサービスがいくつ 

windows 折りたたみ式のマウスって結局何がいいんだろうか。マウスとして普通の押し出し式のもがいいから私が把持って操作したい、そして折りたたんで持ち歩きたいっていうのが気持ちだった 

確かに android 12 が 2020 年 9 月というのはかなり早い感じ。アップグレードのスケジュールはかなりの傾向があるような気がする。だけどいろいろと理由はあるんだろうか。最終的には google 

iPhone の時のプライスタグが貼り付けできないのか、実は AppStore のプライスタグが使えないのか。 tinder は使っているのにおかしい、とか思う人は apple の審査厨になっているから 

考察: 学習させるデータの修正

今回は「ツイートして」という質問に対して適当にサンプルしたツイートを学習させています
コンテキストも何もない状態の質問なので本当にランダムなツイートを生成してしまいます

ツイートする場合には本来ある程度のコンテキストが含まれていることがあり例えば梅雨の時期だったら雨に関するツイートをしたりだとか WWDC が Google I/O が開催される時期なので Apple や Google に関するツイートをしたりします
なので学習させるデータの質問にコンテキストを持たせることができれば特定の事柄や事象、特定の言葉を含んだツイートを生成することができます

単純に「ツイートして」という質問を学習させるのではなく「6月の雨に関するツイートをして」という感じで質問をカスタムできれば生成する際にも6月の雨に関するツイートを生成することができます
実際に生成する際の質問も「ツイートして」だけではなくテンプレートなどを使って「X月のYYYに関するツイートをして」という感じで自動で X, Y を埋めるようにすればコンテキストを持たせた文章を生成することができると思います

時期に関しては tweets.js に含まれているので簡単に学習データに含ませることができそうですが「YYY に関する」の部分はそう簡単にはいきません
例えばツイート情報を形態素解析してもっとも頻出している単語を YYY に設定することができそうですがそれだけだと正確なコンテキスト情報としては少し甘い気がします
ツイートに「Apple」や「Google」など特定の単語が含まれていれば問題ないですがそうでないツイートもたくさんあり単純のカウントではダメなケースがあります
なのでちゃんとやるのであれば例えばツイートの種類のようなものを判定する必要があるかなと思っており例えば「こんにちわ」や「ねる」「はぁー」みたいな文章ではなく短い単語のみのツイートはコンテキストなしという分類にし特定のコンテキストなり得る単語を含むツイートはコンテキストありとしてその場合は形態素解析を使ってそのツイート内に出現する特徴語を抽出してそれを「YYYに関する」の部分に設定すればそれっぽいコンテキストにはなるかなと思います

このあたりはツイートの自動タグ付けやネガポジ判定のような世界になってくるので論文を漁れば手法は死ぬほど出てくると思います

という感じで学習させるデータをもう少し工夫できれば完全ランダムなツイートしか生成しない問題を解決できるかなと思っています

最後に

まだまだ改善の余地はありそうですが自分っぽいツイートをする流れはだいたい把握できました
あとは学習データを修正したり生成時のプロンプトを変更すればもっと自然なツイートになるかなと思います

これまで画像、音声とやってきましたが文章生成が一番たいへんなイメージです
「自然な」日本語にするのがすごい難しいです

0 件のコメント:

コメントを投稿