はじめに
能動学習における主要なサンプリング戦略を学びます。
サンプリング戦略の必要性
能動学習では、限られたリソースの中で効率的に学習を進めるために、適切なサンプリング戦略が不可欠です。主な必要性として以下が挙げられます。
学習効率の最適化
全てのデータにラベル付けを行うのではなく、モデルの性能向上に最も寄与しそうなサンプルを選択することで、限られたリソースを効率的に活用できます。例えば、モデルが最も不確実性を感じるサンプルや、現在の決定境界付近のサンプルを優先的に選ぶことで、少ないラベル付きデータでも高い学習効果が得られます。
コスト削減
データのラベル付けには多大な時間とコストがかかります。(特に専門知識を要する領域で)
効果的なサンプリング戦略によってラベル付けのコストを大幅に削減できます。
データの偏りへの対応
単純なランダムサンプリングでは、データセットに内在する偏りをそのまま反映してしまう可能性があります。適切なサンプリング戦略を用いることで、データの多様性を確保し、より汎用性の高いモデルを構築できます。
サンプリング手法
2つの手法をみていきましょう。
- 不確実性サンプリング
 - 多様性サンプリング
 
不確実性サンプリング(Uncertainty Sampling)
- 学習モデルの予測確信度が低い(不確実性が高い)サンプルを優先的にラベル付けの対象として選択する手法
 - 不確実性の高いサンプルにラベル付けすることで、モデルの決定境界付近のサンプルを効率的に学習できる
 
不確実性サンプリングのいくつかの指標
| 不確実性の指標 | 説明 | 
|---|---|
| クラス事後確率の最大値(Least Confidence) | 最も高い事後確率の値が小さいほど不確実性が高い | 
| マージンサンプリング (Margin Sampling)  | 
最も高い事後確率と2番目に高い事後確率の差が小さいほど不確実性が高い | 
| エントロピーサンプリング (Entropy Sampling)  | 
事後確率分布のエントロピーが大きいほど不確実性が高い | 
多様性サンプリング(Diversity Sampling)
- ラベル付けされたサンプル群の多様性を高めるようにサンプルを選択する手法
 - 特徴空間上でラベル付きサンプルとの距離が遠い、あるいはラベル付きサンプル同士の密度が低い領域からサンプルを選択する
 - データ分布を広くカバーするようなサンプルを選択することで、モデルの汎化性能向上が期待できる
 
多様性サンプリング単体では不確実性を考慮しないため、不確実性サンプリングと組み合わせて使われることが多いです。
多様性サンプリングの手法
k-means clusteringを用いたCluster-based Diversity Samplingなどがあります。
実装例
ユーザーからの質問クエリのサンプリング方法を例に実装例を示します。
ここでは複数のサンプリング手法を組み合わせた戦略をとります。
- 不確実性サンプリング:モデルの予測確信度が低いクエリを選択
 - 多様性サンプリング:既存のラベル付きクエリと異なるクエリを選択
 - 組み合わせ戦略:両者を重み付けで組み合わせ
 
実装例
このコードは以下のような場面で活用を想定しています。
- FAQシステムの拡充
 - チャットボットの学習データ収集
 - カスタマーサポートの質問DB構築
 
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.model_selection import train_test_split
class QuerySampling:
    def __init__(self, strategy='combined', n_samples=5, diversity_weight=0.3):
        """
        質問クエリのサンプリングを行うクラス
        
        Parameters:
        -----------
        strategy: str
            'uncertainty': 不確実性サンプリング
            'diversity': 多様性サンプリング
            'combined': 両者の組み合わせ
        n_samples: int
            選択するサンプル数
        diversity_weight: float
            多様性の重み(combinedの場合に使用)
        """
        self.strategy = strategy
        self.n_samples = n_samples
        self.diversity_weight = diversity_weight
        self.vectorizer = TfidfVectorizer(max_features=1000)
        self.classifier = LogisticRegression(random_state=42)
        
    def fit_vectorizer(self, queries):
        """特徴量抽出器の学習"""
        self.vectorizer.fit(queries)
        
    def get_uncertainty_scores(self, X_unlabeled):
        """不確実性スコアの計算"""
        probas = self.classifier.predict_proba(X_unlabeled)
        # エントロピーベースの不確実性
        entropy = -np.sum(probas * np.log(probas + 1e-10), axis=1)
        return entropy
        
    def get_diversity_scores(self, X_unlabeled, X_labeled):
        """多様性スコアの計算"""
        if len(X_labeled) == 0:
            return np.ones(len(X_unlabeled))
            
        # ラベル付きデータとの最小コサイン類似度を計算
        similarities = cosine_similarity(X_unlabeled, X_labeled)
        min_similarities = similarities.min(axis=1)
        # 類似度が低いほど多様性が高い
        diversity_scores = 1 - min_similarities
        return diversity_scores
        
    def select_samples(self, unlabeled_queries, labeled_queries=None):
        """
        サンプリング戦略に基づいてクエリを選択
        
        Parameters:
        -----------
        unlabeled_queries: list
            ラベルなしの質問クエリリスト
        labeled_queries: list
            ラベル済みの質問クエリリスト
            
        Returns:
        --------
        selected_indices: list
            選択されたクエリのインデックス
        """
        # クエリのベクトル化
        X_unlabeled = self.vectorizer.transform(unlabeled_queries)
        
        if self.strategy == 'uncertainty':
            scores = self.get_uncertainty_scores(X_unlabeled)
            
        elif self.strategy == 'diversity':
            X_labeled = self.vectorizer.transform(labeled_queries) if labeled_queries else None
            scores = self.get_diversity_scores(X_unlabeled, X_labeled)
            
        else:  # combined
            uncertainty_scores = self.get_uncertainty_scores(X_unlabeled)
            X_labeled = self.vectorizer.transform(labeled_queries) if labeled_queries else None
            diversity_scores = self.get_diversity_scores(X_unlabeled, X_labeled)
            
            # スコアの正規化
            uncertainty_scores = (uncertainty_scores - uncertainty_scores.min()) / (uncertainty_scores.max() - uncertainty_scores.min())
            diversity_scores = (diversity_scores - diversity_scores.min()) / (diversity_scores.max() - diversity_scores.min())
            
            # 重み付け組み合わせ
            scores = (1 - self.diversity_weight) * uncertainty_scores + self.diversity_weight * diversity_scores
            
        # スコアの高い順にサンプルを選択
        selected_indices = np.argsort(scores)[-self.n_samples:]
        return selected_indices.tolist()
# 使用例
def main():
    # サンプルの質問クエリ
    queries = [
        "製品の返品方法を教えてください",
        "注文のキャンセル方法は?",
        "配送状況を確認したい",
        "支払い方法を変更できますか",
        "商品の在庫状況を知りたい",
        "会員登録の方法について",
        "パスワードを忘れました",
        "注文履歴の確認方法",
        "クーポンの使い方",
        "送料について知りたい"
    ]
    
    # ラベル(0: 一般的な質問, 1: 技術的な質問)
    labels = [0, 0, 0, 1, 0, 1, 1, 0, 0, 0]
    
    # 初期分割
    labeled_queries, unlabeled_queries, y_labeled, _ = train_test_split(
        queries, labels, test_size=0.7, random_state=42
    )
    
    # サンプラーの初期化と使用
    sampler = QuerySampling(strategy='combined', n_samples=2)
    sampler.fit_vectorizer(queries)
    
    # モデルの初期学習
    X_labeled = sampler.vectorizer.transform(labeled_queries)
    sampler.classifier.fit(X_labeled, y_labeled)
    
    # サンプリング実行
    selected_indices = sampler.select_samples(unlabeled_queries, labeled_queries)
    
    print("選択された質問クエリ:")
    for idx in selected_indices:
        print(f"- {unlabeled_queries[idx]}")
if __name__ == "__main__":
    main()
必要に応じて、以下のようなカスタマイズを行いましょう。
- 異なる特徴抽出方法の実装(例:BERT等の事前学習モデルの使用)
 - 追加のサンプリング戦略の実装
 - スコアリング方法の調整
 
おわりに
能動学習における主要なサンプリング手法をみていきました。
よりよいサンプリングと学習を🙌