3.6  テキストの正規化    3.7  テキストをトークン化するための正規表現

# 3.6　Normalizing Text　テキストの正規化

以前のプログラムの例では、単語で何かを行う前に、テキストを小文字に変換することがよくありました。

```
 set（w.lower（）for w for text）
```
```
lower（）
```
上記のコマンドをを使用して、テキストを小文字に***正規化***して、Theとtheを同じものとしてとらえるようにしました。 多くの場合、これよりも先に進み、接尾辞として知られるタスクである接尾辞を取り除きます。 
 さらなるステップは、結果のフォームが辞書の既知の単語であることを確認することです。これは、***見出し語化***と呼ばれるタスクです。 これらについて順に説明します。 まず、このセクションで使用するデータを定義する必要があります。

```
>>> raw = """DENNIS: Listen, strange women lying in ponds distributing swords
... is no basis for a system of government.  Supreme executive power derives from
... a mandate from the masses, not from some farcical aquatic ceremony."""
>>> tokens = word_tokenize(raw)
```
###  ステマー (Stemmers)
NLTKにはいくつかの既製のステマーが含まれています。
>ステマー (Stemmers)とは、語幹処理《検索エンジンなどが検索語から語幹を認識して活用形や派生形も含めて検索すること》

ステマーが必要な場合は、いずれかを使用して、正規表現を使用した独自の作成よりも優先してください。 ポーターステマーとランカスターステマーは、接辞を除去するための独自のルールに従います。
>・ポーターステマー(Porter stemming)とは、英語の単語から一般的な形態的および屈折的な語尾を削除する方法のことで、最もよく使われている、一番易しいステマーである。
・ランカスターステマー(Lancaster stemming)とは、最も計算は速いが、結果に問題がある場合があるステマーである。

 ポーターステマーは、間違った単語を正しく処理しますが、ランカスターステマーは処理しません。

```
>>> porter = nltk.PorterStemmer()
>>> lancaster = nltk.LancasterStemmer()
>>> [porter.stem(t) for t in tokens]
['denni', ':', 'listen', ',', 'strang', 'women', 'lie', 'in', 'pond',
'distribut', 'sword', 'is', 'no', 'basi', 'for', 'a', 'system', 'of', 'govern',
'.', 'suprem', 'execut', 'power', 'deriv', 'from', 'a', 'mandat', 'from',
'the', 'mass', ',', 'not', 'from', 'some', 'farcic', 'aquat', 'ceremoni', '.']
>>> [lancaster.stem(t) for t in tokens]
['den', ':', 'list', ',', 'strange', 'wom', 'lying', 'in', 'pond', 'distribut',
'sword', 'is', 'no', 'bas', 'for', 'a', 'system', 'of', 'govern', '.', 'suprem',
'execut', 'pow', 'der', 'from', 'a', 'mand', 'from', 'the', 'mass', ',', 'not',
'from', 'som', 'farc', 'aqu', 'ceremony', '.']

```
ステミングは明確に定義されたプロセスでなく、通常、考えているアプリケーションに最適なステマーを選択します。 ポーターステマーは、一部のテキストのインデックスを作成し、代替形式の単語を使用した検索をサポートしたい場合に適しています。

```
class IndexedText(object):

    def __init__(self, stemmer, text):
        self._text = text
        self._stemmer = stemmer
        self._index = nltk.Index((self._stem(word), i)
                                 for (i, word) in enumerate(text))

    def concordance(self, word, width=40):
        key = self._stem(word)
        wc = int(width/4)                # words of context
        for i in self._index[key]:
            lcontext = ' '.join(self._text[i-wc:i])
            rcontext = ' '.join(self._text[i:i+wc])
            ldisplay = '{:>{width}}'.format(lcontext[-width:], width=width)
            rdisplay = '{:{width}}'.format(rcontext[:width], width=width)
            print(ldisplay, rdisplay)

    def _stem(self, word):
        return self._stemmer.stem(word).lower()
```

```
>>> porter = nltk.PorterStemmer()
>>> grail = nltk.corpus.webtext.words('grail.txt')
>>> text = IndexedText(porter, grail)
>>> text.concordance('lie')
r king ! DENNIS : Listen , strange women lying in ponds distributing swords is no
 beat a very brave retreat . ROBIN : All lies ! MINSTREL : [ singing ] Bravest of
       Nay . Nay . Come . Come . You may lie here . Oh , but you are wounded !
doctors immediately ! No , no , please ! Lie down . [ clap clap ] PIGLET : Well
ere is much danger , for beyond the cave lies the Gorge of Eternal Peril , which
   you . Oh ... TIM : To the north there lies a cave -- the cave of Caerbannog --
h it and lived ! Bones of full fifty men lie strewn about its lair . So , brave k
not stop our fight ' til each one of you lies dead , and the Holy Grail returns t
```
図3.6:ステマーを使用したテキストのインデックス作成

### 補題(Lemmatization)

WordNet lemmatizerは、結果の単語が辞書にある場合にのみ接辞を削除します。 
>WordNetとは、英語の概念辞書（意味辞書）
lemmatizerとは、見出し語に変換すること。
例 meet, meeting)
I'll attend meeting. 変換後 - meeting
I met him last night. 変換後 - meet


この追加のチェックプロセスにより、lemmatizerは上記のステマーよりも遅くなります。 嘘をつきませんが、wom*e*nをwom*a*nに変えます。

```
>>> wnl = nltk.WordNetLemmatizer()
>>> [wnl.lemmatize(t) for t in tokens]
['DENNIS', ':', 'Listen', ',', 'strange', 'woman', 'lying', 'in', 'pond',
'distributing', 'sword', 'is', 'no', 'basis', 'for', 'a', 'system', 'of',
'government', '.', 'Supreme', 'executive', 'power', 'derives', 'from', 'a',
'mandate', 'from', 'the', 'mass', ',', 'not', 'from', 'some', 'farcical',
'aquatic', 'ceremony', '.']
```
WordNet lemmatizerは、一部のテキストの語彙を編集し、有効なレンマのリストが必要な場合に適しています。
>レンマ(lemma)とは、見出し語, 辞書に載っている形式の単語のこと。

# 3.7  テキストをトークン化するための正規表現

**トークン化**は、文字列を言語データの一部を構成する識別可能な言語単位にカットするタスクです。 これは基本的なタスクですが、多くのコーパスが既に**トークン化**されており、NLTKにいくつかのトークン化することがが含まれているため、これまで遅延することができました。 正規表現に精通しているので、正規表現を使用してテキストを**トークン化**し、プロセスをより詳細に制御する方法を学習できます。
>トークンとは、自然言語を解析する際、文章の最小単位として扱われる文字や文字列のこと。

###トークン化への簡単なアプローチ
テキストを**トークン化**する最も簡単な方法は、空白で分割することです。 不思議の国のアリスの冒険の次のテキストを考えてみましょう。

```
>>> raw = """'When I'M a Duchess,' she said to herself, (not in a very hopeful tone
... though), 'I won't have any pepper in my kitchen AT ALL. Soup does very
... well without--Maybe it's always pepper that makes people hot-tempered,'..."""
```
raw.split（）を使用して、この生のテキストを空白に分割できます。 正規表現を使用して同じことを行うには、文字列**[1]**のスペース文字と一致させるだけでは不十分です。これにより、\ n改行文字を含むトークンが生成されるためです。 代わりに、スペース、タブ、または改行をいくつでも一致させる必要があります**[2]**：

```
>>> re.split(r' ', raw) [1]
["'When", "I'M", 'a', "Duchess,'", 'she', 'said', 'to', 'herself,', '(not', 'in',
'a', 'very', 'hopeful', 'tone\nthough),', "'I", "won't", 'have', 'any', 'pepper',
'in', 'my', 'kitchen', 'AT', 'ALL.', 'Soup', 'does', 'very\nwell', 'without--Maybe',
"it's", 'always', 'pepper', 'that', 'makes', 'people', "hot-tempered,'..."]
>>> re.split(r'[ \t\n]+', raw) [2]
["'When", "I'M", 'a', "Duchess,'", 'she', 'said', 'to', 'herself,', '(not', 'in',
'a', 'very', 'hopeful', 'tone', 'though),', "'I", "won't", 'have', 'any', 'pepper',
'in', 'my', 'kitchen', 'AT', 'ALL.', 'Soup', 'does', 'very', 'well', 'without--Maybe',
"it's", 'always', 'pepper', 'that', 'makes', 'people', "hot-tempered,'..."]
```
正規表現«[\ t \ n] +»は、1つ以上のスペース、タブ（\ t）、または改行（\ n）に一致します。 キャリッジリターンやフォームフィードなどの他の空白文字も実際に含める必要があります。 
>キャリッジリターンとは、文字コード体系において、カーソルを文頭へ戻すことを表す制御文字である。

代わりに、組み込みの略語\ sを使用します。これは、任意の空白文字を意味します。 上記のステートメントは、re.split（r '\ s +'、raw）として書き換えることができます。

>重要：正規表現の前に文字rを付けることを忘れないでください。これは、含まれるバックスラッシュ文字を処理するのではなく、文字列をリテラルとして処理するようPythonインタープリターに指示します。

空白で分割すると、 '（not'や 'herself、'などのトークンが得られます。代わりに、Pythonが[a-zA-Z0-9_]と同等の単語文字の文字クラス\ wを提供するという事実を使用することもできます。 また、このクラス\ Wの補完、つまり文字、数字、またはアンダースコア以外のすべての文字を定義します。単純な正規表現で\ Wを使用して、単語文字以外の入力を分割できます。
>アンダースコアとは、「＿」のこと。アンダーバーと同じ。

```
>>> re.split(r'\W+', raw)
['', 'When', 'I', 'M', 'a', 'Duchess', 'she', 'said', 'to', 'herself', 'not', 'in',
'a', 'very', 'hopeful', 'tone', 'though', 'I', 'won', 't', 'have', 'any', 'pepper',
'in', 'my', 'kitchen', 'AT', 'ALL', 'Soup', 'does', 'very', 'well', 'without',
'Maybe', 'it', 's', 'always', 'pepper', 'that', 'makes', 'people', 'hot', 'tempered',
'']
```
これにより、開始時と終了時に空の文字列が得られることに注意してください。 同じトークンを取得しますが、re.findall（r '\ w +'、raw）を使用して、スペースではなく単語に一致するパターンを使用して、空の文字列を使用しません。 単語を一致させたので、より広範なケースをカバーするために正規表現を拡張する立場にあります。 正規表現«\ w + | \ S \ w *»は、最初に単語文字のシーケンスとの一致を試みます。 
>シーケンスとは、順番に並んだ一続きのデータや手順のことや、並んだ順番にデータや手順を取り扱う処理方式のこと。

一致するものが見つからない場合、空白文字以外の文字（\ Sは\ sの補数）に続いて、さらに単語文字を一致させようとします。 つまり、句読点は後続の文字（例： 's）でグループ化されますが、2つ以上の句読点文字のシーケンスは分離されます。

```
>>> re.findall(r'\w+|\S\w*', raw)
["'When", 'I', "'M", 'a', 'Duchess', ',', "'", 'she', 'said', 'to', 'herself', ',',
'(not', 'in', 'a', 'very', 'hopeful', 'tone', 'though', ')', ',', "'I", 'won', "'t",
'have', 'any', 'pepper', 'in', 'my', 'kitchen', 'AT', 'ALL', '.', 'Soup', 'does',
'very', 'well', 'without', '-', '-Maybe', 'it', "'s", 'always', 'pepper', 'that',
'makes', 'people', 'hot', '-tempered', ',', "'", '.', '.', '.']
```
上の式の\ w +を一般化して、単語内部のハイフンとアポストロフィを許可します：«\ w +（[-'] \ w +）*»。 この式は、\ w +の後に[-'] \ w +のゼロ個以上のインスタンスが続くことを意味します。 それはホットテンパーと一致します。また、引用文字に一致するパターンを追加して、引用文字が囲むテキストとは別に保持されるようにします。

```
>>> print(re.findall(r"\w+(?:[-']\w+)*|'|[-.(]+|\S\w*", raw))
["'", 'When', "I'M", 'a', 'Duchess', ',', "'", 'she', 'said', 'to', 'herself', ',',
'(', 'not', 'in', 'a', 'very', 'hopeful', 'tone', 'though', ')', ',', "'", 'I',
"won't", 'have', 'any', 'pepper', 'in', 'my', 'kitchen', 'AT', 'ALL', '.', 'Soup',
'does', 'very', 'well', 'without', '--', 'Maybe', "it's", 'always', 'pepper',
'that', 'makes', 'people', 'hot-tempered', ',', "'", '...']
```
上記の式には«[-。（] +»も含まれており、二重ハイフン、省略記号、および開き括弧が個別にトークン化されます。3.4のこのセクションで見た正規表現の文字クラスシンボルと、その他の便利なシンボルがリストされています。

表3.4：正規表現記号

|Symbol|Function|
|:---|:---:
|\b|ワード境界（幅ゼロ）|
|\d|任意の10進数（[0-9]と同等）
|\D|数字以外の文字（[^ 0-9]と同等）
|\s|空白文字（[\ t \ n \ r \ f \ v]と同等）|--:|
|\S|空白以外の文字（[^ \ t \ n \ r \ f \ v]と同等）|align-right|
|\w|任意の英数字（[a-zA-Z0-9_]と同等）|
|\W|任意の非英数字（[^ a-zA-Z0-9_]と同等）|右揃え|
|\t|タブ文字|--:|
|\n|改行文字|align-right|

###NLTKの正規表現トークナイザー

関数nltk.regexp_tokenize（）はre.findall（）に似ています（トークン化に使用しているため）。 ただし、nltk.regexp_tokenize（）はこのタスクに対してより効率的であり、括弧の特別な処理の必要性を回避します。 読みやすくするために、正規表現を複数の行に分割し、各行についてコメントを追加します。 特別な "verbose flag"は、埋め込まれた空白とコメントを取り除くようPythonに指示します。

```
>>> text = 'That U.S.A. poster-print costs $12.40...'
>>> pattern = r'''(?x)     # set flag to allow verbose regexps
...     (?:[A-Z]\.)+       # abbreviations, e.g. U.S.A.
...   | \w+(?:-\w+)*       # words with optional internal hyphens
...   | \$?\d+(?:\.\d+)?%? # currency and percentages, e.g. $12.40, 82%
...   | \.\.\.             # ellipsis
...   | [][.,;"'?():-_`]   # these are separate tokens; includes ], [
... '''
>>> nltk.regexp_tokenize(text, pattern)
['That', 'U.S.A.', 'poster-print', 'costs', '$12.40', '...']
```
詳細フラグを使用する場合、スペース文字と一致させるために「」を使用できなくなりました。 代わりに\ sを使用してください。 regexp_tokenize（）関数には、オプションのgapsパラメーターがあります。 Trueに設定すると、正規表現はre.split（）と同様にトークン間のギャップを指定します。

###トークン化に関するその他の問題
トークン化は、予想よりもはるかに難しいタスクであることが判明しました。 全面的にうまく機能する単一のソリューションはないため、アプリケーションドメインに応じて、トークンとしてカウントするものを決定する必要があります。

トークナイザーを開発するとき、トークナイザーの出力を高品質（または「ゴールドスタンダード」）トークンと比較するために、手動でトークン化された生テキストにアクセスできると役立ちます。 NLTKコーパスコレクションには、未加工のWall Street Journalテキスト（nltk.corpus.treebank_raw.raw（））およびトークン化バージョン（nltk.corpus.treebank.words（））を含むPenn Treebankデータのサンプルが含まれています。

トークン化の最後の問題は、収縮の存在です。 文の意味を分析している場合、おそらくこのフォームを2つの別個のフォームに正規化する方が便利です：didとn't（またはnot）。 ルックアップテーブルを使用して、この作業を行うことができます。
>ルックアップテーブル(Lookup table)とは、計算処理を配列の参照処理にするための配列や連想配列などのデータのことである。
例えば、あるデータベースで項目を選択し、その項目に対応するデータを取り出したい場合、あらかじめ項目に対応するデータをルックアップテーブルとして保存しておけば、ルックアップテーブルから項目に対応する値を参照することで、項目に対応するデータを求められるようになる。要求されるたびに毎回計算を行う必要はなくなるため、コンピュータにかかる計算の負担を軽減することができ、また効率よく処理を行うことができる。