Databricks SparkでMeCabを動かしてみる

Databricksを使い始めて約1年経ちますが、日本語の自然言語処理に手を出していなかったと思い立ちましてトライしました。単にMeCabをインストールするだけではなく、Sparkの並列分散処理を活用できるようにするところまでをまとめました。

# MeCabのインストール

まず、DatabricksクラスターにMeCabがインストールされるように、initスクリプトを作成して設定します。

## initスクリプトの作成

以下を実行して`/dbfs/databricks/scripts/mecab_init_script.sh`にinitスクリプトを出力します。

```py:Python
import os

file_init="/dbfs/databricks/scripts/mecab_init_script.sh"
with open(file_init,"wt") as f1:
  f1.write("""#!/bin/bash
yes | apt-get install mecab
yes | apt-get install libmecab-dev
yes | apt-get install mecab-ipadic-utf8
yes | apt-get install swig
# using neologd
git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git > /dev/null 
echo yes | mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n > /dev/null 2>&1
echo "dicdir = "`mecab-config --dicdir`"/mecab-ipadic-neologd" > "/usr/local/etc/mecabrc"
""")
```

## initスクリプトの設定

使用するDatabricksクラスターの**Configuration**を開き、**Advanced options**の**Init Scripts**タブで上で作成したinitスクリプトのパスを設定します。
![Screen Shot 2021-12-09 at 11.07.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/3ee05966-ed63-69a7-1fab-dda7959546d2.png)

以下のようになります。
![Screen Shot 2021-12-09 at 11.06.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/d058b697-1fe8-1d1a-7ad6-efcde01fe4b6.png)

クラスターを起動して、initスクリプトの処理が完了してクラスターが起動することを確認します。
![Screen Shot 2021-12-09 at 11.15.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/beae6f9a-89ab-6b9f-64ba-8e4370677647.png)

# MeCab Pythonラッパーのインストール

クラスターライブラリとして以下のライブラリをインストールします。

https://pypi.org/project/mecab-python3/

1. Databricksクラスターの**Libraries**を開き、**Install new**をクリックします。
1. 以下のように**Library Source**でPyPIを選択し、**Package**に`mecab-python3`と入力し、Installをクリックします。
![Screen Shot 2021-12-09 at 11.58.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/ea0514f1-ddcd-d2cd-1ed6-d281a9648bfa.png)
1. ライブラリがインストールされたことを確認します。
![Screen Shot 2021-12-09 at 12.24.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/c3dee773-7840-1e1b-4f19-8713f154bf06.png)

## MeCabの動作確認

Databricksノートブックで、以下のセルを実行して形態素解析が行われることを確認します。

```py:Python
import MeCab

mecab = MeCab.Tagger("-Ochasen")
print(mecab.parse("今日はいい天気ですね。"))
```

```
今日	キョウ	今日	名詞-副詞可能		
は	ハ	は	助詞-係助詞		
いい	イイ	いい	形容詞-自立	形容詞・イイ	基本形
天気	テンキ	天気	名詞-一般		
です	デス	です	助動詞	特殊・デス	基本形
ね	ネ	ね	助詞-終助詞		
。	。	。	記号-句点		
EOS
```

### 追加辞書の利用

追加辞書の動作確認は以下のように行います。

```py:Python
mecab = MeCab.Tagger('-d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd')
print(mecab.parse("今日はいい天気ですね。"))
```

```
今日	名詞,副詞可能,*,*,*,*,今日,キョウ,キョー
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
いい	形容詞,自立,*,*,形容詞・イイ,基本形,いい,イイ,イイ
天気	名詞,一般,*,*,*,*,天気,テンキ,テンキ
です	助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
ね	助詞,終助詞,*,*,*,*,ね,ネ,ネ
。	記号,句点,*,*,*,*,。,。,。
EOS
```

### ユーザー定義辞書の作成

こちらを参考にさせていただいています。Databricksでは設定の追加、記述方法の変更が必要です。

https://techblog.gmo-ap.jp/2019/09/18/mecab/

https://qiita.com/shimajiroxyz/items/3b475da3b6cd850d2c22

1. まず、作成するユーザー定義辞書をポイントするようにMecab設定ファイルへのパスを環境変数に定義します。クラスターの**高度なオプション**を展開し、**環境変数**に`MECABRC=/etc/mecabrc`を追加します。
1. ノートブック上で以下の処理を行います。ドライバーノードでシェルを実行するためにマジックコマンド`%sh`を追加します。なお、シェルのデフォルトパスは`/databricks/driver/`となります。

    ```sh
    %sh
    echo '"形態素解析は面白い",-1,-1,1,名詞,一般,*,*,*,*,*,*,*,mydic"' > user_dic.csv
    ```
    
    ```sh
    %sh
    /usr/lib/mecab/mecab-dict-index -d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd -u user_dic.dic -f utf-8 -t utf-8 user_dic.csv
    ```
    ![Screen Shot 2022-08-23 at 10.33.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/920ed13b-d064-b97a-daf8-431635da02de.png)

1. ユーザー辞書へのパスをMecab設定ファイルに追記します。

    ```sh
    %sh
    echo "userdic = /databricks/driver/user_dic.dic" >> /etc/mecabrc
    cat /etc/mecabrc
    ```
    ![Screen Shot 2022-08-23 at 10.35.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/08c19ca3-88a0-1cc6-919d-ab65e2c5caaf.png)

1. 動作確認します。

    ```py:Python
    import MeCab
    text = '形態素解析は面白いのテスト'
    tagger = MeCab.Tagger("-Ochasen")
    result = tagger.parse(text)
    print(result)
    ```
    辞書で定義された単語が抽出できていることが確認できます。
    ![Screen Shot 2022-08-23 at 10.35.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/854e0fa7-f1ca-2358-2ee3-db72a421163b.png)

# ダミーデータの準備

次に上の形態素解析処理をSparkのUDF(ユーザー定義関数)で実行できるようにします。最初に処理対象のダミーデータを作成します。

```py:Python
# sqlモジュールからPySparkのRowクラスをインポート
from pyspark.sql import *

tweet1 = Row(id='123456', tweet='テストのテキストデータです。これを分析することでどのような分析結果を得ることができるのでしょうか。')
tweet2 = Row(id='789012', tweet='感染者数が増えて心配')
tweet3 = Row(id='345678', tweet='ワクチンの効果があるから大丈夫')
tweet4 = Row(id='901234', tweet='海外旅行に行けるのはいつの日か')

tweets = [tweet1, tweet2, tweet3, tweet4]
df_tweet = spark.createDataFrame(tweets)

display(df_tweet)
```
![Screen Shot 2021-12-09 at 12.31.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/46528f41-e446-c228-33e0-0986e43aa324.png)

# Spark UDFの定義

ここでは、MeCabを用いて形態素解析を行い、抽出した名詞を半角スペースで連結した文字列を返却する関数を定義し、Spark UDFとして登録します。

```py:Python
def extract_words(text: str) -> str:
  """
  名詞のみを抽出するUDF
  :param text: Pyspark/SQLのカラム
  :return: 名詞を半角スペースで連結した文字列
  """
  
  word_str = ""
  
  mecab = MeCab.Tagger("-Ochasen")
  mecab.parseToNode('')
  node = mecab.parseToNode(text)
    
  while node:
    # 名詞のみを抽出
    if node.feature.split(",")[0] == "名詞":
      word = node.surface
      
      word_str = word_str + " " + word
      
    node = node.next
  
  return word_str

# Spark UDFとして関数を登録
spark.udf.register("extract_words", extract_words)
```

# 処理の実行

関数をSpark UDFとして登録することで、SQLを用いて容易に形態素解析を行うことが可能となります。

```py:Python
# 上で作成したデータフレームを一時ビューとして登録
df_tweet.createOrReplaceTempView('tweets')
```

```sql:SQL
%sql
SELECT tweet, extract_words(tweet) AS prosessed_tweet FROM tweets;
```

![Screen Shot 2021-12-09 at 12.39.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/39fea403-2344-769f-28f0-3f1924b68fa7.png)


# おまけ：ワードクラウドによる可視化

テーブルを永続化することで、[Databricks SQL](https://qiita.com/taka_yayoi/items/fd0d5e4de3c7f50de617)でデータの可視化を行うことができます。先ほど処理した半角スペース区切りの文字列をワードクラウドの入力に指定することで、キーワードの出現頻度に基づくワードクラウドを作成することができます。

```sql:SQL
%sql
CREATE DATABASE IF NOT EXISTS japanese_nlp_test

CREATE OR REPLACE TABLE japanese_nlp_test.tweet_processed
AS 
SELECT tweet, extract_words(tweet) AS prosessed_tweet FROM tweets;
```

![Screen Shot 2021-12-09 at 12.44.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/d3aa5d42-8a56-fdcd-4689-cdf96318e5c4.png)
![Screen Shot 2021-12-09 at 12.45.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/645af73c-ab55-d687-8270-89233505503e.png)

# サンプルノートブック

https://github.com/taka-yayoi/public_repo/tree/main/mecab_sample

# 参考資料

- [PythonとMeCabで文章を解析する方法【頻出度カウント】 \|](https://your-3d.com/pytho-mecab-frequencywords/)
- [Azure DatabricksでPythonとMeCabを使う \- Qiita](https://qiita.com/ryokita/items/476bf93b4bf51f99c7cb)
- [Azure DatabricksでMeCabを使う \| Nature Insight ネイチャーインサイト株式会社](https://www.n-insight.co.jp/niblog/20190524-1274/)
- [PySparkで日本語形態素解析 \| フライウィール](https://www.flywheel.jp/topics/tokenize_on_pyspark/)


### Databricks 無料トライアル

[Databricks 無料トライアル](https://databricks.com/jp/try-databricks)