marcy's Lab

理系大学生のブログ

動的モード分解にL2正則化を適用する

はじめに

[x_0, x_1, x_2, \cdots, x_T] という時系列データがあったとき、 x_{t+1} = Ax_t と線形に時間発展するとします。

この線形作用素Aをいくつかの固有値(時間成分)と固有モード(空間成分)の組に分解する手法が動的モード分解(Dynamic Mode Decomposition,DMD)です。

固有値の値によって、そのモードが時間的に振動するのか、増幅・減衰するのかが分かります。

僕が実装しているDMDでは、固有値や固有モードには興味がなく、線形作用素Aによる未来の予測に興味があります。

未来の予測を安定化させるために、DMDにL2正則化を適用しました。

普通に線形作用素Aを求める

固有値や固有モードを求めたい場合は特異値分解などを用いた計算が必要ですが、Aだけを求めたい場合は簡単です。

時系列データ[x_0,x_1,x_2,\cdots,x_T]があったとき、

X_0=[x_0,x_1,\cdots,x_{T-1}]

X_1=[x_1,x_2,\cdots,x_T]

とします。

ここでx_tn次元ベクトルとすると、X_0,X_1n \times Tの行列になります。

線形作用素AX_0,X_1を使って

X_1 = A X_0 + \epsilon

という式で表すことができます。

この誤差\epsilonが最小になるように最小化問題、

\min || X_1 - A X_0 || _2 ^2

を解くことでAを求めることができます。

この最小化問題は

 \frac{\partial L}{\partial A} = 0

と書き表されます。 ( L = || X_1 - A X_0 || _2 ^2

 L = \mathrm{Tr} \left( \left(X_1 - A X_0 \right) ^T \left(X_1 - A X_0 \right) \right)

 \frac{\partial L}{\partial A} =  \frac{\partial}{\partial A}  \mathrm{Tr} \left( \left(X_1 - A X_0 \right) ^T \left(X_1 - A X_0 \right) \right)

 \frac{\partial L}{\partial A} = -2 \left( X_1 - A X_0 \right) X_0 ^T

(参考:「ベクトルで微分・行列で微分」公式まとめ - Qiita )

 -2 \left( X_1 - A X_0 \right) X_0 ^T = 0

  X_1 X_0 ^T  - A X_0 X_0 ^T = 0

  X_1 X_0 ^T  = A X_0 X_0 ^T

と式変換され、結果

 A = X_1 X_0 ^T \left( X_0 X_0 ^T \right)^{-1}

Aが求まります。

Aを使った予測

Aを使うことで未来の状態を予測できます。

時刻tの状態x_tを求めたいとき、初期状態をx_0とすると

 x_t = A^{t} x_0

と求まります。

このとき、A固有値全てのノルムが1未満であるとき、x_tは安定となります。

(参考: 離散的な線形システムの安定性を確認する方法:制御工学入門|Tajima Robotics)

また、理論的な事は理解してませんが、Aを求める際にL2正則化を加えることで固有値の絶対値が小さくなり、安定なAを求めることができるようになります。

L2正則化項を加えてAを求める

Aを求めるための最小化問題にL2正則化項を加えると、

 \min {{||  X _ 1  - A X _ 0  || _ {2} ^ {2} } + { \lambda || A||  _ {2} ^ {2} }}

となります。(\lambda正則化の強さを決めるパラメータ)

正則化項がない場合と同様に、

 \frac{\partial}{\partial A} \left( {||  X _ 1  - A X _ 0  || _ {2} ^ {2} } + { \lambda || A||  _ {2} ^ {2} } \right) = 0

と、Aによる偏微分が0になるように求められます。

上式左辺のカッコ内第一項目の偏微分はさきほど求めたので、第二項目の偏微分

 \frac{\partial}{\partial A} { \lambda || A||  _ {2} ^ {2} }

を求めます。

 \frac{\partial}{\partial A} { \lambda || A||  _ {2} ^ {2} } = \frac{\partial}{\partial A} { \lambda \mathrm{Tr} \left( A ^ T A \right) }

 \mathrm{Tr} \left( A ^ T A \right) = \sum _ {i,j} a _ {ij} ^ 2 より、

 \frac{\partial}{\partial a _ {ij}}  {|| A||  _ {2} ^ {2} } = { 2 a _ {ij} }

よって、

 \frac{\partial}{\partial A}  { \lambda || A||  _ {2} ^ {2} } = { 2 \lambda A }

となり、第一項目の偏微分と合わせると、

 \frac{\partial L}{\partial A} = -2 \left( X_1 - A X_0 \right) X_0 ^T + { 2 \lambda A } = 0

となる。

これを解くと、

 -2 \left( X_1 - A X_0 \right) X_0 ^T + { 2 \lambda A } = 0

 -X_1 X_0 ^T + A X_0 X_0 ^T +  \lambda A = 0

 A \left( X_0 X_0 ^T +  \lambda I \right) = X_1 X_0 ^T

 A  = X_1 X_0 ^T \left( X_0 X_0 ^T +  \lambda I \right) ^ {-1}

Aが求まりました。

これがL2正則化を適用した線形作用素Aです。

\lambdaを0にすると正則化がない場合と一致することも確認できます。

結果

手持ちのデータ(100次元)でやってみました。

制御項も加わり、 x _ {t+1} = A x _ t + B u _ tというような式で予測してますが結果に大きな変化はないと思うので気にしないでください。

 \lambda = 0の場合

 Aのノルム(フロベニウスノルム)は394.96

このAで予測をすると発散してしまいました。

f:id:marc326marcy:20200403000756p:plain
Aのヒートマップ

f:id:marc326marcy:20200403001040p:plain
固有値の分布(横軸:実軸、縦軸:虚軸)

 \lambda = 0.1の場合

ノルムは49.24

これも発散してしまいました。

f:id:marc326marcy:20200403001749p:plain
Aのヒートマップ

f:id:marc326marcy:20200403001756p:plain
固有値の分布(横軸:実軸、縦軸:虚軸)

\lambda = 1の場合

ノルムは4.73

発散せず予測することができました。

f:id:marc326marcy:20200403002740p:plain
Aのヒートマップ

f:id:marc326marcy:20200403002818p:plain
固有値の分布(横軸:実軸、縦軸:虚軸)

\lambda = 10の場合

ノルムは4.38と、\lambda = 1と比較してあまり小さくなりませんでした。

\lambda = 1と同じく発散しませんでした。

f:id:marc326marcy:20200403003812p:plain
Aのヒートマップ

f:id:marc326marcy:20200403003831p:plain
固有値の分布(横軸:実軸、縦軸:虚軸)

コード

ノルムや上の画像を表示させるためのPythonコード

import numpy.linalg as la
import matplotlib.pyplot as plt

# ノルム
norm = la.norm(A)

# ヒートマップの描画
plt.imshow(A)

# 固有値取得
w, v = la.eig(A)

# 固有値の複素平面上の分布
plt.scatter(w.real, w.imag)

おわりに

おしまい

Pythonで機械学習に音声データを使うときのメモ

はじめに

機械学習コンペティションで音声データからクラス分類する問題をやることになりました。

これまではベクトルデータの入ったCSVファイルや画像ファイル、文字列などを扱ってきました。

音声データを使った機械学習は初めてだし、それ以外でもPythonで音声ファイルを使ったことがないのでいろいろ調べてまとめてみました。

参考記事

qiita.com

qiita.com

二つの記事を参考にしました。

記事を参考に実装してみて、バージョンや環境の関係でエラーが出たところ、分かりづらかったところを中心に書こうかなと思います。

ライブラリ

librosaというライブラリが一般的に使われるそうです。

あと、Pythonの標準ライブラリにWaveモジュールがあり、これも使うらしいです。

Waveモジュールよりもlibrosaの方が機械学習向けって感じなのかな。あんまり調べてないからわかんないですけど。

上の記事ではWindows環境で、pip install librosaに失敗したと書いてありましたが、僕もWindows環境で失敗しました。

もともとAnaconda環境だったので、conda install librosaでインストールしました。

librosaのバージョンは0.6.2でした。

読み込み・プロット・編集

import numpy as np
import scipy as sp
import matplotlib.pyplot as plt
import wave
import librosa as lr
import librosa.display

インポートします。

librosa.display は波形の表示に使います。ちゃんとインポートしましょう。(インポートせずに使おうとして、何回もエラー吐かれた。よく分からずコピペはダメ、絶対。)

waveモジュール

filename = "sound.wav"
with wave.open(filename, 'r') as wr:
    data = wr.readframes(wr.getnframes())
X = sp.fromstring(data, dtype=sp.int16)
plt.plot(X)

readframes関数で得られるdataはbytes型というものでpython使ってて初めて出会いました。

これをscipyのfromstring関数でbytes型から整数型のnumpy配列に変換します。

それをプロットしたものが以下の図になります。

f:id:marc326marcy:20190118023401p:plain

変換する前はbytes型という気持ち悪さと、変換したあともfloat型じゃなくてint型のnumpy配列という気味の悪さ。

waveモジュールは、ファイルのフレームレートやフレーム数を取得する程度で、編集に関しては軽くトリミングするくらいが主な用途みたいです。

librosa

y, sr = lr.load(filename)
totaltime = len(y) / sr
time = np.arange(0, totaltime, 1/sr)
plt.plot(time, y)

lr.load関数でデータがfloat型のnumpy配列でyに出力され、サンプリングレートがsrに出力されます。

f:id:marc326marcy:20190118024913p:plain

データのプロットを見て一番大きな違いは縦軸の大きさですかね。

librosaはここからさらにスペクトログラムを作成することが出来ます。

S = lr.feature.melspectrogram(y, sr=sr, n_mels=128)
log_S = lr.amplitude_to_db(S, ref=np.max)
librosa.display.specshow(log_S, sr=sr, x_axis='time', y_axis='mel')
plt.colorbar(format='%+02.0f dB')
plt.tight_layout()

f:id:marc326marcy:20190118031541p:plain

参考にした記事では

log_S = librosa.logamplitude(S, ref_power=np.max)

を使ってましたが、バージョンの関係で使えなくなってました。

機械学習での扱い

ニューラルネットワークを用いる手法では、上のスペクトログラムを二次元画像データとして、CNNに入力するのが主流(?)らしいです。



いろいろ調べてまとめてみました。と序盤に書いておきながら、気力が尽きたので

おわり!

すぐ追記するか新しい記事書きます、きっと。

仮想通貨の自動売買システムを作ってみる その1 ~Bitcoinの5分足データを集める~

はじめに

最近、急に仮想通貨の自動売買システム作って一生遊んで暮らしたいなという気持ちが湧いてきました。ほんと急にです。

仮想通貨については、ちょうど一年前くらいに興味を持ったものの当時は未成年だったので成人したら手を出そうと思ってました。

それでその半年後くらいに仮想通貨のバブルがはじけて、NEM流出事件が起きて、成人する前には完全に興味が薄れてました。

でも最近、多分バイトを再開したあたりからですかね。

働きたくないなあと思って、株やFX、仮想通貨の自動売買システムについて調べ始めました。

仮想通貨の取引所はAPIを用意してくれてたんで、仮想通貨に決めました。

取引所もいろいろある中で、Zaifを選びました。理由は特にないです。

口座開設をついこの前数日前に完了したところです。

ブームも過ぎたのか、口座開設には全然時間かかりませんでした。

他の取引所(bitFlyerとか)が良さそうだったらそっちも口座開設することにします。

ということで、APIもあるし機械学習走らせて、ゴリゴリ自動売買していくぞ!

と思ったら肝心のデータがなかったんですよね。

どれくらいのデータがいるのかよくわかってないので、いろんなデータで試していくぜと思ってたんですけど、Zaif公式からは日足のデータしか配布されてません。

データ量的に、1分足、5分足のデータなら一年分あれば、充分かなと思ってたんですけど、それすらもなかなか落ちないんです。

探したら、一年分程度の生の取引データが落ちていたので、この取引データから5分足データを作っていくことにします。

実装

データをダウンロード

データは↓のzaifJPY.csv.gzをダウンロードしました。

bitcoincharts.com

このファイル、解凍してみると2.5GBありました。

中身を確認しようと思ったんですけど当然Excelじゃ開けませんし、その他手持ちのテキストエディタでも開けません。

Pythonで開いてみると、58,000,000行超あることが確認できました。

Pythonで開くにしても180秒ほどかかるし、開発環境のSpyderもフリーズします。

メモリは5GBも使ってます。

まともに作業できたもんじゃないですよ。

とりあえず、それぞれ500,000行のファイルに分割します。

import pandas as pd

dtyp = {'time': 'int32', 'price':'int32', 'amount': 'float32'}
btc = pd.read_csv("data/zaifJPY.csv", names=("time", "price", "amount"), dtype=dtyp)

a = range(0, len(btc.index) + 500000, 500000)

for i in range(len(a)-1):
    btc[a[i]:a[i+1]].to_csv("data/zaifJPY-" + str(i) + ".csv")
    print(i)

これで117個のファイルができました。

分けすぎですかね?まあ適当です。

5分足のデータにする

取引データから5分足のデータにする処理がけっこう難しいんじゃないかと、プログラム書く前からちょっと憂鬱だったんですけど、Pandasで一発でした。

Pandasのすごさに感動した勢いでこの記事書いてるといっても過言ではないです。

import pandas as pd

csv = pd.DataFrame()

for i in range(117):
    btc = pd.read_csv("data/zaifJPY-" + str(i) + ".csv", index_col=0, parse_dates=True)
    
    #時間をインデックスに指定
    btc.set_index('time', inplace=True)
    
    #UnixTimeを日本時間に変換
    btc.index = pd.to_datetime(btc.index, unit='s')
    btc.index = btc.index.tz_localize('UTC').tz_convert('Asia/Tokyo')

    #5分でリサンプリングして、終値をとる
    btc = btc.resample('5T').last()
    csv = pd.concat([csv, btc])
    print(i)

csv.to_csv("data/5min/zaifJPY5min.csv")
print("done!")

なんとpandasを使えば一行で、5分毎のリサンプリングをしてくれます。

しかも、last()の部分を変えることで、平均や中央値、始値などを取る事も出来ます。

Pandasには頭が上がりません。

おわりに

これで約一年分の5分足データが揃いました。

これからRNNらへんを使って、予測するプログラムを作っていきたいと思います。

ちなみに続きそうな記事のタイトルしてますが、続くかはわかりません。

それではまた今度。

matplotlibを使って、ベクトルのアニメーションを作ってみた

はじめに

最近、光工学という講義で偏光というものを学びました。

円偏光、楕円偏光、直線偏光とかです。

最初はなぜ楕円を描くのかとかが分からなかったので、理解を深めようと思い、光ベクトルのアニメーションを作りました。

matplotlibのquiverを使ったアニメーションを説明したウェブサイトがなかったので記事にしてみました。

実装

実行環境

Windows10

Python 3.6.2

Anaconda

ライブラリのインポート

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np

普通のベクトルグラフ

まずはアニメーションではないベクトルのグラフを作成します。

fig = plt.figure(figsize=(6, 6))
plt.xlim([-1, 1])
plt.ylim([-1, 1])

#角度を定義
deg = -90
delta = np.radians(deg)
theta = 45
theta = np.radians(theta)

#ベクトルを定義
x = np.zeros(3)
y = np.zeros(3)
u = (np.cos(theta), 0, np.cos(theta))
v = (0, np.cos(theta + delta), np.cos(theta + delta))

plt.quiver(x, y, u, v, color=('r','g','b'), angles='xy',scale_units='xy',scale=1)

plt.title("δ = " + str(deg) + "°", fontsize=18)    
plt.show()

結果がこちらになります。

f:id:marc326marcy:20180824215806p:plain

赤がX軸方向のベクトル(cos(θ) , 0)、緑がY軸方向のベクトル(0, cos(θ + δ))、青がそれらの合成ベクトル(cos(θ), cos(θ + δ))です。

δは位相差で今回は-90°です。θは位置や時間で変化する変数を表しており、今回は45°の点でのグラフとなってます。

アニメーションにする際には、θを変化させます。

アニメーション作成

グラフの作成とパラメータの代入
fig = plt.figure(figsize=(6, 6))
plt.xlim([-1, 1])
plt.ylim([-1, 1])

deg = -90
delta = np.radians(deg)

x = np.zeros(3)
y = np.zeros(3)

グラフを定義し、位相差を代入、ベクトルの要素の内、x,yを代入しており、ここまでは同じです。

グラフの配列を作成
ims = []
for t in range(0, 360, 5):
    theta = np.radians(t)
    u = (np.cos(theta), 0, np.cos(theta))
    v = (0, np.cos(theta + delta), np.cos(theta + delta))
    im = plt.quiver(x, y, u, v, color=('r','g','b'), angles='xy',scale_units='xy',scale=1)
    ims.append([im])

変数θを5°ずつ変化させてfor文で回していき、グラフを取得し、各グラフを配列に格納していきます。

ここで、普通のグラフと同じように

    ims.append(im)

とすると、エラーが起こるので気を付けましょう。

アニメーションを表示
plt.title("δ = " + str(deg) + "°", fontsize=18)
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True)
plt.show()
ani.save("artistanimation_" + str(deg) + ".gif", writer='imagemagick')

f:id:marc326marcy:20180824231359g:plain

位相差が-90°だと反時計回りの円偏光だということが分かりますね。

FuncAnimation

上のコードではArtistAnimationという関数を使ってアニメーションを作成しました。アニメーションにしたい連続したグラフを配列に入れて、それをArtistAnimationの関数に渡すことで、アニメーションを作成します。

アニメーションを作る関数はもう一つあり、それがFuncAnimationという関数です。 FuncAnimationはその名のとおり、グラフを更新する関数をFuncAnimationに渡します。FuncAnimation内で関数にしたがってグラフを更新していき、アニメーションを作成します。 FuncAnimationを使ったコードも書いてみました。

グラフの作成とパラメータの代入
fig = plt.figure(figsize=(6, 6))
plt.xlim([-1, 1])
plt.ylim([-1, 1])

deg = 180
delta = np.radians(deg)
初期ベクトルの設定
X = np.zeros(3)
Y = np.zeros(3)
U = np.array((np.cos(0), 0, np.cos(0)))
V = np.array((0, np.cos(delta), np.cos(delta)))
Q = plt.quiver(X, Y, U, V, color=('r','g','b'), angles='xy', scale_units='xy', scale=1)
ベクトルの更新
unit = 5
def update_quiver(t, Q, X, Y):
    theta = np.radians(t*unit)
    U = np.array((np.cos(theta), 0, np.cos(theta)))
    V = np.array((0, np.cos(theta + delta), np.cos(theta + delta)))

    Q.set_UVC(U,V)

    return Q,

update_quiverがベクトルを更新する関数です。

unitは一回の更新で何度変化させるかを表す数値で、完成したGIFの滑らかさに影響します。

アニメーションを表示
ani = animation.FuncAnimation(fig, update_quiver, fargs=(Q, X, Y), interval=50, frames=360//unit, blit=True)
plt.show()
ani.save("funcanimation_" + str(deg) + ".gif", writer='imagemagick')

f:id:marc326marcy:20180825001442g:plain

位相差が180°だと直線偏光ですね。

おわりに

無事、ベクトルのアニメーションを作ることができました。 しかしグラフを配列に格納するところで何回もエラーが出て、解決策もよく分からずで結構大変でした。

完成してみるとすごく簡単なコードですね。

ちなみにArtistAnimationとFuncAnimationで位相差を同じにして、GIFを作ってみると、ファイルのハッシュ値が一致しました。

全く同じファイルができているということです。

処理時間も大きな差はなかったので、お好みの方でアニメーション作りましょう。


早速、投稿頻度の雲行きが怪しくなってきました。頑張ります。

それではまた今度。



Railsに手を出してみたい......

Webスクレイピングを使って、「朝までハシゴの旅」のロケ地を一覧化してみた

はじめに

皆さんは、「笑ってコラえて」という番組の「朝までハシゴの旅」というコーナーをご存知でしょうか?

僕はお酒を飲むのが好きで、朝まで居酒屋をハシゴしまくるのに憧れています。それもあってか「朝までハシゴの旅」のコーナーが好きなんです。

あと、今年上京してきたので東京の飲み屋街とかにもあまり知識がありません。駅名は聞いたことあっても、それが東京都のどこら辺にあって、どれくらい賑わっているのか、とかが分かんないんです。

なので、テレビを通してその街の雰囲気が伝わってくるのがすごい好きなんですね。

ということで、これまでのロケ地を調べようと思ったんです。が、どこにもまとめられていませんでした。(自分が調べた限りでは)

で、それをまとめるには、番組のバックナンバーというページから、それぞれの放送日のあらすじが書いてあるページに飛んで、そのあらすじの文章中にある朝までハシゴの旅のロケ地を抜き出して、それを全放送分繰り返すのです。

わざと冗長に書きましたが、これを自力でやるのはさすがにめんどくさいです。

じゃあプログラム書いて自動でやったろう、というのが今回の経緯です。

プログラミング

実行環境

Windows10

Python 3.6.2

Anaconda

ライブラリのインポート

import csv
import re
import bs4
from urllib import request

今回Webスクレイピングに使ったのは、BeautifulSoupとurllib.requestです。

BeautifulSoupはAnacondaに入っていて、コード中のbs4がそれです。urllibはPythonの標準ライブラリです。

csvはファイル出力、reは文字列の検索に使いました。

ちなみにWebスクレイピングは初挑戦で解説サイトを見ながらコーディングしたのですが、僕が見たサイトはどれもPython 2.7でやってました。Python3用に自分でちょっと変えながらコーディングしたので、そこらへんも参考になるかもしれないです。

バックナンバーのページのHTMLを取得

笑ってコラえてのバックナンバーのページをBeautifulSoupで扱います。

url = "http://www.ntv.co.jp/warakora/next/backnumber.html"
html = request.urlopen(url)
soup = bs4.BeautifulSoup(html, "html.parser")

各放送日のページのHTMLを取得

ページのHTMLからliタグを全て抜き出します。

tag = soup.find_all("li")

それが下のようになっていて、各行がリストの要素となってtagリストに格納されています。

<li class="heightLine"><a href="20180801.html">2018年8月1日</a></li>
<li class="heightLine"><a href="20180725.html">2018年7月25日</a></li>
<li class="heightLine"><a href="20180718.html">2018年7月18日</a></li>

ここから、"20180801.html","20180725.html","20180718.html"...というのを抜き出します。

urllist = []
for i in tag:
    urllist.append("http://www.ntv.co.jp/warakora/next/" + i.a.get("href"))

これでそれぞれの放送日のURLをリストに入れることが出来ました。

朝までハシゴの旅コーナーがある放送のあらすじと放送日を取得

各放送日のURLをforで回して、開いていきます。

HTML内にハシゴという単語があるかどうかで、その日の放送に朝までハシゴの旅のコーナーがあるかどうかを判定します。

コーナーがある場合、コーナーのあらすじとその日付をそれぞれ別のリストに追加します。

hashigo_str = []
date = []
for j in urllist:
    url2 = j
    html2 = request.urlopen(url2)
    soup2 = bs4.BeautifulSoup(html2, "html.parser")
    hashigo = soup2.find("h3", text=re.compile("ハシゴ"))
    if bool(hashigo):
        hashigo_str.append(str(hashigo.find_next()))
        date.append(re.sub(r'\D', '', j))

dateというリストには朝までハシゴの旅コーナーを放送した日付が、hashigo_strというリストには日付と同じ順番でコーナーのあらすじが入っています。

このあらすじにはコーナーの出演者や場所などが書かれています。

ファイルに出力

とりあえずこのあらすじをファイルに出力します。

hashigo_list = []
hashigo_list.append(date)
hashigo_list.append(hashigo_str)

with open('file.csv', 'w', encoding='CP932', errors='replace') as f:
    writer = csv.writer(f, lineterminator='\n')
    writer.writerows(hashigo_list)

dateとhashigo_strをリストに入れ、このリストをcsv形式でファイル出力します。

エラー処理

open関数内のencoding='CP932', errors='replace'がないと、

UnicodeEncodeError: 'cp932' codec can't encode character '\u2661' in position 11633: illegal multibyte sequence

というエラーが発生します。

これはHTML内に出てくる「♡」がCP932という文字コードでは使えないことで起こるエラーです。

このエラーを回避するために、encoding='CP932', errors='replace'を加えます。

文字コードはそのままCP932を使いますが、もし文字コードに対応してない文字が現れたときにその文字を「?」に置換するようになります。

場合によっては文字を置換せずにそのまま使いたかったりするので、文字コードを変更したりすることも考えなくてはいけません。

Webスクレイピングを行う際には、プログラムの実行環境とHTMLで文字コードが違い、エラーがよく出るみたいなので、気を付けなきゃいけないですね。

実行結果

放送日 ロケ地1 ロケ地2
2018年8月1日 溜池山王駅
2018年7月18日 神奈川鶴見駅
2018年6月20日 亀有駅
2018年6月6日 下北沢
2018年5月9日 浅草・隅田公園
2018年5月2日 大森駅
2018年4月11日 御徒町駅
2018年3月28日 鶯谷
2018年1月24日 亀戸駅
2018年1月17日 奄美大島
2017年12月27日 水道橋
2017年11月22日 銀座
2017年11月8日 秋山渓谷
2017年11月1日 両国
2017年10月11日 東中野
2017年8月16日 板橋駅
2017年8月2日 新宿三丁目
2017年6月28日 八王子駅
2017年6月21日 新小岩
2017年5月31日 隅田公園
2017年5月3日 王子駅
2017年4月26日 溝の口
2017年4月12日 広島駅
2017年3月8日 愛媛県松山
2017年2月22日 幡ヶ谷駅
2017年2月8日 大門駅
2017年2月1日 大井町
2017年1月11日 自由が丘駅
2016年12月28日 (過去のVTR)
2016年11月2日 湯島駅
2016年10月5日 五反田
2016年9月21日 葛西駅
2016年9月14日 成増
2016年8月24日 鹿児島県天文館
2016年8月3日 大阪
2016年7月6日 江古田
2016年6月15日 蒲田
2016年5月18日 練馬
2016年5月4日 荻窪
2016年4月27日 隅田公園
2016年4月13日 別府
2016年3月2日 国分寺
2016年2月17日 武蔵小山
2016年1月20日 赤羽
2016年1月13日 札幌 沖縄
2015年12月30日 石垣島
2015年11月18日 阿佐ヶ谷駅
2015年11月4日 経堂駅
2015年10月14日 四ツ谷駅
2015年10月7日 横浜
2015年9月9日 京都
2015年8月19日 調布
2015年8月12日 三田駅
2015年8月5日 日本橋
2015年7月29日 博多中洲
2015年7月8日 阿佐ヶ谷駅 大塚駅
2015年5月27日 吉祥寺
2015年5月20日 大井町駅
2015年5月6日 隅田公園
2015年4月22日 学芸大学駅
2015年4月15日 金沢
2015年3月18日 札幌 那覇
2015年3月4日 三軒茶屋
2015年2月25日 中井駅
2015年2月18日 目黒駅
2015年2月11日 門前仲町
2015年2月4日 赤坂駅
2015年1月21日 大宮駅
2014年12月10日 中野駅
2014年12月3日 蒲田駅
2014年11月5日 浅草
2014年10月29日 小岩
2014年10月8日 *立川
2014年9月17日 新橋駅
2014年9月10日 仙台
2014年8月20日 高円寺
2014年8月13日 上野
2014年7月30日 北千住
2014年7月16日 錦糸町
2014年6月18日 恵比寿
2014年5月21日 神田駅
2014年5月14日 町田駅
2014年5月7日 大崎駅
2014年4月9日 西荻窪駅

正確にはプログラムの実行結果ではありません。あらすじから地名や駅名を抜き出すのは手動でやりました。

あらすじをWebから引っ張ってきてまとめやすくすることを今回のプログラムの目標とさせてください。

自然言語処理系の機械学習は経験があるので、あらすじから地名や駅名を抜き出す処理も自動化してみたいとも思ってますが、それはまた今度ということで!