Amazonの過去の売れ筋商品データ(月間)を取得する

Amazonの商品コードを大量に集める必要があり、いろいろ方法を調べていると、次のようなものを見つけた。なかなか有用だと思うので取得方法を纏めておく。

過去の売れ筋商品データ(月間)

f:id:ruby-U:20171111085507p:plain

対象年月と商品カテゴリを選択してボタンを押せばデータへのリンクが表示される。

f:id:ruby-U:20171111085512p:plain

データのURLは固定されているようで、認証なども必要ない。好きなように取ってくればよさそうだ。

中身は普通のTSVで、1行のヘッダあり。なお、これは2011-01の本カテゴリのデータ。懐かしい…。 f:id:ruby-U:20171111090205p:plain

2011-01から2017-09までのデータを集めたものをダウンロードできるようにしておくので、面倒な人はこちら(Google Drive)

なお、注意点として、カメラカテゴリの他にも一部データに抜けがある。DVDカテゴリの2011-11, 2011-12など。恐らく元データが存在しない。

以下のスクリプトで作成した。

gist.github.com

GPUが使えて仮想環境を認識するJupyter環境のDockerfileを書いた

Jupyterが立ち上げるだけでプログラミング環境ができあがる各種の便利なDockerイメージを出してくれているが、GPUが使えない。ので、無理やり使えるようにしてみた。 Dockerfileの継承元のFROMを書き換える必要があったので強引な感じになった。 以下ではcudaのDockerイメージを元にしているが、jupyterを元にcudaをインストールしたほうが完了までの時間が短いはず。

なお、TensorflowがGPUの使えるJupyter入りのDockerイメージをリリースしてくれている。なので、Jupyter本家に拘らなければこんなことをする必要はほぼない…が、まあせっかく書いたので。

適当に継ぎ接ぎしてDockerfileを作る

mkdir ~/Documents/cuda-based-jupyter-datascience-notebook
cd ~/Documents/cuda-based-jupyter-datascience-notebook

wget https://raw.githubusercontent.com/jupyter/docker-stacks/master/base-notebook/Dockerfile -O base-notebook
wget https://raw.githubusercontent.com/jupyter/docker-stacks/master/minimal-notebook/Dockerfile -O minimal-notebook
wget https://raw.githubusercontent.com/jupyter/docker-stacks/master/scipy-notebook/Dockerfile -O scipy-notebook
wget https://raw.githubusercontent.com/jupyter/docker-stacks/master/datascience-notebook/Dockerfile -O datascience-notebook

cat << "EOF" > Dockerfile
# Merging nvidia/cuda and jupyter/datascience-notebook DockerFiles.
FROM nvidia/cuda:8.0-cudnn6-devel-ubuntu16.04

MAINTAINER rubyu

EOF
cat << "EOF" >> Dockerfile

# jupter/base-notebook
EOF
sed -e '1,8 s/^/# /g' base-notebook >> Dockerfile
cat << "EOF" >> Dockerfile

# jupter/minimal-notebook
EOF
sed -e '1,6 s/^/# /g' minimal-notebook >> Dockerfile
cat << "EOF" >> Dockerfile

# jupter/scipy-notebook
EOF
sed -e '1,5 s/^/# /g' scipy-notebook >> Dockerfile
cat << "EOF" >> Dockerfile

# jupter/datascience-notebook
EOF
sed -e '1,5 s/^/# /g' datascience-notebook >> Dockerfile
cat << "EOF" >> Dockerfile

USER $NB_USER

RUN jupyter notebook --generate-config && \
    jupyter serverextension enable --py jupyterlab --sys-prefix && \
    pip install environment_kernels && \
    echo "c.NotebookApp.kernel_spec_manager_class = 'environment_kernels.EnvironmentKernelSpecManager'" >> ~/.jupyter/jupyter_notebook_config.py
EOF

最後のRUNの部分がJupyter Labとconda createで作る仮想環境のための設定。

Dockerイメージ作成に必要なファイルをダウンロードしておく

wget https://github.com/jupyter/docker-stacks/raw/master/base-notebook/fix-permissions -O fix-permissions
wget https://github.com/jupyter/docker-stacks/raw/master/base-notebook/jupyter_notebook_config.py -O jupyter_notebook_config.py
wget https://github.com/jupyter/docker-stacks/raw/master/base-notebook/start-notebook.sh -O start-notebook.sh
wget https://github.com/jupyter/docker-stacks/raw/master/base-notebook/start-singleuser.sh -O start-singleuser.sh
wget https://github.com/jupyter/docker-stacks/raw/master/base-notebook/start.sh -O start.sh
sudo chmod ugo+x fix-permissions
sudo chmod ugo+x jupyter_notebook_config.py
sudo chmod ugo+x start-notebook.sh
sudo chmod ugo+x start-singleuser.sh
sudo chmod ugo+x start.sh

実行できる権限がないとダメ。ここではwgetしているが、普通はgit cloneするのでハマることはないはず。

コンテナを立ち上げる

sudo nvidia-docker build -t rubyu/datascience-notebook-gpu:1.0 .

sudo nvidia-docker run -d --name notebook-gpu -p 8888:8888 --restart=always -v /storage/samba:/home/jovyan rubyu/datascience-notebook-gpu:1.0 start.sh jupyter lab --NotebookApp.token='XXXX'

これで8888ポートでJupyter Labが起動している。

本題には関係ない話ですが、Jupyterのhome(/home/jovyan)をまるごとホストのディレクトリに割り当てて、かつそれをSamba経由でアクセスできるようにするのが好きです。Windowsで処理する必要がある時にいちいち転送する必要がなくなるので。なんやかんやでGUIWindowsが使いやすい(個人の感想です)

アタッチしてconda createで仮想環境を作る

docker exec -it notebook-gpu /bin/bash

conda create -n your-new-envronment python=3.5 jupyter tensorflow-gpu && conda clean -tipsy

作成された仮想環境はJupyterから見えて、カーネルとして選択することができる。

Dockerfile

# Merging nvidia/cuda and jupyter/datascience-notebook DockerFiles.
FROM nvidia/cuda:8.0-cudnn6-devel-ubuntu16.04

MAINTAINER rubyu


# jupter/base-notebook
# # Copyright (c) Jupyter Development Team.
# # Distributed under the terms of the Modified BSD License.
# 
# # Ubuntu 16.04 (xenial) from 2017-07-23
# # https://github.com/docker-library/official-images/commit/0ea9b38b835ffb656c497783321632ec7f87b60c
# FROM ubuntu@sha256:84c334414e2bfdcae99509a6add166bbb4fa4041dc3fa6af08046a66fed3005f
# 
# MAINTAINER Jupyter Project <jupyter@googlegroups.com>

USER root

# Install all OS dependencies for notebook server that starts but lacks all
# features (e.g., download as all possible file formats)
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && apt-get -yq dist-upgrade \
 && apt-get install -yq --no-install-recommends \
    wget \
    bzip2 \
    ca-certificates \
    sudo \
    locales \
    fonts-liberation \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*

RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \
    locale-gen

# Install Tini
RUN wget --quiet https://github.com/krallin/tini/releases/download/v0.10.0/tini && \
    echo "1361527f39190a7338a0b434bd8c88ff7233ce7b9a4876f3315c22fce7eca1b0 *tini" | sha256sum -c - && \
    mv tini /usr/local/bin/tini && \
    chmod +x /usr/local/bin/tini

# Configure environment
ENV CONDA_DIR=/opt/conda \
    SHELL=/bin/bash \
    NB_USER=jovyan \
    NB_UID=1000 \
    NB_GID=100 \
    LC_ALL=en_US.UTF-8 \
    LANG=en_US.UTF-8 \
    LANGUAGE=en_US.UTF-8
ENV PATH=$CONDA_DIR/bin:$PATH \
    HOME=/home/$NB_USER

ADD fix-permissions /usr/local/bin/fix-permissions
# Create jovyan user with UID=1000 and in the 'users' group
# and make sure these dirs are writable by the `users` group.
RUN useradd -m -s /bin/bash -N -u $NB_UID $NB_USER && \
    mkdir -p $CONDA_DIR && \
    chown $NB_USER:$NB_GID $CONDA_DIR && \
    fix-permissions $HOME && \
    fix-permissions $CONDA_DIR

USER $NB_USER

# Setup work directory for backward-compatibility
RUN mkdir /home/$NB_USER/work && \
    fix-permissions /home/$NB_USER

# Install conda as jovyan and check the md5 sum provided on the download site
ENV MINICONDA_VERSION 4.3.21
RUN cd /tmp && \
    wget --quiet https://repo.continuum.io/miniconda/Miniconda3-${MINICONDA_VERSION}-Linux-x86_64.sh && \
    echo "c1c15d3baba15bf50293ae963abef853 *Miniconda3-${MINICONDA_VERSION}-Linux-x86_64.sh" | md5sum -c - && \
    /bin/bash Miniconda3-${MINICONDA_VERSION}-Linux-x86_64.sh -f -b -p $CONDA_DIR && \
    rm Miniconda3-${MINICONDA_VERSION}-Linux-x86_64.sh && \
    $CONDA_DIR/bin/conda config --system --prepend channels conda-forge && \
    $CONDA_DIR/bin/conda config --system --set auto_update_conda false && \
    $CONDA_DIR/bin/conda config --system --set show_channel_urls true && \
    $CONDA_DIR/bin/conda update --all --quiet --yes && \
    conda clean -tipsy && \
    fix-permissions $CONDA_DIR

# Install Jupyter Notebook and Hub
RUN conda install --quiet --yes \
    'notebook=5.2.*' \
    'jupyterhub=0.8.*' \
    'jupyterlab=0.28.*' \
    && conda clean -tipsy && \
    fix-permissions $CONDA_DIR

USER root

EXPOSE 8888
WORKDIR $HOME

# Configure container startup
ENTRYPOINT ["tini", "--"]
CMD ["start-notebook.sh"]

# Add local files as late as possible to avoid cache busting
COPY start.sh /usr/local/bin/
COPY start-notebook.sh /usr/local/bin/
COPY start-singleuser.sh /usr/local/bin/
COPY jupyter_notebook_config.py /etc/jupyter/
RUN fix-permissions /etc/jupyter/

# Switch back to jovyan to avoid accidental container runs as root
USER $NB_USER

# jupter/minimal-notebook
# # Copyright (c) Jupyter Development Team.
# # Distributed under the terms of the Modified BSD License.
# 
# FROM jupyter/base-notebook
# 
# MAINTAINER Jupyter Project <jupyter@googlegroups.com>

USER root

# Install all OS dependencies for fully functional notebook server
RUN apt-get update && apt-get install -yq --no-install-recommends \
    build-essential \
    emacs \
    git \
    inkscape \
    jed \
    libsm6 \
    libxext-dev \
    libxrender1 \
    lmodern \
    pandoc \
    python-dev \
    texlive-fonts-extra \
    texlive-fonts-recommended \
    texlive-generic-recommended \
    texlive-latex-base \
    texlive-latex-extra \
    texlive-xetex \
    vim \
    unzip \
    && apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# Switch back to jovyan to avoid accidental container runs as root
USER $NB_USER

# jupter/scipy-notebook
# # Copyright (c) Jupyter Development Team.
# # Distributed under the terms of the Modified BSD License.
# FROM jupyter/minimal-notebook
# 
# MAINTAINER Jupyter Project <jupyter@googlegroups.com>

USER root

# libav-tools for matplotlib anim
RUN apt-get update && \
    apt-get install -y --no-install-recommends libav-tools && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

USER $NB_USER

# Install Python 3 packages
# Remove pyqt and qt pulled in for matplotlib since we're only ever going to
# use notebook-friendly backends in these images
RUN conda install --quiet --yes \
    'nomkl' \
    'ipywidgets=7.0*' \
    'pandas=0.19*' \
    'numexpr=2.6*' \
    'matplotlib=2.0*' \
    'scipy=0.19*' \
    'seaborn=0.7*' \
    'scikit-learn=0.18*' \
    'scikit-image=0.12*' \
    'sympy=1.0*' \
    'cython=0.25*' \
    'patsy=0.4*' \
    'statsmodels=0.8*' \
    'cloudpickle=0.2*' \
    'dill=0.2*' \
    'numba=0.31*' \
    'bokeh=0.12*' \
    'sqlalchemy=1.1*' \
    'hdf5=1.8.17' \
    'h5py=2.6*' \
    'vincent=0.4.*' \
    'beautifulsoup4=4.5.*' \
    'protobuf=3.*' \
    'xlrd'  && \
    conda remove --quiet --yes --force qt pyqt && \
    conda clean -tipsy && \
    # Activate ipywidgets extension in the environment that runs the notebook server
    jupyter nbextension enable --py widgetsnbextension --sys-prefix && \
    fix-permissions $CONDA_DIR

# Install facets which does not have a pip or conda package at the moment
RUN cd /tmp && \
    git clone https://github.com/PAIR-code/facets.git && \
    cd facets && \
    jupyter nbextension install facets-dist/ --sys-prefix && \
    rm -rf facets && \
    fix-permissions $CONDA_DIR

# Import matplotlib the first time to build the font cache.
ENV XDG_CACHE_HOME /home/$NB_USER/.cache/
RUN MPLBACKEND=Agg python -c "import matplotlib.pyplot" && \
    fix-permissions /home/$NB_USER

USER $NB_USER

# jupter/datascience-notebook
# # Copyright (c) Jupyter Development Team.
# # Distributed under the terms of the Modified BSD License.
# FROM jupyter/scipy-notebook
# 
# MAINTAINER Jupyter Project <jupyter@googlegroups.com>

USER root

# R pre-requisites
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
    fonts-dejavu \
    gfortran \
    gcc && apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# Julia dependencies
# install Julia packages in /opt/julia instead of $HOME
ENV JULIA_PKGDIR=/opt/julia

RUN . /etc/os-release && \
    echo "deb http://ppa.launchpad.net/staticfloat/juliareleases/ubuntu $VERSION_CODENAME main" > /etc/apt/sources.list.d/julia.list && \
    apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3D3D3ACC && \
    apt-get update && \
    apt-get install -y --no-install-recommends \
    julia && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* && \
    # Show Julia where conda libraries are \
    echo "push!(Libdl.DL_LOAD_PATH, \"$CONDA_DIR/lib\")" >> /usr/etc/julia/juliarc.jl && \
    # Create JULIA_PKGDIR \
    mkdir $JULIA_PKGDIR && \
    chown $NB_USER $JULIA_PKGDIR && \
    fix-permissions $JULIA_PKGDIR

USER $NB_USER

# R packages including IRKernel which gets installed globally.
RUN conda config --system --add channels r && \
    conda install --quiet --yes \
    'rpy2=2.8*' \
    'r-base=3.3.2' \
    'r-irkernel=0.7*' \
    'r-plyr=1.8*' \
    'r-devtools=1.12*' \
    'r-tidyverse=1.0*' \
    'r-shiny=0.14*' \
    'r-rmarkdown=1.2*' \
    'r-forecast=7.3*' \
    'r-rsqlite=1.1*' \
    'r-reshape2=1.4*' \
    'r-nycflights13=0.2*' \
    'r-caret=6.0*' \
    'r-rcurl=1.95*' \
    'r-crayon=1.3*' \
    'r-randomforest=4.6*' && \
    conda clean -tipsy && \
    fix-permissions $CONDA_DIR

# Add Julia packages
# Install IJulia as jovyan and then move the kernelspec out
# to the system share location. Avoids problems with runtime UID change not
# taking effect properly on the .local folder in the jovyan home dir.
RUN julia -e 'Pkg.init()' && \
    julia -e 'Pkg.update()' && \
    julia -e 'Pkg.add("HDF5")' && \
    julia -e 'Pkg.add("Gadfly")' && \
    julia -e 'Pkg.add("RDatasets")' && \
    julia -e 'Pkg.add("IJulia")' && \
    # Precompile Julia packages \
    julia -e 'using HDF5' && \
    julia -e 'using Gadfly' && \
    julia -e 'using RDatasets' && \
    julia -e 'using IJulia' && \
    # move kernelspec out of home \
    mv $HOME/.local/share/jupyter/kernels/julia* $CONDA_DIR/share/jupyter/kernels/ && \
    chmod -R go+rx $CONDA_DIR/share/jupyter && \
    rm -rf $HOME/.local && \
    fix-permissions $JULIA_PKGDIR $CONDA_DIR/share/jupyter


USER $NB_USER

RUN jupyter notebook --generate-config && \
    jupyter serverextension enable --py jupyterlab --sys-prefix && \
    pip install environment_kernels && \
    echo "c.NotebookApp.kernel_spec_manager_class = 'environment_kernels.EnvironmentKernelSpecManager'" >> ~/.jupyter/jupyter_notebook_config.py

怪しいダイス(プロットしてみる)

ダイスを振った記録があるとして、その一部で、極端に偏りのあるダイス(この例では奇遇の偏り)が使われているのではという疑念を持ったときに、どうして調べればいいか。

前回 怪しいダイス(hmmlearn) は、隠れマルコフモデルで調べてるんだけど…というお話だったので、hmmlearnを使って、怪しい痕跡が見えるなーということを確かめました。

ただ、思ったようには収束しない場合があり、もうちょっとわかりやすい判別方法がありそうなので、今回は単純に二項分布を使って調べてみます。

ただし、ここで注意点として、このダイスを振った記録は、不正でありつつ、かつ全体では偏りを調節してあるということです。(単に偏りがあるだけなら普通に検定を行えばいい)

やっていることは単純で、

  1. ダイスを振った記録について、ダイスのそれぞれの面ごとに、ある面が出現していない繋がりを求める。

  2. それぞれの繋がりについて、確率を、二項分布 Bi(n, p) n=繋がりの長さ, p=5/6、確率密度関数に x=n として求める。 要するに、ダイスのある面が出る確率は1/6なので、ダイスのある面が出ない確率は5/6になり、それがn回続く確率を求めます。

  3. 必要に応じて、それぞれの面について計算した値を、ダイスを振った記録の位置ごとに足したりする。

ランダムに生成したデータと怪しいデータで若干違いが見えるようになりました。

import itertools
import numpy as np


np.random.seed(1)

nf = 6
s = np.random.randint(0, nf, 200)
ns = len(s)
state = [0 for _ in range(ns)]
result = [[0 for _ in range(ns)] for _ in range(nf)]

i = 0
while(True):
    v = s[i]
    if i > 0:
        for k in range(nf):
            if k == v:
                d = i - state[k]
                score = ss.binom(d, (nf-1.)/nf).pmf(d)
                for j in range(state[k], i):
                    result[k][j] += np.log(score)
                state[k] = i
    i += 1
    if ns == i:
        for k in range(nf):
            d = i - state[k]
            score = ss.binom(d, (nf-1.)/nf).pmf(d)
            for j in range(state[k], i):
                result[k][j] += np.log(score)
        break

%matplotlib inline
import matplotlib.pyplot as plt
plt.title("logP of faces (random generated data)")
for res in result:
    plt.plot(res)

f:id:ruby-U:20171001182048p:plain

import itertools
import numpy as np


np.random.seed(1)

nf = 6
s = np.random.randint(0, nf, 200)
ns = len(s)
state = [0 for _ in range(ns)]
result = [0 for _ in range(ns)]

i = 0
while(True):
    v = s[i]
    if i > 0:
        for k in range(nf):
            if k == v:
                d = i - state[k]
                score = ss.binom(d, (nf-1.)/nf).pmf(d)
                for j in range(state[k], i):
                    result[j] += np.log(score)
                state[k] = i
    i += 1
    if ns == i:
        for k in range(nf):
            d = i - state[k]
            score = ss.binom(d, (nf-1.)/nf).pmf(d)
            for j in range(state[k], i):
                result[j] += np.log(score)
        break
        
%matplotlib inline
import matplotlib.pyplot as plt
plt.title("logP (random generated data)")
plt.plot(result)

f:id:ruby-U:20171001182105p:plain

import itertools
import numpy as np


np.random.seed(1)

nf = 6
s = np.random.randint(0, nf, 200)
ns = len(s)
state = [0 for _ in range(ns)]
result = [[0 for _ in range(ns)] for _ in range(nf)]

i = 0
while(True):
    v = s[i]
    if i > 0:
        for k in range(nf):
            if k == v:
                d = i - state[k]
                score = ss.binom(d, (nf-1.)/nf).pmf(d)
                for j in range(state[k], i):
                    result[k][j] += np.log(score)
                state[k] = i
    i += 1
    if ns == i:
        for k in range(nf):
            d = i - state[k]
            score = ss.binom(d, (nf-1.)/nf).pmf(d)
            for j in range(state[k], i):
                result[k][j] += np.log(score)
        break
        
import numpy as np
result = [np.array(result[0])+np.array(result[2])+np.array(result[4]), 
          np.array(result[1])+np.array(result[3])+np.array(result[5])]

%matplotlib inline
import matplotlib.pyplot as plt
plt.title("logP of odd and even (random generated data)")
for res in result:
    plt.plot(res)

f:id:ruby-U:20171001182117p:plain

import itertools
import numpy as np


np.random.seed(1)

nf = 6
x = "12442552156235244611513665441521334522422516131461324351224626256625324354313241416144163515363423135135313531535155116446122436632362242642626642654256461421553232365354361461451264546335353161532646"
s = [int(s)-1 for s in x]
ns = len(s)
state = [0 for _ in range(ns)]
result = [[0 for _ in range(ns)] for _ in range(nf)]

i = 0
while(True):
    v = s[i]
    if i > 0:
        for k in range(nf):
            if k == v:
                d = i - state[k]
                score = ss.binom(d, (nf-1.)/nf).pmf(d)
                for j in range(state[k], i):
                    result[k][j] += np.log(score)
                state[k] = i
    i += 1
    if ns == i:
        for k in range(nf):
            d = i - state[k]
            score = ss.binom(d, (nf-1.)/nf).pmf(d)
            for j in range(state[k], i):
                result[k][j] += np.log(score)
        break

%matplotlib inline
import matplotlib.pyplot as plt
plt.title("logP of faces (fishy data)")
for res in result:
    plt.plot(res)

f:id:ruby-U:20171001182132p:plain

import itertools
import numpy as np


np.random.seed(1)

nf = 6
x = "12442552156235244611513665441521334522422516131461324351224626256625324354313241416144163515363423135135313531535155116446122436632362242642626642654256461421553232365354361461451264546335353161532646"
s = [int(s)-1 for s in x]
ns = len(s)
state = [0 for _ in range(ns)]
result = [0 for _ in range(ns)]

i = 0
while(True):
    v = s[i]
    if i > 0:
        for k in range(nf):
            if k == v:
                d = i - state[k]
                score = ss.binom(d, (nf-1.)/nf).pmf(d)
                for j in range(state[k], i):
                    result[j] += np.log(score)
                state[k] = i
    i += 1
    if ns == i:
        for k in range(nf):
            d = i - state[k]
            score = ss.binom(d, (nf-1.)/nf).pmf(d)
            for j in range(state[k], i):
                result[j] += np.log(score)
        break

%matplotlib inline
import matplotlib.pyplot as plt
plt.title("logP (fishy data)")
plt.plot(result)

f:id:ruby-U:20171001182142p:plain

import itertools
import numpy as np


np.random.seed(1)

nf = 6
x = "12442552156235244611513665441521334522422516131461324351224626256625324354313241416144163515363423135135313531535155116446122436632362242642626642654256461421553232365354361461451264546335353161532646"
s = [int(s)-1 for s in x]
ns = len(s)
state = [0 for _ in range(ns)]
result = [[0 for _ in range(ns)] for _ in range(nf)]

i = 0
while(True):
    v = s[i]
    if i > 0:
        for k in range(nf):
            if k == v:
                d = i - state[k]
                score = ss.binom(d, (nf-1.)/nf).pmf(d)
                for j in range(state[k], i):
                    result[k][j] += np.log(score)
                state[k] = i
    i += 1
    if ns == i:
        for k in range(nf):
            d = i - state[k]
            score = ss.binom(d, (nf-1.)/nf).pmf(d)
            for j in range(state[k], i):
                result[k][j] += np.log(score)
        break
        
import numpy as np
result = [np.array(result[0])+np.array(result[2])+np.array(result[4]), 
          np.array(result[1])+np.array(result[3])+np.array(result[5])]

%matplotlib inline
import matplotlib.pyplot as plt
plt.title("logP of odd and even (fishy data)")
for res in result:
    plt.plot(res)

f:id:ruby-U:20171001182154p:plain

怪しいダイス(hmmlearn)

ダイスの出目を隠れマルコフモデルでふにふにすることについて質問されたので書いた。 とりあえずhmmlearnで簡単に。

gistのほうが見やすいかも DirtyDice.ipynb

!pip install hmmlearn
Collecting hmmlearn
  Downloading hmmlearn-0.2.0.tar.gz (107kB)
[K    100% |████████████████████████████████| 112kB 1.8MB/s ta 0:00:01
[?25hBuilding wheels for collected packages: hmmlearn
  Running setup.py bdist_wheel for hmmlearn ... [?25ldone
[?25h  Stored in directory: /home/jovyan/.cache/pip/wheels/de/6c/25/c5fc03ff58b509d7b9769a0d5e2f69fc3fe89feebd70b89b86
Successfully built hmmlearn
Installing collected packages: hmmlearn
Successfully installed hmmlearn-0.2.0
import math
import numpy as np
from hmmlearn import hmm


np.random.seed(1) # デバッグのためにシードを設定しておく
# まずマトモなダイスの出力を再現してみる
n_dice = 1 #ダイスの数は1で
n_dice_faces = 6 # 6面とする
model = hmm.MultinomialHMM(n_components=n_dice)
model.startprob_ = np.array([1.0]) # ダイスは1つなので、最初のダイスが選ばれる確率は1
model.transmat_ = np.array([[1.0]]) # ダイスは1つなので、最初のダイスから常に最初のダイスが選ばれる
model.emissionprob_ = np.array([[1/6, 1/6, 1/6, 1/6, 1/6, 1/6]]) # ダイスのそれぞれの面は等しい確率で選ばれる
# ダイスを100回振ったとして
n_try = 100
x, z = model.sample(n_try) 
x = x.reshape(1, -1)[0] # 見やすく変形しているだけ
# ダイスを振った記録
x
array([5, 4, 1, 2, 2, 1, 2, 4, 2, 4, 5, 1, 5, 4, 4, 4, 2, 0, 0, 1, 5, 3, 2,
       1, 1, 5, 0, 2, 1, 5, 3, 0, 2, 3, 3, 4, 3, 0, 5, 3, 5, 5, 3, 3, 0, 4,
       1, 2, 2, 5, 0, 3, 3, 3, 0, 0, 4, 2, 4, 1, 3, 1, 1, 2, 3, 4, 0, 5, 4,
       3, 4, 4, 5, 1, 2, 3, 4, 2, 2, 3, 0, 0, 0, 4, 4, 2, 2, 2, 0, 0, 0, 0,
       5, 5, 0, 3, 5, 4, 2, 1])
# 常に最初のダイスが選ばれる
z
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0])
# この試行結果からそれぞれの面の確率を求めると、
remodel = hmm.MultinomialHMM(n_components=n_dice, n_iter=100)
remodel.fit(np.array([x]).T)
remodel.emissionprob_
array([[ 0.165,  0.17 ,  0.17 ,  0.165,  0.165,  0.165]])
# さて、ここで、"怪しい" サイコロのログを調べてみるとする  
# Ref: http://www.fward.net/archives/1548
x = "12442552156235244611513665441521334522422516131461324351224626256625324354313241416144163515363423135135313531535155116446122436632362242642626642654256461421553232365354361461451264546335353161532646"
x = [int(s)-1 for s in x]

remodel = hmm.MultinomialHMM(n_components=n_dice, n_iter=100)
remodel.fit(np.array([x]).T)
remodel.emissionprob_ # ダイスの面の出現確率は揃っていて、不正を見抜くことができない
array([[ 0.165,  0.17 ,  0.17 ,  0.165,  0.165,  0.165]])
# 復数のダイスがあると仮定してみる

remodel = hmm.MultinomialHMM(n_components=2, n_iter=100) # 2個のダイスを想定する
remodel.fit(np.array([x]).T)
remodel.transmat_
array([[ 0.962044  ,  0.037956  ],
       [ 0.18593815,  0.81406185]])
remodel.emissionprob_
array([[  1.31447796e-01,   2.10098809e-01,   1.31451592e-01,
          2.03925627e-01,   1.27562672e-01,   1.95513504e-01],
       [  3.07222812e-01,   2.71009724e-05,   3.33400979e-01,
          4.49521672e-08,   3.23691279e-01,   3.56577840e-02]])
log_p, state_seq = remodel.decode(np.array([x]).T)
state_seq # 綺麗な結果
array([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
       0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0])
# ただし、事前知識がないのでfit()の結果は安定しない…
# 次のような結果によくなる
remodel.fit(np.array([x]).T)
log_p, state_seq = remodel.decode(np.array([x]).T)
state_seq
array([0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0,
       1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
       0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
       1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1,
       0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1,
       1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1,
       0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0,
       1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0,
       1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1])
# このようなときは、初期値を明示的に設定してみる

remodel = hmm.MultinomialHMM(n_components=2, n_iter=10000, init_params="st", params="ste")
remodel.emissionprob_ = np.array([[1/6, 1/6, 1/6, 1/6, 1/6, 1/6],
                                  [2/6,   0, 2/6,   0, 2/6,   0]]) # 奇数のみ
remodel.fit(np.array([x]).T)
log_p, state_seq = remodel.decode(np.array([x]).T)
state_seq
/opt/conda/lib/python3.6/site-packages/hmmlearn/hmm.py:405: RuntimeWarning: divide by zero encountered in log
  return np.log(self.emissionprob_)[:, np.concatenate(X)].T





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

参考:

あやしいサイコロと『隠れマルコフモデル』 | 株式会社フォワードネットワーク

API Reference — hmmlearn 0.2.1 documentation

Logicoolマウスの敏感なホイールをソフトウェアでどうにかする

小型かつ、標準でL, M, R, W系x4, X1, X2に割り当てがあるLogicool m546をポチったんですが、ホイールが敏感すぎて、美少女紙芝居ゲーで遊ぶのと、ホイールでのマウスジェスチャが辛かったので対策しました。ブラウジングにはいいんですが。もうちょっとホイールのカチカチ感が強いといいバランスかもしれません。

var VWManager = new VerticalWheelManager(
    400, // Duration in milliseconds for suppression of wheel events given after once it have emitted.
    6    // Max number of the wheel events which will be suppressed in the duration for suppression.
);
@do((ctx) =>
{
    if (VWManager.Up.Check())
    {
        // do something
    }
}

VerticalWheelManagerのコンストラクタに渡す値は「連続するホイールメッセージを無効にする期間(ミリ秒)」, 「その期間内でも一定数以上連続すれば有効にする」です。あとはVerticalWheelManager.Up.Check()VerticalWheelManager.Down.Check()を叩けば、truefalseの判定の結果が返ります。

以下、マウスジェスチャツール CreviceAppの設定ファイルサンプルです。M325, M545, M546あたりの軽いホイール。または高速スクロール対応機種にも便利かもしれません。

gist6de4bf1c5f7d936bc2df2873173dec65

このぐらいの処理をマウスジェスチャツール本体ではなく、設定側でどうにかできるというのはなかなか便利です。

LogicoolのSetPointとVMwareの相性が悪く、マウスボタンに割り当てたコマンドの最後が連続入力される問題

例えばホストのSetPointにて、特定のボタンにWin+Tab(アプリケーションスイッチャ)を割り当てて、VMwareでゲストの操作をしているときにそのボタンを押すと、コマンドはゲストに送信されますが、Win+Tab, Tab, Tab ...のような連続したものになってしまい、とっても悲しいです。

ここで、SetPointにはX1, X2ボタンを割り当てて、それらのボタン入力をマウスジェスチャツールで任意のコマンドに割り当てると、Win10 on Win10の環境ではうまく動くようです。

ホストもゲストもこれでうまく動くので、以前Win8だか8.1だかの頃に話題になった、ソフトウェアが送信したAlt+TabをOSが無視する問題とか、緩和されたんでしょうか?(調べてない)

マウスジェスチャーツール CreviceApp 1.0 をリリースしました

2013年10月上旬に、突如としてかざぐるマウスが消えてしまってから乗り換え先を探していましたが、あまりよいものが見つからなかったので自作しました。先月から実際に使ってバグを潰していましたが、そろそろ安定したかなーという感じなので1.0をリリースします。

rubyu/CreviceApp

特徴

必須要件

良い点

  • ジェスチャの取りこぼしがない
  • CSharp Scriptでジェスチャを定義できる

悪い点

  • GUIがショボい

ジェスチャの取りこぼしがないこと、というのを最重要な要件として開発しました。私の必要としない、例えばGUIでの設定機能とか、ジェスチャ軌跡の描写機能とかはバッサリ切ってます。C#で設定を書けて、ジェスチャの取りこぼしなく、安定して動けばいいかなーと。

機能追加にはあんまり興味がないですが、安定性の向上には興味があります。問題などあれば、報告いただければと思います。