【コピペで動く】BERT感情分析×PythonでGoogleマップの口コミを分析する方法

今回は、BERTを使った感情分析に挑戦してみました。「BERTって難しそう...」と思っていたのですが、実際にやってみるとPython初心者でも意外と簡単に実装できたのでまとめてみました。

なぜBERT感情分析をやってみたのか?

きっかけは...

大学の同期の方がBERTを使った資料の分析をしているということからでした。以前文章のポジ・ネガの判定をやってみたことがあったのですが、当時は結果ばかりを重視していて、理論的な理解が浅かったんですよね。現在はAI関連の知識も少しは増えたので、改めてBERTの仕組みを理解しつつ、実際に手を動かしてみたいと思ったわけです。

BERTって何?(1分で分かる超入門)

詳しい理論は他の記事に譲りますが、BERTは「文章の意味を理解できるAI」って感じですかね。

従来の方法との違い

以下のような違いがある感じですかね。

【従来の方法】
「美味しい」という単語 → ポジティブ!
「美味しくない」という文章 → 「美味しい」が入ってる → ポジティブ!(間違い)

【BERT】
「美味しくない」→ 「ない」という否定を理解 → ネガティブ(正解)

BERTは前後の文脈を理解するので、否定表現や皮肉もある程度判定できるのが強みです。

BERTはどこで使われている?

具体的には、以下のような場面で使われているようです。

では実際にやってみよう!

開発環境

今回使った開発環境は以下の通りです。WSLで動くのでMacLinuxユーザーでも参考になると思います。

uvは、torchのような大容量パッケージのインストールが爆速になるので今回は特におすすめです。

作ろうとしているプログラム

今回は2つのプログラムを作成しようと思います。

まず1つ目は、シンプルなテキスト感情分析プログラムコマンドラインでテキストを入力すると、ポジティブ/ネガティブを判定するものになります。このプログラムでBERTの基本的な使い方を学びます。

2つ目は、Googleマップの口コミ自動分析プログラム。店舗名を入力すると、その店舗の口コミを自動取得し、各口コミの感情をBERTで判定、ポジティブ/ネガティブの割合を集計してCSVとして出力するというものです。このプログラムで調べようと思ったのはGoogleマップの口コミと☆の関係の相関がどの程度あるかという点です。これをうまく使えれば、ビジネスに直結する重要な情報が得られると考えました。(自分に使えるのかというと自信はない🙄)

0. 必要なライブラリのインストール

WSLのターミナルを開き、以下のコマンドで必要なライブラリをインストールします。このライブラリは以降の両方のプログラムで使います。また、使用する環境はCPU版とGPU版の2種類がありますが、今回はCPU版で十分ですが、GPU版の方が高速に動作します。

# uvのインストール(必要なら)
$ curl -LsSf https://astral.sh/uv/install.sh | sh
$ uv --version

# プロジェクト用ディレクトリ作成と仮想環境構築
$ mkdir bert_sentiment_ja
$ cd bert_sentiment_ja
$ uv venv
$ source .venv/bin/activate

# CPU版(軽量:約1〜2GB)
$ uv pip install torch --index-url https://download.pytorch.org/whl/cpu
$ uv pip install transformers fugashi ipadic unidic-lite

# 例 GPU版(重量:約5〜7GB)下記⚠️を参照
$ uv pip install torch --index-url https://download.pytorch.org/whl/cu121
$ uv pip install transformers fugashi ipadic unidic-lite

これで必要なライブラリがインストールされました。

⚠️GPU版を使う場合は、NVIDIAのドライバとCUDAツールキットが必要です。 ⚠️GPU版はCUDAのバージョンに依存するのでバージョンも確認してください。上記は12.1の例となっています。

1. シンプルなテキスト感情分析

では、早速シンプルなテキスト感情分析プログラムを作ってみます。 以下のプログラムでは、固定の文字列のネガ・ポジを判定した後に、インタラクティブモードでユーザーが入力したテキストをリアルタイムに分析できるようにしています。

bert_sentiment_analysis.py

このプログラムの説明

1. 初期化処理 (__init__)

この部分では、BERTモデルとトークナイザーを読み込み、使用するデバイス(CPU/GPU)を設定しています。

  • バイス選択 … GPUがあればCUDA、なければCPUを使用します
  • トークナイザーの設定 … テキストをBERTが理解できるトークンに変換する機能を設定します。
  • モデルの設定 … 事前学習済みの感情分類BERTモデルをHugging Faceからダウンロードします。
  • eval() … 推論モードに設定
        print("モデルを読み込んでいます...")
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
        print(f"使用デバイス: {self.device}")
        
        # トークナイザーとモデルの読み込み
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModelForSequenceClassification.from_pretrained(model_name)
        self.model.to(self.device)
        self.model.eval()

初期化後に_setup_labelsメソッドを呼び出して、モデルに応じたラベル設定を行います。


2. ラベル設定 (_setup_labels)

モデルが持つラベル情報(positive/negative等)を取得し、日本語(ポジティブ/ネガティブ)に変換しています。 モデルによってpositive/negative等の英語ラベルが使われていることがあるため、それらをポジティブ/ネガティブに変換しています。

    def _setup_labels(self):
        """モデルに応じたラベル設定"""
        # モデルの設定からラベルを取得
        if hasattr(self.model.config, 'id2label'):
            raw_labels = self.model.config.id2label
            # 英語ラベルを日本語に変換(大文字対応)
            self.labels = {}
            for idx, label in raw_labels.items():
                label_lower = label.lower()
                if label_lower in ['positive', 'ポジティブ', '1', 'pos']:
                    self.labels[idx] = 'ポジティブ'
                elif label_lower in ['negative', 'ネガティブ', '0', 'neg']:
                    self.labels[idx] = 'ネガティブ'
                elif label_lower in ['neutral', 'ニュートラル', '中立', 'neu']:
                    self.labels[idx] = 'ニュートラル'
                else:
                    self.labels[idx] = label
        else:
            # デフォルトのラベル(2クラス分類)
            self.labels = {
                0: "ネガティブ",
                1: "ポジティブ"
            }

3. 予測処理 (predict) ← この処理が重要

このメソッドがテキストの感情を予測する主要な処理です。以下の3つのステップに分かれています。

ステップ1 トークン化

BERTは数値しか処理できないので、テキストを数値に変換する必要があります。そのためにトークナイザーを使って、以下のような処理を行った後、[CLS] トークン列 [SEP]の形式に変換し、数値IDの配列にしています。 [CLS]文の先頭を示す。分類タスクではこの位置の出力を使う[SEP]文の終わりを示しています。

「今日は楽しい」
     ↓ 単語/サブワードに分割
["今日", "は", "楽し", "##い"]
     ↓ 特殊トークンを追加
["[CLS]", "今日", "は", "楽し", "##い", "[SEP]"]
     ↓ 数値IDに変換
[2, 1045, 9, 3782, 1124, 3]
        # テキストのトークン化
        inputs = self.tokenizer(
            text,
            return_tensors='pt',
            truncation=True,
            max_length=512,
            padding=True
        )

ステップ2 予測

ここではモデルに入力値を渡し、各クラス(ポジティブ/ネガティブ)の生スコアが出力します。 このスコアをsoftmax関数で確率に変換し、最も高い確率のクラスを予測結果として採用します。 その確率値がそのまま「信頼度」となります。その後、最大確率のインデックスを取得します。 0ならポジティブ、1ならネガティブという形になります。

つまり、配列のインデックスがネガポジ、値が確率値(信頼度)になります。

👉️Softmax関数とは複数の数値を「合計が1になる確率分布」に変換する関数です。

        with torch.no_grad():
            outputs = self.model(**inputs)
            logits = outputs.logits
            probabilities = torch.softmax(logits, dim=1)
            predicted_class = torch.argmax(probabilities, dim=1).item()
            confidence = probabilities[0][predicted_class].item()

4. バッチ処理 (predict_batch)

複数のテキストをまとめて分析するメソッドです。 内部ではテキストを1件ずつpredict()に渡してループ処理し、結果をリストにまとめて返します。

    def predict_batch(self, texts: List[str]) -> List[Dict]:
        """
        複数テキストの感情を一括予測
        
        Args:
            texts: 分析するテキストのリスト
            
        Returns:
            予測結果のリスト
        """
        results = []
        for text in texts:
            result = self.predict(text)
            results.append(result)
        return results

シンプルなテキスト感情分析プログラムを実行する

先程のプログラムを実行すると以下のように動作します。前半では固定のサンプルテキストを分析し、後半ではインタラクティブモードでユーザーが入力したテキストをリアルタイムに分析できます。インタラクティブモードでは、qまたはquitを入力すると終了します。

$ python bert_sentiment_analysis.py
モデルを読み込んでいます...
使用デバイス: cpu
モデルの読み込みが完了しました!

【サンプルテキストの感情分析】

============================================================
テキスト: 今日はとても楽しい一日でした!
------------------------------------------------------------
感情: ポジティブ
信頼度: 99.40%

確率分布:
  ニュートラル: 0.52%
  ネガティブ: 0.08%
  ポジティブ: 99.40% █████████████████████████████████████████████████
============================================================

(中略)

============================================================
【インタラクティブモード】
分析したいテキストを入力してください(終了: 'q' または 'quit')
============================================================

テキスト: コーヒーが渋い

============================================================
テキスト: コーヒーが渋い
------------------------------------------------------------
感情: ネガティブ
信頼度: 96.62%

確率分布:
  ニュートラル: 2.91% █
  ネガティブ: 96.62% ████████████████████████████████████████████████
  ポジティブ: 0.47%
============================================================

テキスト: 評価が渋い

============================================================
テキスト: 評価が渋い
------------------------------------------------------------
感情: ネガティブ
信頼度: 97.06%

確率分布:
  ニュートラル: 2.45% █
  ネガティブ: 97.06% ████████████████████████████████████████████████
  ポジティブ: 0.49%
============================================================

テキスト: 親父が渋い

============================================================
テキスト: 親父が渋い
------------------------------------------------------------
感情: ネガティブ
信頼度: 62.10%

確率分布:
  ニュートラル: 36.56% ██████████████████
  ネガティブ: 62.10% ███████████████████████████████
  ポジティブ: 1.33%
============================================================

テキスト: q
分析を終了します。

インタラクティブモードでテキストを入力すると、即座に感情を判定してくれます。また、今回はインタラクティブモードで「渋い」という単語を含む3つの文を試してみましたが、文脈によって異なる判定がされていることが分かります。これがBERTの強みとなります。

これでBERTを使ったシンプルなテキスト感情分析プログラムが完成しました。なんとなく雰囲気は掴めたでしょうか?

2. Googleマップ口コミ自動分析

---ここから---

続いて、本題であるGoogleマップの口コミ自動分析プログラムを作成します。

処理は以下のようになります。

  1. Google Maps APIで口コミを自動取得
  2. 各口コミをBERTで感情分析
  3. ポジティブ/ネガティブの割合を集計
  4. CSV出力(必要なら)

Google Maps APIの準備

念の為、Google Maps APIの準備手順を簡単に説明します。

重要:Places API(新版)について

⚠️ 旧版と新版の変更点

Google Maps PlatformのPlaces APIには「旧版(Places API)」と「新版(Places API (New))」があります。

  • Places API(旧版):レガシーステータスとなり、現在は新規で有効にできなくなっています
  • Places API(New):現在推奨されるバージョンです。新機能の開発はこちらでのみ行われます

新しくプロジェクトを作成する場合は、必ず Places API(New) を有効にしてください。

新版(Places API (New))の主な特徴

項目 内容
パフォーマンス 旧版より向上
レスポンス形式 JSONのみ対応
フィールドマスキング 必要なフィールドのみ指定可能(コスト最適化)
プレイスタイプ 約200種類以上に対応(旧版の約2倍)
リアルタイムデータ EV充電器の空き状況、ガソリン価格などの動的情報取得可能

1 Google Cloud Platformにアクセス

  • Google Cloud Consoleにアクセス
  • Googleアカウントでログイン(必要であれば)

console.cloud.google.com

2 プロジェクトの作成

  • 画面上部の【プロジェクトを選択】をクリック

  • 【新しいプロジェクト】をクリック

  • プロジェクト名を入力(例: bert-sentiment-project)して【作成】をクリック

3 Places API(New)の有効化

重要:検索時は「Places API」ではなく「Places API (New)」を選択してください

  • 左メニュー → 【APIとサービス】 → 【ライブラリ】

  • 検索ボックスにPlaces API(new)と入力し、検索結果から 【Places API (New)】 をクリック

⚠️「Places API」(旧版)ではなく、必ず (New) が付いている方を選択

【有効にする】ボタンをクリック

新版で利用できる主なAPI

API 機能
Place Details (New) 場所の詳細情報(住所、営業時間、レビュー等)を取得
Text Search (New) テキストキーワードで場所を検索
Nearby Search (New) 指定した位置の周辺にある場所を検索
Place Photos (New) 場所の写真を取得
Autocomplete (New) 入力補完・予測候補を返す

4 APIキーの作成

  • 左メニュー → 【APIとサービス】 → 【認証情報】

  • 【認証情報を作成】 → 【APIキー】をクリック

  • APIキーが表示されるので、それをコピーして保存

APIキーの形式例

AIzaSyD1234567890abcdefghijklmnopqrstuv

5 APIキーの制限設定(推奨)

セキュリティのため、APIキーに制限を設定します。

  • 作成したAPIキーの【編集】をクリック
  • 【アプリケーションの制限】 → 【なし】(開発中)
  • APIの制限】 → 【キーを制限】
  • 【Places API (New)】 にチェック
    • ※旧版の「Places API」ではなく、新版を選択してください
  • 【保存】をクリック

以下のコードでは、取得したAPIキーを環境変数GOOGLE_MAPS_API_KEYに設定して使用します。.envファイルを作成し、以下のように記述してください。Google Maps APIキーの管理にpython-dotenvを使いました。

.envファイル例

# .env
GOOGLE_MAPS_API_KEY=your_api_key_here

Gitにコミットしてしまう事故を防ぐため、.gitignoreへの追加も忘れずに行いましょう。

料金について

料金体系

従来の「毎月$200分のクレジット」が廃止され、SKU(機能)ごとに無償リクエスト回数が設定される方式に変更されました。

新版(Places API (New))の料金例

SKU 料金 無償枠
Place Details (Pro) 約$0.017/リクエスト SKUカテゴリごとに設定
Text Search (Pro) 約$0.032/リクエスト 毎月5,000件程度
Nearby Search (Pro) 約$0.032/リクエスト SKUカテゴリごとに設定

⚠️料金についてはGoogle Maps Platform料金ページをご確認ください。また、クレジットカード登録は必要ですが、無償枠を超えない限り課金は発生しません。

ソースコード

今回のGoogleマップ口コミ自動分析プログラムのソースコードは以下の通りです。

googlemap_review_sentiment.py

実行の様子

実行は以下のように行います。

$ python googlemap_review_sentiment.py
======================================================================
Googleマップ口コミ感情分析プログラム(Places API New)
======================================================================

✅ APIキーを読み込みました(.env)

BERTモデルを読み込んでいます...
使用デバイス: cpu
BERTモデルの読み込み完了!

Googleマップ口コミ分析の準備完了(Places API New使用)


======================================================================
【店舗検索】
======================================================================

分析したい店舗名を入力してください(終了: 'q'): スターバックス
場所を入力してください(省略可、例: 名古屋市): 名古屋駅

'スターバックス' を検索中...
14件の店舗が見つかりました

【スターバックス ティー&カフェ JR名古屋駅 中央コンコース店】
住所: 日本、〒450-0003 愛知県名古屋市中村区名駅1丁目1−4
評価: 3.7 ★ (11件)

======================================================================
口コミを取得中...
スターバックス ティー&カフェ JR名古屋駅 中央コンコース店: 5件の口コミを分析中...

  [1/5] ポジティブ (81.2%)
  [2/5] ポジティブ (62.2%)
  [3/5] ポジティブ (77.9%)
  [4/5] ポジティブ (67.4%)
  [5/5] ポジティブ (68.0%)


======================================================================
【分析サマリー】
======================================================================

店舗名: スターバックス ティー&カフェ JR名古屋駅 中央コンコース店
分析口コミ数: 5件

【BERT感情分析結果】
  ポジティブ: 5件 (100.0%)
  ネガティブ: 0件 (0.0%)

【Googleレビュー評価】
  平均評価: 4.20 ★
  ★★★★★ (5): 2件
  ★★★★ (4): 2件
  ★★★ (3): 1件

【感情と評価の相関】
  ポジティブ口コミの平均評価: 4.20 ★

【注目すべき口コミ】

✨ 最もポジティブ (信頼度: 81.2%)
   評価: 3★
   「👍JR Nagoya Station Central Concourse Store👍

■After eating at an all-you-can-eat yakiniku restaurant...」

======================================================================

結果をCSVファイルに保存しますか? (y/n): y

結果を保存しました: reviews_スターバックス_ティー&カフェ_JR名古屋駅_中央コンコース店_20251220_002009.csv

全口コミの詳細を表示しますか? (y/n): y

【全口コミ詳細】
======================================================================

[1] 泰心溝口 - a week ago
評価: 3★ | BERT: ポジティブ (81.2%)
口コミ: 👍JR Nagoya Station Central Concourse Store👍

(以下略)

実験してみて面白かった発見🤩

実際に名古屋駅周辺のお店などを分析してみたのですが、面白い発見もありました。 例えば、高評価(5★)なのにネガティブ判定された口コミがそれです。

(評価例)
評価: 5★
口コミ: 「駅直結で便利!ただ朝は混雑がすごくて、待ち時間が長いのが残念」
BERT: ネガティブ (65%)

「混雑」「待ち時間が長い」というネガティブワードに反応したようです。 確かに⭐️としては全体は高評価だけど、改善ポイントが含まれている口コミがこれに当たるようです。

こういう評価は、改善余地につながるのでお店の運営者にとって有益ですよね😊

おわりに

BERTを使った感情分析、思っていたより簡単に実装できました

特に興味深かったのは…

  • Python初心者でも2〜3時間で作れる(このブログのコピペなら30分でもできますよ)
  • 実データ(Googleマップ口コミ)で試せる
  • ビジネス価値が分かりやすい

以前は、BERTもなんとなくしか、分かっていませんでしたが、ようやく意味的な理解もできるようになりました。やっぱり、やりたいことを目的にして、実際に手を動かしてみると理解が深まりますね🤩

参考

このプログラムのGitHubリポジトリ

github.com