StatsBeginner: 初学者の統計学習ノート

統計学およびR、Pythonでのプログラミングの勉強の過程をメモっていくノート。たまにMacの話題。

Pythonの入門書としても使える『言語処理のためのプログラミング入門』

Python及びテキストマイニングの超初心者向け入門書として

 友人と週1回ペースで行っている統計&プログラミングの勉強会で、以前、Pythonの入門書は何がいいだろうかと考えて本書を取り上げました。当時ブログにはまとめていなかったのでまとめておきます。


言語研究のためのプログラミング入門: Pythonを活用したテキスト処理

言語研究のためのプログラミング入門: Pythonを活用したテキスト処理


 本書はほんとにプログラミングというものに全く触れたことがない人向けのものなので、内容はちょっと簡単すぎた感もあるのですが、勉強会ではRばかりやってきてPythonには皆慣れているわけではないし、テキストマイニングの入門という意味もこめて一応やりました。初心者でもすぐ理解できる内容なのでさっさと終わらせようと思い、1回2章ずつのペースで進めました。


 著者は言語学者で、本書は「言語学の研究をしてる(しようとしてる)学生のみなさん、プログラムが書けると研究がとても捗るのでちょっと勉強してみませんか」みたいなノリで書かれているものです。なんというか、「プログラムを書いて何かをやる」ということ自体イメージできない人、というかむしろそういうのにアレルギー反応を示す学生を読者として想定している感じですね。
 コーディングの内容は単純なものばかりで、初心者でも直観的に理解できる書き方しか出てきません。なので、本書を一冊やったぐらいでは得られる知識は限られていますが、Pythonやプログラミングの知識ゼロからでも読めるようになっているので、「Pythonの勉強のために、最初に読む本」としてもけっこう良いかなと思いました。
 もちろんテキストマイニングの入門としても、単語の登場頻度を集計するとか、テキスト全体をまず形態素解析してから統計的・文法的な分析を行う流れとかの基本が分かるので役に立ちます。初歩の初歩ですけどね。


 各章のテーマを並べてみると、


第1章:言語研究とプログラミング(言語研究者がプログラミングを学ぶことの必要性)
第2章:テキストデータに親しもう(テキストファイル、テキストエディタの基本)
第3章:正規表現(正規表現の基本)
第4章:Pythonに触れてみよう(インストール、基本操作)
第5章:Pythonでファイルの内容を表示してみよう(プログラムの書き方、実行の仕方)
第6章:Pythonで検索しよう:条件分岐(inとかifとかの条件式で検索ロジックを書く)
第7章:繰り返し処理を覚えようループ処理(for文の練習)
第8章:単語の一覧表を作ろう:リスト(リスト型データを使う練習)
第9章:頻度表を作ろう:ディクショナリ(辞書型を使う練習と、辞書型を用いた単語の使用頻度集計)
第10章:ファイル操作
第11章:Pythonで正規表現を使ってみよう(3章より実践的な使用)
第12章:形態素解析プログラムを活用しよう(MeCab等を用いた形態素解析)
第13章:Pythonで日本語を扱おう(文字コード問題等)
第14章:PythonでKWICを作ろう(テキストファイルから、指定した語を前後の文脈とともに抽出)
第15章:コロケーション検索を作ろう(「〜にくい」「〜すぎる」などの語がどんな単語にくっつきやすいかの分析)


 となっており、ページ数が少ない割にはけっこう盛りだくさんとも言えます。
 7章まではプログラミング入門、8章以降はテキストマイニング入門という感じですね。


 以下、章ごとに概要をまとめておきます。また、各章に章末問題があって「こういうプログラムを書け」みたいな課題が出るのですが、回答例が教科書には載っておらず、著者の先生にメールで問合せたところ「特に作ってない」とのことだったので、私の回答例を全部載せておきます。問題文を完全に転記はしておらず、本書を読んでいない人は見ても意味がわからないと思うので、無視してください。
 なお私はプログラミング初心者なので、回答例といっても「良いコード」が書けてるわけでは全くなく、一応動くものが書けたというだけですので、そこは注意してください! 

 
 あと、本書中にはコードの誤植等がいくつかあり(オライリーの技術書とかでもコード自体が間違っていることがあるので珍しいことではない)、公式サイトに正誤表(リンク)がありますので、本書を読む方は必ず読み始める前にチェックしたほうがいいと思います。いくつかは私が発見して先生に連絡したものです。


 まとめはとても長くなったので、各章のまとめは省略表示してます。↓の「続きを読む」(Read more)のリンクをクリックしたら続きが表示されます。スマホだと最初から全文が表示されてる気もしますが。


 
 

第1章:言語研究とプログラミング

概要

  • コーパスを用いた統計的アプローチでの言語研究では、プログラミングはスキルとして必須である。
  • コーパスのような大規模データの処理でなくても、テキストデータを操作する単純作業を行うのにプログラミングは便利である。
  • プログラミングにはミスがつきものである。
  • サポートデータのダウンロードについて。

 
 

章末問題

なし。
 
 

第2章:テキストデータに親しもう

概要

  • ワープロソフトではなく、テキストファイルというものに慣れる必要がある。
  • テキストエディタで便利なものがいろいろ出てるので好きなのを使ってくれ。
  • テキストエディタにはgrepによる検索などできて便利である。
  • 文字コードと改行コードに気をつけろ。

 
 

章末問題

なし。
 
 

第3章:正規表現

概要

  • 高度な検索をするために正規表現を覚える必要がある。テキストエディタでも使えるし、プログラミングでも使う。
  • 繰り返しなどの基本的な記法と、後方参照の解説。
  • 例として、本書のサポートデータ「b.txt」(夏目漱石の『坊っちゃん』のテキストファイル)中の、《》でかこまれた読みがな部分を置換で削除するための正規表現の検討。

 
 

章末問題1

テキストエディタで検索しろという問題だが、Python上でやった。

>>> import re
>>> t1 = 'たのしい'
>>> re.findall('^しい', t1)
[]
>>>
>>> t2 = 'うれしい'
>>> re.findall('[^し]い', t2)
[]
>>>
>>> t3 = 'おもしろい'
>>> re.findall('[^し]い', t3)
['ろい']
>>>
>>> t4 = 'いつも'
>>> re.findall('[^し]い', t4)
[]
>>>
>>> t5 = '見ていない'
>>> re.findall('ている?', t5)
['てい']
>>>
>>> t6 = '見ていない'
>>> re.findall('て.い', t6)
[]
>>>
>>> t7 = '見ていない'
>>> re.findall('て.?い', t7)
['てい']
>>>
>>> t8 = '見ていない'
>>> re.findall('て.+い', t8)
['ていない']

 
 

章末問題2

>>> kodomo = '子供と小供とこどもと子ども'
>>> re.findall('子供|小供', kodomo)
['子供', '小供']

 
 

章末問題3

章末問題では、

例えば「約束は約束だ」「教師が教師なら、生徒も生徒だ」というような同語反復(トートロジー)表現が使われている箇所があります

と言われてるのだが、b2.txtにはこれらの文字列は登場しない。ややこしいが、この問題文はあくまで例を適当に挙げたもので、要は「こんな感じの表現があれば見つけてこい」という意味らしい。分かりづらい……。
なお教科書ではテキストエディタの検索機能で探す前提になっているが、以下のとおりPython上でやることにした

#前提として、適切なディレクトリへ移動しておくこと。

# テキストを読み込む
with open('support_data/Python-ex/b.txt', mode='r', encoding='Shift-JIS') as file:  # 文字コードに注意
    b_all = file.read()

 # 読みがな部分を除去する処理
b_noprn = re.sub('《[^》]+》', '', b_all)

# 後方参照を使うのと、マッチした文字列を得るめにfinditerのgroup()メソッドを使う
results_itr = re.finditer(r'(.+)[はも]\1', b_noprn)
for result in results_itr:
    print(result.group())


一応「◯◯は◯◯」みたいなのが抜けますが、以下のとおり意図しない文字列もいっぱい返ってきます。もっと美しい(かつ手軽な)やり方があるんでしょうか。

>>> results_itr = re.finditer(r'(.+)[はも]\1', b_noprn)
>>> for result in results_itr:
...     print(result.group())
...
のもの
のもの
のもの
どはど
のもの
のもの
のもの
議論は議論
のもの
、押しても、押して
親切は親切
声は声
赤シャツも赤シャツ
のもの
赤シャツも赤シャツ
のもの
生徒は生徒
赤シャツさんも赤シャツさん
お嬢さんもお嬢さん
事は事
らもら
山の中も山の中
っ、はっ、
逢いは逢い
ともと
のもの
のもの
こっちはこっち
のもの


 ひらがな1文字のものは除いてみると以下のとおり。

>>> >>> results_itr = re.finditer(r'([^ぁ-ん].*)[はも]\1', b_noprn)
>>> for result in results_itr:
...     print(result.group())
...
議論は議論
、押しても、押して
親切は親切
声は声
赤シャツも赤シャツ
赤シャツも赤シャツ
生徒は生徒
赤シャツさんも赤シャツさん
事は事
山の中も山の中
逢いは逢い
>>>


だいぶ淘汰されました。句読点も除いておきます。

>>> results_itr = re.finditer(r'([^ぁ-ん、。].*)[はも]\1', b_noprn)
>>> for result in results_itr:
...     print(result.group())
...
議論は議論
親切は親切
声は声
赤シャツも赤シャツ
赤シャツも赤シャツ
生徒は生徒
赤シャツさんも赤シャツさん
事は事
山の中も山の中
逢いは逢い
>>>


本当に意図した通りの結果を得ようと思ったら、後で出てくる形態素解析とかも組み合わせる必要がありそうですね。
 
 

章末問題4

>>> computer = r'コンピュータとか、コンピューターとか。'
>>> re.sub(r'コンピュータ([^ー])', r'コンピューター\1', computer)
'コンピューターとか、コンピューターとか。'
>>>

 
 

第4章:Pythonに触れてみよう

概要

  • Pythonとは。
  • Pythonのインストール。
  • 本書ではPython3系を使うよ。
  • コマンドプロンプトから使う方法(私はMacなのでターミナル)
  • 四則演算をさせてみる。
  • 変数にデータを放り込んでみる。
  • 文字列は''で。
  • 文字列を連結してみる。
  • インデックス(0から始まる)を指定して情報を取り出す。
  • データ型を文字列から数値型に変換するなど。

 
 

章末問題1

「変数名に使える文字列はどれか?」
使えるのは2番目のnumber_of_words_in_file2
 
 

章末問題2

 教科書のコードを試してみる。
 実行結果は以下のとおり。

>>> a = 'doki'
>>> a * 3  # へー
'dokidokidoki'
>>>
>>> a = 8
>>> a -= 1
>>> a
7
>>>
>>> a = '5'
>>> b = '8'
>>> a + b
'58'
>>>
>>> filename = 'example.txt'
>>> filename[-4:]  # 後ろから4文字目以降
'.txt'
>>>
>>> i = 0
>>> word = 'bike'
>>> word[i]
'b'

 
 

章末問題3

円ドル換算の計算。

# 関数定義でやる場合の例
def yen2dollar(rate, yen):
    dollar = yen/rate
    return('$' + str(dollar))

yen2dollar(95.44, 114528)

 
 

第5章:Pythonでファイルの内容を表示してみよう

概要

  • 章のタイトルからすると、テキストファイルを開いて中身を読み込む方法が解説されるように思え、実際それも解説されるのだが、本文によると本章はPythonのプログラムをファイルに保存してあとでそれを実行するという手順を解説する章という位置づけらしい。
  • 処理の結果をテキストファイルとして出力する方法も説明。ここではPythonでではなくシェルのリダイレクト機能でやっている。
  • Pythonのスクリプトをコマンドプロンプト(ターミナル)から開いて実行するのだが、その実行内容は文章が書かれた別のテキストファイルを開いて内容を読み込むというもので、読み込んだ内容をまたテキストファイルにして出力するという操作が行われる。
  • この教科書はファイルを開く時、一貫してopen()のみで開いている。withブロックで開く方が良いと言われる場合も多いので、私もそうしている箇所がある
  • Windowsのコマンドプロンプトの使い方を私はよくしらないのだが、ディレクトリを移動したあといきなり「ch5-1.py」というスクリプト名を打ち込んで実行している。Macのターミナルから実行する場合については自分で試してみたのだが、以下のいずれかになる。
    • ターミナル上で「Python」と打ってPythonを起動した状態にし、スクリプトが置いてあるディレクトリまで移動してあることを前提に、「import ch5_2」でスクリプトを読み込む。Spyderなどのインタラクティブシェルから操作する場合も同様。「.py」は付けない。ただ、この呼び方だと、モジュールとしてインポートしているので、もう1回実行したければreloadを使う必要がある。
    • ターミナルでbash(OSの標準の操作をしてる状態)のまま、ディレクトリ移動して、「python ch5_2.py」と打つ。この場合は「.py」も付けるので注意。
  • 教科書ではコードを記載したスクリプトに「ch5-1.py」といったファイル名を付けているのだが、ハイフンを使うと自分の環境では動かなかったので、ch5_1.pyとかにした。

 
 

章末問題1

 プログラムに作成者や日付を記入する。
 私の場合SpyderというIDEを使っており、スクリプトのファイルを作成すると自動で冒頭にコメントとして挿入される。
 
 

章末問題2

ファイルを開いて表示しろ。

#前提として、適切なディレクトリへ移動しておくこと。

datafile = open('support_data/Python-ex/h.txt')
for line in datafile:
    line = line.rstrip()  # 改行をとる
    print(line)

章末問題3

 前の問題の出力結果をリダイレクトで別ファイルに書き出せ。
 シェルのリダイレクト機能でやれとのことなので、Macの場合、

$ cd Ch5
$ python ch5_2.py > h-copy.txt

 
 

章末問題4

 1年の秒数を計算しろ。
 これも簡単な内容だけどスクリプトを書いて保存してから実行しろという課題になっている。
 スクリプトはこんなのでいいのかな……?

sec = 365 * 24 * 3600
print('1 year = ' + str(sec) + ' seconds')

 
 

第6章:条件分岐

概要

  • if文の練習。
  • 文字列にあるキーワードが含まれるかどうかの条件を、inで表す。
  • 文字列について、startswith()やendswith()といったメソッドで条件を書く。
  • and、or、notの考え方。
  • else、elif。
  • if文を使うと、テキストデータをなめていって条件に該当する行だけ表示させることによる「検索」が可能。
  • 空行は表示しないようにするなど、だんだん初心者にとっては複雑なプログラムになってくる

 
 

章末問題1

 問題に示したプログラムの間違いを見つける。以下、訂正したプログラムも書いておきます。

# (1): 3行目にインデントが必要
temperature = -5
if temperature < 0:
     print('Brrr! It is cold!')

# (2): n=じゃなくてn==
n = 100
if n == 100:
     print('n is exactly 100!')

# (3): elseではなくelifにする
n=-5
if n > 0:
     print('n is a positive number.')
elif n < 0:
     print('n is a negative number.')

 
 

章末問題2

 ピリオドで終わる行だけ抜き出す。
 
 

#前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。
datafile = open('/support_data/Python-ex/j.txt')  # パスは適宜
for line in datafile:
    line = line.rstrip()
    if line.endswith('.') :
        print(line)

 
 

章末問題3

 theが含まれない行だけ抜き出す。

#前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。
datafile = open('/support_data/Python-ex/j.txt')
for line in datafile:
    line = line.rstrip()
    if not 'the' in line.lower():  # 一応小文字化
        print(line)

 
 

章末問題4

30文字以上ある行を表示。

#前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。
datafile = open('/support_data/Python-ex/j.txt')
for line in datafile:
    line = line.rstrip()
    if len(line) > 30:
        print(line)

 
 

第7章:ループ処理

概要

  • forループの練習。
  • ループ中断のbreakや、今回のループをスキップするcontinueをif文と組み合わせて使う。
  • カウンターになる変数を用意してループごとに1ずつ足していき、カウンターの数字によってifで分岐させるというやり方(こういうカウンターを使うやり方は望ましくないと聞いたことあるけど)。
  • テキスト中に特定の文字列が含まれていたら、全文を表示した後にそれが存在したという事実を表示したい。このため、フラグとなる変数を用意して、forでの検索中に当該文字列が存在したらフラグをTrueにし、検索終了後にフラグを出力する。
  • 逆にある文字列が無いことを確かめたいときは、テキストを順に見ていって、あったらループを中断することにしてよい。
  • (ifではなく)forループのelseというものがあり、これを使って、ループが中断しなかった時の実行内容を記述するという方法もある。しかし直観的ではないので分かりにくいと感じたらこんなやり方は使わなくて良い。

 
 

章末問題1

>>> for char in 'apple':
...     print(char)
...
a
p
p
l
e

 
 

章末問題2

 行A、行Bが何回実行されるか。

#前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。
datafile = open('Ch7/sample.txt', mode='r', encoding='utf-8')
for line in datafile:
    print('hello!')  # 行A ←3回(単なるカウンターになる)

print('good bye!')  # 行B ←1回

 
 

章末問題3

 行Aは何回実行されるか。

#前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。
counter = 0
datafile = open('support_data/Python-ex/h.txt')
for line in datafile:
    counter += 1
    if counter == 5:
        break

    line = line.rstrip()
    print(line)  # 行A  ←4回実行される(空白行の出力含む)

 
 

章末問題4

 最初の行をスキップし、2行めから画面に表示するプログラムを書け。

#前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。
counter = 0
datafile = open('support_data/Python-ex/h.txt')
for line in datafile:

    counter += 1
    if counter < 2:
        continue
    elif counter == 20:  # 全部出力されると大変なので、20行目で止める設定にしておく
        break

    line = line.rstrip()
    print(line)

 
 

第8章:単語の一覧表を作ろう(リストを使う練習)

概要

  • リストをつくってインデックスで要素にアクセスしたり、appendで要素を追加したり、sortedで並べ替えたり、reverseで逆転させたりする練習。
  • forループでリストの要素に順次処理を行う。
  • mapやリスト内包表記は紹介しないので他のテキストで勉強してねとのこと。
  • 文字列をsplit関数で区切り文字で区切ってリストに格納する。
  • 単語リストをつくる。具体的には読み込んだテキスト(英語なのでスペースで区切られてる)をsplitで単語に分割して、リストに収録していく。既にリストに入っているものは飛ばすという処理を入れておく。
  • CMU辞書という、英語の発音辞書のファイル(タブ区切り)を読み込む。forループで1行ずつ読んで、1行をタブ区切りで分割し、何番目の要素が音素であり・・・みたいな決まりから要素を取り出す。これで特定の発音で始まる単語などを抽出することができる。

 
 

章末問題1

days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
days[1]
days[-1]
days[:5]

 
 

章末問題2

a = [1, 2]
b = [3, 4]
a.append(b)
a

 
 

章末問題3

numbers = [33, 5, 12, 8, 60]
numbers.sort()
numbers.reverse()

 
 

章末問題4

#前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。
all_words = []

datafile = open('support_data/Python-ex/j.txt')
for line in datafile:
    line = line.rstrip(' .,!?')
    words_in_line = line.split()

    for word in words_in_line:
        if not word in all_words:
            all_words.append(word)

for word in all_words:
    print(word)

all_sorted = all_words.sort()

 
 

章末問題5

#前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。
# 発音記号部分に'S'が出てくるやつを拾えばいいので、

s_included = []
datafile = open('support_data/Python-ex/c.txt', 'r')
for line in datafile:
    line = line.rstrip()
    if not line.startswith(';;;'):  # c.txt中のコメント行を除く
        line = line.split('  ')
    line = [line[0] ,line[1].split(' ')]
    if line[0].isalpha():  # '('など記号の項目を除く
        if 'S' in line[1]:  # inで完全一致での検索になる
            s_included.append(line)

# 最初の100件を表示させてみる
for word in s_included[0:100]:
    print(word)

 
 

第9章:ディクショナリ

概要

  • 辞書型の基本。
  • 辞書型を使って、テキストに登場する単語の頻度表を作る。「{"単語":頻度, "単語":頻度, ...}」という風に、単語名とその登場回数がペアになった辞書を作る。
  • 具体的には、テキストを読み込んで単語単位で見ていき、辞書中に存在しない単語であれば辞書に追加して頻度を1とする。辞書中にすでに存在する単語だった場合は、頻度を+1する。
  • 表示する際は、単語のアルファベット順に並べたり、頻度順に並べたりできる。

 
 

章末問題1

 辞書をつくって表示を試してみる。

capital = {
    'Japan' : 'Tokyo',
    'France' : 'paris',
    'Germany' : 'Berlin'}

capital['Japan']
capital['Paris']  # エラー
capital[2]  # エラー

 
 

章末問題2

 問題1の辞書に項目を追加する。

capital['China'] = 'Beijing'

 
 

章末問題3

 ディクショナリをキーのアルファベット順に並べ替え、タブで区切って表示させる。

jisho = {
    'me' : 'eye',
    'hana' : 'nose',
    'mimi' : 'ear',
    'kuchi' : 'mouth'
}

for word in sorted(jisho, key=jisho.get, reverse=True):
    print(word + '\t' + str(jisho[word]))

 
 

章末問題4

 サポートデータのh.txtの中で頻度の高い語を確認する。記号は除去し、小文字に統一する。

# 前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。
freq = {}  # からっぽのディクショナリを用意

datafile = open('support_data/Python-ex/h.txt')
for line in datafile:
    line = line.rstrip()
    line = line.lower()
    words = line.split()

    # ディクショナリに頻度を反映させる
    for word in words:
        word = word.rstrip('.,"?!:;')  # 記号を除去
        word = word.lstrip('"')  # 左の引用符を除去
        if word in freq:
            freq[word] += 1
        else:
            freq[word] = 1

# トップ30を表示
for word in sorted(freq, key=freq.get, reverse=True)[0:30]:
    print(word + '\t' + str(freq[word]))

 
 

第10章:ファイル操作

概要

  • これまでの章ではファイルを開くときファイル名をプログラム中に書いていたが、同じ処理を次々と別のファイルに適用したい場合に困る。
  • また、ファイルを書き出す時、OSのリダイレクト機能を使っていたが、Pythonのプログラム側で決めたいときもある。
  • その他もろもろ、Pythonでのファイル操作について学ぶ。
  • ファイル名をコマンドライン引数でその都度決めさせる方法があり得る。
  • 複数あるファイルを一気に処理する方法(os.listdir()を使ってファイル名一覧をリストで取得し、open()のファイル名に適用していく)。
  • Pythonの場合、pathを書くときにディレクトリの区切りは、Windowsでも"/"でいける。
  • 教科書では書かれていないが、withブロックを使わずにopen()だけでファイルに書き込む場合、最後にflushメソッドを使ってやらないと、内容がディスクに書き込まれないことがあるはず

 
 

章末問題1

 ファイルの中身に'hello world!'とだけ書かれたテキストファイルを生成するプログラムを書く。

outputfile = open('test.txt', 'w')
print('hello, world!', file=outputfile)
outputfile.flush()

 
 

章末問題2

 コマンドライン引数として打ち込んだ文字列をそのまま返すスクリプトを書く。

import sys
print(sys.argv[1])


というスクリプトをecho.pyという名前で保存し、ターミナル(コマンドプロンプト)から、

$ cd 'hogehoge'  # スクリプトの置き場へ移動
$ echo 'hello'
hello

 
 

章末問題3

 .txtという拡張子を持つファイルを一覧表示する。
 教科書がどういう回答を期待しているのかは分からないが、リスト内包表記で書いておく。

import os
filenames = os.listdir()  # カレントディレクトリで実施する場合
textfiles = [filename for filename in filenames if filename.endswith('.txt')]
print(textfiles)

 
 

章末問題4

 多ファイルを検索したときに、検索結果がどのファイルからのものかをあわせて表示させる。

# 前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。

import os

folder = 'ch10'
filenames = os.listdir(folder)

for filename in filenames:
    datafile = open(folder + '/' + filename)
    alltext = datafile.read()
    if 'the' in alltext:
        print('\n' + filename)
    datafile = open(folder + '/' + filename)  # なぜかもう1回openしないとダメだった
    for line in datafile:
        line = line.rstrip()
        if 'the' in line:
            print(line)

 
 

章末問題5

 j.txtは空白行で区切られた7つの部分から成っているので、この区切りで分割してそれぞれ別名のテキストファイルとして保存するプログラムを書く。
 いいやり方は知らないけど、以下では、テキストファイルの1行が1要素となっているリストに対し、「空行」は改行コード1個の行であることを利用して検索し、ebumerate()関数(for文で使うと、インデックス番号と要素を組にして利用できる)を使って空行の行番号を取得する。
 テキストファイル全体の先頭行と最終行、そして空行の前後の行番号が分かれば、あとは何行めから何行目という指定で文章のカタマリを抜き出すことができる。

# 前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。

import os
datafile = open('/support_data/Python-ex/j.txt')

list = datafile.readlines()

# 空行は改行しか入ってない行を探し、enumerateでそのインデックスを返させる
# その前後の行をカタマリの終点・始点と判断する

blank = [i for i, v in enumerate(list) if v == '\n']
stop = [i-1 for i in blank] + [len(list)]  # 最終行まで
start = [0] + [i+1 for i in blank]  # 先頭行から

counter = 1
for i in (range(7)):
    p = list[start[i]:stop[i]]  # 上記の始点・終点リストを使う
    outputfile = open('j' + str(counter) + '.txt', 'w')
    for line in p:
        print(line, file=outputfile)
    outputfile.flush()
    counter += 1

 
 

第11章:正規表現

概要

  • reモジュールによる正規表現検索の基本。
  • "if re.search():"で、マッチしたかどうかを条件とする分岐が書ける。
  • 検索条件の前後を\bで挟むと、\bは語境界を表すので、検索条件が長い単語の一部ではなく独立した単語であること(wasの一部ではなくasであるとか)を表現できる。
  • 検索結果を変数に保存すると、マッチオブジェクトとして扱うことができ、メソッドで様々な情報を引き出すことができる。
  • re.findall()でマッチした全てを得る。
  • 置換のre.sub()の練習。

 
 

章末問題1

 発音辞書のファイルから発音が/Z/で終わる語を抜き出す。

# ↓のアプローチだと、正規表現をどこで使えばいいか思いつかなかったので、ヘッダを無視する処理でムリヤリ使った
# しかしこんな複雑なことをせず、単純に' Z$'という正規表現(スペース+Zで終わる)で各行をみればいいことに後で気づいたw
# 前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。

pronunciation = {}  # まとめ用ディクショナリ

datafile = open('support_data/Python-ex/c.txt')
for line in datafile:
    line = line.rstrip()
    columns = line.split()
    if re.search('[A-Z]', columns[0]):  # ヘッダ行を除くため
        word = columns[0]
        phonemes = columns[1:]
        if phonemes[-1] == 'Z':
            pronunciation[word] = phonemes  # 同じ語は出てこないはずだから辞書に単純追加でよい

for word in pronunciation:
    print(word)

 
 

章末問題2

ある文中に-ingで終わる語があるかどうか正規表現で調べ、あれば出力する。

sentence = 'She was watching a movie.'

result = re.findall(r'\b[A-Za-z]+ing\b', sentence)
if result:
    print(result)

 
 

章末問題3

 テキストファイルからwake upの用例を抜き出す。
 もっと効率的な書き方があると思うけど・・・。

# 前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。

>>> datafile = open('support_data/Python-ex/h.txt')
>>> text = datafile.read()
>>> result = re.findall(r'\bwake [A-Za-z]+ up\b|\bwoke [A-Za-z]+ up\b|\bwaking [A-Za-z]+ up\b|\bwake up\b|\bwoke up\b|\bwaking up\b', text)
>>> print(result)
['wake up', 'woke up', 'wake up', 'waking me up', 'wake up', 'woke up', 'wake them up', 'woke me up', 'waking you up', 'woke him up', 'wake up']
>>>

 
 

第12章:形態素解析

概要

  • 形態素解析は、解析エンジンと辞書をセットで使う。
  • 日本語形態素解析のエンジンはChaSenやMeCab、辞書はIPADicやUniDicがある。
  • 本書ではMeCabとUniDicを使うのでインストール。
  • 本書ではMeCabを生で使うのではなく、茶まめというGUIのツールを介して使うのでその使い方の説明。ただし私はMac版がないので茶まめは使ってない。同じ機能がブラウザから使えるWeb茶まめ(リンク)は試しに使ってはみて、これはこれで大変便利だと思ったが、教科書で使われているクライアント用の茶まめとはデフォルトの出力設定が少し違うので注意。
  • 形態素解析エンジンはとにかく適当に設定してテキストを与えれば結果を返してくれるので、結果の見方だけわかってればよい。
  • MeCabの導入については過去のエントリも参照してください。

 
 

章末問題1

インターネットから新聞記事を入手し、茶まめをつかって解析。⇒省略
 
 

章末問題2

「ここではきものを脱いでください」など曖昧な文章など、いろいろ茶まめに入れてみて解析。⇒省略
 
 

第13章:Pythonで日本語を扱おう

概要

  • 前章で導入した形態素解析を前提に、日本語処理について学ぶ。
  • 日本語を扱う時は、各種処理においてencoding=などの引数で文字コードをきちんと設定するよう注意。
  • Pythonでテキストファイルの文字コードを変換・上書きしてみる。
  • 日本語の文章の単語頻度表を作ってみる。(英語のは9章で学んだ。)
  • 形態素解析エンジンを介すると、文中に現れる単語の活用を判断でき、出力として「基本形(語彙素)」を得ることができるので、「飛ば」「飛び」などを同一の単語として集計することも容易である。
  • 形態素解析エンジンは、各単語の品詞が何なのかも返してくれるので、動詞だけ抜き出して集計するなども容易。
  • 夏目漱石の小説のテキストデータを使って集計してみる。
  • 本章では、前章で『坊っちゃん』のテキストを茶まめを使って解析した結果の出力ファイルを用いて集計等を行っていくのだが、私はPython内で完結させたいので、MeCabモジュールをインポートしてPython上で直接解析した。以下は本章の処理のためのコード。得られる結果は、教科書の茶まめによる解析結果とは列の構成などが少し異なるので注意。章末問題もこの処理を前提に行った。
# 前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。

import MeCab
import re

m = MeCab.Tagger('-d /usr/local/lib/mecab/dic/unidic')  # パスは各自の環境による

# ファイルを丸ごとstrとして読み込み
with open('support_data/Python-ex/b.txt', 'r', encoding = 'Shift-JIS') as inputfile:
    fulltext = inputfile.read()

# 読みがなの《》を外しておく
fulltext = re.sub('《[^》]+》', '', fulltext)

# 空白も削ったほうがいいと思われる
fulltext = re.sub(' ', '', fulltext)

# 形態素解析結果を得る
fulltext_parsed = m.parse(fulltext)

# 行に分けてみる
lines_parsed = fulltext_parsed.split('\n')

# 空行とEOSの行を消す
lines_parsed = [line for line in lines_parsed if not line == '' and not line == 'EOS']

# ファイルで出力しておく
with open('Ch13/parsed.txt', 'w', encoding = 'UTF-8') as outputfile:
    outputfile.writelines(lines_parsed)

 
 

章末問題1

b.txt(『坊っちゃん』)のテキストを読み込んで表示するだけ(文字コードの設定に注意)。

# 前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。
# 行単位で読み込んでおく
with open('support_data/Python-ex/b.txt', 'r', encoding = 'Shift-JIS') as inputfile:
    lines = inputfile.readlines()

# コンソール上に見やすく表示するならforでprintしたほうがいい
for i in range(5):  # とりあえず5行だけ
    print(lines[i])

 
 

章末問題2

『坊っちゃん』に登場する形容詞を抽出し頻度順に並べる。

freq_adj = {}
for line in lines_parsed:
   columns = line.split('\t')
   base_form = columns[3]  # 基本形の取り出し
   pos = columns[4]  # 私のやりかただと、教科書とは違って品詞は5要素目
   if pos.startswith('形容詞'):
       if base_form in freq_adj:
           freq_adj[base_form] += 1
       else:
           freq_adj[base_form] = 1

# 上位20件だけ表示
counter = 0
for word in sorted(freq_adj, key=freq_adj.get, reverse=True):
    if counter < 21:
        print(word + '\t' + str(freq_adj[word]))
        counter += 1
    else:
        break

 
 

章末問題3

『坊っちゃん』のテキストから形容詞を抽出し、形態素解析結果における読みがなの情報を利用して五十音順に並べる。

list_adj = []

# もっと上手いやり方考える必要あるが、for文中で「既に含まれる」かどうかだけを判定するためのリストをつくる。

check_adj = []

for line in lines_parsed:
   columns = line.split('\t')
   base_form = columns[3]  # 既に述べたとおり。基本形が4要素目かは、環境・設定による
   pho = columns[2]  # 出現したままの読み方ではなく、基本形の読み方
   pos = columns[4]  # 読み方と品詞が、3要素目と5要素目かどうかは、環境・設定による
   if pos.startswith('形容詞'):
       if base_form in check_adj:
           continue
       else:
           check_adj.append(base_form)
           list_adj.append([base_form, pho, pos])

list_adj_sorted = sorted(list_adj, key=lambda x: x[1], reverse=False)

for adj in list_adj_sorted:  # 縦に並べたほうが見やすいのでforで表示してる
    print(adj)

 
 

章末問題3

 『坊っちゃん』のテキスト内で、「知る」という動詞がどの活用形で多く使用されているかを調べる。

freq_shiru_conj = {}
for line in lines_parsed:
   columns = line.split('\t')
   base_form = columns[3]  # 基本形は私の設定では4要素目
   conj = columns[6]  # 活用は私の設定では7要素目
   if base_form == '知る':
       if conj in freq_shiru_conj:
           freq_shiru_conj[conj] += 1
       else:
           freq_shiru_conj[conj] = 1
>>> print(freq_shiru_conj)
{'連用形-一般': 2, '終止形-一般': 2, '未然形-一般': 32, '連体形-一般': 2, '連用形-促音便': 21}

 
 

第14章:PythonでKWICを作ろう

概要

  • KWICというのは、KeyWord In Contextの略で、単語をその前後の文脈とともに抽出して使用例を分析するためのもの。
  • 形態素解析結果を用いて処理するのだが、形態素解析結果データというのは、単語ごとに1行になっていて、書字形やら基本形やら品詞やら読み方やらの情報がタブ区切りやカンマ区切りで与えられたものになっている。そこで個々の単語解析結果をそれぞれ1つの辞書にし、解析結果データを保持させる。たとえばつづりと品詞を保持したいなら、{'spelling':'good', 'pos':'adj'}という辞書を作っていく。
  • そしてこれをテキスト全体について順に行い、リストに格納する。つまりテキスト全体が、「『形態素解析結果を格納した辞書』のリスト」になる。リストの要素が辞書になってるわけ。
  • なんでそんなことをするかというと、あとでテキストの全体を、活用を考慮せず基本形で検索できるようにするため。
  • dict()関数やzip()関数の使い方。
  • あとは、検索語に対して、文中に見つかったらその単語と、前後の指定数分の単語を並べて表示するプログラムを書けば良い。
  • なお本章では、サポートデータに入っている形態素解析結果ファイルb2.txtが使われるので、形態素解析自体の処理は省かれてる。

 
 

章末問題1

「が」という単語をKWIC検索する。

# 前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。

target = 'が'
context_width = 10
words = []
header = True

# ファイルを読み込んで単語リストを作成

datafile = open('b2.txt', encoding='utf-8')
for line in datafile:
    line = line.rstrip()
    if header:
        header = False
        keys = line.split('\t')
        continue

    values = line.split('\t')
    word = dict(zip(keys, values)) # ディクショナリの作成

    words.append(word) # 単語リストに追加

# 検索
for i in range(len(words)):
    # 検索語が見つかったら
    if words[i]['語彙素'] == target:

        # 左文脈を作成
        left_context = ''
        for j in range(i-context_width, i):
            if j < 0:
                continue
            left_context += words[j]['書字形']

        # 右文脈を作成
        right_context = ''
        for j in range(i+1, i+1+context_width):
            if j >= len(words):
                continue
            right_context += words[j]['書字形']

        # 出力
        output = '\t'.join([
            left_context,
            words[i]['書字形'],
            right_context])
        print(output)

 
 

章末問題2

 接続助詞の「が」に限定する。

target = 'が'
target_pos = '助詞-接続助詞'  # これを付け加えた
context_width = 10
words = []
header = True

# ファイルを読み込んで単語リストを作成

datafile = open('b2.txt', encoding='utf-8')
for line in datafile:
    line = line.rstrip()
    if header:
        header = False
        keys = line.split('\t')
        continue

    values = line.split('\t')
    word = dict(zip(keys, values)) # ディクショナリの作成

    words.append(word) # 単語リストに追加

# 検索
for i in range(len(words)):
    # 検索語が見つかったら
    if (words[i]['語彙素'] == target
    and words[i]['品詞'] == target_pos):  # ここを付け加えた

        # 左文脈を作成
        left_context = ''
        for j in range(i-context_width, i):
            if j < 0:
                continue
            left_context += words[j]['書字形']

        # 右文脈を作成
        right_context = ''
        for j in range(i+1, i+1+context_width):
            if j >= len(words):
                continue
            right_context += words[j]['書字形']

        # 出力
        output = '\t'.join([
            left_context,
            words[i]['書字形'],
            right_context])
        print(output)

章末問題3

 形態素解析結果の文境界の情報を利用して、検索語が登場する「文」単位で結果を出力するようにする。

target = 'が'
target_pos = '接続助詞'  # これを付け加えた
words = []
header = True

# ファイルを読み込んで単語リストを作成

datafile = open('b2.txt', encoding='utf-8')
for line in datafile:
    line = line.rstrip()
    if header:
        header = False
        keys = line.split('\t')
        continue

    values = line.split('\t')
    word = dict(zip(keys, values)) # ディクショナリの作成

    words.append(word) # 単語リストに追加

# 文頭のインデックスを作成
beginning_index = []
for k in range(len(words)):
    if words[k]['文境界'] == 'B':
        beginning_index.append(k)

# 検索
for i in range(len(words)):
    # 検索語が見つかったら
    if (words[i]['語彙素'] == target
    and target_pos in words[i]['品詞']):  # ここを付け加えた

        # 文を抜き出す処理
        beginning = max([l for l in beginning_index if l < i])  # iより前で最も後ろの文頭
        ending = min([l for l in beginning_index if l > i]) - 1  # iより後ろで最も手前の文頭の1つ前

        # 左文脈を作成
        left_context = ''
        for j in range(beginning, i):
            if j < 0:
                continue
            left_context += words[j]['書字形']

        # 右文脈を作成
        right_context = ''
        for j in range(i+1, ending+1):  # rangeの指定なのでendingに1足しておかなければならない
            if j >= len(words):
                continue
            right_context += words[j]['書字形']

        # 出力
        output = '\t'.join([
            left_context,
            words[i]['書字形'],
            right_context])
        print(output)

 
 

第15章:コロケーション検索

概要

  • 「〜にくい」がどういう動詞にくっついて使われているかなどのコロケーションを調べるプログラムを書く。
  • 検索条件の指定の仕方がけっこう複雑になる。動詞がきて、その後に語彙素読みが「ニクイ」でありかつ品詞が「接尾辞-形容詞的」である語が続く、といった複数条件指定を行うので。
  • 複数の検索条件の指定をするため、「辞書のリスト」形式で検索条件を記述するようにする。わかりやすくはないが・・・。
  • なお、教科書のスクリプトで、非常に気づきにくい間違いがある(著者の先生には連絡済み)。p.237の"ch15-3.py"のスクリプトの37行目で、「if i + min(positions) < 0 or len(words) < i + max(positions):」という部分があるのだが、「len(words)」は「len(words)-1」にしておかないと、44行目の「words[i+cond['position']]」の値が最大値を超えてしまう場合がある。教科書のこのスクリプトではたまたまエラーが出ないから気づかないのだが、このスクリプトを流用して章末問題を解こうとするとエラーが出て苦しむことになる(なった)。

 
 

章末問題1-1

 格助詞「を」の直前に現れる語を調べる。
 以下は単に文脈表示してるが。

# 前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。

target = 'が'
target_pos = '接続助詞'  # これを付け加えた
words = []
header = True

# ファイルを読み込んで単語リストを作成

datafile = open('b2.txt', encoding='utf-8')
for line in datafile:
    line = line.rstrip()
    if header:
        header = False
        keys = line.split('\t')
        continue

    values = line.split('\t')
    word = dict(zip(keys, values)) # ディクショナリの作成

    words.append(word) # 単語リストに追加

# 文頭のインデックスを作成
beginning_index = []
for k in range(len(words)):
    if words[k]['文境界'] == 'B':
        beginning_index.append(k)

# 検索
for i in range(len(words)):
    # 検索語が見つかったら
    if (words[i]['語彙素'] == target
    and target_pos in words[i]['品詞']):  # ここを付け加えた

        # 文を抜き出す処理
        beginning = max([l for l in beginning_index if l < i])  # iより前で最も後ろの文頭
        ending = min([l for l in beginning_index if l > i]) - 1  # iより後ろで最も手前の文頭の1つ前

        # 左文脈を作成
        left_context = ''
        for j in range(beginning, i):
            if j < 0:
                continue
            left_context += words[j]['書字形']

        # 右文脈を作成
        right_context = ''
        for j in range(i+1, ending+1):  # rangeの指定なのでendingに1足しておかなければならない
            if j >= len(words):
                continue
            right_context += words[j]['書字形']

        # 出力
        output = '\t'.join([
            left_context,
            words[i]['書字形'],
            right_context])
        print(output)

 
 

章末問題1-2

 「強すぎる」などの「すぎる」の直前の単語を調べる。これも以下は文脈表示をしている。

# 前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。

# positionは数字なので注意
conditions = [
    {'position':0, 'key':'語彙素読み', 'value':'スギル'}
]

context_width = 10
words = []
header = True

# ファイルを読み込んで単語リストを作成
datafile = open('source/b2.txt', encoding='utf-8')
for line in datafile:
    line = line.rstrip()
    if header:
        header = False
        keys = line.split('\t')
        continue

    values = line.split('\t')
    word = dict(zip(keys, values)) # ディクショナリの作成

    words.append(word) # 単語リストに追加

# 検索
for i in range(len(words)):

    # 検索条件がコーパスの範囲内でないときはスキップ
    positions = []
    for cond in conditions:
        positions.append(cond['position'])
    if i + min(positions) < 0 or len(words)-1 < i + max(positions):
        continue
    # 検索条件を全て満たすかチェック
    matched = True
    for cond in conditions:
        if cond['key'] in ['品詞', '活用形']:
            if not words[i+cond['position']][cond['key']].startswith(cond['value']):
                matched = False
                break
        else:
            if not words[i+cond['position']][cond['key']] == cond['value']:
                matched = False
                break

    # 検索条件にマッチしたら
    if matched:

        # 左文脈を作成
        left_context = ''
        for j in range(i-context_width, i):
            if j < 0:
                continue
            left_context += words[j]['書字形']

        # 右文脈を作成
        right_context = ''
        for j in range(i+1, i+1+context_width):
            if j >= len(words):
                continue
            right_context += words[j]['書字形']

        # 出力
        output = '\t'.join([
            words[i]['出典'],
            left_context,
            words[i]['書字形'],
            right_context,
            words[i]['品詞']
            ])
        print(output)

 
 

章末問題2

 「〜にくい」か「〜やすい」のどちらかを満たせばいいという検索にする。
 matchedをまずFalseにセットして、条件を満たしたらTrueに変えてループを終了するようにすればいい。

# 前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。

# positionは数字なので注意
conditions = [
    {'position':1, 'key':'語彙素読み', 'value':'ニクイ'},
    {'position':1, 'key':'語彙素読み', 'value':'ヤスイ'}
]

context_width = 10
words = []
header = True

# ファイルを読み込んで単語リストを作成
datafile = open('source/b2.txt', encoding='utf-8')
for line in datafile:
    line = line.rstrip()
    if header:
        header = False
        keys = line.split('\t')
        continue

    values = line.split('\t')
    word = dict(zip(keys, values)) # ディクショナリの作成
    words.append(word) # 単語リストに追加

# 検索
for i in range(len(words)):

    # 検索条件がコーパスの範囲内でないときはスキップ
    positions = []
    for cond in conditions:
        positions.append(cond['position'])
    if i + min(positions) < 0 or len(words)-1 < i + max(positions):
        continue
    # 検索条件を全て満たすかチェック
    matched = False
    for cond in conditions:
        if cond['key'] in ['品詞', '活用形']:
            if words[i+cond['position']][cond['key']].startswith(cond['value']):
                matched = True
                break
        else:
            if words[i+cond['position']][cond['key']] == cond['value']:
                matched = True
                break

    # 検索条件にマッチしたら
    if matched:

        # 左文脈を作成
        left_context = ''
        for j in range(i-context_width, i):
            if j < 0:
                continue
            left_context += words[j]['書字形']

        # 右文脈を作成
        right_context = ''
        for j in range(i+1, i+1+context_width):
            if j >= len(words):
                continue
            right_context += words[j]['書字形']

        # 出力
        output = '\t'.join([
            words[i]['出典'],
            left_context,
            words[i]['書字形'],
            right_context])
        print(output)

 
 

章末問題3

 「〜にくい」の直前に現れる動詞がどんなものが多いか調べ、頻度表を得る。

# 前提として、適切なディレクトリへ移動しておくか、パスをきちんと指定すること。

# positionは数字なので注意
conditions = [
    {'position':0, 'key':'品詞', 'value':'動詞'},
    {'position':1, 'key':'語彙素読み', 'value':'ニクイ'},
    {'position':1, 'key':'品詞', 'value':'接尾辞-形容詞的'}
]

words = []
header = True

# ファイルを読み込んで単語リストを作成
datafile = open('source/b2.txt', encoding='utf-8')
for line in datafile:
    line = line.rstrip()
    if header:
        header = False
        keys = line.split('\t')
        continue

    values = line.split('\t')
    word = dict(zip(keys, values)) # ディクショナリの作成

    words.append(word) # 単語リストに追加

# 検索
results = []
for i in range(len(words)):

    # 検索条件がコーパスの範囲内でないときはスキップ
    positions = []
    for cond in conditions:
        positions.append(cond['position'])
    if i + min(positions) < 0 or len(words)-1 < i + max(positions):
        continue
    # 検索条件を全て満たすかチェック
    matched = True
    for cond in conditions:
        if cond['key'] in ['品詞', '活用形']:
            if not words[i+cond['position']][cond['key']].startswith(cond['value']):
                matched = False
                break
        else:
            if not words[i+cond['position']][cond['key']] == cond['value']:
                matched = False
                break

    # 検索条件にマッチしたら
    if matched:

        # 出力
        results.append(words[i]['語彙素'])

freq_nikui = {}
for result in results:
    if result in freq_nikui:
            freq_nikui[result] += 1
    else:
            freq_nikui[result] = 1

print(freq_nikui)