unhurried

コンピュータ関連ネタがほとんど、ときどき趣味も…

PythonでCSVから折れ線グラフを作る

これまではExcelを使ってCSVファイルをグラフ化していましたが、見た目の調整やそもそもセルの範囲が正しく選択されているかを確認するなど、かなり手間がかかっていました。Python(pandas)を使うと簡単にグラフ化できたため、手順をまとめてみました。

github.com

# Generate a fake csv file.

import datetime
import random

file = open("data.csv", "w")

# header
file.write("date,data1,data2\n")

# data
date = datetime.datetime.now()
data1 = 0.5
data2 = 0.5

for i in range(100):
    date = date + datetime.timedelta(seconds=1)
    data1 = data1 + random.uniform(-0.01,0.01)
    data2 = data2 + random.uniform(-0.01,0.01)
    
    file.write(date.strftime("%Y/%m/%d %H:%M:%S"))
    file.write(",")
    file.write(str(data1))
    file.write(",")
    file.write(str(data2))
    file.write("\n")

file.close()
# A function that draws a line graph from a csv file.

%matplotlib inline
import pandas as pd
import matplotlib.pyplot as plt
import os

def draw_line_graph_from_csv(file_name, x_axis, y_axes=None):
    df = pd.read_csv(file_name)
    filter = [x_axis] + y_axes

    title = os.path.basename(file_name)
    df[filter].plot(x=x_axis, title=title, grid=True, rot=10, figsize=(6, 4))
draw_line_graph_from_csv('data.csv', 'date', ['data1'])

f:id:unhurried:20180710065427p:plain

draw_line_graph_from_csv('data.csv', 'date', ['data1', 'data2'])

f:id:unhurried:20180710065425p:plain

Spring Boot:Restful APIサンプルプロジェクト

Spring Bootを使ってRestful APIを実装してみました。

github.com

ライブラリ

利用しているライブラリは以下の通り。

EclipseAPIを起動する方法

  • DBMSMySQL)と DBスキーマを用意する。
    • MySQLサーバーをインストールする。
    • /src/main/resources/application.yamlMySQLサーバーの接続情報を記載する。
    • /sql/create.sql を実行してDBスキーマを作成する。
  • EclipseSpring Tool Suiteをインストールする。
  • Eclipseにプロジェクトをインポートする。
    • ファイルインポート既存プロジェクトをワークスペースへ
  • パッケージ・エクスプローラーのApplication.javaを右クリック → 実行Spring Bootアプリケーション

APIリクエスト・レスポンス

ToDoリソースに対してCRUD操作を行うAPIが実装されている。

# Create a ToDo item.
$ curl --request POST \
>  --url http://localhost:8080/example/api/todos \
>  --header 'Content-Type: application/json' \
>  --data '{"title":"Test","content":"This is a test.","date": "2018-06-01"}' \
>  --include

HTTP/1.1 200
Content-Type: application/json
Content-Length: 71

{"id":1,"title":"Test","content":"This is a test.","date":"2018-06-01"}

Laravel 概要

PHPのWebアプリケーションフレームワークデファクトスタンダードになりつつある(?)Laravelを学ぶ機会があったため、概要を簡単にまとめてみました。

対象バージョン:Laravel 5.6

はじめに

開発環境のセットアップ

Laravel Homestead(Laravel開発に必要なミドルウェア等をパッケージしたVagrant Boxファイル)を使うと簡単に開発環境をセットアップできる。

Homestead.yamlIPアドレスなどの設定が記載されている。

LaravelでのMVCモデル

Ruby on Railsを模倣したモデル構造をしている。

Router --> Controller <--> Model <--> DB
                      <--> View
Laravelのディレクトリ構造
  • /routes/
    • ルーティング設定ファイルを格納する。
  • /app/Http/Controllers/
    • コントローラを格納する。
  • /app/
    • モデルを格納する。
  • /database/migrations/
  • /resources/view/
    • テンプレートファイルを格納する。
  • /public/
    • Webに公開する静的ファイルを格納する。

リソースに対するCRUD操作画面を作成する流れ

artisanコマンドでモデル・コントローラの雛形を作成する
php artisan make:model {モデル名} -m -c -r
  • -m:DBマイグレーションファイルを作成する。
  • -c:コントローラを作成する。
  • -r:コントローラにリソースメソッドを作成する。
データベーススキーマを定義する
  • 雛形 /database/migrations/xxxx.php のupメソッドにテーブル定義を記述する。
  • php artisan migrate を実行してテーブル定義を反映する。

参考:https://readouble.com/laravel/5.6/ja/migrations.html

コントローラーを実装する

雛形 /app/Http/Controllers/xxx.php の各メソッドを書き換えてモデル操作とビュー返却を行う。

  • 雛形コントローラーにはCRUD操作で必要になる画面用のメソッドが定義されている。
  • コントローラーメソッドではモデルクラスを使ってデータ操作を行い、ビューにデータを渡してレンダリングして返却する。
ビューを実装する
  • コントローラーから渡されたデータをBladeテンプレート使ってHTML構造に埋め込む。
  • CSRF対策には@csrfを記述することでCSRFトークンをhiddenフィールドに埋め込める。
ルーティングを設定する

/routes/web.phpにコントローラーメソッドへのルーティング設定を記載する。

  • Route::resource を使うことでコントローラのリソースメソッドのルーティング設定が簡単にできる。

参考:https://readouble.com/laravel/5.6/ja/controllers.html#resource-controllers

(おまけ)データベースにダミーデータを追加する

Seederを作成してデータベースにダミーデータを投入(シーディング)する。

  • php artisan make:seeder {Seeder} を実行して雛形を作成する。
  • 雛形 /database/seeds/{Seeder}.php を編集してダミーデータ作成コードを記述する。

参考:https://readouble.com/laravel/5.6/ja/seeding.html

その他補足

バリデーション

コントローラーにリクエストのバリデーション処理を以下の2通りの方法で記述できる。

  1. Requestオブジェクトのvalidateメソッドを使う方法
    • バリデーション違反時のエラーレスポンスを自動生成してくれる。
  2. Validatorファサードを使ってバリデータを生成する方法
    • バリデーション処理のみ実施してエラーレスポンスを自分で構築する。
エラー処理

ハンドリングされない例外に対する処理は app/Exceptions/Handler.php に記述できる。

  • reportメソッド:ログ出力や外部サービス(監視サービスなど)への通知を行う。
  • renderメソッド:発生した例外に対するレスポンスを出力する。
ミドルウェア

ミドルウェアを使うことで、HTTPリクエストに対して、コントローラの処理の前後に処理を挟むことができる。認証やCSRF保護などコントローラーの共通処理をまとめるのに役立つ。

参考:https://readouble.com/laravel/5.6/ja/middleware.html

例:Laravel組み込みの auth.basic ミドルウェアを使ってBasic認証を設定する。

Route::get('profile', function() {
    // ...
})->middleware('auth.basic');

Spring BootでREST APIを実装する

Spring BootでREST APIを実装するときのツールの組み合わせについて、2018年6月時点での個人的なベストプラクティスをまとめてみました。

開発環境(IDE

選択肢

IDEとしてはAndroidの公式開発環境に採用されていることもあり、今後はIntelliJ IDEAが普及していきそうだが、Spring開発サポート機能を利用するにはUltimate Editionが必要となる。ライセンスを購入するほどでもないプロジェクトではEclipseを使うのが良い。

参考

ビルドツール

選択肢

→ Groovyで柔軟に処理が書けるGradleが今後主流になっていきそう。とはいえ依存関係解決とビルドに使うのみであればMavenでも十分である。

参考

HTTP通信の制御(REST APIJava APIの相互変換)

選択肢
  • spring-boot-starter-web:Spring MVC
  • spring-boot-starter-jersey:JAX-RS

JAX-RSJavaの標準仕様であり、運用レベルで使える実装ライブラリも充実してきているので、spring-boot-starter-jerseyを使うのが良い。

参考

アスペクト指向プログラミング(AOP

選択肢

→ Spring AOPAspectJアノテーションで設定するのが簡単で、設定もコードにまとめることができてことができる。ただし、Spring管理外のインスタンスアスペクトを設定したい場合や、メソッド実行以外のタイミングに設定したい場合にはAspectJを使う必要がある。

参考

データベースアクセス

選択肢
  • spring-boot-starter-jdbcJDBCをそのまま使う
  • spring-boot-starter-data-jpaJPAベースのO/Rマッパー

→ spring-boot-starter-data-jpaを使うと単純なデータベース操作はSQLなしで記述できるため便利である。

なお、各DBとの接続するには対応するコネクターを導入する必要がある。(例えばMySQLの場合はmysql-connector-javaを導入する。)

参考

コネクションプール

選択肢

→ パフォーマンスが優れていて、Spring Bootでも推奨されているHikariCPを使うのが良い。

トランザクション管理

選択肢

→ 単一のトランザクション対象リソース(DB、MQなど)に対してトランザクションを使う場合はSpring Frameworkトランザクション管理機能で十分である。Springのリファレンスでは、複数のリソースにまたがってトランザクションを使いたい場合には、SpringとアプリケーションサーバーのJTA機能を組み合わせるか、Atomikosなどのトランザクションライブラリを使うかのどちらかとすると良いとのこと。

参考

コード生成ライブラリ

選択肢

→ AutoValueは厳密にImmutable Value Classを作るためのライブラリとなっているが、Lombokでは一般的なクラスにアクセッサメソッドを追加するだけ、といった使い方もでき、使い道が多くて良い。ただし、コード生成機能を拡張したい場合はAutoValueのExtensionを実装するのが良い。(LombokではExtensionMethodが試験的にサポートされているが、正式版に取り込まれるかは不明である。)

参考

設定ファイルの読み込み

spring-boot-configuration-processorを使うと簡単に設定ファイルの読み込みができる。3つの設定ファイルの読み込み方法が提供されている。

  • @Valueを使ってメンバ変数に設定ファイルのあるキーに対応する値を設定する
  • Environmentインターフェースを使って設定ファイルの値を取得するコードを記述する
  • @ConfigurationPropertiesを使ってBeanに設定ファイルの内容を設定する

→ 設定は階層構造を持つことが多いため、@ConfigurationPropertiesを使うのが簡単である。

参考

ロギング

選択肢

→ 今後はSLF4J + Logbackが主流になっていきそう。他のロギングシステムを使っている場合でもBridging legacy APIsに紹介される方法でSLF4Jに集約できる。

参考

テスト

特に問題がなければspring-boot-testを使うのが良い。AssertJアサーション)、Hamcrest(マッチ操作)、Mockito(モック)などテストに必要なライブラリは一通り揃っている。

参考

scikit-learnを使って糖尿病データ(diabetes)を解析する

scikit-learnを使って糖尿病データ(diabetes)を解析してみました。以下の機械学習モデルを使っています。

github.com

%matplotlib inline
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import GridSearchCV

from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import Ridge
from sklearn.linear_model import Lasso
from sklearn.ensemble import RandomForestRegressor
from sklearn.svm import SVR
# GridSearchCVの結果をヒートマップで表示するメソッド
def plot_heatmap_from_grid(grid):
    # チューニング対象のパラメータを特定する。
    params = [k for k in grid.cv_results_.keys() if k.startswith('param_')]
    if len(params) != 2: raise Exception('grid has to have exact 2 parameters.') 

    # ヒートマップの行、列、値に使うキーを定義する。
    index = params[0]
    columns = params[1]
    values = 'mean_test_score'
    
    # ヒートマップの行、列、値に使うキーを定義する。
    index = params[0]
    columns = params[1]
    values = 'mean_test_score'

    # gridから必要なキーのみを抽出する。
    df_dict = {k: grid.cv_results_[k] for k in grid.cv_results_.keys() & {index, columns, values}}

    # dictをDataFrameに変換してseabornでヒートマップを表示する。
    df = pd.DataFrame(df_dict)
    data = df.pivot(index=index, columns=columns, values=values)
    sns.heatmap(data, annot=True, fmt='.3f')
# データを読み込む。
diabetes = load_diabetes()
# データの説明文を表示する。
print(diabetes.DESCR)
Diabetes dataset
================

Notes
-----

Ten baseline variables, age, sex, body mass index, average blood
pressure, and six blood serum measurements were obtained for each of n =
442 diabetes patients, as well as the response of interest, a
quantitative measure of disease progression one year after baseline.

Data Set Characteristics:

  :Number of Instances: 442

  :Number of Attributes: First 10 columns are numeric predictive values

  :Target: Column 11 is a quantitative measure of disease progression one year after baseline

  :Attributes:
    :Age:
    :Sex:
    :Body mass index:
    :Average blood pressure:
    :S1:
    :S2:
    :S3:
    :S4:
    :S5:
    :S6:

Note: Each of these 10 feature variables have been mean centered and scaled by the standard deviation times `n_samples` (i.e. the sum of squares of each column totals 1).

Source URL:
http://www4.stat.ncsu.edu/~boos/var.select/diabetes.html

For more information see:
Bradley Efron, Trevor Hastie, Iain Johnstone and Robert Tibshirani (2004) "Least Angle Regression," Annals of Statistics (with discussion), 407-499.
(http://web.stanford.edu/~hastie/Papers/LARS/LeastAngle_2002.pdf)
# データ構造を確認する。
print(diabetes.keys())
# DESCR: データの説明文
# feature_names: 特徴量の名前
#   age: 年齢, sex: 性別, bmi: BMI,
#   bp:平均血圧, s1~s6: 血清成分値
# data: 各特徴量の値(numpy配列)
# target: 回帰対象(numpy配列)
#   糖尿病の進行度合いを数値化したもの
dict_keys(['data', 'target', 'DESCR', 'feature_names'])
# データを可視化して分析する。
df = pd.DataFrame(diabetes.data, columns=diabetes.feature_names)
df['target'] = diabetes.target
fig, axes = plt.subplots(2, 5, figsize=(16,8))
for key, ax in zip(df.keys(), axes.ravel()):
    df.plot(ax=ax, kind='scatter', x=key, y='target')

plt.subplots_adjust(wspace=0.4, hspace=0.2)
plt.show()
# bmi, s3, s5にtargetとの相関がありそう。

f:id:unhurried:20180616224810p:plain

# データを分割する
# train_test_split関数:データの75%を訓練セットに、残り25%をテストセットに分割する。
# scikit-learnでは、データを大文字のXで、ラベルを小文字のyで示すのが一般的である。
X_train, X_test, y_train, y_test = train_test_split(diabetes.data, diabetes.target,
random_state=0)
# 多項式特徴量 (PolynomialFeatures) + 線形モデル L1正則化 (Lasso)
# Lasso max_iter: パラメータ更新の繰り返し最大回数。
# alpha(後述)の値を小さくした場合は学習の収束までに必要なパラメータ更新回数
# が多くなるため、max_iterをデフォルト値の1000より大きくする必要がある。
pipe = make_pipeline(PolynomialFeatures(), Lasso(max_iter=100000))
# チューニング対象パラメータ
#   PolynomialFeatures degree: 多項式特徴量の次数
#   Lasso alpha: 正則化パラメータ。値が大きいほどモデルの係数が大きくなるのを強く抑制する。
param_grid = {'polynomialfeatures__degree': [1, 2, 3], 'lasso__alpha': [0.001, 0.01, 0.1]}
grid = GridSearchCV(pipe, param_grid=param_grid, cv=5, n_jobs=-1)
grid.fit(X_train, y_train)
print('best_score_: ', grid.best_score_)
print('best_params: ', grid.best_params_)
print('test score: ', grid.score(X_test, y_test))
plot_heatmap_from_grid(grid)
best_score_:  0.527902898728
best_params:  {'lasso__alpha': 0.01, 'polynomialfeatures__degree': 2}
test score:  0.362733231304

f:id:unhurried:20180616224807p:plain

# 多項式特徴量 + 線形モデル L2正則化 (Ridge)
pipe = make_pipeline(PolynomialFeatures(), Ridge(max_iter=100000))
# チューニング対象パラメータ
#   PolynomialFeatures degree: 多項式特徴量の次数
#   Ridge alpha: 正則化パラメータ。値が大きいほどモデルの係数が大きくなるのを強く抑制する。
param_grid = {'polynomialfeatures__degree': [1, 2, 3], 'ridge__alpha': [0.001, 0.01, 0.1]}
grid = GridSearchCV(pipe, param_grid=param_grid, cv=5, n_jobs=-1)
grid.fit(X_train, y_train)
print('best_score_: ', grid.best_score_)
print('best_params_: ', grid.best_params_)
print('test score: ', grid.score(X_test, y_test))
plot_heatmap_from_grid(grid)
best_score_:  0.524453646282
best_params_:  {'polynomialfeatures__degree': 3, 'ridge__alpha': 0.01}
test score:  0.366247197036

f:id:unhurried:20180616224804p:plain

# ランダムフォレスト
# n_estimators: 構築する決定木の数。大きい方が頑健なモデルになるが計算量が増加する。
pipe = make_pipeline(RandomForestRegressor(n_estimators=100, random_state=0))
# チューニング対象パラメータ
# max_features: 各決定木に利用する特徴量の最大数
# max_depth: 各決定木の最大の深さ
# max_featuresとmax_depthは小さくすると過剰適合しにくくなるが、
# max_featuresが小さいときにmax_depthがを小さくし過ぎると適合不足になる。
param_grid = {'randomforestregressor__max_features' : ['auto', 'sqrt', 'log2'], 'randomforestregressor__max_depth': [10, 5, 3, 1, None]}
param_grid = {'randomforestregressor__max_features' : ['auto', 'sqrt', 'log2'], 'randomforestregressor__max_depth': [5, 3, 1]}
grid = GridSearchCV(pipe, param_grid=param_grid, cv=5, n_jobs=-1)
grid.fit(X_train, y_train)
print('best_score_: ', grid.best_score_)
print('best_params: ', grid.best_params_)
print('test score: ', grid.score(X_test, y_test))
plot_heatmap_from_grid(grid)
best_score_:  0.509514863965
best_params:  {'randomforestregressor__max_depth': 5, 'randomforestregressor__max_features': 'sqrt'}
test score:  0.301299273427

f:id:unhurried:20180616224757p:plain

# サポートベクターマシン 線形カーネル (SVR kernel='linear')
pipe = make_pipeline(SVR(kernel='linear'))
# チューニング対象パラメータ
# C: 正則化パラメータ。値が大きいほどモデルの係数が大きくなるのを強く抑制する。
# epsilon: 正則化ペナルティを与えない回帰直(曲)線からの距離を決定する値。
param_grid = {'svr__C': [100, 1000, 10000], 'svr__epsilon': [1, 10, 100]}
grid = GridSearchCV(pipe, param_grid=param_grid, cv=5, n_jobs=-1)
grid.fit(X_train, y_train)
print('best_score_: ', grid.best_score_)
print('best_params: ', grid.best_params_)
print('test score: ', grid.score(X_test, y_test))
plot_heatmap_from_grid(grid)
best_score_:  0.506482547026
best_params:  {'svr__C': 1000, 'svr__epsilon': 10}
test score:  0.353230115014

f:id:unhurried:20180616224754p:plain

# サポートベクターマシン ガウシアンカーネル (SVR kernel='rbf')
pipe = make_pipeline(SVR(kernel='rbf'))
# チューニング対象パラメータ
# C: 正則化パラメータ。値が大きいほどモデルの係数が大きくなるのを強く抑制する。
# epsilon: 正則化ペナルティを与えない回帰直(曲)線からの距離を決定する値。
# ※ gammaは固定(1/特徴量数)とする。
param_grid = {'svr__C': [10000, 100000, 1000000], 'svr__epsilon': [1, 10, 100]}
grid = GridSearchCV(pipe, param_grid=param_grid, cv=5, n_jobs=-1)
grid.fit(X_train, y_train)
print('best_score_: ', grid.best_score_)
print('best_params: ', grid.best_params_)
print('test score: ', grid.score(X_test, y_test))
plot_heatmap_from_grid(grid)
best_score_:  0.502624272873
best_params:  {'svr__C': 100000, 'svr__epsilon': 10}
test score:  0.357268214488

f:id:unhurried:20180616225808p:plain

機械学習を理解するための数学のきほん

私が機械学習を始めるときに最初に読んだ本を簡単に紹介します。

概要

線形モデルを使った機械学習の数学的理論が、高校数学の知識で理解できるように解説されている。取り上げられている主な内容は以下の通り。

  • 回帰問題
    • 最小二乗法を使って、誤差関数を定義し、最急降下法でパラメータ更新式を求める手順
    • 多項式回帰、重回帰、確率的勾配法の適用方法
  • 分類問題
    • パーセプトロンを使って、座標的な意味からパラメータ更新式を求める手順
    • ロジスティック回帰の尤度関数を定義し、最急降下法でパラメータ更新式を求める手順
    • ロジスティック回帰への多項式特徴量の適用方法
  • モデルを評価するための交差検証
  • 線形モデルの過学習を防ぐための正則化(L1、L2)

また、これらの機械学習モデルをPythonでの実装方法(scikit-learnなどのライブラリを使わずに、NumPyを使って自分で実装する)も解説されている。

感想

機械学習を始めるに当たって、取っ付きやすい書籍を探して行きついたのが本書であった。グラフを使って数式の図形的な意味が解説されていて、巻末には数学を離れて久しい人が忘れていそうな基礎知識もまとめられているため、線形モデルの数学的理論の基礎を途中で詰まることなく理解することができた。

本書は機械学習で最近よく使われるアルゴリズムや、実践的なテクニックまでは触れていないが、数学を忘れかけている人が機械学習の基礎となる線形モデルの理論を理解するのにとても役に立つ本であると感じた。

Google Java Style Guide

Googleが様々な言語のコーディング規約(Style Guide)を公開していることを最近知りました。Javaのコーディング規約についてざっと見てみて、興味深かった点をまとめました。

2 Source files basics

非ASCII文字をコード中に記述するときにUnicodeエスケープするかどうかは、どちらの方が理解しやすいかによる。(2.3.3 Non-ASCII characters)

String unitAbbrev = "μs"; // "\u03bcs"とすると理解しにくいためUnicodeのまま記述する。

3 Source file structure

ワイルドカード * を使ったimportstatic かどうかに関わらず使わない。(3.3.1 No wildcard imports)

4 Formatting

iffor などで使うブレース {} は中身がからであっても省略しない。(4.1.1 Braces are used where optional)

空でないブロックは下記のようにK&Rスタイルで記述する。(4.1.2 Nonempty blocks: K&R style)

if (condition()) {
  method();
} else if (condition()) {
  method();
}

iftry などで複数ブロックで構成されていない限り、空のブロックは {} と簡略化できる。(4.1.3 Empty Blocks: may be concise)

インデントには2つのスペースを用いる。(4.2 Block indentation: +2 spaces)

1行は100文字以内に収まるようにする。(4.4 Colum limit: 100)

1行が長い場合に改行するときは4文字以上のスペースを入れる。(4.5.2 Indent continuation lines at least +4 spaces)

予約語 ( for など) と ( の間、}予約語 ( else など) の間、{ の前にはスペースを入れる。(4.6.2 Horizontal whitespaces)

5 Naming

特別な意味を持つプレフィックスやサフィックスは使わない。例:name_mName (5.1 Rules common to all identifiers)

略語など一般的に大文字で構成される単語もキャメルケースにする。例:XmlHttpRequest (5.3 Camel case: defined)

既にキャメルケースで広まっている名称はそのまま残してもよい。例:YouTubeImporter (5.3 Camel case: defined)