- 初めに
- 開発環境
- Google Colobの準備
- データセットの作成
- データをhuggingfaceにアップロード
- wandbの準備(必要なければスキップ可)
- 学習・評価
- 学習したモデルをhuggingfaceにアップロード
- モデルをWindowsで推論する
初めに
1週間ほど前に sbintuitions/modernbert-ja-130mが公開されたので遊んでいきます。
今回は bertモデルなのでテキストクラスタリングをやってみます.文章を入れたら、その文章がVTuberっぽいのかどうかを判定するモデルを作ってみます。 (25/02/17時点では、データセットがyoutube apiの制限上 あまり集められていないので精度は悪いです)
この記事の学習Colobおよび推論のリポジトリは以下で公開しています
学習Colob
推論
データセット
学習済みモデル
開発環境
Google Colobの準備
以下のAPIキーを使うため シークレットキーを登録します

データセットの作成
まずは学習をするためのyoutubeからVTuberと非VTuberのチャンネルの情報を取得します。 (youtubeAPIの制限により多くは取得できないです)
# 必要なライブラリのインストール(初回のみ) !pip install -U google-api-python-client huggingface_hub # APIキーはGoogle Colabのシークレット値から取得 import os from google.colab import userdata API_KEY = userdata.get('YOUTUBE_API_KEY') if API_KEY is None: raise ValueError("YOUTUBE_API_KEYが環境変数に設定されていません。ColabのシークレットにAPIキーを登録してください。") from googleapiclient.discovery import build # YouTube Data APIのクライアントを作成 youtube = build('youtube', 'v3', developerKey=API_KEY) def fetch_channels(query, max_results=50, page_limit=3): """指定した検索クエリでチャンネル情報を取得する関数""" channels = [] next_page_token = None for _ in range(page_limit): request = youtube.search().list( q=query, type="channel", part="id,snippet", maxResults=max_results, pageToken=next_page_token ) response = request.execute() for item in response.get("items", []): channel_id = item["id"]["channelId"] title = item["snippet"]["title"] description = item["snippet"]["description"] channels.append({"channel_id": channel_id, "title": title, "description": description}) next_page_token = response.get("nextPageToken") if not next_page_token: break return channels # VTuber候補のチャンネル情報を取得(例:"VTuber"で検索) vtuber_channels = fetch_channels(query="VTuber", max_results=500, page_limit=10) print("VTuber候補のチャンネル数:", len(vtuber_channels)) # 非VTuber候補のチャンネル情報を取得(例:"料理"で検索) non_vtuber_channels = fetch_channels(query="料理", max_results=10, page_limit=10) print("非VTuber候補のチャンネル数:", len(non_vtuber_channels))
データをhuggingfaceにアップロード
作成したデータを整理して,huggingfaceにアップロードします
def add_label_and_text(item, label): # "title"と"description"を結合して"text"を作成し、ラベルを追加 item["text"] = item["title"] + " " + item["description"] item["label"] = label return item # VTuber候補にはラベル1を付与 vtuber_channels_labeled = [add_label_and_text(item, 1) for item in vtuber_channels] # 非VTuber候補にはラベル0を付与 non_vtuber_channels_labeled = [add_label_and_text(item, 0) for item in non_vtuber_channels] # 両方のリストを連結して1つのリストにする all_channels = vtuber_channels_labeled + non_vtuber_channels_labeled # JSONL形式で保存する import json def save_to_jsonl(data, filename): with open(filename, "w", encoding="utf-8") as f: for item in data: f.write(json.dumps(item, ensure_ascii=False) + "\n") save_to_jsonl(all_channels, "vtuber_youtube_list.jsonl") # Hugging Face CLIのインストール(必要な場合) !pip install huggingface_hub # Hugging Faceにログイン(アクセストークンを入力するプロンプトが表示されます) !huggingface-cli login # 必要なライブラリのインストール(初回のみ) !pip install huggingface_hub from huggingface_hub import HfApi, upload_file from google.colab import userdata import time # Colabのシークレットからアクセストークンを取得("HF_TOKEN"として登録済み) hf_token = userdata.get('HF_TOKEN') if hf_token is None: raise ValueError("HF_TOKENがシークレットに登録されていません。") # アップロード先のリポジトリID("your_username" をあなたのHugging Faceユーザー名に置き換えてください) repo_id = "ayousanz/vtuber-youtube-list-dataset" # HfApiを利用してリポジトリを作成(既に存在する場合はスキップ) api = HfApi() try: api.create_repo(repo_id=repo_id, repo_type="dataset", exist_ok=True, token=hf_token) print(f"リポジトリ '{repo_id}' が作成済み、または既に存在します。") except Exception as e: print("リポジトリ作成時のエラー:", e) # Hub上に反映されるまで待機(例:10秒) print("Hub上に反映されるまで10秒待ちます...") time.sleep(10) # JSONLファイル(例:vtuber_channels.jsonl, non_vtuber_channels.jsonl)のアップロード for filename in ["vtuber_youtube_list.jsonl"]: try: upload_file( path_or_fileobj=filename, path_in_repo=filename, repo_id=repo_id, repo_type="dataset", token=hf_token ) print(f"{filename} のアップロードが完了しました。") except Exception as e: print(f"{filename} のアップロード時にエラーが発生しました:", e) print("すべてのファイルのアップロードが完了しました。")
wandbの準備(必要なければスキップ可)
!pip install -U transformers>=4.48.0 datasets evaluate wandb # wandbのログイン(初回のみ実行) import wandb from google.colab import userdata wandb_api_key = userdata.get('WANDB_API_KEY') !wandb login $wandb_api_key
学習・評価
以下で学習および評価(テスト推論)を行います
# ------------------------------- # 0. Flash Attention の無効化(GPUがAmpere未満の場合) # ------------------------------- import os os.environ["FLASH_ATTN_DISABLE"] = "1" # ------------------------------- # 1. 必要なライブラリのインストール(初回のみ) # ------------------------------- !pip install -U transformers datasets evaluate wandb huggingface_hub # ------------------------------- # 2. 必要なモジュールのインポート # ------------------------------- import torch import numpy as np from transformers import AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer from datasets import load_dataset, DatasetDict, Features, Value import evaluate import wandb from wandb import Settings import time # ------------------------------- # 3. wandb の初期化(project と entity を適切に設定) # ------------------------------- wandb.init( project="modernbert-vtuber", entity="yousan", # ご自身の wandb ユーザー名に変更してください config={ "model_name": "sbintuitions/modernbert-ja-130m", "epochs": 3, "batch_size": 4, "learning_rate": 2e-5 }, settings=Settings(init_timeout=210) ) # ------------------------------- # 4. Hugging Face Hub からデータセットの読み込み # ------------------------------- # JSONL ファイルは、各レコードが "channel_id", "title", "description", "text", "label" を持つ前提 features = Features({ "channel_id": Value("string"), "title": Value("string"), "description": Value("string"), "text": Value("string"), "label": Value("int64") }) # ここでは、アップロード済みのデータセット名(例:"ayousanz/vtuber-youtube-list-dataset")を利用します dataset = load_dataset("ayousanz/vtuber-youtube-list-dataset", features=features) # もしデータセットに "train" と "validation" の split が存在しない場合は、単一の split から分割 if not ("train" in dataset and "validation" in dataset): single_split = list(dataset.keys())[0] split_dataset = dataset[single_split].train_test_split(test_size=0.2, seed=42) dataset = DatasetDict({ "train": split_dataset["train"], "validation": split_dataset["test"] }) # JSONL に "text" フィールドが既に存在する前提ですが、念のため title と description を連結する処理を追加 def add_text_field(example): if not example.get("text"): example["text"] = example["title"] + " " + example["description"] return example dataset = dataset.map(add_text_field) # ★ 念のため、ラベルが None でないレコードのみ残す dataset = dataset.filter(lambda x: x["label"] is not None) print("サンプルデータ(train):") print(dataset["train"][0]) print("サンプルデータ(validation):") print(dataset["validation"][0]) # ------------------------------- # 5. モデルとトークナイザーの読み込み・前処理 # ------------------------------- model_name = "sbintuitions/modernbert-ja-130m" tokenizer = AutoTokenizer.from_pretrained(model_name) # モデルはデフォルト(FP32)でロードする(fp16はTrainerで管理) model = AutoModelForSequenceClassification.from_pretrained( model_name, num_labels=2 ) # GPUが利用可能な場合、モデルをGPUに移動 device = "cuda" if torch.cuda.is_available() else "cpu" model.to(device) # 前処理:各レコードの "text" をトークナイズ(最大長128、padding) def preprocess_function(examples): return tokenizer(examples["text"], truncation=True, max_length=128, padding="max_length") tokenized_datasets = dataset.map(preprocess_function, batched=True) # ------------------------------- # 6. 評価指標およびトレーニング設定(wandb連携) # ------------------------------- accuracy_metric = evaluate.load("accuracy") def compute_metrics(eval_pred): logits, labels = eval_pred predictions = np.argmax(logits, axis=-1) return accuracy_metric.compute(predictions=predictions, references=labels) training_args = TrainingArguments( output_dir="./modernbert_vtuber_model", evaluation_strategy="epoch", # 将来的には eval_strategy に変更 learning_rate=2e-5, per_device_train_batch_size=4, per_device_eval_batch_size=4, num_train_epochs=3, weight_decay=0.01, save_strategy="epoch", logging_dir="./logs", report_to=["wandb"], run_name="modernbert_vtuber_finetuning", fp16=True # Trainer による混合精度トレーニングを有効化 ) trainer = Trainer( model=model, args=training_args, train_dataset=tokenized_datasets["train"], eval_dataset=tokenized_datasets["validation"], compute_metrics=compute_metrics, ) # ------------------------------- # 7. ファインチューニング実行 # ------------------------------- trainer.train() # 学習済みモデルとトークナイザーの保存 model.save_pretrained("./modernbert_vtuber_model") tokenizer.save_pretrained("./modernbert_vtuber_model") wandb.finish() # ------------------------------- # 8. 推論関数の定義と使用例 # ------------------------------- def classify_vtuber(text, threshold=50.0): """ 入力文章に対して VTuber 判定を行い、VTuber である確信度 (rate) を算出します。 threshold 以上なら VTuber と判定します。 """ inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=128, padding="max_length") inputs = {k: v.to(device) for k, v in inputs.items()} outputs = model(**inputs) logits = outputs.logits probabilities = torch.softmax(logits, dim=-1).squeeze().tolist() # [非VTuber確率, VTuber確率] vtuber_rate = probabilities[1] * 100 is_vtuber = vtuber_rate >= threshold return {"isVTuber": is_vtuber, "rate": round(vtuber_rate, 3)} # 使用例 input_text = "この動画では、バーチャルなキャラクターがリアルタイムに動く様子を配信しています。" result = classify_vtuber(input_text) print("入力文章の判定結果:") print(result)
推論の結果は以下のようになります
入力文章の判定結果: {'isVTuber': True, 'rate': 100.0}
学習したモデルをhuggingfaceにアップロード
以下で学習したモデルをhuggingfaceにアップロードします
# huggingface_hub ライブラリからリポジトリ作成用の関数をインポート from huggingface_hub import create_repo # アップロード先のリポジトリ名を指定(既に作成済みならこのステップはスキップ可能) repo_id = "ayousanz/modernbert-vtuber-finetuned-1" # ご自身のユーザー名とリポジトリ名に変更してください create_repo(repo_id, exist_ok=True) # 学習済みモデルとトークナイザーをアップロード model.push_to_hub(repo_id) tokenizer.push_to_hub(repo_id)
モデルをWindowsで推論する
(ColobのT4を使い切ってしまったので) Windowsで推論を行なっていきます。推論だけ試したい方はこちらのみで良さそうです
環境作成
uv venv -p 3.11 source venv/bin/activate uv pip install -r requirements.txt
推論
以下のコードを実行します
import torch from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline # GPU が利用可能か確認 device = "cuda" if torch.cuda.is_available() else "cpu" print("Using device:", device) # Hugging Face Hub 上のリポジトリからモデルとトークナイザーをロード model_name = "ayousanz/modernbert-vtuber-finetuned" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained(model_name) model.to(device) # GPU が利用可能なら GPU に移動 # 推論用パイプラインの作成 vtuber_classifier = pipeline("text-classification", model=model, tokenizer=tokenizer, device=0 if device=="cuda" else -1) # 5つのサンプルテキストで推論例を実行 sample_texts = [ "この動画では、バーチャルなキャラクターがリアルタイムに動く様子を配信しています。", "このチャンネルは、料理レシピの動画を投稿しています。", "最新のVTuberがライブ配信を行っており、視聴者との交流が盛んです。", "旅行動画を中心に、世界各地の観光地を紹介しています。", "ここでは、3Dモデルを使ったアニメーション動画を配信しています。" ] for text in sample_texts: result = vtuber_classifier(text) print("入力:", text) print("推論結果:", result) print("-" * 50)
実行結果は以下のようになります
Using device: cuda Device set to use cuda:0 入力: この動画では、バーチャルなキャラクターがリアルタイムに動く様子を配信しています。 推論結果: [{'label': 'LABEL_1', 'score': 1.0}] -------------------------------------------------- 入力: このチャンネルは、料理レシピの動画を投稿しています。 推論結果: [{'label': 'LABEL_0', 'score': 1.0}] -------------------------------------------------- 入力: 最新のVTuberがライブ配信を行っており、視聴者との交流が盛んです。 推論結果: [{'label': 'LABEL_1', 'score': 1.0}] -------------------------------------------------- 入力: 旅行動画を中心に、世界各地の観光地を紹介しています。 推論結果: [{'label': 'LABEL_0', 'score': 1.0}] -------------------------------------------------- 入力: ここでは、3Dモデルを使ったアニメーション動画を配信しています。 推論結果: [{'label': 'LABEL_1', 'score': 0.8205193877220154}] --------------------------------------------------