LiveReload(Python)のshellが便利すぎる

LiveReload-0.15から使えるようになったshellが便利。

例えばHTML書くのにSass(Compass)+Jade+CoffeeScriptという組み合わせを使っているとそれぞれwatchしてコンパイルすることになるので端末を3つ無駄にするが、Livereloadでコマンドを定義するのもちょっと面倒。

そんな時にはshellを使うといい

#!/usr/bin/env python

from livereload.task import Task
from livereload.compiler import shell

Task.add('sass/*.scss', shell('compass compile'))
Task.add('index.jade', shell('jade index.jade'))

と書くだけでOK

西野かな?

あうのかあわないのかはっきりしろや!的なCLIを書いてみた。

自分のブログに似ているかどうかをベイズ分類しています。似てないのサンプルを集めるのが面倒だったけど、アメブロあたりから適当にチョイスしました。もちろん西野カナはあわないほうに分類しておいた。

一応、5/25の入門機械学習読書会の宣伝も兼ねているので、機械学習に興味があったり、つけナポリタンを食べたかったり、会いたかったり会えなかったりするヒトは参加するといいです。今日はPythonで書いていますが、次回の読書会では由緒正しきRでベイズ分類器を実装していきます。

ProductName 入門 機械学習
Drew Conway
オライリージャパン / 3360円 ( 2012-12-22 )


例えば伊東のGentoo過激派と私の相性は

$ ./nk "http://blog.karky7.net/feeds/posts/default?alt=rss"
きみにあうよ

あうかな: ffmepgコマンドでmp4の動画からm4a(音声)を抜き出してみた
あうかな: HACKING: 美しき策謀(第2版) がたまらなく面白い
あうかな: gentooのpython-pptxで美人の水着画像をpptx化する
あうかな: 静岡Python会、Shizuoka.py行ってきました
あうかな: gentooのJuicyPixelsのebuildを作りました
あうかな: gentooでudevのUpdateにはご注意下さい...
あうかな: gentooで emacs + cscope を使ってタグジャンプでコードを飛びまくる
あうかな: 2日酔いからPersistentでキーを使って直接データを引く
あうかな: WebデザイナーこそGentooを使うべき4つの理由
あわない: 2013年 プログラマーの皆さん河津桜の季節です
あうかな: Linuxでカーネルオプションを探す方法
あわない: 2013年 伊東オレンジビーチマラソン走りました
あうかな: セガール君、お土産 in America
あうかな: Sabayon Linuxにちょっと惹かれてしまった
あわない: 新年の山走り行ってきました
あうかな: HaskellのPersistent MySQLを試してみた
あわない: 2012年大晦日オフロードツーリングへ行ってきたよ
あうかな: LXCのネットワーク設定...続き
あうかな: GentooでPersistent-MySQLのebuildを作ってみた
あうかな: GentooのLXC(Linux Container)をやってみた
あうかな: Gentooでemacs+haskell環境を作る
あうかな: HaskellのFunctorのおさらい
あうかな: TemplateHaskellを調べてみた
あうかな: btrfsを実際に触ってみた
あうかな: Gentoo + nginx + FastCGI PHP で高速PHP環境を構築する

余裕であえますね。これっぽっちも切なくなんかない。

eしずおかからお酒のブログ

$ ./nk http://osake.eshizuoka.jp/index.xml
きみにあうよ

あうかな: 富士山、世界遺産登録勧告ということで
あわない: チケット3日で完売の、焼津の酒イベント
あわない: さぁ今日は無礼講だ!季節限定ベアード
あわない: ZUNビールの味に近いベアード販売中
あうかな: 再々入荷しました!幻の米の臥龍梅
あわない: 豊田一丁目、倉庫火災出動
あうかな: お寺の庭のゆず♪ 季節限定ベアード
あうかな: 【入荷】志太泉の普通酒が普通じゃない
あうかな: 5.3 由比桜えびまつりヽ(゚∀゚)ノ
あわない: 裏鈴木酒店 『とある酒屋の超萌酒会2』
あうかな: 【入荷】英君の特別純米の袋吊り雫だ
あうかな: 【入荷】杉錦の誉富士山廃純米生原酒
あうかな: 再入荷しました♪ 臥龍梅の渡船!
あわない: 駿河区にまた大型ショッピングセンター
あわない: 眼鏡っ娘のめがね拭きができました!
あわない: 焼津のあの娘()の、大きめ画像
あうかな: お手頃♪ 臥龍梅、純米吟醸ワンカップ
あうかな: 【再入荷】最後の、そに子の痛茶です!
あうかな: 【入荷】臥龍梅。幻すぎる、短稈渡船だ
あわない: 静鉄の長沼駅でさりげなく萌えてみる

萌えよりも日本酒ってことでしょうか?あと、ビールも飲めってことかな。

というわけで、適当に作ったわりには良い感じで分類できている気がしますね。

使い方

$ ./nk --help
Nishino Kana

Usage:
    nk ([-l <conf>|--learn=<conf>] | <url>)

Options:
    -h --help                       show this screen
    -l <conf> --learn=<conf>        training

設定ファイルはjsonです。kanaがあうほうでanakがあわないほうです。

{
"kana": ["http://127.0.0.1:5000/rss/"],
"anak": [
    "http://feedblog.ameba.jp/rss/ameblo/nishino-kana/rss20.xml",
    "http://feedblog.ameba.jp/rss/ameblo/yamamo-tomato/rss20.xml",
    "http://feedblog.ameba.jp/rss/ameblo/financilthory011/rss20.xml",
    "http://feedblog.ameba.jp/rss/ameblo/hazu-r72t/rss20.xml",
    "http://feedblog.ameba.jp/rss/ameblo/hitomi19800911/rss20.xml",
    "http://feedblog.ameba.jp/rss/ameblo/1983mayumayu/rss20.xml",
    "http://feedblog.ameba.jp/rss/ameblo/2pmoneday2/rss20.xml",
    "http://feedblog.ameba.jp/rss/ameblo/to-meee/rss20.xml",
    "http://feedblog.ameba.jp/rss/ameblo/sa-ku-ra-0706/rss20.xml",
    "http://feedblog.ameba.jp/rss/ameblo/taiyakisuki8/rss20.xml",
    "http://feedblog.ameba.jp/rss/ameblo/pikatyu-tyu/rss20.xml",
    "http://feedblog.ameba.jp/rss/ameblo/urakamimieko/rss20.xml",
    "http://feedblog.ameba.jp/rss/ameblo/wins-motomiya/rss20.xml",
    "http://feedblog.ameba.jp/rss/ameblo/taisukekmft2/rss20.xml"
        ]
}

コード

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import feedparser
import MeCab
import re
import json
import nltk
from docopt import docopt
import pickle

html_tag = re.compile(r'<[^>]+>')

cmd = """Nishino Kana

Usage:
    nk ([-l <conf>|--learn=<conf>] | <url>)

Options:
    -h --help                       show this screen
    -l <conf> --learn=<conf>        training
"""

def morph(entry):
    txt = html_tag.sub('', entry.summary_detail.value)
    words = []
    try:
        t = MeCab.Tagger()
        m = t.parseToNode(txt.encode('utf-8'))
        while m:
            if m.stat < 2:
                if re.match('名詞', m.feature): words.append(m.surface)
            m = m.next
    except RuntimeError:
        pass
    return words

def feed_parse(url):
    d = feedparser.parse(url)
    words = []
    for entry in d.entries:
        words.extend(morph(entry))
    return words

def extract_features(document):
    documentwords = set(document)
    return dict([('contains(%s)' % word, True) for word in documentwords])

def learn(conf):
    c = json.load(open(conf), encoding="utf-8")
    kana = []
    anak = []

    for url in c["kana"]:
        kana.extend(feed_parse(url))

    for url in c["anak"]:
        anak.extend(feed_parse(url))

    training_documents = [(kana, "kana"), (anak, "anak")]
    train_set = nltk.classify.apply_features(extract_features, training_documents)
    classifier = nltk.NaiveBayesClassifier.train(train_set)
    with open("classifier.pickle", "wb") as f:
        pickle.dump(classifier, f)

def test(url):
    classifier = pickle.load(open("classifier.pickle"))
    d = feedparser.parse(url)
    rcode = {"kana": "あうかな", "anak": "あわない"}
    total = 0
    result = {"status": None, "entry": []}
    for entry in d.entries:
        doc = morph(entry)
        r = classifier.classify(extract_features(doc))
        if r == "kana":
            total += 1
        result['entry'].append((rcode[r], entry.title.encode("utf-8")))
    if total > 3:
        result['status'] = "きみにあうよ"
    else:
        result['status'] = "きみにあわないよ"
    return result

if __name__ == '__main__':
    args = docopt(cmd)
    conf = args.get('--learn')
    url = args.get('<url>')
    if conf:
        learn(conf)
    elif url:
        r = test(url)
        print r['status']
        print 
        for code, title in r['entry']:
            print "{}: {}".format(code, title)

尚、TDMを構築するのが面倒だったのでNLTKを使いました。

ProductName 入門 自然言語処理
Steven Bird
オライリージャパン / 3990円 ( 2010-11-11 )


入門機械学習の1章のUFOデータをpandasで集計した

区切り文字の数が変だったり、日付がおかしい(19950000みたいなの)場合はpandasの場合は容赦なくコケる(しかもエラー内容がわからなかったりする)。

Rの場合しれっとスキップするのでさくさく感はあるのだけど、どのデータ落としたのか注意していないとわからないので、自分の場合はまずは綺麗なデータにするということをきちっとやるほうが、自分でデータをネグったという認識ができて後々悩まなくてよいかなと思っている。

実行結果。pandasのmulti indexは便利ですね。

In [2]: execfile("ml1.py")

In [3]: d.head()
Out[3]: 
                     DateOccurred  DateReported  Location  YEAR  MONTH
Location YEAR MONTH                                                   
AB       2004 3                 1             1         1     1      1
         2005 4                 1             1         1     1      1
         2010 7                 1             1         1     1      1
AK       1990 1                 1             1         1     1      1
              3                 1             1         1     1      1

この先がよくわからないんだけどdate_rangeかなんかで月ごとのSeriesを作ってからjoinすればいいのかなぁ。

コード。最初に汚い行(tabの数がおかしい、日付の表現が変)というデータを捨ててpandasに読み込めるようにしてからpandasで処理しています。

import pandas as pd
from datetime import datetime
from numpy import nan
import re

re_date = re.compile("\d{4}[01]\d[0123]\d")

with open("ufo.tsv", "w") as w:
    with open("ufo_awesome.tsv") as f:
        for l in f:
            s = l.split("\t")
            if len(s) == 6:
                if re_date.match(s[0]) and re_date.match(s[1]):
                    if int(s[0][4:6]) > 0 and int(s[0][6:8]) > 0:
                        w.write(l)

def get_loc_code(loc):
    l = loc.split()
    if len(l) == 2 and len(l[1]) == 2:
        return l[1].upper()
    else:
        return nan

d = pd.read_table("ufo.tsv",
                  header=None,
                  names=["DateOccurred", "DateReported",
                           "Location", "ShortDescription",
                           "Duration", "LongDescription"
                         ],
                  parse_dates=["DateOccurred", "DateReported"],
                  date_parser=lambda x: datetime.strptime(x, '%Y%m%d')
                  )
d = d.drop(["LongDescription", "ShortDescription", "Duration"], axis=1)
d.Location = d.Location.apply(get_loc_code)
d = d.dropna()
d = d[d.DateOccurred > datetime(1990, 01, 01)]
d["YEAR"] = d.DateOccurred.apply(lambda x: x.year)
d["MONTH"] = d.DateOccurred.apply(lambda x: x.month)
d = d.groupby(["Location", "YEAR", "MONTH"]).count()

ProductName 入門 機械学習
Drew Conway
オライリージャパン / 3360円 ( 2012-12-22 )


入門機械学習の1章のUFOデータをpandasで読み込めない

1章のデータをpandasのread_tableで読み込もうとすると

CParserError: Error tokenizing data. C error: Expected 6 fields in line 755, saw 7

となって、エラー終了する。 これはlong descriptionの中に区切り文字であるtabが含まれているせいで、カラムの長さがちゃうよっていうエラーなんだけど、こういうダメな行をスキップするオプションが見つからなかった。

ProductName Python for Data Analysis
Wes Mckinney
Oreilly & Associates Inc / 3634円 ( 2012-10-29 )


pythonでデータのクリーニングをやる場合にはpandas使うよりもforループ回して、要素を一つ一つチェックしていくほうがやりやすいかも。対話的じゃないので、途中のデータを取っておきにくいのでデータがでかい場合試行錯誤しにくいけど。

ProductName 入門 機械学習
Drew Conway
オライリージャパン / 3360円 ( 2012-12-22 )


Shizuoka.pyでSphinxの紹介をした

先週のShizuoka.pyでのSphinxの紹介スライド。

Sphinxの使い方とかは日本語のドキュメントが充実しているので、編集時や公開時に便利なちょっとした小技を多めに。

本当は Sphinx -> mobi -> KDPに出版 というところまでやってから発表したかったんだけど、出版するネタがなかったのでtips紹介になってしまった。

でも、ドキュメントをHTMLとepubの二種類用意できるっていうのは非常にメリットがあるのでSphinxはオススメですね。(7インチタブレットで読むにはpdfは読みづらくて、epubかmobiのほうがいいんじゃないかなぁと思っている)

fabricでブログデータのバックアップをとる

このブログはFlask製でデータはSQLiteに溜めているのでscpすればいいのだが、@ando_ando_andoがShizuoka.pyで紹介をしていたということもありfabricで書いてみた。

from fabric.api import get

def backup():
    get('/usr/local/blog/blog.db', '/Users/kzfm/Dropbox/blog.db')

とfabfile.pyを作っておいて

fab --host=[server] --port=[port] backup

と打てば、手元のDropboxの同期用フォルダにsftpされ、Dropboxで同期される

Shizuoka.py楽しかった

参加者の皆様、発表者の皆様お疲れ様でした。特に色々と調整していただいた@secondarykeyには大変お世話になりました。

  • Emacsのインデントの表示にhighlight-indentation
  • Emacsでgistいじるのにgist.el
  • Windows AzureでDjangoを動かすデモで管理画面が分かりやすくていいなと思った
  • 形態素解析というかテキストマイニング面白かった。
  • pythonのマルチスレッドとかいまいちよく理解してないわ
  • 死活監視もちゃんとやらなきゃあかんなぁ

懇親会で、次回どうしようかっていう話もちょっとしてきた。

  • Kivyをもう少し深く
  • pandasの紹介
  • Flask
  • 入門者向けの演題
  • @ando_ando_andoによる(続)Fabric入門

上3つはいいとして、入門者向けっていうのがなぁ。プログラミング全然わからないような初心者はおそらく参加しないだろうからあまりにも入門過ぎると飽きるしちょっと悩む

というわけで人工無能をPythonで作ってtwitterのボットにするくらいのハンズオンでもするのが軽くていいんじゃなかろうか? 形態素解析もできるし、外部のAPIを使ったプログラミングも経験できるし。Flaskでwikiつくるのもいいんだろうけど新鮮味がないから、やっぱマイニングの方向に持っていけるサンプルのほうがいいんじゃないかなぁ。

ちなみに次回は夏のあたり(7月のどこか)にやれればいいかなぁと思っているので、発表ネタを温めておいてもらえばと。

スライド

pythonでつくるiPhoneアプリ

pythonでpptx

Python で munin plugin を書いてみる

Subprocess no susume

pythonでiPhoneアプリを作るまで

Shizuoka.pyでpythonでiPhoneアプリを作る紹介をしたけど、さらっと流しただけで環境構築には触れなかったのでメモっておく。

iPhoneアプリ開発者登録をする

Titanium MobileでもObj-Cでもそうですが実機転送するためには開発者ライセンスがいるのでよろしく設定しておいてください。お金かかります。

Kivyのインストール

プラットフォームに応じたバイナリをダウンロードしてきてインストール。OSXの場合はアプリケーションフォルダにドラッグドロップして、make-symlinksをクリックしてシンボリックリンクを張る。

Kivy

これでkivyコマンドが使えるようになってmac上でアプリ開発ができる。

iOS用の設定

注) 1.6.0では安定版ではないのでそのつもりで。

KIvy for iOSに書いてある手順で。

  1. brewで必要なライブラリを入れる
  2. build_all.shでビルド
  3. create-xcode-project.shでテンプレートを用意して開発
  4. xcodeで開いてrunすると実機転送

という仕組み

pythonでpptxを作ればGit管理下におけて素敵

Shizuoka.pyお疲れ様でした、想定以上のヒトに参加していただいて感謝しております。特に今までお会いしたことのなかった静岡のPythonistaにお会いできて満足です。

それからpygamessユーザーとお話できたのも嬉しかったです(あまり製薬業界以外で使われているとは思わなかったので)。

また、懇親会で良い感じの場所を提供していただいたphotoにも感謝!

python-pptxでつくったスライドのソースを張っておきます。水着ループはたった 4 行のコードでひたすらアイドル水着画像をあつめる(Python だよ)を参考にしました。

#!/usr/bin/env python
# -*- encoding:utf-8 -*-

from pptx import Presentation
from pptx.util import Inches, Px
import re
import requests

prs = Presentation()
title_slidelayout = prs.slidelayouts[0]
bullet_slidelayout = prs.slidelayouts[1]

shapes = prs.slides.add_slide(title_slidelayout).shapes
shapes.title.text = 'Pythonでpptx'
shapes.placeholders[0].text = 'Pythonでpptx'
shapes.placeholders[1].text = '@kzfm'

shapes = prs.slides.add_slide(bullet_slidelayout).shapes
shapes.placeholders[0].text = 'GUIに頼らずpptxを作れると素敵'
tf = shapes.placeholders[1].textframe
tf.text = 'パターンの再利用'
tf.add_paragraph().text = '作業の自動化'
tf.add_paragraph().text = 'Sphinxに慣れすぎた'
tf.add_paragraph().text = 'powepointたまに死ぬ(->発狂する)'

shapes = prs.slides.add_slide(bullet_slidelayout).shapes
shapes.placeholders[0].text = 'python-pptx'
tf = shapes.placeholders[1].textframe
tf.text = 'https://github.com/scanny/python-pptx'
tf.add_paragraph().text = 'pip install python-pptx'
tf.add_paragraph().text = '開発はじまったばかり'
tf.add_paragraph().text = '超期待!'

shapes = prs.slides.add_slide(bullet_slidelayout).shapes
shapes.placeholders[0].text = 'アイドル水着画像をあつめてpptxに貼る'
top = Inches(2)
left = Inches(0.5)
width  = Px(280)
height = int(width * 1.427)
txBox = shapes.add_textbox(left, top, width, height)
txBox.textframe.text = """c = requests.get('http://matome.naver.jp/odai/2135350364969742801').content
urls = [x.group(1) for x in re.finditer(r'<img src="(.+)".*?class="MTMItemThumb".*?/>', c)]

for i, url in enumerate(urls[:10], 1):
    img_path = "{}.jpg".format(i)
    r = requests.get(url)
    if r.status_code == 200:
        img = r.content
        with open(img_path, 'w') as f:
            f.write(img)
        shapes = prs.slides.add_slide(bullet_slidelayout).shapes
        shapes.placeholders[0].text = '水着アイドル ({})'.format(i)
        top = Inches(1.5)
        left = Inches(3)
        width  = Px(280)
        height = int(width * 1.427)
        pic = shapes.add_picture(img_path, left, top, width, height)
"""

c = requests.get('http://matome.naver.jp/odai/2135350364969742801').content
urls = [x.group(1) for x in re.finditer(r'<img src="(.+)".*?class="MTMItemThumb".*?/>', c)]

for i, url in enumerate(urls[:10], 1):
    img_path = "{}.jpg".format(i)
    r = requests.get(url)
    if r.status_code == 200:
        img = r.content
        with open(img_path, 'w') as f:
            f.write(img)
        shapes = prs.slides.add_slide(bullet_slidelayout).shapes
        shapes.placeholders[0].text = '水着アイドル ({})'.format(i)
        top = Inches(1.5)
        left = Inches(3)
        width  = Px(280)
        height = int(width * 1.427)
        pic = shapes.add_picture(img_path, left, top, width, height)

shapes = prs.slides.add_slide(bullet_slidelayout).shapes
shapes.placeholders[0].text = 'このスライドはpython-pptx製'
tf = shapes.placeholders[1].textframe
tf.text = 'スクリプトっぽい(DSLっぽくはない)'
tf.add_paragraph().text = '再利用性は高められそう'
tf.add_paragraph().text = 'Gitで管理できる(重要!)'
tf.add_paragraph().text = 'Sphinxの拡張にするのは面白そう'
tf.add_paragraph().text = 'livereloadで更新時にリロードしないかなぁ?'

shapes = prs.slides.add_slide(bullet_slidelayout).shapes
shapes.placeholders[0].text = 'まとめ (真のアイドルは焼津)'.format(i)
top = Inches(1.5)
left = Inches(3)
width  = Px(280)
height = int(width * 1.427)
pic = shapes.add_picture("yaidumoe.jpg", left, top, width, height)

prs.save('test.pptx')

PythonでAndroidアプリ

Kivyで作られたParticle Pandaをいじってみたけど滑らかに動いてる。

1365679146

なかなか面白そうだ。