主に感想。

を書いています。

Stratified train/test-split in PyTorch

日本語版はこちらです。The Japanese version can be found here. qiita.com

What is Stratified Splitting?

When you do machine learning, you often separate the data set into two parts: training data and validation data. Especially in the case of classification tasks, it is preferable to split the data so that the ratio of each class in the split data is the same as that of the original one. This way of splitting data is called Stratified Splitting.

Example in PyTorch

In scikit-learn, you can do a Stratified Split by passing the stratify option to the function sklearn.model_selection.train_test_split().

PyTorch, on the other hand, does not have such a function. So, we will use a function in scikit-learn to achieve a Stratified Split in PyTorch.

An example code would look like the following.

import torch
import torchvision.transforms as transforms
from sklearn.model_selection import train_test_split

transformer = transforms.Compose([
    transforms.ToTensor(),
])

# load images
dataset = torchvision.datasets.ImageFolder(root='directory_name', transform=transformer)

# split the data set into train and validation
train_indices, val_indices = train_test_split(list(range(len(dataset.targets))), test_size=0.2, stratify=dataset.targets)
train_dataset = torch.utils.data.Subset(dataset, train_indices)
val_dataset = torch.utils.data.Subset(dataset, val_indices)

# create DataLoader
train_data_loader = torch.utils.data.DataLoader(train_dataset, batch_size=4, shuffle=True, num_workers=4)
val_data_loader = torch.utils.data.DataLoader(val_dataset, batch_size=4, shuffle=True, num_workers=4)

Let me explain the details.

First, create a Dataset by importing images with ImageFolder.

transformer = transforms.Compose([
    transforms.ToTensor(),
])

dataset = torchvision.datasets.ImageFolder(root='directory_name', transform=transformer)

Next, we split the data with train_test_split(), but since we can't pass the Dataset to it directly, we generate an index array of Datasets [0,1,2,3,...num_data] with list(range(len(dataset.targets))) and pass it instead. Then, by passing the class labels dataset.targets as a stratify option, we can split the index array into training and validation while keeping the class label ratio of the original data.

train_indices, val_indices = train_test_split(list(range(len(dataset.targets))), test_size=0.2, stratify=dataset.targets)

Since it is just the index array that we have split just now, we will split the dataset itself based on the index array. Subset, as the name implies, is a class for creating subsets of data. By passing the original Dataset and the index array to it, we can get the Dataset corresponding to the index.

train_dataset = torch.utils.data.Subset(dataset, train_indices)
val_dataset = torch.utils.data.Subset(dataset, val_indices)

After that, just pass the Dataset to the DataLoader as usual.

train_data_loader = torch.utils.data.DataLoader(train_dataset, batch_size=4, shuffle=True, num_workers=4)
val_data_loader = torch.utils.data.DataLoader(val_dataset, batch_size=4, shuffle=True, num_workers=4)

NetlifyにデプロイしているサイトにGoogle Analyticsを導入する

Netlifyを使って静的ホスティングしているサイトにGoogle Analyticsを導入する方法です。

HTMLに直接トラッキングコードを貼り付けてもいいですが、Netlifyにはデプロイ後のHTMLにスクリプトを挿入する機能があります。 この機能を使うことで、ソースコードを汚すことなくGoogle Analyticsを導入できます。

Netlifyにデプロイしたアプリの画面を開き、
Build & deployPost processingSnippet injectionAdd snippetと進み、 Insert before </head>を選択します。

f:id:sin9270:20200719195022p:plain

あとは、HTMLにGoogle Analyticsのトラッキングコードを貼り付ければ完了です。 Script nameは適当で大丈夫です。

参考

docs.netlify.com

EC2上のTomcatにServletアプリケーションをデプロイする方法

タイトルの通りです。EC2にTomcatをインストールし、そのTomcat上にServletアプリケーションをデプロイします。

EC2のOSはAmazon Linux2(64bit) を想定しています。Amazon LinuxはRedHat系で、Ubuntu等のDebian系とはコマンドが若干異なるので、Ubuntuを使う際はご注意ください。

Tomcatには8080番ポートからアクセスするため、EC2を作成する際にセキュリティグループの設定で8080番ポートを開けておきます。具体的には、タイプはカスタムTCP、プロトコルはTCP、ポート範囲は8080、ソースはカスタムの0.0.0.0/0とします。

また、EC2にSession Managerからアクセスするために、EC2にSession Managerのアクセス権限を与えておく必要があります。AWS管理ポリシーであるAmazonEC2RoleforSSMをアタッチしたIAMロールを作成し、EC2にアタッチしておきます。

セキュリティグループとIAMロール以外の項目はデフォルトのまま、SSHのキーは発行せずにEC2を起動します。この記事ではSession Managerを使うことを前提としていますが、普通にSSHでEC2にアクセスする場合はSSHキーを発行してください。

Session ManagerからEC2の中に入ったら、Javaのバージョンを確認します。Javaが入っていればそのまま続行してください。

java -version

入ってない場合は自分でJavaをインストールする必要があります。Amazon Linux2はデフォルトではJavaは入っていないので注意が必要です。

自分でインストールする場合、まずyumのリポジトリを更新します。

sudo yum update -y

次に、インストール可能なJavaのバージョン一覧を取得します。

sudo yum search java

Java8まででよければ、OpenJDKが使えます。名前にdevelが付いていないものはランタイムのみ、付いているものはコンパイラが付属しています。

sudo yum install java-1.8.0-openjdk

or

sudo yum install java-1.8.0-openjdk-devel

Javaをインストールできたら、TomcatとTomcatの管理用アプリをインストールします。

sudo yum install tomcat
sudo yum install tomcat-webapps
sudo yum install tomcat-admin-webapps

Tomcatを起動します。

sudo systemctl start tomcat

EC2のコンソールから作成した仮想マシンのインスタンスを選択し、グローバルIPアドレスを確認します。 その後ブラウザからそのIPアドレスの8080ポート(http://xxx.xxx.xxx.xxx:8080/)にアクセスし、Tomcatの管理画面が表示されることを確認します。

次に、TomcatのManager AppからServletアプリケーションをデプロイします。デフォルトではTomcatの管理ユーザーが存在せず、Manager Appにログインできないため、管理ユーザーを作成します。

管理ユーザーはTomcatの設定ファイルに記述する必要があるので、そのファイルをvimで編集します。

sudo vim /usr/share/tomcat/conf/tomcat-users.xml

最下部の次の2行(おそらく下から2行目と10行目)のコメントアウトを外して、管理ユーザーを作成します。

<role rolename="admin"/>
<user name="admin" password="adminadmin" roles="admin,manager,admin-gui,admin-script,manager-gui,manager-script,manager-jmx,manager-status" />

作成した管理ユーザーを有効にするため、Tomcatを再起動します。

sudo systemctl restart tomcat

TomcatのManager Appにadmin/adminadminでログインします。アプリケーションの管理画面が表示されたら、下の方のメニューからWARファイルを配備します。

デプロイができたら、Tomcat上のアプリケーションのURLhttp://xxx.xxx.xxx.xxx:8080/{Javaプロジェクト名}/からアプリケーションにアクセスできるようになります。

リダイレクトについてのメモ

リダイレクトを実現するには3つの手段がある。

HTTPリダイレクト

HTTPレスポンスに300番台のステータスコードを含め、Locationヘッダーにリダイレクト先のURLを指定する方法。ブラウザは300番台のレスポンスを受け取ると、直ちに新しいURLにアクセスする。リダイレクトレスポンスについてはブラウザの履歴に残らず、(おそらく)HTMLのパース・レンダリングも行われない。

ステータスコードによって、メソッドやリクエストボディを元のリクエストから引き継ぐかを指定できる。WebページでFormの内容をPOSTメソッドで送信した後に完了画面を表示する際は、リダイレクトで完了画面のページに飛ばすのが良いとされているが、これは完了画面ページにGETメソッドでアクセスさせ、ブラウザの履歴にGETメソッドを残すことで、ページをリロードした際にForm内容の二重送信を防ぐためである。PRG(Post-Redirect-Get)パターンとか呼ばれる。

基本的にこのHTTPリダイレクトを用いるべきだが、サーバー側の設定が必要なため、フロントのみしか触れない開発者は別の手段をとる必要がある。

HTMLリダイレクト

HTMLのmeta要素を利用した方法。head内に例えば次のコードを入れる。

<head>
    <meta http-equiv="refresh" content="0; URL=http://www.xxx.com/" />
</head>

秒数に0を設定すれば瞬時にリダイレクトされるが、HTML内にリダイレクトコードを埋め込む以上、HTMLのパースが必要であり、ブラウザの履歴にも残る。なのでブラウザバックをしても瞬時に元のページに飛ばされてしまい、戻れなくなる。

また、HTMLのmetaタグを利用するため当然ながらHTML文書にしか使えず、画像やJsonのレスポンスはこの方法ではリダイレクトできない。

JavaScriptリダイレクト

JavaScriptのwindow.locationプロパティにURLを設定すると新しくページが読み込まれることを利用し、実質的にリダイレクトを実現する。例えば次のようなコードを入れる。

<script>
window.location = "http://www.xxx.com/";
</script>

HTMLリダイレクトと同じくHTMLのパースが行われ、ブラウザの履歴にも残る。よくアダルトサイトとかでブラウザバックしても戻れずに延々と元のページに飛ばされるのは、おそらくこれ。

JavaScriptを用いるため、当然JavaScriptが動かない環境では動作しない。今日日JavaScriptをブロックするようなブラウザは無いと思うが、Webスクレイピングのためにヘッドレスブラウザを使っている場合は問題になる。


参考サイト: developer.mozilla.org

正規表現についてのメモ

正規表現について勉強したことのメモ。 正規表現の確認にはrubularというサイトを使う。

文字自体を表す正規表現

  • .は「任意の1文字」を表す
  • \wは「英単語を構成する文字(半角英数字とアンダースコア)」を表す
  • \dは「半角数字1文字」を表す
  • \tは「タブ文字」を表す
  • \nは「改行文字」を表す
  • \sは「空白文字(スペース、タブ文字、改行文字)」を表す

文字の条件を指定する正規表現

  • a|bは「aまたはb」を表す
  • abc|defは「文字列abcまたは文字列def」を表す
  • [abc]は「aまたはbまたはcが1文字」を表す
  • [0-9]は「0から9が1文字」を表す
  • [a-z]は「aからzが1文字」(文字コードで範囲を判定している)
  • [^a]は「aを除く任意の1文字」を表す
    • [^abc]は「aとbとcを除く任意の1文字」を表す

 文字の数量を指定する正規表現

これ単独ではなく、文字または文字を表す正規表現と組み合わせて使う。

  • {n}は「直前の文字がn文字」を表す
  • {n,m}は「直前の文字がn文字以上かつm文字以下」を表す
  • {n,}は「直前の文字がn個以上」を表す
  • {,n}は「直前の文字がn個以下」を表す
  • ?は「直前の文字が0個または1個」を表す
  • +は「直前の文字が1個以上」を表す
  • *は「直前の文字が0個以上」を表す
  • *+は条件にマッチする最長の文字列を返す。最短マッチが欲しい場合は、*?+?とする

最後のはいわゆる貪欲なマッチ(greedy match)を防ぎ、怠惰なマッチ(lazy match)を行うための方法。例えば、我輩は猫である。名前はまだ無い。という文章から一文目を抽出したい場合、^.+。だと最長マッチの我輩は猫である。名前はまだ無い。が抽出されてしまうので、代わりに^.+?。を使う。

位置を表す正規表現

文字そのものではなく、文字の前後の位置を表す。

  • ^は「行頭」を表す
  • $は「行末」を表す
  • \bは単語の境界を表す
  • (?=a)は「aの直前の位置」を表す(肯定の先読み、Positive Lookahead)
    • (?=abc)は「abcという文字列の直前の位置」を表す
    • (?=a|b|c)は「aまたはbまたはcの直前の位置」を表す
  • (?!a)は「aでない文字の直前の位置」を表す(否定の先読み、Negative Lookahead)
  • (?<=a)「aの直後の位置」を表す(肯定の後読み、Positive Lookbehind)
  • (?<!a)「aでない文字の直後の位置」を表す(否定の後読み、Negaive Lookbehind)

先読み/後読みはちょっと複雑で覚えづらいが、利用場面は多い。例えば、aaa,bbb,ccc,dddのようなcsv文字列から3列目の値だけを置換したい場合、カンマそのものはキャプチャしたくないので、(?<=,)(.*?)(?=,)でカンマの間の文字をキャプチャし、\2で参照すれば良い。

キャプチャ関連の正規表現

  • ()で挟んだ部分はグループ化、またはキャプチャされる
  • (?:)でキャプチャを防ぐ
  • キャプチャした文字列は正規表現内でも\1\2といった連番で参照できる(後方参照)

後方参照は例えば、HTMLソースの中から<p></p>もしくは<span></span>タグで囲われている文字列を抽出したい場合、<(p|span)>.*<\/p|\/span>とすると、<p></span><span></p>のように間違ったタグで囲われたものまで抽出されてしまう。このような場合は<(p|span)>.*<\/\1>とすることで、正しく抽出することができる。(<\/\1>の初めのバックスラッシュは、閉じタグのスラッシュをエスケープするためのもの)