700万件の悪性URL分類に挑戦:ColabでTF-IDFとLightGBMをやり切った話
📌 このプロジェクトのソースコードはこちら(GitHub):
🔗 https://github.com/hyeon-marina/malicious-url-classification
📌 Qiitaでは概要をまとめているので、気軽に読みたい方はこちら👇
🔗 https://qiita.com/hyeon/items/f2a99f6a54742360fc0f
はじめに
この技術記事では、Daconの「悪性URL分類AI競技大会」に参加し、Colab環境でTF-IDFとLightGBMを用いて大規模データを分析・分類モデルの構築に取り組んだ過程を記録しています。
最終的には、Colabのメモリ制約や実行時間の問題により提出ファイルの作成には至りませんでしたが、実務に通じる設計・改善の経験を多く得ることができました。本記事は、その実践的な学びと取り組みを振り返るものです。
- 競技大会の概要
- タイトル:悪性URL分類AI競技大会
- 主催:Dacon
- 目的:「URLのテキスト情報のみで悪性かどうかを判定するAIモデル」を開発
- 提供データ:
- train.csv(約700万件)
- test.csv(約175万件)
- 評価指標:ROC-AUC
- データの構造と特徴
- カラム:ID, URL, label
- label:1(悪性)、0(正常)
- URLは [.] などで表現されたマスキングが施されており、実務でも見られる形式
- プレーンな機械学習では扱いづらい非構造テキストであり、特徴量抽出が鍵となる
- モデル設計:TF-IDF + LightGBM
🔹 TF-IDFベクトル化
🔍 TF-IDFベクトライザーを使った文字n-gramベクトル化
以下のコードは、URLデータを文字単位の3〜5グラムでベクトル化し、最大100,000個の特徴量を生成します。
from sklearn.feature_extraction.text import TfidfVectorizer
# 文字単位(char)の3〜5グラムを使ってTF-IDFベクトルを作成する
# 最大特徴量数は100,000に制限
vectorizer = TfidfVectorizer(
analyzer='char',
ngram_range=(3, 5),
max_features=100000
)
# URL列に対してTF-IDFベクトル化を実行
X = vectorizer.fit_transform(train_df['URL'])
# ラベル列を抽出(分類の目的変数)
y = train_df['label']
- 文字単位のn-gram(3〜5)でURLの構造的特徴を抽出
- 特徴量数を100,000に設定し、多様なパターンを網羅
🔹 モデル:LightGBM
🌳 LightGBMを使った分類モデルの学習
以下のコードは、LightGBMを用いて学習を行う分類モデルの例です。
import lightgbm as lgb
# LightGBMの分類モデルを定義
model = lgb.LGBMClassifier(
n_estimators=100, # 決定木の数(ブースティングの回数)
max_depth=7, # 決定木の最大深さ
learning_rate=0.2, # 学習率(ステップサイズ)
subsample=0.8, # データのサブサンプリング率(過学習防止)
colsample_bytree=0.8, # 各決定木ごとの特徴量サブサンプリング率
random_state=42 # 乱数シード(再現性のため)
)
# モデルの学習を実行
model.fit(X, y)
- 高速で精度の高い勾配ブースティング
- 学習後は predict_proba を使って確率予測を行い、ROC-AUCで評価
🔁 Stratified K-Fold 交差検証とROC-AUCによる評価
以下のコードは、Stratified K-Foldを用いた3分割交差検証と、ROC-AUCスコアによるモデル評価の例です。
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import roc_auc_score
import numpy as np # 平均を求めるために必要
# Stratified K-Foldで3分割し、各層にラベルの比率を保つ
skf = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)
auc_scores = []
# 各foldごとに学習と評価を行う
for fold, (train_idx, val_idx) in enumerate(skf.split(X, y)):
# 学習用と検証用のデータを分割
X_train, X_val = X[train_idx], X[val_idx]
y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]
# モデルの定義と学習
model = lgb.LGBMClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
# 検証データに対する予測確率(ポジティブクラスのスコア)を取得
val_pred = model.predict_proba(X_val)[:, 1]
# ROC-AUCスコアを計算
auc = roc_auc_score(y_val, val_pred)
auc_scores.append(auc)
# 各foldのスコアを表示
print(f"Fold {fold+1} ROC-AUC: {auc:.5f}")
# 全foldの平均ROC-AUCスコアを表示
print("平均 ROC-AUC:", np.mean(auc_scores))
- サンプル実験(データの10%)で 平均 ROC-AUC: 0.94095 を達成
- 実用モデルとしても通用する精度と安定性を確認
- Colabでの進行確認用(Tips)
✨ JavaScriptの関数定義例(アロー関数)
以下は、コンソールに "Awesome" と出力するアロー関数の例です。
// "Awesome" と表示する関数を定義(アロー関数を使用)
const great = () => {
console.log("Awesome");
}
ColabやZennでの進捗表示やメンタル維持のためにこういう遊びも挟んでました。
- 全体学習とColabの壁
- 全データ(約700万件)での学習に挑戦
- TF-IDF(10万次元)+LightGBMはメモリ負荷が高く、Colab ProでもRAMを超過
- セッションが強制終了し、学習が中断
- 対応策として:
- 特徴量数を50,000に削減
- データのサブサンプリング
- joblib を使ったモデル・ベクトル化器の保存分割
- 得られた学びと工夫
- 現実的な制約下(Colab環境)でのパフォーマンス最大化
- テキスト特徴量の設計、メモリ管理、逐次保存の重要性
- “うまくいかなかったこと”を含めて書けるのが、ポートフォリオとしての価値
- 最後に
結果として、最終提出までは至らなかったものの、
- 700万件規模のデータ処理
- メモリエラーと学習失敗の対応
- スケーラブルなモデル設計の検討
といった点で、技術面・実務面の両方から多くの経験を積むことができました。
本記事は、自身のAI・機械学習エンジニアとしての成長記録として、Zenn上にも公開しておきたいと思い執筆しました。
同じように、限られた環境の中でも最後までやり切ることの意味を感じてくれる人がいれば嬉しいです。
自分の成長や学びを共有することで、どこかの誰かの参考になれば幸いです。
✅ 実行環境
- Google Colab Pro
- Python 3.11
- scikit-learn 1.3.0
Discussion