!date
!python --version
Thu May 30 04:39:14 AM UTC 2024
Python 3.10.12

20. テキストをトークン出現回数でベクトル化する例(Spacy版)#

基本的には「形態素解析して単語に分割し、その回数をカウントしたうえでベクトル化する」という手順を取る。形態素解析には様々なツールがあるが、ここでは spacy.load("ja_ginza") を用いた例を示す。単語分割した文字列を作成したら、後はCountVectorizerを使うのが楽だ。

!pip install -U spacy ja_ginza
Requirement already satisfied: spacy in /usr/local/lib/python3.10/dist-packages (3.7.4)
Collecting ja_ginza
  Downloading ja_ginza-5.2.0-py3-none-any.whl (59.1 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 59.1/59.1 MB 12.4 MB/s eta 0:00:00
?25hRequirement already satisfied: spacy-legacy<3.1.0,>=3.0.11 in /usr/local/lib/python3.10/dist-packages (from spacy) (3.0.12)
Requirement already satisfied: spacy-loggers<2.0.0,>=1.0.0 in /usr/local/lib/python3.10/dist-packages (from spacy) (1.0.5)
Requirement already satisfied: murmurhash<1.1.0,>=0.28.0 in /usr/local/lib/python3.10/dist-packages (from spacy) (1.0.10)
Requirement already satisfied: cymem<2.1.0,>=2.0.2 in /usr/local/lib/python3.10/dist-packages (from spacy) (2.0.8)
Requirement already satisfied: preshed<3.1.0,>=3.0.2 in /usr/local/lib/python3.10/dist-packages (from spacy) (3.0.9)
Requirement already satisfied: thinc<8.3.0,>=8.2.2 in /usr/local/lib/python3.10/dist-packages (from spacy) (8.2.3)
Requirement already satisfied: wasabi<1.2.0,>=0.9.1 in /usr/local/lib/python3.10/dist-packages (from spacy) (1.1.2)
Requirement already satisfied: srsly<3.0.0,>=2.4.3 in /usr/local/lib/python3.10/dist-packages (from spacy) (2.4.8)
Requirement already satisfied: catalogue<2.1.0,>=2.0.6 in /usr/local/lib/python3.10/dist-packages (from spacy) (2.0.10)
Requirement already satisfied: weasel<0.4.0,>=0.1.0 in /usr/local/lib/python3.10/dist-packages (from spacy) (0.3.4)
Requirement already satisfied: typer<0.10.0,>=0.3.0 in /usr/local/lib/python3.10/dist-packages (from spacy) (0.9.4)
Requirement already satisfied: smart-open<7.0.0,>=5.2.1 in /usr/local/lib/python3.10/dist-packages (from spacy) (6.4.0)
Requirement already satisfied: tqdm<5.0.0,>=4.38.0 in /usr/local/lib/python3.10/dist-packages (from spacy) (4.66.4)
Requirement already satisfied: requests<3.0.0,>=2.13.0 in /usr/local/lib/python3.10/dist-packages (from spacy) (2.31.0)
Requirement already satisfied: pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4 in /usr/local/lib/python3.10/dist-packages (from spacy) (2.7.1)
Requirement already satisfied: jinja2 in /usr/local/lib/python3.10/dist-packages (from spacy) (3.1.4)
Requirement already satisfied: setuptools in /usr/local/lib/python3.10/dist-packages (from spacy) (67.7.2)
Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.10/dist-packages (from spacy) (24.0)
Requirement already satisfied: langcodes<4.0.0,>=3.2.0 in /usr/local/lib/python3.10/dist-packages (from spacy) (3.4.0)
Requirement already satisfied: numpy>=1.19.0 in /usr/local/lib/python3.10/dist-packages (from spacy) (1.25.2)
Collecting sudachipy<0.7.0,>=0.6.2 (from ja_ginza)
  Downloading SudachiPy-0.6.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.6 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.6/2.6 MB 51.0 MB/s eta 0:00:00
?25hCollecting sudachidict-core>=20210802 (from ja_ginza)
  Downloading SudachiDict_core-20240409-py3-none-any.whl (72.0 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 72.0/72.0 MB 9.1 MB/s eta 0:00:00
?25hCollecting ginza<5.3.0,>=5.2.0 (from ja_ginza)
  Downloading ginza-5.2.0-py3-none-any.whl (21 kB)
Collecting plac>=1.3.3 (from ginza<5.3.0,>=5.2.0->ja_ginza)
  Downloading plac-1.4.3-py2.py3-none-any.whl (22 kB)
Requirement already satisfied: language-data>=1.2 in /usr/local/lib/python3.10/dist-packages (from langcodes<4.0.0,>=3.2.0->spacy) (1.2.0)
Requirement already satisfied: annotated-types>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4->spacy) (0.7.0)
Requirement already satisfied: pydantic-core==2.18.2 in /usr/local/lib/python3.10/dist-packages (from pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4->spacy) (2.18.2)
Requirement already satisfied: typing-extensions>=4.6.1 in /usr/local/lib/python3.10/dist-packages (from pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4->spacy) (4.11.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests<3.0.0,>=2.13.0->spacy) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests<3.0.0,>=2.13.0->spacy) (3.7)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests<3.0.0,>=2.13.0->spacy) (2.0.7)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests<3.0.0,>=2.13.0->spacy) (2024.2.2)
Requirement already satisfied: blis<0.8.0,>=0.7.8 in /usr/local/lib/python3.10/dist-packages (from thinc<8.3.0,>=8.2.2->spacy) (0.7.11)
Requirement already satisfied: confection<1.0.0,>=0.0.1 in /usr/local/lib/python3.10/dist-packages (from thinc<8.3.0,>=8.2.2->spacy) (0.1.4)
Requirement already satisfied: click<9.0.0,>=7.1.1 in /usr/local/lib/python3.10/dist-packages (from typer<0.10.0,>=0.3.0->spacy) (8.1.7)
Requirement already satisfied: cloudpathlib<0.17.0,>=0.7.0 in /usr/local/lib/python3.10/dist-packages (from weasel<0.4.0,>=0.1.0->spacy) (0.16.0)
Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/dist-packages (from jinja2->spacy) (2.1.5)
Requirement already satisfied: marisa-trie>=0.7.7 in /usr/local/lib/python3.10/dist-packages (from language-data>=1.2->langcodes<4.0.0,>=3.2.0->spacy) (1.1.1)
Installing collected packages: sudachipy, plac, sudachidict-core, ginza, ja_ginza
Successfully installed ginza-5.2.0 ja_ginza-5.2.0 plac-1.4.3 sudachidict-core-20240409 sudachipy-0.6.8

20.1. spacyで単語分割#

  • 単に分割するだけではなく、token.lemma_ により基本形に変換している。元のままが良ければ token.text にしよう。

  • タスクによっては不要な単語や品詞もあるだろう。その場合には不要なものを除外しよう。

  • タスクによっては集約(例えば数字を全て<数字>という単語に集約する。感情語を全て<感情>に集約する)を検討すると良いだろう。

import spacy

# テキスト例
texts = ['特になし',
        '正直わかりずらい。むだに間があるし。',
        '例題を取り入れて理解しやすくしてほしい。']

# 解析器を用意
nlp = spacy.load("ja_ginza")

# 解析例
doc = nlp("正直わかりずらい。")
for token in doc:
    print(f"{token.i=}, {token.text=}, {token.lemma_=}")
token.i=0, token.text='正直', token.lemma_='正直'
token.i=1, token.text='わかり', token.lemma_='わかる'
token.i=2, token.text='ずらい', token.lemma_='ずらい'
token.i=3, token.text='。', token.lemma_='。'
def text2tokens(nlp:spacy.language.Language, text:str, sep=' '):
    """テキストを単語に分割した文字列に変換。
    args:
      nlp: spacy.load()で用意した解析器。
      text: テキスト。
      sep: セパレータ。単語と単語の間を埋める記号。

    >>> nlp = spacy.load("ja_ginza")
    >>> result = text2tokens(nlp, "これはテストです")
    >>> result
    'これ は テスト です'
    """
    doc = nlp(text)
    tokens = []
    for token in doc:
        tokens.append(token.lemma_)
    result = sep.join(tokens)
    return result

# 実行例
tokens = []
for text in texts:
    tokens.append(text2tokens(nlp, text))

tokens
['特に なし', '正直 わかる ずらい 。 むだ だ 間 が ある し 。', '例題 を 取り入れる て 理解 する やすい する て ほしい 。']

20.2. CountVectorizerでベクトル化#

デフォルトでは1-gramモデル(各単語の出現回数に基づいた特徴)によりベクトル化する。引数指定により以下のような設定も可能。詳細はドキュメント参照。

  • ngram_range

    • 2-gram, 3-gram,,,といった「連続した語=フレーズ」に基づいたベクトル化を行う。

  • stop_words

    • 無視したい単語(ストップワード)を指定することができる。標準で用意されているリストを利用することも可能。

  • analyzer

    • デフォルトでは単語を特徴として捉えるが、この単語とは「スペースで区切られたもの」として解釈される。

    • ‘char’ を指定すると「文字」を特徴として捉えるようになる。

from sklearn.feature_extraction.text import CountVectorizer

# 1-gramで特徴ベクトル作成
vectorizer = CountVectorizer() # デフォルトでは単語出現回数でベクトル化
X = vectorizer.fit_transform(tokens) # ベクトル構築
features = vectorizer.get_feature_names_out() # ベクトル構築した際の単語一覧
print(f"{features=}")
print(X.toarray())
features=array(['ある', 'する', 'ずらい', 'なし', 'ほしい', 'むだ', 'やすい', 'わかる', '例題', '取り入れる',
       '正直', '特に', '理解'], dtype=object)
[[0 0 0 1 0 0 0 0 0 0 0 1 0]
 [1 0 1 0 0 1 0 1 0 0 1 0 0]
 [0 2 0 0 1 0 1 0 1 1 0 0 1]]
# 2-gramで特徴ベクトル作成
vectorizer = CountVectorizer(ngram_range=(2, 2))
X = vectorizer.fit_transform(tokens)
features = vectorizer.get_feature_names_out()
print(f"{features=}")
print(X.toarray())
features=array(['する ほしい', 'する やすい', 'ずらい むだ', 'むだ ある', 'やすい する', 'わかる ずらい',
       '例題 取り入れる', '取り入れる 理解', '正直 わかる', '特に なし', '理解 する'], dtype=object)
[[0 0 0 0 0 0 0 0 0 1 0]
 [0 0 1 1 0 1 0 0 1 0 0]
 [1 1 0 0 1 0 1 1 0 0 1]]