2017年10月22日日曜日

【Python】芥川龍之介の『鼻』からワードクラウドを作ってみる

nose

 『Pythonによるクローラー&スクレイピング入門 設計・開発から収集データの解析・運用まで』P283~を参考にして芥川龍之介の『鼻』のテキストから上のようなワードクラウドを作成してみました。
 書籍掲載のものは形態素解析にJanomeを使用していますが、今回はMeCabで行っています。辞書はipadic-neologdを使用。
 また、名詞、副詞、動詞、形容詞のみ抽出し、動詞・形容詞は基礎型に変換しています。

from datetime import datetime
import requests
import MeCab
from bs4 import BeautifulSoup
from wordcloud import WordCloud
import matplotlib.pyplot as plt


def get_wordcloud(text, file_path):
    '''
    :param text, file_path: str
    :return:
    '''

    # ストップワードの設定。出力結果を見ながら手動で設定した。
    stop_words = ["ある", "ない", "いる", "する", "の", "よう", "なる", "それ", "そこ", "これ", "こう", "ため", "そう", "れる", "られる"]

    # 単語に分割し名詞、副詞、動詞、形容詞のみ抽出(動詞・形容詞は基礎型を取得)
    tagger = MeCab.Tagger('-Ochasen')
    pos1 = ("名詞", "副詞")  # 表層形を取得する品詞
    pos2 = ("動詞", "形容詞")  # 基礎形を取得する品詞
    node = tagger.parseToNode(text)
    words = []
    while node:
        features = node.feature.split(",")
        if (features[0] in pos1) and not (node.surface in stop_words):
            words.append(node.surface)
        elif features[0] in pos2 and not (features[6] in stop_words):
            words.append(features[6])
        node = node.next

    # wordcloudオブジェクトの作成
    font_path = r"C:\WINDOWS\Fonts\SourceHanSerif-SemiBold.otf"
    wordcloud = WordCloud(background_color="white", font_path=font_path, width=1600, height=1066, regexp=r"\w+").generate(" ".join(words))

    # wordcloudファイル出力
    wordcloud.to_file(file_path)

    # wordcloud表示
    plt.imshow(wordcloud, interpolation="bilinear")
    plt.axis("off")
    plt.show()


if __name__ == '__main__':
    # 青空文庫からテキストの取得
    url = "http://www.aozora.gr.jp/cards/000879/files/42_15228.html"  # 芥川龍之介 『鼻』
    r = requests.get(url, timeout=10)
    soup = BeautifulSoup(r.content, "lxml")

    # 本文の抽出
    text_elm = soup.find("div", attrs={"class": "main_text"})

    # ルビの削除
    [e.extract() for e in text_elm.select("rt")]
    text = text_elm.text

    # wordcloudの作成
    file_path = r"D:\nose.png"  # 出力する画像ファイルのパス
    get_wordcloud(text, file_path)

2017年10月21日土曜日

1画面Markdownエディタ「Typora」がなかなか良いです

撮る人
α6500 & SIGMA 35mm F1.4 DG HSM Art

 このところ「Typora」というMarkdownエディタを試用中なのですが、なかなか良い具合なので御紹介。

 Typoraはなかなか珍しい1画面タイプのMarkdownエディタです。Markdown記法のエディタとプレビューを1画面内で切り替えて行います。
 切り替えて、といっても、プレビュー画面内で随時Markdown記法で入力していけるので、ワードプロセッサ感覚で使える感じですね。生のMarkdownを表示させる必要はあまりありません。
 Windows、Mac、Linux の3つのOSで使えます。私が使っているのはWindowsバージョンですが、64bit版と32bit版の両方がありました。

 Markdownといっても様々な記法がありますが、Typoraが採用しているのはGFM(Github Flavored Markdown)です。
 加えて独自機能として目次(TOC)、数式表現、ダイアグラムなどもサポートされています。

 起動した直後の画面は極めてシンプル。
 表示されるのはメニューバー、画面、ステータスバーの3つのみです。
 でも、機能もシンプルというわけではなく、使い勝手もよく考えられています。

 特に気に入ったのは次のとおり。

  • 画面がシンプルで美しい。
  • テーマ機能があり、配色などの切り替えが可能。カスタムもできる。
  • サイドバーを開いてファイルリストを表示できる。おまけにアウトラインも表示できる。
  • キーボードショートカットが充実。
  • テーブルの作成が簡単。

 HTMLとPDFへの変換が本体だけでできるほか、別途、「Pandoc」というツールをインストールすると、docx形式(Microsoft Word)やEpub形式、リッチテキスト形式、OpenOffice形式など入出力できるファイルタイプが大幅に増えます。
 メニューバーの「Help」から「Install and Use Pandoc」を選ぶと出てくるマニュアルのとおりにPandocをダウンロードしてインストールだけなので大した手間ではないです。 

 Typoraのダウンロードは「Typora — a minimal markdown editor, markdown reader.」からできます。
 現在はまだβバージョンのようですが、私が使っていた限りでは特にバグなどは出てきませんでした。

2017年10月13日金曜日

【Python】Windowsでなるべく楽にMeCabをインストールする

花束
α6500 & E 50mm F1.8 OSS SEL50F18

 Pythonで形態素解析をしようと考えたとき、まず最初に選択肢にあがるツールはMecabだと思います。
 しかしながら、WindowsでMecabを使うのは、特に64bit版を使うのは意外と準備が面倒。

 というわけで、当初はpipで簡単にインストールできるJanomeを使っていたのですが、小さなデータならともかく大きなデータを扱うには残念ながら処理速度が足りませんでした。
 試しに10MBぐらいのテキストファイルで含まれる名詞の出現数を数える処理をしてみたら(joblibでcorei7論理8コアでの並列処理)、janome(mecab-ipadic-neologd内包版)で3分15秒程度かかりました。GB単位のデータを処理するにはちょっと大変そうです。

 そこで、仕方なく、ネットで検索しながら64bit版Mecab(加えて「mecab-ipadic-NEologd」も)を頑張ってインストールしてみましたので、備忘録として書き残します。先人に感謝。

64bit版Mecabをインストールする

 Mecabの公式サイト(MeCab: Yet Another Part-of-Speech and Morphological Analyzer)では、Windows用の64bit版MeCabは配布されていません。
 知識がある方ならソースコードを元に64bit版を自分で作成できるようですが(知識がなくても先人のものを参考にしながらなら不可能ではなさそうですが)、なるべく楽をしたいので、先人が作成してくださったReleases · ikegami-yukino/mecab · GitHubからMeCabの64bit版をダウンロードしてインストールします。

環境変数を設定する

 環境変数PATHに「\bin」を追加し、環境変数MECABRCに「\etc\mecabrc」を設定します。MECABRCがないとPythonからMeCabを使用する際にエラーが出ます。

Pythonにmecab-pythonを入れる

 一般的に使われる「mecab-python」は、Windowsではそのままpipでインストールとはいきません。ソースをダウンロードしてsetup.pyを書き換えてコンパイラをインストールしてetcの作業が必要になります。
 そこで、先人が作成してくださったWindows用のパッケージであるmecab-python-windows 0.9.9.6 : Python Package Indexを使用させていただいたのですが、前処理をしないとこれもそのままではpipでインストールできませんでした。

  1. Visual C 2015 Build Tools([Download the Visual C Build Tools (standalone C++ compiler, libraries and tools)](http://landinghub.visualstudio.com/visual-cpp-build-tools))のインストール
  2. \sdk」の「C:\Program Files (x86)\MeCab\sdk」へのコピー

の2つの作業が必要になります。この作業をしておくと、コマンドプロンプトから「pip install mecab-python-windows」でインストールできます。

mecab-ipadic-NEologdをインストール

 mecab-ipadic-NEologdは、多数のWeb上の言語資源から得た新語を追加したMeCab用の辞書です。本やマンガのタイトルなど固有名詞を分割させないようにするには必須。
 これをインストールするには「Bash on Ubuntu on Windows」を使います。
 まず、「Bash on Ubuntu on Windowsをインストールしてみよう! - Qiita」などを参考にして、「Bash on Ubuntu on Windows」を導入します。
 次に「mecab-ipadic-neologd/README.ja.md at master · neologd/mecab-ipadic-neologd · GitHub」の「Windows 10 バージョン 1607 以降の場合」の項のとおりに「mecab-ipadic-NEologd」をインストールします。
 インストールした際に、git cloneしたディレクトリ配下に「build\mecab-ipadic-2.7.0-20070801-neologd-日付」というディレクトリができます。この中にmecab-ipadic-NEologdの辞書が入っています。
 最後に「\ipadic」の中身を上記ディレクトリの中身と入れ替えれば完成。
 なお、入れ替えなくても-dオプションで辞書を指定すれば使えますが(例:tagger = MeCab.Tagger(r"-Owakati -d C:\src\mecab-ipadic-neologd\build\mecab-ipadic-2.7.0-20070801-neologd-20171009"))、そのたびに指定するのも面倒なので、私は標準辞書のディレクトリ名をipadic_oldと変更して残した上で入れ替えちゃってます。

おまけ:JanomeとMeCabの速度差

 冒頭で述べた約10MBのテキストファイルに含まれる名詞の出現数を数える処理(joblibでcorei7論理8コアでの並列処理)ですが、Janomeで3分15秒ぐらいかかっていたものが、MeCabで同等の作業をしたら2~3秒で終了!
 スピード差は10倍どころか少なくとも50倍以上はありそうです。

2017年10月1日日曜日

好みの傾向

グラス
α6500 & Sonnar T* E 24mm F1.8 ZA SEL24F18Z

 ここに掲載した写真は何か気に入ったところがあって掲載しているわけで、過去の写真を眺めれば自分がどういうものに心惹かれるのかわかるかなと眺め返して見たのですが、いったいこれのどこが気に入ったのだろうと首をかしげるものもそれなりにありまして、自分を理解するのもなかなか大変だな、と改めて思った次第であります。

2017年9月30日土曜日

9月が終わる

夕方の過ごし方
α6500 & SIGMA 35mm F1.4 DG HSM Art

 9月の終わりは夏の終わり。
 日が短くなったなぁ。

2017年9月27日水曜日

Visual Studio Codeも併用中

撮る人
α6500 & Sonnar T* E 24mm F1.8 ZA SEL24F18Z

 最近、ノートパソコン用の Markdown エディタとして Visual Studio Code がお気に入りです。
 拡張機能などは Atom の方が揃っているように思いますが、何しろ Atom は重いのが弱点。メモリが 4GB しかない私のノートパソコンには、Visual Studio Code の軽さがピッタリです。どうせノートパソコンで Markdown を書くときは複雑なことはしないですし。
 ユーザー設定で改行を Markdown のプレビューの改行に反映されるよう設定さえしておけば、ほぼ問題なし。
 「ほぼ」、というのはひとつだけ弱点がありまして、文書冒頭の全角スペースが Markdown のプレビューに反映されないこと。2行目以降は反映されるのに、どうしてファイルの先頭だけは無視されちゃうのでしょうかね?

2017年9月17日日曜日

【Python】「言語処理100本ノック 2015」を始めてみました

夏の一コマ
α6500 & SIGMA 35mm F1.4 DG HSM Art

 言語処理100本ノック 2015を始めてみました。
 まずは「第1章: 準備運動」からですが、準備運動でもプログラミング素人にはなかなか大変です。
 環境は、Windows10 + Anaconda3.4.4 です。

00. 文字列の逆順

文字列”stressed”の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ.

s = "stressed"
ans = s[::-1]
print(ans)

# > desserts

文字列のスライスのステップを使ってみました。
ステップに-数値を指定すると逆順にステップします。
ちなみに、スライスで指定する数字は、要素(文字列なら文字)の位置ではなく、要素と要素の間(ナイフを入れて切るところ)と考えると理解しやすかったです。

01. 「パタトクカシーー」

「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ.

s = "パタトクカシーー"
ans = s[::2]
print(ans)

# > パトカー

むしろこちらの方が1問目に相応しいような気がする問題。
文字列のスライスのステップを知っていれば簡単ですね。

02. 「パトカー」+「タクシー」=「パタトクカシーー」

「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ.

s1 = "パトカー"
s2 = "タクシー"
ans = "".join([c1 + c2 for c1, c2 in zip(s1, s2)])
print(ans)

# > パタトクカシーー

リスト内包表記を使用しています。for文より処理速度が速いとのことなので、使えるときは内包表記を使用するように心がけています。
“区切り文字列”.join(文字列のリスト)で区切り文字列を挟んで文字列のリストを結合した文字列を得られます(上の例は空文字列で結合)。
zip()は複数のシーケンスオブジェクトを同時にループしたいときに便利。要素数が違う場合は一番少ないものに合わせられます。

03. 円周率

“Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.”という文を単語に分解し,各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ.

s = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
ans = [len(word.strip(",.")) for word in s.split()]
print(ans)

# > [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]

文字列をjoin()で区切ってリストにしてから”,”と”.”をstrip()で除去。リスト内包表記でリスト化しています。
“文字列A”.split(“区切り文字”)で文字列Aを区切り文字列で区切った文字列のリストを得ることができます(区切り文字列は除去される)。区切り文字を指定しない場合は空白文字(半角スペース、全角スペース、改行、改ページ、タブ、垂直タブ、復帰)が区切り文字として扱われます。
strip()は文字列の両端から指定した文字列を削除するメソッドです。
len()はシーケンスの要素数を返す関数ですね。

04. 元素記号

“Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can.”という文を単語に分解し,1, 5, 6, 7, 8, 9, 15, 16, 19番目の単語は先頭の1文字,それ以外の単語は先頭に2文字を取り出し,取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ.

s = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
ans = {word[:1] if i in [1, 5, 6, 7, 8, 9, 15, 16, 19] else word[:2]: i for i, word in enumerate(s.split(), start=1)}
print(ans)

# > {'H': 1, 'He': 2, 'Li': 3, 'Be': 4, 'B': 5, 'C': 6, 'N': 7, 'O': 8, 'F': 9, 'Ne': 10, 'Na': 11, 'Mi': 12, 'Al': 13, 'Si': 14, 'P': 15, 'S': 16, 'Cl': 17, 'Ar': 18, 'K': 19, 'Ca': 20}

split()メソッドでリスト化してから条件式(三項演算子)と内包表記で辞書型にまとめています。
enumerate関数はfor文でループ処理をするときカウンターと要素の両方を取得できる関数です。startでカウンターの初期値を指定できます(何も指定しないと0からスタート)。

05. n-gram

与えられたシーケンス(文字列やリストなど)からn-gramを作る関数を作成せよ.この関数を用い,”I am an NLPer”という文から単語bi-gram,文字bi-gramを得よ.

# 単語n-gram
def w_n_gram(n, text):
    words = [word.strip(",.") for word in text.split()]
    return [words[i:i + n] for i in range(len(words) - n + 1)]

# 文字n-gram
def c_n_gram(n, text):
    return [text[i:i + n] for i in range(len(text) - n + 1)]

s = "I am an NLPer"
ans1 = c_n_gram(2, s)
ans2 = w_n_gram(2, s)
print(ans1)
print(ans2)

# > ['I ', ' a', 'am', 'm ', ' a', 'an', 'n ', ' N', 'NL', 'LP', 'Pe', 'er']
# > [['I', 'am'], ['am', 'an'], ['an', 'NLPer']]

n-gramの意味が分からなくて検索してしまいました。
問題で指定された文字列だけでいうなら、単語n-gramのstrip(“,.”)は要らないですね。逆により汎用化するなら”;”や”:”など必要な記号を列記する必要があります。

06. 集合

“paraparaparadise”と”paragraph”に含まれる文字bi-gramの集合を,それぞれ, XとYとして求め,XとYの和集合,積集合,差集合を求めよ.さらに,’se’というbi-gramがXおよびYに含まれるかどうかを調べよ.

s1 = "paraparaparadise"
s2 = "paragraph"

# 文字bi-gram
def get_bi_gram(text):
    return {text[i:i + 2] for i in range(len(text) - 1)}

x = get_bi_gram(s1)
y = get_bi_gram(s2)

print("X: {0}".format(x))
print("Y: {0}".format(y))
print("和集合: {0}".format(x | y))
print("積集合: {0}".format(x & y))
print("差集合: {0}".format(x - y))
print("Xに'se'は含まれるか? {0}".format('se' in x))
print("Yに'se'は含まれるか? {0}".format('se' in y))

# > X: {'ad', 'ra', 'is', 'ar', 'se', 'ap', 'pa', 'di'}
# > Y: {'ra', 'ag', 'ar', 'ap', 'ph', 'pa', 'gr'}
# > 和集合: {'is', 'se', 'gr', 'ad', 'ra', 'ag', 'ar', 'ap', 'ph', 'pa', 'di'}
# > 積集合: {'pa', 'ap', 'ar', 'ra'}
# > 差集合: {'ad', 'is', 'di', 'se'}
# > Xに'se'は含まれるか? True
# > Yに'se'は含まれるか? False

題名のとおり集合型を使う問題ですね。
今回はbi-gram限定ですので、集合型のbi-gramを返す関数を作って対応しています。内包表記を使えば一行で済むものなので、関数にする必要性も薄いような気もしますが。
あとは集合型の演算方法さえ知っていればOK。

07. テンプレートによる文生成

引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ.さらに,x=12, y=”気温”, z=22.4として,実行結果を確認せよ.

def xyz(x, y, z):
    return "{0}時の{1}は{2}".format(x, y, z)

x = 12
y = "気温"
z = 22.4
print(xyz(x, y, z))

# > 12時の気温は22.4

なんだか拍子抜けする問題。
format()メソッドの使用方法さえ知っていれば簡単。

08. 暗号文

与えられた文字列の各文字を,以下の仕様で変換する関数cipherを実装せよ.

  • 英小文字ならば(219 - 文字コード)の文字に置換
  • その他の文字はそのまま出力

この関数を用い,英語のメッセージを暗号化・復号化せよ.

def cipher(text):
    return "".join([chr(219-ord(c)) if c.islower() else c for c in text])

s = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind."
s_ciphered = cipher(s)
print("原文: {0}".format(s))
print("暗号化後: {0}".format(s_ciphered))
print("復号化後: {0}".format(cipher(s_ciphered)))

# > 原文: I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind.
# > 暗号化後: I xlfowm'g yvorvev gszg I xlfow zxgfzoob fmwvihgzmw dszg I dzh ivzwrmt : gsv ksvmlnvmzo kldvi lu gsv sfnzm nrmw.
# > 復号化後: I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind.

リスト内包表記と条件式(三項演算子)で文字列を1文字ずつ指定の処理をしてリスト化したものをjoin()メソッドで結合しています。
islower()は文字列がアルファベットの小文字かどうかを判定するメソッド。
chrは文字コードから文字を返す関数、ord()は文字から文字コードを返す関数です。

09. Typoglycemia

スペースで区切られた単語列に対して,各単語の先頭と末尾の文字は残し,それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ.ただし,長さが4以下の単語は並び替えないこととする.適当な英語の文(例えば”I couldn’t believe that I could actually understand what I was reading : the phenomenal power of the human mind .”)を与え,その実行結果を確認せよ.

from random import shuffle

def randomize_text(text):
    words = [randomize_word(word) for word in text.split()]
    return " ".join(words)

def randomize_word(word):
    if len(word) <= 4:
        return word
    else:
        mid_char = [c for c in word[1:-1]]
        shuffle(mid_char)
        return word[:1] + "".join(mid_char) + word[-1:]

s = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
print(randomize_text(s))

# > I cd'nolut bvlieee that I cluod atlaulcy unendatsrd what I was radneig : the pomnaheenl pweor of the hmuan mind .

問題文から”.”や”:”もスペースで区切られていることを前提に作成。
まずsplit()で文を単語のリスト化。
次に4文字以上の単語の先頭と末尾を除いたものを文字のリストとしてから、randomモジュールのshuffleを使ってランダムに並び替え。
先頭と末尾を戻して再結合して単語を作り、最後にスペースを区切りにして単語のリストを結合して文に再構築しています。

2017年9月16日土曜日

夏のバラ

夏のバラ
α6500 & Sonnar T* E 24mm F1.8 ZA SEL24F18Z

 本日も旅行先での写真。
 夏なのにそれなりの形で咲いているのは、やはり涼しいところだからなのでしょうね。

夏のバラ
α6500 & Sonnar T* E 24mm F1.8 ZA SEL24F18Z

夏のバラ
α6500 & Sonnar T* E 24mm F1.8 ZA SEL24F18Z

2017年9月10日日曜日

名残のかき氷

かき氷
α6500 & Sonnar T* E 24mm F1.8 ZA SEL24F18Z

 天候不順の影響もあって、かき氷を食べたのは旅行先での1回だけだった今年の夏。
 そんな年もあります。

2017年9月5日火曜日

『Pythonクローリング&スクレイピング』を購入しました

夏の湖
Ricoh GR

 より実践的なPythonのスクレイピングのテクニックを知りたくて、『Pythonクローリング&スクレイピング -データ収集・解析のための実践開発ガイド-』という本を購入しました。
 基本的なスクレイピング処理から自動での処理まで、相手先への負荷への配慮とか、エラーが出た場合の処理方法なども含めて載っています。以前に購入した『Pythonによるスクレイピング&機械学習 開発テクニック BeautifulSoup,scikit-learn,TensorFlowを使ってみよう』のスクレイピング部分をより実践的にした内容。
 とりあえずこれがあれば、私がしたいと思っていたことはできそうです。
 体系的な本があるとWebで個別に情報を探すより効率的で良いですね。