言語処理100本ノック-80(正規表現で置換):コーパスの整形

[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)の80本目「コーパスの整形」の記録です。
ついに第9章「ベクトル空間法 (I)」まで来ました。今回は、前処理系で正規表現を使った文字置換だけなので簡単です。

# 参考リンク

|リンク|備考|
|:--|:--|
|[080.コーパスの整形.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/09.%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E7%A9%BA%E9%96%93%E6%B3%95%20(I)/080.%E3%82%B3%E3%83%BC%E3%83%91%E3%82%B9%E3%81%AE%E6%95%B4%E5%BD%A2.ipynb)|回答プログラムのGitHubリンク|
|[素人の言語処理100本ノック:80](http://qiita.com/segavvy/items/ea485e66dd96eee891da)|言語処理100本ノックで常にお世話になっています|
|[言語処理100本ノック 2015年版 (80～82)](http://kenichia.hatenablog.com/entry/2016/03/03/170718)|第9章は参考にしました|

# 環境

|種類|バージョン|内容|
|:--|:--|:--|
|OS|Ubuntu18.04.01 LTS|仮想で動かしています|
|pyenv|1.2.15|複数Python環境を使うことがあるのでpyenv使っています|
|Python|3.6.9|pyenv上でpython3.6.9を使っています<br>3.7や3.8系を使っていないことに深い理由はありません<br>パッケージはvenvを使って管理しています|

# 課題
## [第9章: ベクトル空間法 (I)](http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch9)
>[enwiki-20150112-400-r10-105752.txt.bz2](http://www.cl.ecei.tohoku.ac.jp/nlp100/data/enwiki-20150112-400-r10-105752.txt.bz2)は，2015年1月12日時点の英語のWikipedia記事のうち，約400語以上で構成される記事の中から，ランダムに1/10サンプリングした105,752記事のテキストをbzip2形式で圧縮したものである．このテキストをコーパスとして，単語の意味を表すベクトル（分散表現）を学習したい．第9章の前半では，コーパスから作成した単語文脈共起行列に主成分分析を適用し，単語ベクトルを学習する過程を，いくつかの処理に分けて実装する．第9章の後半では，学習で得られた単語ベクトル（300次元）を用い，単語の類似度計算やアナロジー（類推）を行う．
>
>なお，問題83を素直に実装すると，大量（約7GB）の主記憶が必要になる． メモリが不足する場合は，処理を工夫するか，1/100サンプリングのコーパス[enwiki-20150112-400-r100-10576.txt.bz2](http://www.cl.ecei.tohoku.ac.jp/nlp100/data/enwiki-20150112-400-r100-10576.txt.bz2)を用いよ．

今回は*「1/100サンプリングのコーパス[enwiki-20150112-400-r100-10576.txt.bz2](http://www.cl.ecei.tohoku.ac.jp/nlp100/data/enwiki-20150112-400-r100-10576.txt.bz2)」*を使っています。

### 80. コーパスの整形
>文を単語列に変換する最も単純な方法は，空白文字で単語に区切ることである． ただ，この方法では文末のピリオドや括弧などの記号が単語に含まれてしまう． そこで，コーパスの各行のテキストを空白文字でトークンのリストに分割した後，各トークンに以下の処理を施し，単語から記号を除去せよ．
>
>- トークンの先頭と末尾に出現する次の文字を削除: .,!?;:()[]'"
>- 空文字列となったトークンは削除
>
>以上の処理を適用した後，トークンをスペースで連結してファイルに保存せよ．

過去の言語処理100本ノック[「言語処理100本ノック-71(StanfordNLP使用):ストップワード」](https://qiita.com/FukuharaYohei/items/60719ddaa47474a9d670)で似たことをしているので楽勝です。

# 回答
## 回答プログラム [080.コーパスの整形.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/09.%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E7%A9%BA%E9%96%93%E6%B3%95%20(I)/080.%E3%82%B3%E3%83%BC%E3%83%91%E3%82%B9%E3%81%AE%E6%95%B4%E5%BD%A2.ipynb)

```python
import bz2
import re

# 先頭と末尾の記号除去の正規表現(末尾は改行コードも)
reg_sym = re.compile(r'^[.,!?;:\(\)\[\]\'"]+|[.,!?;:\(\)\[\]\'"\n]+$')

with bz2.open('./enwiki-20150112-400-r100-10576.txt.bz2', 'rt') as data_file, \
         open('./080.corpus.txt', mode='w') as out_file:
    for i, line in enumerate(data_file):

        # 空白で分解、前後の記号除去
        tokens = []
        tokens.extend([reg_sym.sub('', chunk) for chunk in line.split(' ') if len(reg_sym.sub('', chunk)) > 0])
        
        # 空行は対象外
        if tokens:
            # ファイル出力
            print(*tokens, sep=' ', end='\n', file=out_file)
        
        # 3行はコンソールにも出力
        if i < 3:
            print(i, line, tokens)
```

## 回答解説
だいたい1分くらいで処理が終わります。
コンソールにはこんな結果が出ます。

```
0 Anarchism
 ['Anarchism']
1 
 []
2 Anarchism is a political philosophy that advocates stateless societies often defined as self-governed voluntary institutions, but that several authors have defined as more specific institutions based on non-hierarchical free associations. Anarchism holds the state to be undesirable, unnecessary, or harmful. While anti-statism is central, anarchism entails opposing authority or hierarchical organisation in the conduct of human relations, including, but not limited to, the state system.
 ['Anarchism', 'is', 'a', 'political', 'philosophy', 'that', 'advocates', 'stateless', 'societies', 'often', 'defined', 'as', 'self-governed', 'voluntary', 'institutions', 'but', 'that', 'several', 'authors', 'have', 'defined', 'as', 'more', 'specific', 'institutions', 'based', 'on', 'non-hierarchical', 'free', 'associations', 'Anarchism', 'holds', 'the', 'state', 'to', 'be', 'undesirable', 'unnecessary', 'or', 'harmful', 'While', 'anti-statism', 'is', 'central', 'anarchism', 'entails', 'opposing', 'authority', 'or', 'hierarchical', 'organisation', 'in', 'the', 'conduct', 'of', 'human', 'relations', 'including', 'but', 'not', 'limited', 'to', 'the', 'state', 'system']
```


ファイルはbz2パッケージを使って圧縮されたものを直接開いています。また、読込ファイルと書込ファイルを同時に開いています。

```python
with bz2.open('./enwiki-20150112-400-r100-10576.txt.bz2', 'rt') as data_file, \
         open('./080.corpus.txt', mode='w') as out_file:
```

今回のメインとなる記号除去の正規表現です。簡単な解説です。

|正規表現|意味|
|:-:|:-:|
|^|先頭|
|[]|グルーピングを意味していて`[]`で囲んでいる文字のいずれか|
|.,!?;:\(\)\[\]\'"|除去する記号。先頭と末尾で同じ。`\`はエスケープ|
|+|先頭/末尾に記号が連続していた場合も対象|
|\n|改行コード(末尾のみ)|
|縦棒|和集合(または)|
|$|末尾|

```python
reg_sym = re.compile(r'^[.,!?;:\(\)\[\]\'"]+|[.,!?;:\(\)\[\]\'"\n]+$')
```

### `\xa0`について
後で記号で消えていないと思ったのですが、中身をよく見ると`\xa0`を含んでいる箇所がありました。`\xa0`は残しておきました。
例えば"However B. nutans"と書き込んだファイル内になっていて、「`B.`の末尾のドットが除去されてない」と思ったのですが、内部的には`B.\xa0nutans`となっていて「B. nutans」で1単語らしいです。
記事[「【Python3】スクレイピング中に[\xa0]に遭遇した時の対処法」](https://tech-knygt51.com/python-xa0-deal/)を見て気づきました