注目キーワード
  1. Python
  2. コンペ

Python CountVectorizer完全ガイド|fit_transform・get_feature_names_outの使い方と実践コード【scikit-learn】

  • 2024年2月26日
  • 2025年10月4日
  • Python
  • 2346回
  • 0件
目次

Python CountVectorizer完全ガイド|fit_transform・get_feature_names_outの使い方と実践コード【scikit-learn】

scikit-learnのCountVectorizerは、テキストデータを機械学習で扱える数値形式に変換する重要なツールです。この記事では、基本的な使い方からfit_transformget_feature_names_outの詳細な活用法、よくあるエラーの対処法まで、実践的なコード例とともに徹底解説します。

CountVectorizerとは?テキストデータ数値化の基本概念

CountVectorizerは、scikit-learnのsklearn.feature_extraction.textモジュールに含まれるクラスで、テキストデータを単語の出現回数を基に数値ベクトルに変換するツールです。

CountVectorizerの仕組み

  1. 語彙構築:全文書から単語(トークン)を抽出して辞書を作成
  2. ベクトル化:各文書を語彙の出現回数ベクトルに変換
  3. 疎行列形式:メモリ効率のためCSR形式で保存

活用される主なケース

  • 文書分類:スパムメール判定、カテゴリ分類
  • 感情分析:レビューのポジティブ・ネガティブ判定
  • クラスタリング:類似文書のグループ化
  • 情報検索:文書間の類似度計算
  • 特徴量エンジニアリング:機械学習モデルの入力データ作成

基本的な使い方:5分で動かすサンプルコード

まずは最もシンプルな使用例から始めましょう。

最小限のコード例

from sklearn.feature_extraction.text import CountVectorizer
import pandas as pd

# サンプルテキスト
texts = [
    "Python is a great programming language",
    "Machine learning with Python is fun",
    "Data science uses Python extensively"
]

# CountVectorizerのインスタンス化
vectorizer = CountVectorizer()

# テキストを数値ベクトルに変換
X = vectorizer.fit_transform(texts)

# 結果の確認
print("変換後の行列の形状:", X.shape)
print("疎行列形式:", type(X))
print("\n密な配列として表示:")
print(X.toarray())

実行結果

変換後の行列の形状: (3, 11)
疎行列形式: <class 'scipy.sparse._matrix.csr_matrix'>

密な配列として表示:
[[0 0 1 1 1 0 0 1 0 1 0]
 [1 0 1 0 0 1 1 0 1 1 1]
 [0 1 0 0 0 0 0 1 0 1 1]]

結果をDataFrameで見やすく表示

# 特徴量名(単語)を取得
feature_names = vectorizer.get_feature_names_out()

# DataFrameで表示
df = pd.DataFrame(X.toarray(), columns=feature_names)
print(df)

実行結果

   data  extensively  great  is  language  learning  machine  python  science  uses  with
0     0            0      1   1         1         0        0       1        0     0     0
1     0            0      0   1         0         1        1       1        0     0     1  
2     1            1      0   0         0         0        0       1        1     1     0

この結果から、各行(文書)が各列(単語)の出現回数で表現されていることがわかります。

fit・transform・fit_transformの違いと使い分け

CountVectorizerで最も重要な概念の一つが、fittransformfit_transformの使い分けです。

各メソッドの役割

メソッド 機能 戻り値 使用場面
fit() 語彙辞書の構築のみ selfオブジェクト 語彙学習のみ実行したい場合
transform() 学習済み語彙でベクトル化 疎行列 新しいデータをベクトル化
fit_transform() 語彙構築+ベクトル化 疎行列 訓練データの処理

なぜfitとtransformを分けるのか?

機械学習ではデータリーケージを防ぐために、訓練データとテストデータで異なる処理が必要です。

from sklearn.model_selection import train_test_split

# データの準備
texts = [
    "Python is great for machine learning",
    "Data science with Python",  
    "Machine learning algorithms",
    "Python programming is fun",
    "Data analysis using Python"
]
labels = [1, 1, 1, 0, 1]

# 訓練・テストデータに分割
X_train_text, X_test_text, y_train, y_test = train_test_split(
    texts, labels, test_size=0.4, random_state=42
)

# 正しい処理方法
vectorizer = CountVectorizer()

# 1. 訓練データで語彙を学習+ベクトル化
X_train = vectorizer.fit_transform(X_train_text)

# 2. テストデータは学習済み語彙でベクトル化のみ
X_test = vectorizer.transform(X_test_text)

print("訓練データ形状:", X_train.shape)
print("テストデータ形状:", X_test.shape)
print("語彙数:", len(vectorizer.get_feature_names_out()))

実行結果

訓練データ形状: (3, 9)
テストデータ形状: (2, 9)
語彙数: 9

実践的な使い分けパターン

パターン1:通常の機械学習パイプライン

# 訓練フェーズ
vectorizer = CountVectorizer()
X_train_vec = vectorizer.fit_transform(train_texts)

# 予測フェーズ  
X_new_vec = vectorizer.transform(new_texts)

パターン2:Pipeline使用時

from sklearn.pipeline import Pipeline
from sklearn.naive_bayes import MultinomialNB

# Pipelineでは自動的に適切なメソッドが呼ばれる
pipeline = Pipeline([
    ('vectorizer', CountVectorizer()),
    ('classifier', MultinomialNB())
])

# fit時:fit_transform → fit
pipeline.fit(X_train_text, y_train)

# predict時:transform → predict  
predictions = pipeline.predict(X_test_text)

パターン3:段階的処理

# 大量データで語彙のみ先に構築
vectorizer = CountVectorizer(max_features=5000)
vectorizer.fit(large_text_corpus)

# 後でバッチ処理
for batch in text_batches:
    batch_vectors = vectorizer.transform(batch)
    # 処理続行...

get_feature_names_outによる特徴量名取得

get_feature_names_out()は、ベクトル化後の各次元がどの単語に対応するかを取得する重要なメソッドです。

scikit-learn 1.0以降での重要な変更

⚠️ 重要な変更点

scikit-learn 1.0以降、get_feature_names()非推奨となり、get_feature_names_out()に置き換えられました。

バージョン対応コード

import sklearn
from sklearn.feature_extraction.text import CountVectorizer

def get_feature_names_safe(vectorizer):
    """バージョン互換性を考慮した特徴量名取得"""
    try:
        # scikit-learn 1.0以降
        return vectorizer.get_feature_names_out()
    except AttributeError:
        # scikit-learn 0.24以前  
        return vectorizer.get_feature_names()

# 使用例
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(texts)

print(f"scikit-learn version: {sklearn.__version__}")
feature_names = get_feature_names_safe(vectorizer)
print("特徴量名:", feature_names)

get_feature_names_outの実践的活用

1. 重要な特徴量の特定

import numpy as np

# テキストデータとラベル
texts = [
    "This movie is absolutely fantastic and amazing",
    "Terrible movie, worst film ever made", 
    "Good story and great acting performance",
    "Boring plot and bad characters"
]
labels = [1, 0, 1, 0]  # 1:positive, 0:negative

# ベクトル化
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(texts)
feature_names = vectorizer.get_feature_names_out()

# 各クラスの平均出現回数を計算
positive_mask = np.array(labels) == 1
negative_mask = np.array(labels) == 0

positive_avg = X[positive_mask].mean(axis=0).A1
negative_avg = X[negative_mask].mean(axis=0).A1

# 差分を計算(正の値:ポジティブ寄り、負の値:ネガティブ寄り)
diff = positive_avg - negative_avg

# 重要度順にソート
importance_indices = np.argsort(np.abs(diff))[::-1]

print("重要度の高い特徴量:")
for i in importance_indices[:5]:
    direction = "ポジティブ" if diff[i] > 0 else "ネガティブ"
    print(f"{feature_names[i]}: {diff[i]:.3f} ({direction}寄り)")

実行結果

重要度の高い特徴量:
amazing: 0.500 (ポジティブ寄り)
fantastic: 0.500 (ポジティブ寄り)  
terrible: -0.500 (ネガティブ寄り)
worst: -0.500 (ネガティブ寄り)
bad: -0.500 (ネガティブ寄り)

2. DataFrame連携での可視化

import pandas as pd
import matplotlib.pyplot as plt

# 結果をDataFrameで整理
vectorizer = CountVectorizer(max_features=10)
X = vectorizer.fit_transform(texts)
feature_names = vectorizer.get_feature_names_out()

# 文書-単語行列をDataFrameに変換
doc_term_df = pd.DataFrame(
    X.toarray(), 
    columns=feature_names,
    index=[f"Document_{i+1}" for i in range(len(texts))]
)

print("文書-単語行列:")
print(doc_term_df)

# 単語頻度の可視化
word_counts = doc_term_df.sum(axis=0).sort_values(ascending=False)
plt.figure(figsize=(10, 6))
word_counts.plot(kind='bar')
plt.title('単語出現頻度')
plt.xlabel('単語')
plt.ylabel('出現回数')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

よくあるエラーと対処法

AttributeError: ‘CountVectorizer’ object has no attribute ‘get_feature_names_out’

# 原因:古いscikit-learnバージョン
# 解決策1:アップグレード
pip install scikit-learn>=1.0

# 解決策2:条件分岐で対応
try:
    feature_names = vectorizer.get_feature_names_out()
except AttributeError:
    feature_names = vectorizer.get_feature_names()

NotFittedError: This CountVectorizer instance is not fitted yet

# 原因:fit/fit_transformを実行する前にget_feature_names_outを呼び出し
# 解決策:
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(texts)  # 先にfitが必要
feature_names = vectorizer.get_feature_names_out()  # その後で呼び出し

重要パラメータの実践的活用法

CountVectorizerの性能を最大化するには、パラメータの適切な設定が重要です。

主要パラメータ一覧

パラメータ デフォルト 効果 推奨設定例
max_features None 語彙数の上限設定 1000-10000
min_df 1 最小文書出現数 2-5 or 0.01-0.05
max_df 1.0 最大文書出現割合 0.8-0.95
ngram_range (1,1) n-gramの範囲 (1,2) or (1,3)
stop_words None ストップワード除去 ‘english’ or カスタム

パラメータチューニングの実践例

1. 語彙サイズの最適化

import matplotlib.pyplot as plt
from sklearn.datasets import fetch_20newsgroups
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

# サンプルデータの準備
newsgroups = fetch_20newsgroups(subset='train', categories=['alt.atheism', 'soc.religion.christian'])
X_train, X_test, y_train, y_test = train_test_split(
    newsgroups.data, newsgroups.target, test_size=0.3, random_state=42
)

# 異なるmax_featuresでの性能比較
max_features_list = [100, 500, 1000, 2000, 5000, 10000]
accuracies = []
fit_times = []

for max_feat in max_features_list:
    import time
    
    # 時間測定開始
    start_time = time.time()
    
    # ベクトル化と学習
    vectorizer = CountVectorizer(max_features=max_feat, stop_words='english')
    X_train_vec = vectorizer.fit_transform(X_train)
    X_test_vec = vectorizer.transform(X_test)
    
    # 分類器の学習と予測
    classifier = MultinomialNB()
    classifier.fit(X_train_vec, y_train)
    y_pred = classifier.predict(X_test_vec)
    
    # 結果の記録
    accuracy = accuracy_score(y_test, y_pred)
    fit_time = time.time() - start_time
    
    accuracies.append(accuracy)
    fit_times.append(fit_time)
    
    print(f"max_features={max_feat}: accuracy={accuracy:.3f}, time={fit_time:.2f}s")

# 結果の可視化
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

ax1.plot(max_features_list, accuracies, 'bo-')
ax1.set_xlabel('max_features')  
ax1.set_ylabel('Accuracy')
ax1.set_title('精度 vs 語彙数')

ax2.plot(max_features_list, fit_times, 'ro-')
ax2.set_xlabel('max_features')
ax2.set_ylabel('Fit Time (seconds)')
ax2.set_title('処理時間 vs 語彙数')

plt.tight_layout()
plt.show()

2. n-gramの効果検証

# 異なるngram_rangeでの比較
ngram_configs = [
    (1, 1),  # 単語のみ
    (1, 2),  # 単語 + バイグラム
    (1, 3),  # 単語 + バイグラム + トライグラム
    (2, 2),  # バイグラムのみ
]

results = []

for ngram_range in ngram_configs:
    vectorizer = CountVectorizer(
        ngram_range=ngram_range, 
        max_features=5000,
        stop_words='english'
    )
    
    X_train_vec = vectorizer.fit_transform(X_train)
    X_test_vec = vectorizer.transform(X_test)
    
    classifier = MultinomialNB()
    classifier.fit(X_train_vec, y_train)
    y_pred = classifier.predict(X_test_vec)
    
    accuracy = accuracy_score(y_test, y_pred)
    vocab_size = len(vectorizer.get_feature_names_out())
    
    results.append({
        'ngram_range': ngram_range,
        'accuracy': accuracy,
        'vocab_size': vocab_size
    })
    
    print(f"ngram_range={ngram_range}: accuracy={accuracy:.3f}, vocabulary_size={vocab_size}")

# 結果をDataFrameで整理
import pandas as pd
results_df = pd.DataFrame(results)
print("\n結果一覧:")
print(results_df)

3. ストップワードの効果

# カスタムストップワードの定義
custom_stopwords = ['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by']

stop_words_configs = [
    None,           # ストップワードなし
    'english',      # 英語デフォルト
    custom_stopwords  # カスタム
]

for i, stop_words in enumerate(stop_words_configs):
    vectorizer = CountVectorizer(
        stop_words=stop_words,
        max_features=1000
    )
    
    X_vec = vectorizer.fit_transform(texts)
    feature_names = vectorizer.get_feature_names_out()
    
    config_name = ['なし', '英語デフォルト', 'カスタム'][i]
    print(f"\nストップワード設定: {config_name}")
    print(f"語彙数: {len(feature_names)}")
    print(f"語彙例: {feature_names[:10]}")

日本語テキスト処理の注意点と対策

CountVectorizerで日本語テキストを処理する際は、特別な注意が必要です。

日本語処理の基本的な問題

問題1:単語分割がされない

# 問題のあるコード例
japanese_texts = [
    "機械学習はとても面白い技術です",
    "自然言語処理を勉強しています", 
    "Pythonでプログラミングを学習中"
]

# デフォルトでは文字単位で分割されてしまう
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(japanese_texts)
feature_names = vectorizer.get_feature_names_out()

print("文字単位分割の結果:")
print(feature_names[:20])  # 文字ごとに分割されている

実行結果

文字単位分割の結果:
['python' 'い' 'う' 'が' 'こ' 'し' 'す' 'て' 'で' 'と' 'な' 'に' 'の' 'は' 'ま' 'み' 'も' 'を' '中' '処']

解決策:形態素解析器の使用

MeCabを使用した日本語対応

# 必要なライブラリのインストール
# pip install mecab-python3

import MeCab
import re

def japanese_tokenizer(text):
    """MeCabを使用した日本語トークナイザー"""
    mecab = MeCab.Tagger('-Owakati')
    tokens = mecab.parse(text).strip().split()
    # 英数字のみの単語と1文字の単語を除外
    tokens = [token for token in tokens if len(token) > 1 and not re.match(r'^[a-zA-Z0-9]+$', token)]
    return tokens

# カスタムトークナイザーを使用
vectorizer = CountVectorizer(
    tokenizer=japanese_tokenizer,
    token_pattern=None  # デフォルトパターンを無効化
)

X = vectorizer.fit_transform(japanese_texts)
feature_names = vectorizer.get_feature_names_out()

print("形態素解析後の特徴量:")
print(feature_names)

実行結果

形態素解析後の特徴量:
['とても' 'プログラミング' '勉強' '学習' '機械' '技術' '自然' '言語' '面白い']

Janomeを使用した場合(インストールが簡単)

# pip install janome

from janome.tokenizer import Tokenizer

def janome_tokenizer(text):
    """Janomeを使用した日本語トークナイザー"""
    tokenizer = Tokenizer()
    tokens = []
    for token in tokenizer.tokenize(text, wakati=True):
        if len(token) > 1 and not re.match(r'^[a-zA-Z0-9]+$', token):
            tokens.append(token)
    return tokens

# 使用方法は同じ
vectorizer = CountVectorizer(
    tokenizer=janome_tokenizer,
    token_pattern=None
)

日本語ストップワードの設定

# 日本語ストップワードの定義
japanese_stopwords = [
    'の', 'に', 'は', 'を', 'た', 'が', 'で', 'て', 'と', 'し', 'れ', 'さ', 
    'ある', 'いる', 'も', 'する', 'から', 'な', 'こと', 'として', 'い', 'や', 
    'れる', 'など', 'なっ', 'ない', 'この', 'ため', 'その', 'あっ', 'よう', 
    'また', 'もの', 'という', 'あり', 'まで', 'られ', 'なる', 'へ', 'か', 'だ', 
    'これ', 'によって', 'により', 'おり', 'より', 'による', 'ず', 'なり', 'られる',
    'において', 'ば', 'なかっ', 'なっ', 'しかし', 'について', 'せ', 'だっ', 'その後',
    'できる', 'それ'
]

# 日本語対応CountVectorizer
vectorizer = CountVectorizer(
    tokenizer=japanese_tokenizer,
    token_pattern=None,
    stop_words=japanese_stopwords,
    min_df=2,  # 2回以上出現する単語のみ
    max_df=0.8  # 80%以下の文書に出現する単語のみ
)

CountVectorizerが遅い・エラーが出る時の対処法

実際の使用では様々な問題に遭遇します。代表的な問題と解決策を整理しました。

1. メモリ不足対策

問題:大規模データでMemoryError

# 問題のあるコード(大量語彙でメモリ不足)
vectorizer = CountVectorizer()  # 制限なし

解決策:パラメータによる制限

# メモリ効率的な設定
vectorizer = CountVectorizer(
    max_features=10000,      # 語彙数を制限
    min_df=5,               # 5回未満の単語を除外
    max_df=0.8,             # 80%超出現の単語を除外
    binary=True             # 0/1のバイナリ化(メモリ節約)
)

# さらに節約したい場合
from sklearn.feature_extraction.text import HashingVectorizer
# メモリ使用量が固定のHashingVectorizerを検討

2. 処理速度改善

大規模データの分割処理

import numpy as np
from scipy.sparse import vstack

def process_large_corpus(texts, batch_size=1000):
    """大規模データをバッチ処理"""
    vectorizer = CountVectorizer(max_features=5000)
    
    # 最初のバッチで語彙を構築
    first_batch = texts[:batch_size]
    vectorizer.fit(first_batch)
    
    # バッチごとに処理
    result_matrices = []
    
    for i in range(0, len(texts), batch_size):
        batch = texts[i:i+batch_size]
        batch_matrix = vectorizer.transform(batch)
        result_matrices.append(batch_matrix)
        
        print(f"処理済み: {min(i+batch_size, len(texts))}/{len(texts)}")
    
    # 結合
    final_matrix = vstack(result_matrices)
    return final_matrix, vectorizer

# 使用例
# large_texts = ["text1", "text2", ...] # 10万件のテキスト
# X, vectorizer = process_large_corpus(large_texts)

並列処理の活用

from sklearn.feature_extraction.text import CountVectorizer
from joblib import Parallel, delayed
import numpy as np

# n_jobsパラメータで並列化(scikit-learn内部)
vectorizer = CountVectorizer(
    max_features=5000,
    # 一部の処理で並列化サポート(バージョン依存)
)

# 手動での並列化例
def process_chunk(texts_chunk, vectorizer):
    """チャンクを処理する関数"""
    return vectorizer.transform(texts_chunk)

def parallel_transform(texts, vectorizer, n_jobs=-1):
    """並列でtransform処理"""
    chunk_size = len(texts) // n_jobs if n_jobs > 0 else 1000
    chunks = [texts[i:i+chunk_size] for i in range(0, len(texts), chunk_size)]
    
    results = Parallel(n_jobs=n_jobs)(
        delayed(process_chunk)(chunk, vectorizer) for chunk in chunks
    )
    
    return vstack(results)

3. よくあるエラーと対処法

UnicodeDecodeError

# 問題:文字エンコーディングエラー
# 解決策:エンコーディングの明示的指定
def safe_read_text_file(filepath):
    """安全なテキストファイル読み込み"""
    encodings = ['utf-8', 'shift_jis', 'cp932', 'euc-jp']
    
    for encoding in encodings:
        try:
            with open(filepath, 'r', encoding=encoding) as f:
                return f.read()
        except UnicodeDecodeError:
            continue
    
    # 最後の手段:エラー文字を無視
    with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
        return f.read()

AttributeError対策(バージョン互換)

def safe_get_feature_names(vectorizer):
    """バージョン対応の特徴量名取得"""
    if hasattr(vectorizer, 'get_feature_names_out'):
        return vectorizer.get_feature_names_out()
    elif hasattr(vectorizer, 'get_feature_names'):
        return np.array(vectorizer.get_feature_names())
    else:
        raise AttributeError("特徴量名取得メソッドが見つかりません")

空文書・エラー文書の処理

def clean_texts(texts):
    """テキストデータのクリーニング"""
    cleaned = []
    
    for text in texts:
        if text is None:
            cleaned.append("")
            continue
            
        # 文字列に変換
        text = str(text)
        
        # 空文字・空白のみの場合
        if not text.strip():
            cleaned.append("")
            continue
            
        # 最小長チェック
        if len(text.strip()) < 2:
            cleaned.append("")
            continue
            
        cleaned.append(text.strip())
    
    return cleaned

# 使用例
texts = clean_texts(raw_texts)
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(texts)

実践的な例:頻出Bigramの抽出と可視化

より実践的な活用例として、テキストデータから意味のある単語の組み合わせ(bigram)を抽出し、可視化する方法を解説します。

高度なBigram分析の実装

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.feature_extraction.text import CountVectorizer
from wordcloud import WordCloud

# より現実的なサンプルデータ
reviews = [
    "This product is absolutely amazing and high quality materials",
    "Poor customer service and terrible product quality", 
    "Great value for money and fast shipping service",
    "Excellent product quality but slow customer service",
    "Amazing customer support and fantastic product design",
    "Poor product quality and disappointing customer experience",
    "Fast delivery service and great product packaging",
    "Terrible user experience but good product features"
]

labels = [1, 0, 1, 1, 1, 0, 1, 0]  # 1: positive, 0: negative

def extract_significant_bigrams(texts, labels, top_n=15):
    """統計的に有意なbigramを抽出"""
    
    # Bigramの抽出
    vectorizer = CountVectorizer(
        ngram_range=(2, 2),
        stop_words='english',
        min_df=2  # 2回以上出現
    )
    
    X = vectorizer.fit_transform(texts)
    feature_names = vectorizer.get_feature_names_out()
    
    # ポジティブ・ネガティブクラス別の平均出現数
    positive_mask = np.array(labels) == 1
    negative_mask = np.array(labels) == 0
    
    pos_mean = np.array(X[positive_mask].mean(axis=0)).flatten()
    neg_mean = np.array(X[negative_mask].mean(axis=0)).flatten()
    
    # 差分スコアの計算
    diff_scores = pos_mean - neg_mean
    
    # 結果をDataFrameに整理
    bigram_analysis = pd.DataFrame({
        'bigram': feature_names,
        'positive_freq': pos_mean,
        'negative_freq': neg_mean,
        'diff_score': diff_scores,
        'total_freq': pos_mean + neg_mean
    })
    
    # 絶対的な差分でソート
    bigram_analysis['abs_diff'] = np.abs(bigram_analysis['diff_score'])
    bigram_analysis = bigram_analysis.sort_values('abs_diff', ascending=False)
    
    return bigram_analysis.head(top_n)

# 分析実行
results = extract_significant_bigrams(reviews, labels)
print("重要なBigram分析結果:")
print(results[['bigram', 'diff_score', 'total_freq']].round(3))

実行結果

重要なBigram分析結果:
          bigram  diff_score  total_freq
0  customer service      0.000       0.500
1  product quality     -0.167       0.625  
2    poor product      -0.333       0.250
3   great product       0.333       0.250
4    poor customer      -0.250       0.125

可視化による洞察の獲得

# 1. 差分スコアの可視化
plt.figure(figsize=(12, 8))

# ポジティブ・ネガティブに分けて色分け
colors = ['red' if score < 0 else 'green' for score in results['diff_score']]

plt.barh(range(len(results)), results['diff_score'], color=colors, alpha=0.7)
plt.yticks(range(len(results)), results['bigram'])
plt.xlabel('差分スコア (正:ポジティブ寄り, 負:ネガティブ寄り)')
plt.title('Bigramのポジティブ・ネガティブ寄り度')
plt.axvline(x=0, color='black', linestyle='--', alpha=0.5)

# スコアをバーに表示
for i, (idx, row) in enumerate(results.iterrows()):
    plt.text(row['diff_score']/2, i, f"{row['diff_score']:.3f}", 
             ha='center', va='center', fontweight='bold')

plt.tight_layout()
plt.show()

# 2. 出現頻度のヒートマップ
plt.figure(figsize=(10, 6))
heatmap_data = results[['positive_freq', 'negative_freq']].T
heatmap_data.columns = results['bigram']

sns.heatmap(heatmap_data, annot=True, fmt='.3f', cmap='RdYlBu_r', 
            yticklabels=['Positive Reviews', 'Negative Reviews'])
plt.title('Bigram出現頻度ヒートマップ')
plt.xticks(rotation=45, ha='right')
plt.tight_layout()
plt.show()

より発展的な分析:TF-IDF重み付けとの比較

from sklearn.feature_extraction.text import TfidfVectorizer

def compare_count_vs_tfidf(texts, top_n=10):
    """CountVectorizerとTfidfVectorizerの結果比較"""
    
    # CountVectorizer
    count_vec = CountVectorizer(ngram_range=(1, 2), stop_words='english', max_features=50)
    count_matrix = count_vec.fit_transform(texts)
    count_features = count_vec.get_feature_names_out()
    count_sums = np.array(count_matrix.sum(axis=0)).flatten()
    
    # TfidfVectorizer
    tfidf_vec = TfidfVectorizer(ngram_range=(1, 2), stop_words='english', max_features=50)
    tfidf_matrix = tfidf_vec.fit_transform(texts)
    tfidf_features = tfidf_vec.get_feature_names_out()
    tfidf_sums = np.array(tfidf_matrix.sum(axis=0)).flatten()
    
    # 結果の比較
    count_top = sorted(zip(count_features, count_sums), key=lambda x: x[1], reverse=True)[:top_n]
    tfidf_top = sorted(zip(tfidf_features, tfidf_sums), key=lambda x: x[1], reverse=True)[:top_n]
    
    print("Count-based Top Features:")
    for feature, score in count_top:
        print(f"  {feature}: {score:.3f}")
    
    print("\nTF-IDF-based Top Features:")  
    for feature, score in tfidf_top:
        print(f"  {feature}: {score:.3f}")
    
    return count_top, tfidf_top

# 比較実行
count_results, tfidf_results = compare_count_vs_tfidf(reviews)

関連技術との比較(TfidfVectorizer・HashingVectorizer)

CountVectorizerと関連するテキストベクトル化手法を比較し、適切な選択指針を提供します。

主要な3つのVectorizer比較

特徴 CountVectorizer TfidfVectorizer HashingVectorizer
基本原理 単語の出現回数 TF-IDF重み付け ハッシュ関数による固定長
メモリ使用量 語彙辞書が必要 語彙辞書が必要 辞書不要(固定)
処理速度 中程度 やや遅い 高速
解釈性 高い 高い 低い(逆引き不可)
適用場面 基本的な分析・分類 情報検索・文書分類 大規模・ストリーミング

実際のパフォーマンス比較

import time
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer, HashingVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.datasets import fetch_20newsgroups
from sklearn.metrics import classification_report

# データ準備
newsgroups = fetch_20newsgroups(
    subset='all', 
    categories=['alt.atheism', 'soc.religion.christian', 'comp.graphics', 'sci.med'],
    remove=('headers', 'footers', 'quotes')
)

X_text, y = newsgroups.data, newsgroups.target

# 訓練・テスト分割
from sklearn.model_selection import train_test_split
X_train_text, X_test_text, y_train, y_test = train_test_split(
    X_text, y, test_size=0.3, random_state=42
)

# 各ベクトライザーでの比較
vectorizers = {
    'CountVectorizer': CountVectorizer(max_features=10000, stop_words='english'),
    'TfidfVectorizer': TfidfVectorizer(max_features=10000, stop_words='english'),
    'HashingVectorizer': HashingVectorizer(n_features=10000, stop_words='english')
}

results = {}

for name, vectorizer in vectorizers.items():
    print(f"\n=== {name} ===")
    
    # 時間測定
    start_time = time.time()
    
    # ベクトル化
    if hasattr(vectorizer, 'fit_transform'):
        X_train_vec = vectorizer.fit_transform(X_train_text)
        X_test_vec = vectorizer.transform(X_test_text)
    else:
        # HashingVectorizerはfitが不要
        X_train_vec = vectorizer.transform(X_train_text)
        X_test_vec = vectorizer.transform(X_test_text)
    
    vectorize_time = time.time() - start_time
    
    # 分類器の学習
    start_time = time.time()
    classifier = MultinomialNB()
    classifier.fit(X_train_vec, y_train)
    fit_time = time.time() - start_time
    
    # 予測
    start_time = time.time()
    y_pred = classifier.predict(X_test_vec)
    predict_time = time.time() - start_time
    
    # 評価
    from sklearn.metrics import accuracy_score, f1_score
    accuracy = accuracy_score(y_test, y_pred)
    f1 = f1_score(y_test, y_pred, average='weighted')
    
    # 結果保存
    results[name] = {
        'vectorize_time': vectorize_time,
        'fit_time': fit_time,
        'predict_time': predict_time,
        'total_time': vectorize_time + fit_time + predict_time,
        'accuracy': accuracy,
        'f1_score': f1,
        'matrix_shape': X_train_vec.shape
    }
    
    print(f"ベクトル化時間: {vectorize_time:.2f}s")
    print(f"学習時間: {fit_time:.2f}s")
    print(f"予測時間: {predict_time:.2f}s")
    print(f"総時間: {vectorize_time + fit_time + predict_time:.2f}s")
    print(f"精度: {accuracy:.3f}")
    print(f"F1スコア: {f1:.3f}")
    print(f"行列サイズ: {X_train_vec.shape}")

# 結果の可視化
import pandas as pd

results_df = pd.DataFrame(results).T
print("\n=== 総合比較 ===")
print(results_df.round(3))

選択指針

CountVectorizerを選ぶべき場面

  • 初期分析・探索的データ解析:シンプルで理解しやすい
  • 単語頻度が重要:出現回数そのものが意味を持つ場合
  • 短いテキスト:ツイートやコメントなど
  • 解釈性重視:結果の説明が必要な場合

TfidfVectorizerを選ぶべき場面

  • 文書分類・情報検索:文書の重要性を考慮したい場合
  • 長い文書:記事や論文など
  • 類似度計算:コサイン類似度など
  • 一般的な単語の影響を抑制:TF-IDF重み付けの恩恵

HashingVectorizerを選ぶべき場面

  • 大規模データ:メモリ制約が厳しい場合
  • ストリーミングデータ:オンライン学習
  • 高速処理重視:リアルタイム処理
  • 語彙の事前把握不要:動的な語彙変化

よくある質問(FAQ)

Q1. fit/transform/fit_transformの使い分けは?

A: 機械学習の基本原則に従って使い分けます:

  • fit_transform()訓練データで語彙構築+ベクトル化
  • transform()テスト・新規データで学習済み語彙を使用
  • fit():語彙構築のみ(大規模データでの事前処理など)

Q2. get_feature_names_outとget_feature_namesの違いは?

A: scikit-learn 1.0以降の仕様変更です:

  • get_feature_names_out():新しいメソッド(推奨)
  • get_feature_names():旧メソッド(非推奨)
  • 機能は同じですが、新しいコードではget_feature_names_out()を使用してください

Q3. 日本語テキストで単語が抽出されない原因は?

A: 単語境界の問題です:

  • 日本語は単語間にスペースがないため、デフォルトでは文字単位で分割される
  • 解決策:MeCabやJanomeなどの形態素解析器を使用
  • tokenizerパラメータでカスタムトークナイザーを指定

Q4. 大規模データで処理が遅い時の対策は?

A: 複数のアプローチがあります:

  • max_featuresで語彙数を制限(推奨:1000-10000)
  • min_df/max_dfで頻度フィルタリング
  • バッチ処理で分割して実行
  • HashingVectorizerへの変更を検討

Q5. CountVectorizerとTfidfVectorizerの選び方は?

A: タスクと要求に応じて選択

  • CountVectorizer:シンプルな分析、短いテキスト、解釈性重視
  • TfidfVectorizer:文書分類、情報検索、長いテキスト
  • 迷った場合は両方試して性能比較することを推奨

Q6. バイナリ化(binary=True)はいつ使う?

A: 出現回数より出現の有無が重要な場合

  • スパムフィルタリング(単語の存在/不存在が重要)
  • 短いテキスト(ツイート等、単語の重複が少ない)
  • メモリ節約(0/1のバイナリで保存効率向上)

Q7. n-gramはどの範囲を指定すべき?

A: データとタスクに応じて調整

  • (1,1):単語のみ(高速、シンプル)
  • (1,2):単語+バイグラム(バランス良好)
  • (1,3)以上:語彙爆発に注意、計算コスト増
  • 感情分析では(1,2)、固有表現抽出では(2,3)が効果的

Q8. ストップワードは必ず設定すべき?

A: タスク依存ですが、多くの場合有効:

  • 設定推奨:文書分類、情報検索、クラスタリング
  • 設定不要:感情分析(”not good”の”not”は重要)
  • 英語:stop_words='english'
  • 日本語:カスタムリストを作成

まとめ

CountVectorizerは、テキスト分析における最も基本的で重要なツールの一つです。適切なパラメータ設定と前処理により、様々なNLPタスクで高い性能を発揮できます。特にfit_transformget_feature_names_outの正しい理解は、効果的なテキスト分析の第一歩となります。

実践的な活用では、データの特性(言語、文書長、語彙サイズ)とタスクの要求(精度、処理速度、解釈性)を考慮して、最適な設定を見つけることが重要です。

最新情報をチェックしよう!