AI

自然言語処理の感情分析をPythonで実装する手順:辞書・BERT・評価コード【2026年10月】

自然言語処理の感情分析をPythonで実装する手順:辞書・BERT・評価コード【2026年10月】

自然言語処理の感情分析は、Pythonなら数十行のコードで動かせます。ただし日本語では、形態素解析の辞書の取り違え、モデルごとに違うラベルの並び、512トークンの上限、学習データのライセンスといった、英語の入門記事に出てこない箇所がつまずきやすいところです。この記事では、fugashiとMeCab辞書の検証環境づくりから、osetiによる辞書ベースの判定、Hugging Faceの日本語BERTによる3値判定、scikit-learnでの精度評価、追加学習、ネガティブな声の頻出語の集計まで、コード付きで順に説明します。最後に、Pythonで内製するか受託開発へ切り替えるかの判断基準を示します。

まとめ:Pythonの感情分析は辞書で試してBERTで判定し自社データで測る

日本語の感情分析をPythonで始めるなら、まず学習済みの日本語BERTをpipelineで呼び、自社の文章200〜300件に人が付けた正解と突き合わせて精度を測ります。辞書ベースのosetiは根拠語が見えるので、判定の傾向を説明する用途や、GPUの無い環境での一次ふるい分けに向きます。

実装で事故が起きやすいのは3点です。モデルごとにラベルの番号と表記が違うこと、長文を切り詰めないとエラーになるモデルがあること、公開データセットの多くが商用の追加学習に使えないこと。この3点を最初に確かめれば、検証から本番まで同じコードを使い回せます。正解率が業務の許容ラインに届かない場合だけ、自社データで追加学習に進みます。

Pythonで日本語の感情分析を動かす検証環境と3つの実装方式の分担

感情分析の手法の理屈(ルールベースと機械学習の違い、生成AIに判定させる方式の弱点)は感情分析とは?仕組み・手法の違いからAI時代の精度と導入判断までで整理しています。ここではPythonで手を動かす前提で、方式ごとの準備を比べます。

辞書・学習済みBERT・自前の追加学習で変わる準備工数と向く用途

方式 主なライブラリ 出力 準備 向く用途
辞書ベース oseti 文ごとの極性値と根拠語 pip導入のみ 根拠の説明・GPU無しの一次判定
学習済みBERT transformers 3値ラベルと確率 モデルのダウンロード レビューや問い合わせの本判定
追加学習 transformers・datasets 自社ラベルと確率 正解付きデータ数千件 業界用語が多い文章・独自の分類

辞書ベースは「なぜネガティブなのか」を語単位で返せる反面、否定の入れ子や皮肉には弱いままです。学習済みBERTは文脈を読みますが、学習元の文章(多くは商品レビュー)と自社の文章が離れるほど精度が落ちます。どちらで本番を組むかは、後述の評価コードの数値で決めます。

fugashiとMeCab辞書をpipで入れる検証環境と辞書の取り違え

日本語BERTのトークナイザは、内部でfugashi(MeCabのPythonラッパー。2026年10月時点で1.5系)を呼んで単語に分けます。どのMeCab辞書を使うかはモデルごとに決まっていて、合わない辞書しか入っていないと読み込み時にエラーになります。

python -m venv .venv
pip install "transformers[torch]" fugashi unidic-lite ipadic
pip install oseti neologdn pandas scikit-learn datasets

Hugging Face上の設定ファイル(tokenizer_config.json)を見ると、tohoku-nlp/bert-base-japanese-v3とchristian-phu版の感情分析モデルはmecab_dicにunidic_liteを指定しています。koheiduck版は指定が無く、BertJapaneseTokenizerの既定であるipadicを使います。複数のモデルを比べるなら、上のように辞書を2つとも入れておくと詰まりません。単語の区切りが判定にどう効くかは形態素解析とは?仕組み・具体例・ツールの選び方で解説しています。

前処理を入れてosetiの辞書ベースで文ごとの極性スコアを出す手順

辞書ベースの判定は、表記の揺れで辞書の語と一致しなくなるのが弱点です。判定に渡す前に正規化を1段入れます。

neologdnで半角カナと長音を正規化してから判定に渡す前処理

neologdn(0.5系・Apache-2.0)は、半角カタカナの全角化、全角英数字と記号の半角化、「ーーーー」のような長音の短縮、空白の整理を1関数で行います。0.5.6でPython 3.14に対応しました。

import neologdn

def clean(text: str) -> str:
    # 半角カナの全角化・長音の短縮・空白の整理をまとめて行う
    return neologdn.normalize(text, tilde="normalize")

print(clean("サポートの返信が遅すぎるーーー。でも説明は丁寧でした〜"))

tildeを指定したのには理由があります。READMEの例では引数を省くと「1995〜2001年」が「19952001年」になり、チルダ類が削除されます。数値の範囲や「〜でした〜」の語尾を残したい文章では、”normalize”などの値を明示してください。

osetiのanalyzeとanalyze_detailで文ごとのスコアと根拠語を取る

oseti(MIT・PyPIで0.4.3.1)は、小林ほか(2005)と東山ほか(2008)の日本語評価極性辞書(用言編ver.1.0・名詞編ver.1.0)を使う判定器です。PyPIのメタデータ上はmecabとbunkaiに依存し、文を分けてから文ごとに値を返します。

import oseti
import neologdn

analyzer = oseti.Analyzer(word_dict={"バグ": "n", "サクサク": "p"})

text = neologdn.normalize("アプリはサクサク動く。けれど更新のたびにバグが出る。")
print(analyzer.analyze(text))          # 文ごとの極性値のリスト
print(analyzer.analyze_detail(text))   # 文ごとの positive・negative の根拠語と score
print(analyzer.count_polarity(text))   # 文ごとの肯定語と否定語の個数

READMEの例では、「遅刻したけど楽しかったし嬉しかった。すごく充実した!」に対してanalyzeが[0.333…, 1.0]を返し、2文がそれぞれ採点されます。「お金も希望もない!」はanalyze_detailで「お金-NEGATION」「希望-NEGATION」と否定が付いた根拠語として返ります。

word_dictで足す語は、MeCabが切り出す単位と一致させる必要があります。READMEのwago_dictの例が「イカ する」と空白区切りになっているのはこのためです。業界用語が複合語として切られる場合は、先にfugashiで分割結果を確かめてから登録します。

Hugging Faceの日本語BERTをpipelineで呼び出して3値で判定する

Transformersのpipelineはモデル名を省くと英語のモデルが選ばれるため、日本語では必ずモデルを明示します。ここで例に使うのはchristian-phu/bert-finetuned-japanese-sentimentです。tohoku系のbert-base-japanese-v2をAmazonの日本語商品レビュー約2万文でファインチューニングしたモデルで、モデルカードにはAccuracy 0.813、F1 0.728と記載されています。

pipelineにモデル名を明示して全ラベルの確率をtop_kで受け取る

TransformersのPipelinesのドキュメント(2026年10月時点はv5.20系)では、text-classificationのtop_kの既定値は1で、指定するとラベルごとに辞書が1つずつ返ると説明されています。中立と否定の差が小さい文を拾うには、全ラベルの確率を受け取ります。

from transformers import pipeline

clf = pipeline(
    "text-classification",
    model="christian-phu/bert-finetuned-japanese-sentiment",
    device="cpu",
)

texts = [
    "注文から2日で届き、梱包も丁寧でした。",
    "問い合わせて3日たっても返事がない。",
    "可もなく不可もなくという印象です。",
]
results = clf(texts, top_k=3, truncation=True, max_length=512, batch_size=8)
for text, scores in zip(texts, results):
    best = max(scores, key=lambda s: s["score"])
    print(best["label"].lower(), round(best["score"], 3), text)

GPUが無い環境でもdevice=”cpu”で動きます。ドキュメントではバッチ化について、ハードウェアとデータ次第で10倍速くも5倍遅くもなると注意しているため、batch_sizeは手元の件数で測ってから決めます。

モデルごとに違うラベル順と512トークン上限で起きる2つの実装事故

1つ目はラベルです。各モデルのconfig.jsonのid2labelを見ると、koheiduck/bert-japanese-finetuned-sentimentは0=NEUTRAL・1=NEGATIVE・2=POSITIVE、christian-phu版は0=negative・1=neutral・2=positiveです。番号で集計するとモデルを差し替えた瞬間に中立と否定が入れ替わります。上のコードのように、ラベル名を小文字にそろえて扱ってください。

2つ目は長さです。christian-phu版はtokenizer_config.jsonのmodel_max_lengthが事実上の無制限値になっている一方、位置埋め込みは512までしかありません。truncation=Trueとmax_length=512を渡さないと、長い問い合わせ文でインデックス範囲外のエラーが出ます。512トークンを超える文は先頭しか読まれないので、メール本文のような長文は文に分けて判定し、文ごとの結果を集計する設計にします。

自社データで判定精度を測るためにscikit-learnで評価コードを書く手順

モデルカードの数値は学習元のレビューで測ったものです。自社の問い合わせやアンケートで同じ精度が出る保証はありません。正解付きの評価データを作り、同じコードで方式を比べます。

正解付きCSVからclassification_reportで適合率と再現率を出す

評価用のCSVは、text列と、人が付けたlabel列(negative・neutral・positive)の2列だけで足ります。scikit-learnのclassification_report(1.9系・Python 3.11以上)で、ラベルごとの適合率・再現率・F1をまとめて出します。

import pandas as pd
from sklearn.metrics import classification_report, confusion_matrix
from transformers import pipeline

gold = pd.read_csv("gold.csv")  # 列は text と label
clf = pipeline("text-classification",
               model="christian-phu/bert-finetuned-japanese-sentiment")
preds = clf(gold["text"].tolist(), truncation=True, max_length=512)
gold["pred"] = [p["label"].lower() for p in preds]

labels = ["negative", "neutral", "positive"]
print(classification_report(gold["label"], gold["pred"], labels=labels, digits=3))
print(confusion_matrix(gold["label"], gold["pred"], labels=labels))
gold[gold["label"] != gold["pred"]].to_csv("errors.csv", index=False)

業務で見るべきは全体の正解率より、negativeの再現率です。クレームの取りこぼしを減らしたいのか、誤報を減らしたいのかで、合格ラインを置く指標が変わります。最後の行で書き出した誤判定の一覧を読むと、皮肉や業界用語など、辞書の追加や追加学習で直すべき型が見えてきます。

2人の正解付けの一致度をcohen_kappa_scoreで確かめてから評価する

正解ラベル自体が揺れていると、どのモデルの評価も信用できません。同じ文章を2人に独立して付けてもらい、cohen_kappa_scoreで偶然の一致を除いた一致度を測ります。

import pandas as pd
from sklearn.metrics import cohen_kappa_score

a = pd.read_csv("annotator_a.csv")["label"]
b = pd.read_csv("annotator_b.csv")["label"]
print(cohen_kappa_score(a, b))

値が低いときは、モデルより先に判定基準を直します。「要望は否定か中立か」「お礼と苦情が混ざる文はどちらか」を文書にし、付け直してから評価に進む手順です。発注前のPoCとして正解付けを組む手順はAI感情分析ツールの比較と選び方で扱っています。

追加学習に使う日本語データセットのライセンスと商用利用の可否を確認

評価で精度が足りないと分かったら追加学習を検討します。その前に確かめる必要があるのは、使うデータとモデルのライセンスです。ここを飛ばすと、作ったモデルを業務に載せられなくなります。

WRIMEは非営利限定でMARC-jaは配布停止という公開データの現状

WRIMEは、Plutchikの8感情(喜び・悲しみ・期待・驚き・怒り・恐れ・嫌悪・信頼)の強度を0〜3の4段階で付けた日本語データセットです。Ver.1は80人の43,200投稿、Ver.2は60人の35,000投稿で、Ver.2には−2〜2の5段階の感情極性も付いています。ライセンスはCC BY-NC-ND 4.0で、NC(非営利)条項があるため、社内業務や顧客向けシステムのための学習には使えないと考えてください。

商品レビューの2値分類として知られたMARC-jaは、JGLUEのリポジトリに、Amazonが元データの配布を止めたためJGLUEでは提供できなくなったと注記されています。学習済みモデルの側も、christian-phu版はCC BY-SA 4.0で、koheiduck版にはライセンスの記載がありません。商用で使うなら、自社の文章に正解を付けたデータで、ライセンスが明確なApache-2.0のtohoku-nlp/bert-base-japanese-v3から学習するのが安全な道筋です。

tohoku-nlpのBERTを自社の正解データでファインチューニングする

bert-base-japanese-v3は、CC-100の日本語部分と日本語版Wikipediaで事前学習された語彙32,768のモデルです。評価に使ったものと別に、学習用と検証用のCSV(text列と0〜2の整数のlabel列)を用意します。

import numpy as np
from datasets import load_dataset
from sklearn.metrics import f1_score
from transformers import (AutoModelForSequenceClassification, AutoTokenizer,
                          Trainer, TrainingArguments)

base = "tohoku-nlp/bert-base-japanese-v3"
id2label = {0: "negative", 1: "neutral", 2: "positive"}
tokenizer = AutoTokenizer.from_pretrained(base)
model = AutoModelForSequenceClassification.from_pretrained(
    base, num_labels=3, id2label=id2label,
    label2id={v: k for k, v in id2label.items()})

ds = load_dataset("csv", data_files={"train": "train.csv", "validation": "valid.csv"})
ds = ds.map(lambda b: tokenizer(b["text"], truncation=True, max_length=256), batched=True)

def compute_metrics(p):
    preds = np.argmax(p.predictions, axis=-1)
    return {"macro_f1": f1_score(p.label_ids, preds, average="macro")}

args = TrainingArguments(output_dir="sentiment-ja", num_train_epochs=3,
                         learning_rate=2e-5, per_device_train_batch_size=16,
                         eval_strategy="epoch", save_strategy="epoch",
                         load_best_model_at_end=True, metric_for_best_model="macro_f1")
trainer = Trainer(model=model, args=args, train_dataset=ds["train"],
                  eval_dataset=ds["validation"], processing_class=tokenizer,
                  compute_metrics=compute_metrics)
trainer.train()
trainer.save_model("sentiment-ja")

id2labelを学習時に書いておくと、保存したモデルをpipelineで読んだときにnegativeなどの名前で返り、前章のラベル順の事故を防げます。v5.20系のTrainerのドキュメントでは、トークナイザを渡す引数はprocessing_classで、tokenizer引数はありません。評価の間隔もeval_strategyで指定するため、古い記事にあるtokenizer=やevaluation_strategy=をそのまま写すと動きません。学習を終えたら、前章のgold.csvで学習前のモデルと同じ評価をかけ、negativeの再現率が上がったかで採否を決めます。

判定結果を集計してネガティブな声の頻出語をPythonで数える手順

判定ラベルが付いたら、「何に不満が集まっているか」を数えるところまで進めると業務で使える形になります。テキストマイニングの手法全体(共起分析やワードクラウドなど)はテキストマイニングとは?手法・やり方・無料ツールから企業の導入判断までで解説しています。

ネガティブ判定の文だけを抜き出しfugashiで名詞の出現回数を数える

前章のgold[“pred”]と同じ形で全件に判定を付けたデータフレームから、negativeの文だけを取り出して名詞を数えます。unidic-liteの品詞はpos1で大分類を引けます。

from collections import Counter

import pandas as pd
from fugashi import Tagger

df = pd.read_csv("predicted.csv")  # 列は text と pred
tagger = Tagger()
stop = {"こと", "もの", "よう", "ため", "方"}

counter = Counter()
for text in df.loc[df["pred"] == "negative", "text"]:
    for word in tagger(text):
        if word.feature.pos1 == "名詞" and word.surface not in stop and len(word.surface) > 1:
            counter[word.surface] += 1

for word, n in counter.most_common(20):
    print(word, n)

頻出語の上位は「返信」「配送」「ログイン」のように窓口や機能の名前になりやすく、改善の担当部署へ振り分ける材料になります。positiveの文でも同じ集計をして、両方に多い語(たとえば「対応」)は評価が割れている論点として別に読むと、件数の多さだけで判断する誤りを避けられます。

Pythonで内製するか受託開発へ切り替えるかを決める判断基準と見送り条件

ここまでのコードは検証用としてはそのまま動きますが、毎日の問い合わせを自動で判定して業務システムへ戻すとなると、別の仕事が増えます。どこまで内製で進め、どこから外に出すかを条件で分けます。

学習済みモデルのまま運用してよい条件と誤判定から追加学習へ進む条件

自社のgold.csvでnegativeの再現率が業務の許容ラインを超え、誤判定が皮肉や長文など人が見ても迷う文に偏っているなら、学習済みモデルのまま運用して構いません。人の確認を挟む前提の一次ふるい分けなら、これで足ります。

追加学習へ進むのは、誤判定が業界用語や製品名に集中している場合と、3値ではなく「解約の予兆」「機能要望」のような自社独自のラベルが要る場合です。反対に、評価データが数百件しかなく正解の一致度も低い段階での追加学習は見送ります。学習データの揺れをそのまま覚えたモデルができるだけです。クラウドのAPIで済ませる選択肢との比較は自然言語処理APIとは?クラウド3社・LLM・OSSの比較と実装組み込み手順にまとめています。

本番の業務システムに組み込む段階で受託開発へ出す範囲と見送る場面

検証から本番へ移すときに増えるのは、問い合わせ管理やCRMからの定期的な取り込み、判定結果の書き戻し、モデルの再学習と評価の手順化、個人情報を含む文章の保管範囲の設計です。社内にPythonで機械学習を回せる担当者が1人しかいない、または判定結果を既存の業務システムの画面に出す必要がある場合は、この部分を外に出すと立ち上がりが早くなります。正解データの設計から追加学習、既存システムへの組み込みまでは生成AI開発・AI受託開発として相談できます。

一方、月に数十件しか文章が無く担当者が全件を読める規模なら、開発自体を見送ってください。判定の仕組みを作っても、読む手間はほとんど減りません。通話の音声から感情を取りたい場合は録音設備の要件が別に掛かるので、コールセンターの音声感情分析とはを先に確認します。

よくある質問

Pythonで日本語の感情分析を実装するときによく出る質問に、2026年10月時点の公式ドキュメントとモデルカードの記載をもとに答えます。

Pythonの感情分析は無料でできますか?

できます。この記事で使ったoseti・neologdn・fugashi・transformers・scikit-learnはいずれも無料のオープンソースで、学習済みモデルもHugging Faceから無料でダウンロードできます。費用が掛かるのは、GPUのクラウド利用料と、評価や追加学習のための正解付けの人件費です。ただし、モデルやデータセットのライセンスは無料かどうかとは別に確かめてください。

TextBlobやVADERは日本語の感情分析に使えますか?

おすすめしません。TextBlobやNLTKのVADERは英語の語彙と規則で採点する仕組みのため、日本語の文章をそのまま渡しても意味のある値は返りません。英語に翻訳してから判定する方法もありますが、翻訳の揺れが判定に混ざります。日本語ならosetiのような日本語の極性辞書か、日本語で学習したBERTを使うほうが確実です。

pipelineで判定すると日本語がうまく判定されないのはなぜですか?

多くはモデルを指定していないことが原因です。pipeline(“sentiment-analysis”)のようにタスク名だけを渡すと、英語で学習した既定のモデルが読み込まれます。model引数に日本語で学習したモデル名を明示してください。モデルを指定しているのに読み込みで止まる場合は、fugashiとモデルが求めるMeCab辞書(unidic-liteかipadic)が入っているかを確認します。

喜怒哀楽のような細かい感情もPythonで判定できますか?

できます。WRIMEの8感情で学習したモデルは、Hugging Faceから入手可能です。ただし、Mizuiro-sakura/luke-japanese-large-sentiment-analysis-wrimeのようにconfig.jsonのラベル名がLABEL_0〜LABEL_7のままのモデルもあり、どの番号がどの感情かはモデルカードで確かめる必要があります。学習元のWRIMEが非営利ライセンスである点も、商用で使う前に確認してください。

ChatGPTなどの生成AIに判定させる方法とBERTはどう使い分けますか?

件数と再現性で分けます。BERTは自社のサーバーで動き、同じ文には毎回同じ確率を返すため、毎日数千件を判定して推移を見る用途向けの選択肢です。生成AIはラベルの定義を文章で渡せるので、正解データを作る前の試作や、理由を文章で添えたい少数の判定に向きます。生成AIによる判定の弱点は感情分析とはの手法の章で整理しています。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.23 テックブログ Renta!不正アクセスでサービス停止:再開未定の現状と運営側の停止判断・証拠保全・段階的再開
  2. 2026.10.21 テックブログ ふるさとチョイス不正アクセスで445万件流出:対象確認・パスワード変更と再開時期
  3. 2026.10.14 テックブログ ローソンID不正アクセス:215万件漏えいの対象確認と本物メールの見分け方
  4. 2026.10.24 テックブログ ブラストメール不正アクセス:配信先288件の漏えいと利用企業の報告・通知の手順
  5. 2024.11.05 テックブログ Python 3.13の新機能と3.12との違い|3.13.17の変更点・サポート期限・GIL無効化の実測

RELATED POSTS 関連記事

目次