【GCP】GKEでrolling-updateデプロイ

目的

GKE(Google Container Engine)で動かしているデプロイメントのdockerイメージを更新したいという想定で、 rolling-updateしてみる。

rolling-updateとは

システム全体を同じ機能を持った複数のコンピュータで構成している場合によく用いられる方式で、システムを稼動状態のまま一台ずつ順番に更新する。更新中の機材は運用を停止しているが、他の機材でシステムの稼動状態を維持する。 IT用語辞典

サービスを稼働させたまま徐々にコンテナを新しくできる。 roll-backにも対応。

手段

  • v1を普通にデプロイ
  • v2をrolling-updateでデプロイ
  • roll-backしてみる

v1を普通にデプロイ

まずこんな感じの設定ファイルを準備した。 GCR(Google Container Registry)に予め登録しておいたkdy-project/kdy-image:v1を用いてkdy-deploymentをデプロイする。

# deployment-v1.yml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: kdy-deployment
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: kdy-deployment
    spec:
      containers:
      - name: test-server
        image: gcr.io/kdy-project/kdy-image:v1

デプロイ

$ kubectl create -f deployment-v1.yml
deployment "kdy-deployment" created

デプロイを確認

$ kubectl get deployment
NAME             DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
kdy-deployment   2         2         2            2           36s

OK

v2をrolling-updateでデプロイ

GCRにkdy-project/kdy-image:v2を登録して、 使用するdocker-imageをv2に変更。

# deployment-v2.yml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: kdy-deployment
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: kdy-deployment
    spec:
      containers:
      - name: test-server
        image: gcr.io/kdy-project/kdy-image:v2

rolling-updateを実行

$ kubectl apply -f deployment-v2.yml

挙動を見てみる

rolling-update前

$ kubectl get pods
NAME                              READY     STATUS    RESTARTS   AGE
kdy-deployment-4242080677-2p3xp   1/1       Running   0          11m
kdy-deployment-4242080677-sc06g   1/1       Running   0          11m

rolling-update直後

$ kubectl get pods
NAME                              READY     STATUS        RESTARTS   AGE
kdy-deployment-2046165440-3r77k   1/1       Running       0          42s
kdy-deployment-2046165440-rtlrs   1/1       Running       0          42s
kdy-deployment-4242080677-2p3xp   0/1       Terminating   0          13m
kdy-deployment-4242080677-sc06g   0/1       Terminating   0          13m

1分後

$ kubectl get pods
NAME                              READY     STATUS    RESTARTS   AGE
kdy-deployment-2046165440-3r77k   1/1       Running   0          1m
kdy-deployment-2046165440-rtlrs   1/1       Running   0          1m

v2に完全に移行された

roll-backしてみる

$ kubectl rollout undo deployment kdy-deployment
deployment "kdy-deployment" rolled back
$ kubectl get pods
NAME                              READY     STATUS    RESTARTS   AGE
kdy-deployment-4242080677-fl96r   1/1       Running   0          35s
kdy-deployment-4242080677-vhxvk   1/1       Running   0          35s

戻った!

感想

GCPいけてる

【python】tfidfで世界各国の特徴的なキーワードを抽出してみる

目的

wikiペディアで世界各国について書かれたテキストデータを分析して、 それぞれの国の特徴的なキーワードを抽出してみる。

手順

  • gensimの準備
  • テキストデータの前処理
  • tfidf_modelを生成
  • 実験してみる

gensimの準備

一般的に、tfidfを行うのにはgensimというパッケージが使われる。 また、テキストデータを扱うのにmecabも必要。

まずはmecabをインストー

$ brew install mecab mecab-ipadic

次にpythonmecabを使うのに必要なパッケージをインストー

$ pip install mecab-python3

gensimをインストー

$ pip install gensim

テキストデータの前処理

今回の前処理は形態素解析をして名詞だけを抽出する。

使うテキストデータはwikipediaから取得してきた、世界各国について書かれた説明文。

kdy.hatenablog.com

取得してきたデータは、

日本.txt
チリ.txt
タンザニア.txt

みたいな感じで保存してある。

これらのファイルからtextデータを読み込んで名詞のみを抽出する。

import MeCab

def extract_nouns(text):
    tagger = MeCab.Tagger("-Ochasen")
    lines = tagger.parse(text).split("\n")
    nouns = [l.split("\t")[0] for l in lines if l.find("名詞") != -1]
    return nouns

もっといいやり方ありそうな気がする。。

これで各国の名詞リストができた。 そして各国の名詞リストのリストを作る。

こんな感じ。

docs = [
    ["アメリカ", "テキサス", ...],
    ["日本", "歌舞伎", ...],
    ...
]

以上が前処理。

tfidf_modelを生成

流れは、

  1. docsからdictionary作成
  2. dictionaryとdocsからcorpusを作成
  3. corpusからtfidf_modelを生成
  4. tfidf_modelを用いて各ドキュメントにおける単語ごとのtfidf値を計算する
from gensim import corpora, models

# dictionaryは各単語にidが割り振られたもの
dictionary = corpora.Dictionary(docs)
# corpusは各ドキュメントにどの単語が何回含まれているかカウントしたもの
corpus = [dictionary.doc2bow(doc) for doc in docs]
# tfidf_modelを生成
tfidf = models.TfidfModel(corpus)
# 各ドキュメントにおける単語ごとのtfidf値を計算
tfidf_values = [tfidf[x] for x in corpus]

実験してみる

計算した値を見てみると、単語のidとtfidf値が入っている

tfidf_values[0] = [
    (0, 0.9132503121273562),
    (1, 0.0037954215381356105),
    (2, 0.001606577372285196),
    (3, 0.021411927133364605),
    (4, 0.032117890700046905),
    (5, 0.00042249072410897783),
    ...
]

これじゃあよく分からないので、分かりやすいように変換。

具体的には、

  • ドキュメント毎にtfidf値上位20件の単語を抽出する
  • 単語idを単語に変換
  • 国名をkeyにしたdictにする

をした。

topics = {}
for country_name, values in zip(countries, tfidf_values):
    top20_values = sorted(values, key=lambda x: -x[1])[:20]
    topics[country_name] = {dictionary.get(k): v for k, v in top20_values}

完成!

再び実験

まずは我らがジャポン。

In [16]: sorted(topics["日本"].items(), key=lambda x: -x[1])
Out[16]:
[('平成', 0.20201770858956575),
 ('昭和', 0.16806236299803576),
 ('', 0.16476257462909494),
 ('琉球', 0.16053789322834894),
 ('天皇', 0.14507211984602714),
 ('>', 0.13761147979713498),
 ('倭国', 0.1334366425394185),
 ('日本書紀', 0.1301688298870359),
 ('」-', 0.1188498012012067),
 ('九州大学', 0.11319028685829208),
 ('沖縄', 0.11181114820192148),
 ('列島', 0.11117604373803007),
 ('所蔵', 0.10984171641939662),
 ('朝鮮', 0.10601424142594291),
 ('明治', 0.09979678569121815),
 ('', 0.09960583630066722),
 ('アイヌ', 0.09884195743660629),
 ('ヤマト', 0.09621174382954828),
 ('政令', 0.0950129439446579),
 ('附属', 0.09497115891577232)]

ふむふむ

次にイタリア

In [18]: sorted(topics["イタリア"].items(), key=lambda x: -x[1])
Out[18]:
[('イタリア', 0.6384797473274801),
 ('Italy', 0.1706914498606513),
 ('ミラノ', 0.16002323424436057),
 ('マフィア', 0.12365853620609034),
 ('ローマ', 0.10780564559223393),
 ('フィレンツェ', 0.10680423990969665),
 ('シチリア', 0.10319396907929852),
 ('ナポリ', 0.09601394054661636),
 ('カラビニエリ', 0.09345370992098458),
 ('ヴェネト', 0.0816072462990336),
 ('トスカーナ', 0.0816072462990336),
 ('サルデーニャ', 0.0797266087738087),
 ('ジェノヴァ', 0.07180690936238167),
 ('カラブリア', 0.06675264994356041),
 ('北イタリア', 0.06400929369774423),
 ('トリノ', 0.06283104569208396),
 ('', 0.06183303037242836),
 ('アドリア海', 0.06019648196292414),
 ('ムッソリーニ', 0.058290890213595424),
 ('ローマ帝国', 0.056789343996432505)]

ふむふむ

最後に北朝鮮

In [35]: sorted(topics["朝鮮民主主義人民共和国"].items(), key=lambda x: -x[1])
Out[35]:
[('北朝鮮', 0.5856926222951162),
 ('朝鮮', 0.4146451308891748),
 ('朝鮮民主主義人民共和国', 0.3281019474905615),
 ('', 0.2031771107167868),
 ('日成', 0.19871240504291468),
 ('平壌', 0.1951639692385769),
 ('正日', 0.13313183269492182),
 ('韓国', 0.12182239506873974),
 ('朝鮮半島', 0.1010844240425654),
 ('労働党', 0.10008468935542882),
 ('', 0.08547320261973776),
 ('', 0.07291249805294742),
 ('大韓民国', 0.06973664606281749),
 ('', 0.06686809714616936),
 ('ウォン', 0.06032340867374195),
 ('朝鮮人民軍', 0.058448121670941294),
 ('中国', 0.05782157995245687),
 ('', 0.05763953131590944),
 ('委員', 0.05697402977425215),
 ('開城', 0.056889591000700304)]

核!!!( ゚д゚)

以上

tfidf、サクッと簡単にできてそれっぽい分析ができるな〜

【python】GridSearchとRandomizedSearch

目的

GridSearchとRandomizedSearchを比較する

アウトライン

  • 概念の違い
  • 実装の違い
  • 使い分け案

概念の違い

GridSearch

  • 有限数のグリッドを抜けなく調べる
    • 精度は高いが時間がかかりがち
  • 取り得るパラメータを予め決めて渡す
    • パラメータの値域が検討つかない時どうしよう問題
  • 離散変数のみ
    • 連続変数には対応していない

f:id:KDY:20170616211011p:plain

RandomizedSearch

  • 無限数のグリッドをランダムに調べる
  • 各パラメータに対して確率分布を与える
  • 連続変数に対応できる

f:id:KDY:20170616211040p:plain

実装の違い

XGBoostのパラメータ推定をそれぞれの方法でやってみる。

GridSearch

from xgboost import XGBClassifier
from sklearn.grid_search import GridSearchCV

param_grid = {
    "max_depth": list(range(1, 11)),
    "subsample": [x * 0.1 for x in range(5, 16)],
    "colsample_bytree": [x * 0.1 for x in range(5, 16)]
}
gs = GridSearchCV(XGBClassifier(), param_grid)

GridSearchはパラメータをリストで渡す。

RandomizedSearch

from scipy import stats
from xgboost import XGBClassifier
from sklearn.grid_search import RandomizedSearchCV

param_dist = {
    "max_depth": stats.randint(1,10),
    "subsample": stats.uniform(0.5, 1),
    "colsample_bytree": stats.uniform(0.5, 1)
}
rs = RandomizedSearchCV(XGBClassifier(), param_dist)

RandomizedSearchはパラメータをリストもしくは確率分布で渡す。 リストでも確率分布でも、よしなにランダムでサンプリングしてくれる。

確率分布はscipy.statsから選ぶ。 今回は、subsampleやcolsample_bytreeに0.5~1.5の値域の一様分布を採用してみた。

他にも色々と確率分布は選択できる。

例えば、

正規分布:stats.norm()
指数分布:stats.expon()

などなど、 パラメータに合わせて最適な確率分布を選ぶと良い。

【使える確率分布一覧】 https://docs.scipy.org/doc/scipy/reference/stats.html

使い分け案

連続変数のパラメータを推定したいときや、 パラメータがどんな値を取るのか全く検討がつかないときは、 RandomizedSearchを使ってみるとよいかも。

【python】seleniumのwebdriverでチョチョイとスクレイピング

目的

pythonwikiペディアのテキストデータをスクレイピングしてくる

手順

webdriverでページに遷移

アメリカ合衆国について書かれたページをスクレイピングしてみる

URL: https://ja.wikipedia.org/wiki/アメリカ

f:id:KDY:20170616010424p:plain

以下のようにしてwebdriverでプログラムからページにアクセスできる

from selenium import webdriver

driver = webdriver.PhantomJS()
driver.get("https://ja.wikipedia.org/wiki/{}".format("アメリカ"))

ちなみにselenium

pip install selenium

phantomjsは

brew install phantomjs

で一発でインストールできる

スクリーンショットを撮って動作を確認

期待通りにページに遷移できているか確かめないと不安なので、 スクリーンショットを撮って確かめてみる。

driver.save_screenshot("screenshot.png")

撮れたスクリーンショット

f:id:KDY:20170616011809p:plain

あれ。。アメリカ合衆国のページじゃない。。

原因

urlをquoteする必要があった

https://ja.wikipedia.org/wiki/アメリカ
↓
https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%A1%E3%83%AA%E3%82%AB%E5%90%88%E8%A1%86%E5%9B%BD

ので、ページ遷移部分を以下のように変更!

from urllib.parse import quote
from selenium import webdriver

driver = webdriver.PhantomJS()
driver.get("https://ja.wikipedia.org/wiki/{}".format(quote("アメリカ")))

これで無事に期待通りのページに遷移できた。

スクレイピングするときは、スクリーンショット取って確認しながら進めるのが結構重要だと思う。

テキストを取得

最後にページのテキストを取得する。

wikieペディアの場合、id=“content"のdiv要素のテキストを取得するのが良さそうだったので、idを指定してテキストを抽出した。

text = driver.find_element_by_id("content").text

でけた。

コードまとめ

# coding: utf-8

from urllib.parse import quote
from selenium import webdriver

def get_text_from_wiki(name):
    driver = webdriver.PhantomJS()
    driver.get("https://ja.wikipedia.org/wiki/{}".format(quote(name)))
    text = driver.find_element_by_id("content").text
    if text.find("ウィキペディアには現在この名前の項目はありません。") != -1:
        return None
    return text

奇天烈なnameを渡してしまいページが存在しなかった場合にはNoneを返すようにしといた。

以上

次回は世界各国のテキストデータを取得してきて、 コネコネ分析してみようと思う。

kdy.hatenablog.com