技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

Pandas の練習がてら、勉強会のトレンドを探ってみた

この辺の続きです。

white-azalea.hatenablog.jp

4月の勉強会の開催状況と、事前申し込みの状況を眺めてみたが正解。

勉強会情報を拾ってくる

前回の流れですが、2サイト以上でやるので、少しだけ汎用化を考えてみた。
調べてみたら、ATND と COMPASSAPI仕様がほぼ同一だった。

まずは共通の定義を用意して

import requests
import sys

class ClassRoom:
    def __init__(self, title, limit, accepted, waiting):
        def or_zero(v):
            if isinstance(v, int):
                return v
            return 0
        self.title = title
        self.limit = or_zero(limit)
        self.joins = or_zero(accepted) + or_zero(waiting)

    def __str__(self):
        return f'ClassRoom({self.title}, {self.limit}, {self.joins})'


class ClassRoomLoader:
    def load_class_rooms(self, month: str) -> list:
        pass


class AbstractCompassAtnd(ClassRoomLoader):
    def __init__(self, api_url: str, max_event_num: int):
        self.api_url = api_url
        self.max_event_num = max_event_num


    def create_request_parameter(self, target_month: str, top: int):
        return {"ym": target_month, "count": self.max_event_num, "format": "json", "start": top}


    def request_to_compass(self, target_month: str, top: int) -> requests.Response:
        params = self.create_request_parameter(target_month, top)
        query = '&'.join([f'{key}={params[key]}' for key in params.keys()])
        print(f'Request: {self.api_url}?{query}')
        return requests.get(self.api_url + '?' + query)


    def convert(self, event) -> ClassRoom:
        try:
            return ClassRoom(event['title'], event['limit'], event['accepted'], event['waiting'])
        except:
            print(f'Error: {sys.exc_info()[0]}')
            print(f'Value: {event}')
            raise Exception('Cannot convert')


    def load_request(self, target_month: str, top: int):
        class_rooms = []
        r_get = self.request_to_compass(target_month, top)
        print(r_get.status_code)

        respond_json = r_get.json()

        for ev in respond_json['events']:
            class_rooms.append(self.convert(ev))

        return (respond_json['results_returned'], class_rooms)


    def load_class_rooms(self, month: str) -> list:
        getted = self.max_event_num
        current_top = 1
        class_rooms = []
        request = 0

        while getted == self.max_event_num:  # 最大数以下が返ってきている == まだ次がある
            request += 1

            getted, tmp_rooms = self.load_request(month, current_top)
            current_top += self.max_event_num
            class_rooms.extend(tmp_rooms)

            print(
                f'Request: {request}, Return: {getted}, currentTop: {current_top}')

        return class_rooms

単純に読み込む。
尚、読み取った勉強会情報は、pickle でダンプしておく。

class AtndLoader(AbstractCompassAtnd):
    COMPASS_API = 'http://api.atnd.org/events/'
    WANT_EVENT_NUM = 100

    def __init__(self):
        super().__init__(AtndLoader.COMPASS_API, AtndLoader.WANT_EVENT_NUM)

    def convert(self, event) -> ClassRoom:
        return super().convert(event['event'])

    def create_request_parameter(self, target_month: str, top: int):
        return {"ym": target_month, "count": self.max_event_num, "format": "json", "start": top}



class CompassLoader(AbstractCompassAtnd):
    COMPASS_API = 'https://connpass.com/api/v1/event/'
    WANT_EVENT_NUM = 100

    def __init__(self):
        super().__init__(CompassLoader.COMPASS_API, CompassLoader.WANT_EVENT_NUM)

    def create_request_parameter(self, target_month: str, top: int):
        return {"ym": target_month, "count": self.max_event_num, "order": 1, "start": top}

# import pickle for backup dump.
import pickle
if __name__ == "__main__":
    class_rooms = AtndLoader().load_class_rooms("201904")
    class_rooms.extend(CompassLoader().load_class_rooms("201904"))
    with open('backup.pickle', 'wb') as f:
        pickle.dump(class_rooms, f)
    print("\n".join([str(s) for s in class_rooms]))
続きを読む

SQLAlchemy のさわりだけ

SQLAlchemy?

Python の O/R マッパーです。
スクリプト言語だし、たかが数行試すだけならSQL 文書いても…とは思ったのだけど、せっかくなので

検証は

  • python : 3.6.8 (anaconda)
  • sqlite3 インストール済み
  • SQLAlchemy : 1.3

インストール

$ pip install SQLAlchemy

スキーマ作成

まずはインストールして

# Importing SQLAlchemy
from sqlalchemy import create_engine, Column, Integer, String, Float
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.exc import NoResultFound

どうもテーブルがなければ作るという挙動らしい。

# Create Schema.
Base = declarative_base()

class ClassRoomInfo(Base):
    __tablename__ = 'class_room_info'
    id = Column(Integer, primary_key = True, autoincrement = True)
    title = Column(String(512))
    limit = Column(Integer)
    joins = Column(Integer)

    def __repr__(self):
        return "<ClassRoomInfo(id='%s', title='%s', limit='%s', joins='%s')>" % (self.id, self.title, self.limit, self.joins)

Base.metadata.create_all(engine)

セッションを作って操作

コネクションの類は全部セッションがどうにかしてくれるご様子。

# Database session start.
Session = sessionmaker(bind=engine)
session = Session()

# ここに何かやりたいこと

# End of session.
session.close()

INSERT / SELECT

さすが Python 見たままやなー

# Isertion to database.
session.add(ClassRoomInfo(title='ひゃっはー', limit=20, joins=10))
# 中略(500 件くらい挿入)

# commit
session.commit()

# Selection
id50 = session.query(ClassRoomInfo).filter_by(id = 50).one()
print(f'ID 50 = {id50}')

Anaconda パッケージのアップデート

といっても、そんな大したコマンドではないので、あくまでメモ

$ sudo conda update --all

Python 3.6.8 までが今日時点で出てたのでサクッと入れた。
問題は pandas とかアップデート入ってたから、前に書いたサンプルとか動かなくなってそうな点…

結構 Depricate あったからなぁ(汗

AmazonLinux2 に Java11 + Tomcat9 を Ansible2.7 で突っ込む

やろうと思ったきっかけは、そんなわりかし新しい構成でサーバ作ってと頼まれたので。

そして、やってみるとわかるのだけど、2019/03/21 時点でこれをリポジトリサポートしてるLinuxがそうそう無いと言うことに気づく。
強いて言えば Ubuntu18 が JDK11 だけサポートしてた。

でも Tomcat の Java11 対応って、Tomcat9 からなので、それなりに最新を入れざるを得なかったという問題があった。

前回もそんな流れで、JDK11 だけ先行して入ってりゃどうにかなんだろーと思ったためだ。

white-azalea.hatenablog.jp

しかし甘かった…Tomcat9 の問題が待ち構えていたのだ。

なので、今回は前回をパワーアップチャレンジした。

インストールの段階を分離

Playbook は本来やりたいことの単位で区切っておいた方が、可読性の問題から言ってもいいので、全体として 4 つの playbook を作った。

  • main.yml
    メインの playbook。他の platbook を呼び出すハブ
    • common.yml
      yum update だけしておく。
    • java.yml
      内部で Java バージョンを指定し、既存のインストールバージョン指定
    • tomcat.yml
      Tomcat のインストールとサービス登録までします。
続きを読む

AmazonLinux2 に ansible 2.7 で JDK11 をぶち込む

qiita.com

VM 作ったらまずは署名認証

VM を立ち上げて、 ssh を確認(今回は 192.168.56.2 にした)したら、署名で SSH できるようにする。
とりあえず mac 側で署名署名などを作る

$ cd ~/.ssh
$ ssh-keygen -t rsa -b 4096 -C "hoge@example.com" -f ~/.ssh/virtualbox
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /Users/xxxx/.ssh/virtualbox.
Your public key has been saved in /Users/xxxx/.ssh/virtualbox.pub.
The key fingerprint is:
SHA256:eKnjs4jiHsEktydZ7McMGULVDBQ/Wg3kHpeewVfGVmc hoge@example.com
The key's randomart image is:
+---[RSA 4096]----+
| .oo**o    .o.. E|
|   o =o+ . oo  o |
|... = * * ..     |
|+. = B * =       |
| o+ + * S        |
|  .o . o         |
| .    o          |
|. .. o..         |
|o+. . oo         |
+----[SHA256]-----+
$ ls 
known_hosts   virtualbox  virtualbox.pub

したら公開鍵を転送

$ scp virtualbox.pub ec2-user@192.168.56.2:~/
ec2-user@192.168.56.2's password: 
virtualbox.pub 
$ ssh ec2-user@192.168.56.2                            

authorized_keys に登録する

$ ssh ec2-user@192.168.56.2 
$ cd .ssh/
[ec2-user@amazonlinux .ssh]$ ls -l
合計 0
-rw------- 1 ec2-user ec2-user 0  319 21:22 authorized_keys
[ec2-user@amazonlinux .ssh]$ cat ~/virtualbox.pub >> authorized_keys 

ansible 設定

適当に ansible.cfg を作る。
曰く、ローカルディレクトリにおいとけば効くのだそうだ

[defaults]
host_key_checking=False
inventory=/Users/xxxx/workspace/VirtualBox/hosts
private_key_file=/Users/xxxx/workspace/VirtualBox/virtualbox

なんか勢いでディレクトリ作っちゃったので、署名の類も移動するか

mv ~/.ssh/virtualbox* /Users/xxxx/workspace/VirtualBox/

hosts も作って

[develop-server]
192.168.56.2

これでもうコマンドは通るはずなので

$ ansible all -u ec2-user -m ping
Enter passphrase for key '/Users/xxxx/workspace/VirtualBox/virtualbox': 
192.168.56.2 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

OK通ったね。

Amazon Corretto11 を DL させてインストールする

といっても、単純に Playbook を書くだけ。

- hosts:  develop-server
  user:   ec2-user
  sudo:   yes
  vars:
    corretto: https://d3pxv6yz143wms.cloudfront.net/11.0.2.9.3/java-11-amazon-corretto-devel-11.0.2.9-3.x86_64.rpm
    java_home: /usr/lib/jvm/java-11-amazon-corretto
  tasks:
    - name: Update all package
      yum:
        name: '*'
        state: latest
    - name: Install wget.
      yum:
        name: wget
        state: latest
    - name: Install corret.
      yum:
        name: '{{ corretto }}'
        state: present
    - name: Alternatives.
      alternatives:
        name: '{{ item }}'
        link: "/usr/bin/{{ item }}"
        path: "{{ java_home }}/bin/{{ item }}"
      with_items:
        - java
        - javac

実行すると

$ ansible-playbook java11.yml 
[DEPRECATION WARNING]: Instead of sudo/sudo_user, use become/become_user and make sure become_method is 'sudo' (default). This feature will be removed in version 2.9. Deprecation 
warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.

PLAY [develop-server] ******************************************************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************************************************************
Enter passphrase for key '/Users/xxxx/workspace/VirtualBox/virtualbox': 
ok: [192.168.56.2]

TASK [Update all package] **************************************************************************************************************************************************************
ok: [192.168.56.2]

TASK [Install wget.] *******************************************************************************************************************************************************************
ok: [192.168.56.2]

TASK [Install corret.] *****************************************************************************************************************************************************************
ok: [192.168.56.2]

TASK [Alternatives.] *******************************************************************************************************************************************************************
ok: [192.168.56.2] => (item=java)
ok: [192.168.56.2] => (item=javac)

PLAY RECAP *****************************************************************************************************************************************************************************
192.168.56.2               : ok=5    changed=0    unreachable=0    failed=0   

問題なく終了し、

$ ssh ec2-user@192.168.56.2
ec2-user@192.168.56.2's password: 
Last login: Tue Mar 19 23:55:37 2019 from 192.168.56.1

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
[ec2-user@amazonlinux ~]$ java --version
openjdk 11.0.2 2019-01-15 LTS
OpenJDK Runtime Environment Corretto-11.0.2.9.3 (build 11.0.2+9-LTS)
OpenJDK 64-Bit Server VM Corretto-11.0.2.9.3 (build 11.0.2+9-LTS, mixed mode)

完了。
調べながらでも 2h で済むか…

Compass から勉強会情報を拾う

Compass から勉強会名と参加率を拾ってみる

まず何がしたかったかというと、勉強会のタイトルを形態素解析して、その募集人数と参加者(+参加待ち)の統計とったら、勉強会界隈のトレンドが追いやすくなるんじゃね?と思ったのが始まり。
で、その為の第一歩として、Compass から勉強会情報を拾ってくる処理をまずは考えた。

まずは requests でAPIを叩いてみる

COMPASSAPI仕様 が公開されているので、そこから拾ってみる。

import requests

パラメータ組み立てて

compass_rest_api = 'https://connpass.com/api/v1/event/'
params = {"ym":"201904", "count":100, "order":3,"start":1}  # 4 月、100 件(max)、着信順、先頭から

query = '&'.join([f'{key}={params[key]}' for key in params.keys()])

投げてみる。

r_get = requests.get(compass_rest_api + '?' + query)
r_get.status_code

200

OKOK
中身を拾うと

r_get.json()

応答が

    {'results_returned': 100,
     'events': [{'event_url': 'https://aaa.connpass.com/event/124784/',
       'event_type': 'participation',
       'owner_nickname': 'Wakamatz',
       'series': {'url': 'https://aaa.connpass.com/',
        'id': 1886,
        'title': 'Mathematik und Programmierung'},
       'updated_at': '2019-03-19T00:04:09+09:00',
       'lat': '34.775820300000',
       'started_at': '2019-04-14T13:30:00+09:00',
       'hash_tag': 'functional_programming, data_structures',
       'title': '純粋関数型データ構造読書会 第23回',
       'event_id': 124784,
       'lon': '135.544884900000',
       /* 中略 */
       'accepted': 4,
       'ended_at': '2019-04-14T18:00:00+09:00',
       'place': 'FG-Space'}],
     'results_start': 1,
     'results_available': 511}

一度に 100 件縛りがあるので、そこだけが問題だろうか?
とりあえず 100 件でリクエストしてみて、100 件未満になるまでループしてリクエストしてみる。

実際に勉強会データ(名前と参加者数)を拾ってみた

尚、やってみてわかったのだけど、ソート順がデフォルト以外だと、タイムアウトでコケるようだ。
多分インデックスが不足してるか何かしてるんだろうなーと思いつつ。
(いや、それ以外はそこそこ高速で返ってくる事を考えれば、RDB じゃなくて Mongo とか Cassandra とか使ってそう)

class ClassRoom:
    def __init__(self, title, limit, accepted, waiting):
        def or_zero(v):
            if isinstance(v, int):
                return v
            return 0
        self.title = title
        self.limit = or_zero(limit)
        self.joins = or_zero(accepted) + or_zero(waiting)
        self.score = self.joins / self.limit if self.limit != 0 else 1
        
    def __str__(self):
        return f'ClassRoom({self.title}, {self.limit}, {self.joins}, {self.score})'

def convert(event):
    return ClassRoom(event['title'], event['limit'], event['accepted'], event['waiting'])


def request_to_compass(start):
    params = {"ym":"201904", "count":WANT_EVENT, "order":1,"start":start}
    query = '&'.join([f'{key}={params[key]}' for key in params.keys()])
    return requests.get(compass_rest_api + '?' + query)


WANT_EVENT = 100
getted = WANT_EVENT
current_start = 1
class_rooms = []

while getted == WANT_EVENT:
    r_get = request_to_compass(current_start)
    respond_json = r_get.json()

    getted = respond_json['results_returned']
    current_start += WANT_EVENT
    print(f'{getted}, {current_start}')

    for ev in respond_json['events']:
        class_rooms.append(convert(ev))

# least 100
r_get = r_get = request_to_compass(current_start)
respond_json = r_get.json()
for ev in respond_json['events']:
    class_rooms.append(convert(ev))
100, 101
100, 201
100, 301
100, 401
100, 501
14, 601
[str(s) for s in class_rooms]
続きを読む

教師あり学習アルゴリズム

pandas とか matplotlib とか色々弄っていく。
インプットはコレ

Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎

Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎

全部は書かない。

先ずは必要なライブラリのインポート

Python で始める機械学習 という本の第二章先頭なのだけど、いきなり暗黙 import じみた事やってて初見殺しにも程があんだろ(汗

%matplotlib inline
from IPython.display import set_matplotlib_formats, display
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import mglearn

データの種類別散布図

で、改めてデータロード

X, y = mglearn.datasets.make_forge()
print(f"X.shape: {X.shape}")
X.shape: (26, 2)

そしてグラフ化
ちなみに、mglearn.discrete_scatter は、どうも pyplot の scartter を第3引数のデータごとに記号を変えて呼び出してくれるものらしい。

参考: テラテイルでの質問

mglearn.discrete_scatter(X[:, 0], X[:, 1], y)

plt.legend(["Class 0", "Class 1"], loc=4)
plt.xlabel("First feature")
plt.ylabel("Second feature")
Text(0, 0.5, 'Second feature')

[f:id:white-azalea:20190318232637p:plain]

それぞれのデータの中身を見ていく。
y はデータの種類を表して

y
array([1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0,
       0, 0, 1, 0])

X は、その座標を示してる。
こっちは x, y 座標のペアだ。

X

array([[ 9.96346605, 4.59676542], [11.0329545 , -0.16816717], [11.54155807, 5.21116083], [ 8.69289001, 1.54322016], [ 8.1062269 , 4.28695977], 中略 [11.563957 , 1.3389402 ]])

もっと他にも散布図

wave データセットというらしい。

他の散布図も作画してみる。
単純に x, y の配列だけを指定すると、データ分類のないシンプルな散布図ができる。

X, y = mglearn.datasets.make_wave(n_samples = 40)

plt.plot(X, y, 'o')
plt.ylim(-3, 3)
plt.xlabel('Feature')
plt.ylabel('Target')
Text(0, 0.5, 'Target')

[f:id:white-azalea:20190318232752p:plain]

y
array([-0.44822073,  0.33122576,  0.77932073,  0.03497884, -1.38773632,
       -2.47196233, -1.52730805,  1.49417157,  1.00032374,  0.22956153,
       -1.05979555,  0.7789638 ,  0.75418806, -1.51369739, -1.67303415,
       -0.90496988,  0.08448544, -0.52734666, -0.54114599, -0.3409073 ,
        0.21778193, -1.12469096,  0.37299129,  0.09756349, -0.98618122,
        0.96695428, -1.13455014,  0.69798591,  0.43655826, -0.95652133,
        0.03527881, -2.08581717, -0.47411033,  1.53708251,  0.86893293,
        1.87664889,  0.0945257 , -1.41502356,  0.25438895,  0.09398858])
X
array([[-0.75275929],
       [ 2.70428584],
       [ 1.39196365],
       [ 0.59195091],
       [-2.06388816],
       [-2.06403288],
       [-2.65149833],
       [ 2.19705687],
       [ 0.60669007],
       [ 1.24843547],
       /* 中略 */
       [ 1.10539816],
       [-0.35908504]])

乳癌データセット

恐ろしいなまえだなおい…マジか O'Reily 先生よ

from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
print(f"Cancer keys: {cancer.keys()}")
Cancer keys: dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names', 'filename'])
cancer.data.shape
(569, 30)
[f'{n}: {v}' for n, v in zip(cancer.target_names, np.bincount(cancer.target))]
['malignant: 212', 'benign: 357']
cancer.feature_names
array(['mean radius', 'mean texture', 'mean perimeter', 'mean area',
       'mean smoothness', 'mean compactness', 'mean concavity',
       'mean concave points', 'mean symmetry', 'mean fractal dimension',
       'radius error', 'texture error', 'perimeter error', 'area error',
       'smoothness error', 'compactness error', 'concavity error',
       'concave points error', 'symmetry error',
       'fractal dimension error', 'worst radius', 'worst texture',
       'worst perimeter', 'worst area', 'worst smoothness',
       'worst compactness', 'worst concavity', 'worst concave points',
       'worst symmetry', 'worst fractal dimension'], dtype='<U23')

ボストンデータセット

1970 年代のボストン郊外の住宅価格の中央値、犯罪率、チャールズ川からの距離、高速道路への利便性。

from sklearn.datasets import load_boston

boston = load_boston()
boston.data.shape
(506, 13)

さらにこのデータをオライリー先生は拡張し、特徴量を 104 まで増やしたデータセットを作ったという。
13 の 2 組み合わせで、重複ありの 91 種類が追加されてるらしい。

X, y = mglearn.datasets.load_extended_boston()
X.shape
(506, 104)

K-NN アルゴリズム

これは不明なデータ(図では ☆)を、そこから「最も近いデータと多分同じ仲間なのだろう」とか、その捜索範囲で近い点 N 個の平均で、「多分○○の仲間なのだろう」という様な推測をするアルゴリズム
直近1個で決めるとこんな感じ。

尚、星の分類は色で示してる。

mglearn.plots.plot_knn_classification(n_neighbors=1)

f:id:white-azalea:20190318232934p:plain

これを直近3点で考えた時の分類。

mglearn.plots.plot_knn_classification(n_neighbors=3)

f:id:white-azalea:20190318233000p:plain

この様なアルゴリズムを scikit-learn でやってみる。
先ずはデータの読み込みと、学習/試験データへの分離。

from sklearn.model_selection import train_test_split

X, y = mglearn.datasets.make_forge()

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state = 0)

そして、K 近傍法アルゴリズムの設定と学習。

from sklearn.neighbors import KNeighborsClassifier

clf = KNeighborsClassifier(n_neighbors=3)
clf.fit(X_train, y_train)
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
           metric_params=None, n_jobs=None, n_neighbors=3, p=2,
           weights='uniform')

さて、正答率は?

clf.score(X_test, y_test)
0.8571428571428571
続きを読む