ChatGPTとPythonで学ぶ 共起ネットワーク分析



皆さんこんにちは。今日も引き続きChatGPT先生をお迎えして、「ChatGPTとPythonで学ぶ 共起ネットワーク分析」というテーマで雑談したいと思います。それではChatGPT先生、よろしくお願いします。

---

### **assistant**:

こんにちは！今日は共起ネットワーク分析についてお話ししましょう。

共起ネットワーク分析は、特定の文脈内で2つ以上の要素が同時に出現する頻度を調べる手法です。これはテキストデータを分析する際に特に有用であり、時折NLP(Natural Language Processing)の一部として使用されます。

Pythonを使用した共起ネットワーク分析を行うには、まず必要なライブラリをインポートします。一般的には以下のライブラリが使われます：

1. `NetworkX`: ネットワーク構造の分析と生成を行うライブラリ
2. `matplotlib`: データの可視化を行うライブラリ
3. `nltk`: 自然言語処理のライブラリ。ここでは文章を単語に分割するために使用します。
4. `collections`: Pythonの内蔵ライブラリで、ここでは単語の頻度を集計します。

具体的なコードを書く前に、質問などありますか？

---

### **user**:

ありがとうございます。質問は後でしますので、共起ネットワーク分析について学ぶための具体的なPythonコードを書いてください。その際に、型ヒントも導入してください。コード中に日本語での説明も可能な限り詳しくお願いします。Pythonのコーディング規約PEP8に準拠してください。


---

### **assistant**:

もちろんです、それではまずはコードから始めましょう。ここでは、「Alice in Wonderland」という英文の文章から共起ネットワークを作る例を示します。

まずは、必要なライブラリをインポートします。

```python
import networkx as nx
import matplotlib.pyplot as plt
from nltk import word_tokenize
from collections import Counter
from typing import List, Tuple
```

次に、テキストから共起ネットワークを作成する関数を定義します。

```python
def co_occurrence_network(text: str, n_most_common: int=30) -> nx.Graph:
  # 単語ごとに分割
  words = word_tokenize(text)

  # 一番出現頻度が高い単語を集計
  common_words = [word for word, freq in Counter(words).most_common(n_most_common)]

  # 共起ネットワークを作成
  co_occurrence_graph = nx.Graph()

  # 共起ネットワークに頂点を追加
  co_occurrence_graph.add_nodes_from(common_words)

  # 共起ネットワークにエッジを追加
  for i, word in enumerate(common_words[:-1]):
    for next_word in common_words[i+1:]:
      # 単語が並んでいるか確認
      if f"{word} {next_word}" in text or f"{next_word} {word}" in text:
        co_occurrence_graph.add_edge(word, next_word)

  return co_occurrence_graph
```

この関数ではテキストを単語に分割した後、最も出現頻度が高い単語（ここでは30語）を集計します。そして、これらの単語から共起ネットワークを作成します。ネットワークにノードとエッジを追加します。ここで、エッジの追加は、最も出現頻度が高い単語がお互いにテキスト中で隣り合って出現した場合のみ行います。

次に、この関数を使ってテキストから共起ネットワークを作り、プロットする関数を作りましょう。

```python
def plot_co_occurrence_network(text: str, n_most_common: int=30) -> None:
  co_occurrence_graph = co_occurrence_network(text, n_most_common)

  # ネットワークを描画
  pos = nx.spring_layout(co_occurrence_graph)
  nx.draw(co_occurrence_graph, pos, with_labels=True, font_weight='bold')

  # プロットを表示
  plt.show()
```

これらの関数を活用すれば、共起ネットワーク分析を行うことができます。ただし、簡単化のためにこの例では共起関係を単語が連続しているか否かで判断しています。一般的には共起関係は同じ文脈（同じ文章や段落など）内で発生する単語のペアを分析します。

---

### **user**:

ありがとうございます。上記の関数を使って、実際に何かのデータを可視化してください。

---

### **assistant**:

ここではPythonコードを実際に動かす機能は無いので、コード実行はご自身の環境で試していただく形となります。今回は、「Alice in Wonderland」の初めの一部を例に使ってみましょう。

```python
text = '''
Alice was beginning to get very tired of sitting by her sister on the bank,
and of having nothing to do: once or twice she had peeped into the book her sister was reading,
but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice
'without pictures or conversations?'
'''

plot_co_occurrence_network(text)
```

このコードを実行すると、上位30語の共起ネットワークがプロットされます。ピックアップする単語数（ここでは30語）は、`plot_co_occurrence_network`関数の引数`n_most_common`で調整可能です。

この可視化の結果を解釈するときには、頻度が高い単語がネットワークの中心に配置されることが多く、その単語がテキスト全体のテーマに関連している可能性があります。また、同じクラスター（エッジで接続された単語群）に属する単語は、同じ文脈やテーマに関連している可能性があります。

このように共起ネットワーク分析をすることで、テキストデータの概要を理解したり、あるテーマや単語がどのように他の要素と関連しているのかを視覚的に把握することができます。

---

### **user**:

ありがとうございます。上記コードをベースに、下記エラーを修正した全コードを書いてください。またその際に、型ヒントも導入してください。コード中に日本語での説明も可能な限り詳しくお願いします。Pythonのコーディング規約PEP8に準拠してください。

```
---------------------------------------------------------------------------
LookupError                               Traceback (most recent call last)
Cell In[6], line 8
      1 text = '''
      2 Alice was beginning to get very tired of sitting by her sister on the bank,
      3 and of having nothing to do: once or twice she had peeped into the book her sister was reading,
      4 but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice
      5 'without pictures or conversations?'
      6 '''
----> 8 plot_co_occurrence_network(text)

Cell In[5], line 2, in plot_co_occurrence_network(text, n_most_common)
      1 def plot_co_occurrence_network(text: str, n_most_common: int=30) -> None:
----> 2   co_occurrence_graph = co_occurrence_network(text, n_most_common)
      4   # ネットワークを描画
      5   pos = nx.spring_layout(co_occurrence_graph)

Cell In[4], line 3, in co_occurrence_network(text, n_most_common)
      1 def co_occurrence_network(text: str, n_most_common: int=30) -> nx.Graph:
      2   # 単語ごとに分割
----> 3   words = word_tokenize(text)
      5   # 一番出現頻度が高い単語を集計
      6   common_words = [word for word, freq in Counter(words).most_common(n_most_common)]

File ~/.local/lib/python3.10/site-packages/nltk/tokenize/__init__.py:129, in word_tokenize(text, language, preserve_line)
    114 def word_tokenize(text, language="english", preserve_line=False):
    115     """
    116     Return a tokenized copy of *text*,
    117     using NLTK's recommended word tokenizer
   (...)
    127     :type preserve_line: bool
    128     """
--> 129     sentences = [text] if preserve_line else sent_tokenize(text, language)
    130     return [
    131         token for sent in sentences for token in _treebank_word_tokenizer.tokenize(sent)
    132     ]

File ~/.local/lib/python3.10/site-packages/nltk/tokenize/__init__.py:106, in sent_tokenize(text, language)
     96 def sent_tokenize(text, language="english"):
     97     """
     98     Return a sentence-tokenized copy of *text*,
     99     using NLTK's recommended sentence tokenizer
   (...)
    104     :param language: the model name in the Punkt corpus
    105     """
--> 106     tokenizer = load(f"tokenizers/punkt/{language}.pickle")
    107     return tokenizer.tokenize(text)

File ~/.local/lib/python3.10/site-packages/nltk/data.py:750, in load(resource_url, format, cache, verbose, logic_parser, fstruct_reader, encoding)
    747     print(f"<<Loading {resource_url}>>")
    749 # Load the resource.
--> 750 opened_resource = _open(resource_url)
    752 if format == "raw":
    753     resource_val = opened_resource.read()

File ~/.local/lib/python3.10/site-packages/nltk/data.py:876, in _open(resource_url)
    873 protocol, path_ = split_resource_url(resource_url)
    875 if protocol is None or protocol.lower() == "nltk":
--> 876     return find(path_, path + [""]).open()
    877 elif protocol.lower() == "file":
    878     # urllib might not use mode='rb', so handle this one ourselves:
    879     return find(path_, [""]).open()

File ~/.local/lib/python3.10/site-packages/nltk/data.py:583, in find(resource_name, paths)
    581 sep = "*" * 70
    582 resource_not_found = f"\n{sep}\n{msg}\n{sep}\n"
--> 583 raise LookupError(resource_not_found)

LookupError: 
**********************************************************************
  Resource punkt not found.
  Please use the NLTK Downloader to obtain the resource:

  >>> import nltk
  >>> nltk.download('punkt')
  
  For more information see: https://www.nltk.org/data.html

  Attempted to load tokenizers/punkt/PY3/english.pickle

  Searched in:
    - '/home/ikemen/nltk_data'
    - '/usr/local/nltk_data'
    - '/usr/local/share/nltk_data'
    - '/usr/local/lib/nltk_data'
    - '/usr/share/nltk_data'
    - '/usr/local/share/nltk_data'
    - '/usr/lib/nltk_data'
    - '/usr/local/lib/nltk_data'
    - ''
**********************************************************************
```
---

### **assistant**:

エラーメッセージを見ると、NLTKの`punkt`というリソースが見つからないと言っています。これは、NLTKのトークン化（テキストを単語に分割する）処理に使われるリソースです。

したがって、はじめに`punkt`をダウンロードするためのコードを追加する必要があります。

エラーを修正した全コードは以下のとおりです：

```python
import networkx as nx
import matplotlib.pyplot as plt
from nltk import word_tokenize
from nltk import download as nltk_download
from collections import Counter
from typing import List, Tuple

# NLTKのリソースをダウンロード
nltk_download('punkt')


def co_occurrence_network(text: str, n_most_common: int=30) -> nx.Graph:
    # 単語ごとに分割
    words = word_tokenize(text)

    # 一番出現頻度が高い単語を集計
    common_words = [word for word, freq in Counter(words).most_common(n_most_common)]

    # 共起ネットワークを作成
    co_occurrence_graph = nx.Graph()

    # 共起ネットワークに頂点を追加
    co_occurrence_graph.add_nodes_from(common_words)

    # 共起ネットワークにエッジを追加
    for i, word in enumerate(common_words[:-1]):
        for next_word in common_words[i+1:]:
            # 単語が並んでいるか確認
            if f"{word} {next_word}" in text or f"{next_word} {word}" in text:
                co_occurrence_graph.add_edge(word, next_word)

    return co_occurrence_graph


def plot_co_occurrence_network(text: str, n_most_common: int=30) -> None:
    co_occurrence_graph = co_occurrence_network(text, n_most_common)

    # ネットワークを描画
    pos = nx.spring_layout(co_occurrence_graph)
    nx.draw(co_occurrence_graph, pos, with_labels=True, font_weight='bold')

    # プロットを表示
    plt.show()


text = '''
Alice was beginning to get very tired of sitting by her sister on the bank,
and of having nothing to do: once or twice she had peeped into the book her sister was reading,
but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice
'without pictures or conversations?'
'''

plot_co_occurrence_network(text)
```

![output_6_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/4792a7b4-8ecd-ed0e-99e7-4f5f61183dfe.png)


このコードを実行すれば、エラーは発生せずに共起ネットワークが描画されるはずです。

---

### **user**:

ありがとうございます。共起ネットワークが描画されました。

---

### **assistant**:

それは良かったです。共起ネットワークを描画して解釈することで、テキストデータの構造や主題を視覚的に理解することが可能になります。更に詳しい分析を行いたい場合は、共起ネットワークの他の特性（例えば各ノードの中心性やクラスターなど）も考慮に入れてみてください。

もしご不明な点や他に知りたいことがあればお知らせください。それ以外でしたら、このセッションも無事終了となります。ともあれ、あなたがPythonと共起ネットワーク分析について学ぶことができたことを私は嬉しく思います。

---

### **user**:

この共起ネットワークは、何に使うんでしょうか？

---

### **assistant**:

共起ネットワーク分析は、テキストデータに含まれる単語やフレーズがどのように関連しているかを理解するのに非常に有用です。具体的な応用例を幾つか挙げてみましょう。

1. **テキストデータの概要を把握する**: 大量のテキストデータがあるとき、全てを読むのは難しいですよね。共起ネットワークを作成して可視化することで、テキストの主要なテーマや単語の関連性を短時間で把握することができます。

2. **意見抽出(オピニオンマイニング)やセンチメント分析**: 商品のレビューやソーシャルメディアの投稿から、人々の感情や意見を抽出するために共起ネットワークが使われることがあります。たとえば、特定の製品名と「良い」や「悪い」といった評価語が頻繁に共起するかどうかを分析するなどです。

3. **自然言語処理(NLP)のための特徴抽出**: テキスト分析や機械学習モデルの入力として用いる特徴量を抽出するために、共起ネットワークの統計（例えば各ノードの中心性やクラスターなど）を用いることがあります。

4. **キーワード提案ツール**: サーチエンジン最適化(SEO)においては、人々が検索するキーワードとその関連キーワードを理解することが重要です。共起ネットワークは、あるキーワードと頻繁に共起する単語を見つけ出すのに役立ちます。

以上のように、共起ネットワーク分析は幅広い分野で活用されています。

---

### **user**:

ありがとうございます。何となく、ノイズが非常に多くなりそうな気がするんですが、それに対してどのような対処をしていますか？

---

### **assistant**:

共起ネットワーク分析では、確かに「ノイズ」（重要でない情報や意味のない情報）が問題になることがあります。具体的には、使われる頻度が高いが、それほど意味を持たない「ストップワード」（英語では "the", "is", "at", など）や、品詞（接続詞や助詞など）などがこれに相当します。

そのようなノイズを取り除くためのプレプロセスの一部として次の処理を行います：

1. **ストップワードの除去**: 特定の言語で共通に使われるストップワードを取り除くために、多くの自然言語処理ライブラリがストップワードのリストを提供しています。また、特定のタスクやドメインで重要でない単語を独自にストップワードとして定義することもあります。

2. **低頻度語の除去**: 非常に稀にしか出現しない単語は、ネットワーク分析においてあまり情報を提供しない場合が多いです。したがって、ある頻度以下の単語を取り除くという処理を行うことがあります。

3. **単語の正規化**: 単語の異なる形態やスペリングを統一するために、単語の正規化を行います。例えば、「run」、「running」、「runs」すべてを「run」に統一するなどです。具体的な処理としては、全て小文字に変換したり、語幹抽出（stemming）や見出し語化（lemmatization）を行います。

これらのプレプロセスを行うことで、ノイズを軽減し、分析の精度を向上させることが可能です。

---

### **user**:

なんとなく雰囲気はわかりました。ありがとうございます。ひょっとして、ChatGPTのような大規模言語モデルの普及に伴って、あまり使われなくなるんじゃないかなという印象を持ってしまいました。

---

### **assistant**:

ますます進歩を遂げている大規模言語モデルは、自然言語処理の様々なタスクにおいて、共起ネットワーク分析などの伝統的な手法を凌駕する結果を出しています。GPT-3やBERTなどのモデルは、大量のテキストデータから単語やフレーズの関連性を学習し、より高度で微妙な言語理解タスクを実行することができます。これは、共起ネットワーク分析が表面的な単語の共起のみを考慮するのに対し、大規模言語モデルが深度と広範囲な文脈的理解を可能にするためです。

しかし、それでも共起ネットワーク分析は以下のような理由から一定の価値があります：

1. **透明性と解釈可能性**：共起ネットワーク分析は単純かつ直感的で、結果を視覚的に表現できます。一方、大規模言語モデルは「ブラックボックス」的な側面があり、決定がどのように行われるのかを理解することは難しいです。

2. **計算リソース**：大規模言語モデルの訓練や実行には多大な計算リソースが必要ですが、共起ネットワーク分析は比較的軽量であるため、計算リソースに制限がある状況では有用です。

3. **特定の目的に対するカスタマイズ**：共起ネットワーク分析では、特定の目的や問いに答えるために分析方法やパラメータを自由に調整できます。一方、大規模言語モデルは特定の目的に対してカスタマイズするのが難しい場合があります。

以上のような理由から、大規模言語モデルの進化にもかかわらず、共起ネットワーク分析などの伝統的な手法もまた重要な役割を果たし続けています。どの手法を使用するべきかは、特定のタスクや問い、利用可能なリソースによって異なるため、一概には言えません。大切なのは、それぞれの手法の長所と短所を理解し、それらを適切に利用することです。

---

### **user**:

ありがとうございました。それでは、以上の議論を踏まえたほのぼのギャグをお願いします。

---

### **assistant**:

もちろんです、軽いジョークで終わりましょう！  

なぜコンピュータ内の単語たちは大規模言語モデルにとても恐がっているんでしょうか？

それは、彼らがずっと「共起」していたのに、突然「トークン化」されてしまうからです。分かったような、分からなかったような？でも、単語たちも心配ご無用。僕たちが紐解いてくれるからさ！  

以上、一緒に楽しい時間を過ごせてとても嬉しかったです。また質問があればいつでもどうぞ！

---