mecab設定の覚え書き

新環境を構築するとき忘れがちなので、覚え書きとして残しておく。

1.インストール

sudo apt update
sudo apt upgrade
sudo apt install mecab libmecab-dev mecab-ipadic-utf8 git make
curl xz-utils file
sudo apt install python3-pip
pip3 install mecab-python3 unidic-lite neologdn
git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
echo yes | mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n -a

Usage:
    $ mecab -d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd ...

2.使用方法

import re
import neologdn
import subprocess
import MeCab

CONTENT_WORD_POS = ('名詞', '動詞', '形容詞', '形容動詞', '副詞')
STOP_WORD_POS = ('代名詞', '助動詞', '非自立', '数', '人名')

# 正規化
def normalize(text):
  return neologdn.normalize(re.sub(r'[0-9]+', '0', text).lower())

# 形態素解析
def analysis(text):
  cmd = 'echo `mecab-config --dicdir`"/mecab-ipadic-neologd"'
  path = (subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()[0]).decode('utf-8')
  tagger = MeCab.Tagger('-d {0}'.format(path))
  tagger.parse('')
  node = tagger.parseToNode(normalize(text))
  word = ''
  pre_features = []

  while node:
    features = node.feature.split(',')[:6]
    # 名詞、動詞、形容詞、形容動詞、副詞であるか判定
    valid = False
    for pos in CONTENT_WORD_POS:
      valid = pos in features or valid
    # 以下に該当する場合は除外(ストップワード)
    for pos in STOP_WORD_POS:
      valid = (not pos in features) and valid
    if valid:
      if ('名詞接続' in pre_features and '名詞' in features) or ('接尾' in features):
        word += '{0}'.format(node.surface)
      else:
        word += ' {0}'.format(node.surface)
      #print('{0} {1}'.format(node.surface, features))
    pre_features = features
    node = node.next
  return word[1:]

複数の機械学習アルゴリズムを比較する

Recommendation Systemを考える その2で、機械学習を実施した結果、ロジスティック回帰が良いスコアになったが、その他の分類器ではどうか大まかに調べてみる。

1.複数のアルゴリズムを比較する

ライブラリをインポートする。

# data analysis and wrangling
import pandas as pd
import numpy as np
import random as rnd

# visualization
import matplotlib.pyplot as plt
%matplotlib inline

import seaborn as sns
color = sns.color_palette()
sns.set_style('darkgrid')

import warnings
def ignore_warn(*args, **kwargs):
  pass
warnings.warn = ignore_warn # Ignore annoying warning (from sklearn and seaborn)

# machine learning
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC, LinearSVC
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, GradientBoostingClassifier, ExtraTreesClassifier, VotingClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import Perceptron, SGDClassifier
from sklearn.tree import DecisionTreeClassifier

from sklearn.model_selection import GridSearchCV, cross_val_score, StratifiedKFold, learning_curve

保存しておいたデータを読み込む。

from pathlib import Path

TRAIN_DATA_PATH = str(Path.home()) + '/vscode/syosetu/train_data.pkl'

train = pd.read_pickle(TRAIN_DATA_PATH)

トレーニングデータとテストデータに分割する。

from sklearn.model_selection import train_test_split

X = train['vectors']
y = train['female']

X_array = np.array([np.array(v) for v in X])
y_array = np.array([i for i in y])

X_train, X_test, y_train, y_test = train_test_split(X_array, y_array, random_state=0)

クロスバリデーションのスコアが良い順に表示する。

kfold = StratifiedKFold(n_splits=10)
cls_weight = (y_train.shape[0] - np.sum(y_train)) / np.sum(y_train)
random_state = 0
classifiers = []
classifiers.append(LogisticRegression(random_state=random_state))
classifiers.append(SVC(random_state=random_state))
classifiers.append(KNeighborsClassifier(n_neighbors=3))
classifiers.append(GaussianNB())
classifiers.append(Perceptron(random_state=random_state))
classifiers.append(LinearSVC(random_state=random_state))
classifiers.append(SGDClassifier(random_state=random_state))
classifiers.append(DecisionTreeClassifier(random_state=random_state))
classifiers.append(RandomForestClassifier(n_estimators=100, random_state=random_state))
classifiers.append(AdaBoostClassifier(DecisionTreeClassifier(random_state=random_state), random_state=random_state, learning_rate=0.1))
classifiers.append(GradientBoostingClassifier(random_state=random_state))
classifiers.append(ExtraTreesClassifier(random_state=random_state))

cv_results = []
for classifier in classifiers :
  cv_results.append(cross_val_score(classifier, X_train, y=y_train, scoring='accuracy', cv=kfold, n_jobs=4))

cv_means = []
cv_std = []
for cv_result in cv_results:
  cv_means.append(cv_result.mean())
  cv_std.append(cv_result.std())

cv_res = pd.DataFrame({'CrossVal Means': cv_means, 'CrossVal Errors': cv_std,
                       'Algorithm': ['LogisticRegression', 'SVC', 'KNeighborsClassifier',
                                     'GaussianNB', 'Perceptron', 'LinearSVC', 'SGDClassifier',
                                     'DecisionTreeClassifier', 'RandomForestClassifier',
                                     'AdaBoostClassifier', 'GradientBoostingClassifier',
                                     'ExtraTreesClassifier']})

cv_res.sort_values(by=['CrossVal Means'], ascending=False, inplace=True)

cv_res
CrossVal MeansCrossVal ErrorsAlgorithm
00.9053570.105357LogisticRegression
30.8964290.094761GaussianNB
10.8928570.100699SVC
40.8910710.101157Perceptron
60.8910710.101157SGDClassifier
110.8785710.099360ExtraTreesClassifier
80.8660710.120387RandomForestClassifier
20.8446430.092461KNeighborsClassifier
50.8250000.107381LinearSVC
100.8017860.112783GradientBoostingClassifier
70.7464290.145248DecisionTreeClassifier
90.7089290.121389AdaBoostClassifier

回帰系のアルゴリズムでスコアが良い傾向があるように見える。

g = sns.barplot('CrossVal Means', 'Algorithm', data=cv_res, palette='Set3', orient='h', **{'xerr': cv_std})
g.set_xlabel('Mean Accuracy')
g = g.set_title('Cross validation scores')

2.パラメーターの最適化

続いて、いくつか選んだアルゴリズムのパラメーターを変更して、スコアの変化を見てみる。

### META MODELING WITH LR, RF and SGD

# Logistic Regression
LR = LogisticRegression(random_state=0)

## Search grid for optimal parameters
lr_param_grid = {'penalty': ['l1','l2']}

gsLR = GridSearchCV(LR, param_grid=lr_param_grid, cv=kfold, scoring='accuracy', n_jobs=4, verbose=1)

gsLR.fit(X_train, y_train)

LR_best = gsLR.best_estimator_

# Best score
gsLR.best_score_

Fitting 10 folds for each of 2 candidates, totalling 20 fits
0.9053571428571429

gsLR.best_params_

{‘penalty’: ‘l2’}

# Random Forest Classifier
RF = RandomForestClassifier(random_state=0)

## Search grid for optimal parameters
'''rf_param_grid = {'criterion': ['gini', 'entropy'],
                 'max_features': [1, 3, 10],
                 'min_samples_split': [2, 3, 10],
                 'min_samples_leaf': [1, 3, 10],
                 'n_estimators':[50, 100, 200]}'''
rf_param_grid = {'criterion': ['gini', 'entropy'],
                 'max_features': [40, 50, 60],
                 'min_samples_split': [2, 3, 10],
                 'min_samples_leaf': [1, 3, 10],
                 'n_estimators':[20, 30, 40]}

gsRF = GridSearchCV(RF, param_grid=rf_param_grid, cv=kfold, scoring='accuracy', n_jobs=4, verbose=1)

gsRF.fit(X_train, y_train)

RF_best = gsRF.best_estimator_

# Best score
gsRF.best_score_

Fitting 10 folds for each of 162 candidates, totalling 1620 fits
0.8946428571428573

gsRF.best_params_

{‘criterion’: ‘gini’,
‘max_features’: 50,
‘min_samples_leaf’: 3,
‘min_samples_split’: 2,
‘n_estimators’: 30}

# SGD Classifier
SGD = SGDClassifier(random_state=0)

## Search grid for optimal parameters
sgd_param_grid = {'alpha'  : [0.0001, 0.001, 0.01, 0.1],
                  'loss'   : ['log', 'modified_huber'],
                  'penalty': ['l2', 'l1', 'none']}

gsSGD = GridSearchCV(SGD, param_grid=sgd_param_grid, cv=kfold, scoring='accuracy', n_jobs=4, verbose=1)

gsSGD.fit(X_train, y_train)

SGD_best = gsSGD.best_estimator_

# Best score
gsSGD.best_score_

Fitting 10 folds for each of 24 candidates, totalling 240 fits
0.8660714285714285

gsSGD.best_params_

{‘alpha’: 0.0001, ‘loss’: ‘modified_huber’, ‘penalty’: ‘l1’}

3.学習曲線の確認

def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None,
                        n_jobs=-1, train_sizes=np.linspace(.1, 1.0, 5)):
  '''Generate a simple plot of the test and training learning curve'''
  plt.figure()
  plt.title(title)
  if ylim is not None:
    plt.ylim(*ylim)
  plt.xlabel('Training examples')
  plt.ylabel('Score')
  train_sizes, train_scores, test_scores = learning_curve(
      estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes)
  train_scores_mean = np.mean(train_scores, axis=1)
  train_scores_std = np.std(train_scores, axis=1)
  test_scores_mean = np.mean(test_scores, axis=1)
  test_scores_std = np.std(test_scores, axis=1)
  plt.grid()

  plt.fill_between(train_sizes, train_scores_mean - train_scores_std,
                   train_scores_mean + train_scores_std, alpha=0.1,
                   color='r')
  plt.fill_between(train_sizes, test_scores_mean - test_scores_std,
                   test_scores_mean + test_scores_std, alpha=0.1,
                   color='g')
  plt.plot(train_sizes, train_scores_mean, 'o-', color='r',
           label='Training score')
  plt.plot(train_sizes, test_scores_mean, 'o-', color='g',
           label='Cross-validation score')

  plt.legend(loc='best')
  return plt

g = plot_learning_curve(gsLR.best_estimator_, 'LR mearning curves', X_train, y_train, cv=kfold)
g = plot_learning_curve(gsRF.best_estimator_, 'RF learning curves', X_train, y_train, cv=kfold)
g = plot_learning_curve(gsSGD.best_estimator_, 'SGD learning curves', X_train, y_train, cv=kfold)

Random Forestも良い気がしてきた。何とかトレーニングデータを増やしてもう少し確認したい。

最後に

全くトレーニングで使用していない未知のデータに対して、 Random Forest の予測モデルで判定してみたところ83%程の正解率だった。なろう小説のTop300はファンタジー系の小説が多いため、ファンタジー系の小説において、この正解率とみた方が良いかも知れない。ジャンルが違う小説の場合どのような結果になるか現時点では予測不能である。

Recommendation Systemを考える その2

Recommendation Systemを考えるの続き。

更新履歴:2021/03/19 データ処理に誤りがあったため訂正

初めに

小説をユーザーに推薦するかしないかで分けたいので、これは分類問題となる。分類問題についてはこちらで解説している。

分類するために、作者、関連作品やタグ付けも参照した方が精度が良くなるかもしれないが、今回はDoc2Vecのベクトル情報のみで行ってみる。Doc2Vec で作成した文書ベクトルを教師あり学習の説明変数として使用する。

文書ベクトルを教師あり学習の説明変数とした分類問題ではRandom Forestが有効という論文を見たので、まずは Random Forest を試してみる。また、今回使用するDoc2Vecの文書ベクトルは次元数が100ほどあるため、事前に次元圧縮を行うことも検討する。

最初は、このモデルを正しく評価するために、ユーザーの好みかの様なあいまいな指標で分類するのではなく、機械的に分類できる指標にする。

分類の実施

データセットの作成

以前作成した学習済みモデルを読み込み、データセットを作成する。

from gensim.models.doc2vec import Doc2Vec

MODEL_DATA_PATH = 'drive/My Drive/Colab Notebooks/syosetu/doc2vec_100.model'

m = Doc2Vec.load(MODEL_DATA_PATH)
vectors_list = [m.docvecs[n] for n in range(len(m.docvecs))]
import pandas as pd

MODEL_TITLES_CSV_PATH = 'drive/My Drive/Colab Notebooks/syosetu/novel_titles.csv'

df = pd.read_csv(MODEL_TITLES_CSV_PATH, header=None, names=['url', 'title', 'tag'])
df = df.drop(columns=['url'])
df['vectors'] = vectors_list

df.head(2)
titletagvectors
0転生したらスライムだった件R15 残酷な描写あり 異世界転生 スライム チート[5.356541, 1.455931, 1.0426528, 1.1359314, 3.9…
1無職転生 - 異世界行ったら本気だす –R15 残酷な描写あり 異世界転生[0.010144993, -12.32704, 2.8596005, 6.5284176,…

タグを見て機械的に分類したいため、どんなタグが存在するのか最初に確認する。

import collections
import itertools

tag = collections.Counter(list(itertools.chain.from_iterable(df['tag'].str.split().tolist())))
sorted(tag.items(), key=lambda pair: pair[1], reverse=True)[:20]
[('R15', 233),
 ('残酷な描写あり', 215),
 ('異世界転生', 98),
 ('異世界', 87),
 ('異世界転移', 86),
 ('チート', 84),
 ('ファンタジー', 80),
 ('魔法', 73),
 ('男主人公', 65),
 ('冒険', 63),
 ('主人公最強', 59),
 ('書籍化', 53),
 ('ハーレム', 48),
 ('成り上がり', 43),
 ('ほのぼの', 33),
 ('ハッピーエンド', 32),
 ('女主人公', 31),
 ('恋愛', 30),
 ('オリジナル戦記', 30),
 ('転生', 28)]

2極化しやすそうな、男主人公、女主人公の小説を抽出することにする。

df_m = df[df['tag'].str.contains('男主人公')]
df_m['female'] = 0
df_f = df[df['tag'].str.contains('女主人公|女性視点|悪役令嬢|後宮|少女マンガ')]
df_f['female'] = 1

上記の抽出方法の場合、女主人公と分類した小説の中に男主人公の小説が混じっている可能性がある。

df_f[df_f['tag'].str.contains('男主人公')]
titletagvectors
47乙女ゲー世界はモブに厳しい世界ですR15 残酷な描写あり 異世界転生 乙女ゲーム 悪役令嬢 冒険 人工知能 男主人公 学園 …[6.775977, 4.3698134, -1.9109731, 9.731645, -0…
74【Web版】町人Aは悪役令嬢をどうしても救いたい【本編完結済み】R15 残酷な描写あり 異世界転生 乙女ゲーム 悪役令嬢 オリジナル戦記 男主人公 西洋 …[2.9941628, 1.892081, 1.9338807, 4.0745897, 6….

上記は男主人公の小説と思われるため、女主人公の小説の一覧から除外する。

df_f = df_f.drop([47, 74])
df = pd.concat([df_m, df_f])
df['female'].value_counts()

0 67
1 51
Name: female, dtype: int64

男主人公の小説の方が数が多いため、女主人公の小説の数と合わせる。

#男主人公の一覧
df_0 = df[df['female'] == 0]
#女主人公の一覧
df_1 = df[df['female'] == 1]

df_0 = df_0.sample(n=len(df_1), random_state=0)

df = pd.concat([df_0, df_1])
機械学習の実施

データセットの作成が出来たので、 Random Forest を用いて、分類を実施してみる。

X = df['vectors']
y = df['female']

X_array = np.array([np.array(v) for v in X])
y_array = np.array([i for i in y])
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X_array, y_array, random_state=0)
from sklearn.ensemble import RandomForestClassifier

random_forest = RandomForestClassifier(random_state=0)
random_forest.fit(X_train, y_train)

random_forest.score(X_test, y_test)

0.8076923076923077

random_forest.predict(X_test)

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

試しに、ロジスティック回帰でも実施してみる。

from sklearn.linear_model import LogisticRegression

logreg = LogisticRegression()
logreg.fit(X_train, y_train)

logreg.score(X_test, y_test)

0.9230769230769231

logreg.predict(X_test)

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

ロジスティック回帰の方が良いスコアになった。しかし、過学習を起こしている可能性もある。

まとめ

Random Forest が一番良い結果になると予想していたが、違った。
データセットは大体問題なさそうなので、この問題を解くにはどの分類器が適しているのか、また最適なパラメーターを探す作業は次回行いたいと思う。

複数の機械学習アルゴリズムを比較するへ続く。

Windows Subsystem for Linux 2でCUDAを使えるようにする

1.cuda toolkitをインストール

sudo apt-key adv --fetch-keys 
http://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/3bf863cc.pub
cat <<EOF | sudo tee /etc/apt/sources.list.d/cuda.list
deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64 /
EOF
sudo apt update
sudo apt install cuda-toolkit-11-5

2.libcudnn8、libncclをインストール(任意)

sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu2004/x86_64/7fa2af80.pub
cat <<EOF | sudo tee /etc/apt/sources.list.d/cuDNN.list
deb https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu2004/x86_64 /
EOF
sudo apt update
sudo apt install libcudnn8 libnccl-dev

3.動作確認

Tensorflowをインストールする。

pip install --upgrade tensorflow

以下を実行する。

from tensorflow.python.client import device_lib

device_lib.list_local_devices()

実行結果の一部に下記が表示されていれば正常に導入できている。

 name: "/device:GPU:0"
 device_type: "GPU"

Windows上にPythonの開発環境を構築する

前提

Windows Subsystem for Linuxを設定し、Ubuntuのインストールが済んでいること。

1. VSCodeをインストール

Windows版をダウンロードし、インストールする。
Windows Subsystem for Linuxがインストール済みであれば、初回起動時に下記が表示されるため、インストールする。

一度、VSCodeを終了し、Ubuntuコンソール上で下記を実行する。

code .

VSCodeが起動した後、下記の「Python」をクリックして、インストールする。

Python 拡張機能のインストールが完了すると、さらに下記が表示されるので、インストールする。

2. 動作確認

VSCode上で下記の通り記入したhello.pyファイルを作成する。

msg = 'Hello World'
print(msg)

Run Python File in Terminalをクリックし、実行結果が表示されれば動作確認完了。

xxx@xxx:~/test$ /usr/bin/python3 /home/xxx/test/hello.py
Hello World

Windows Subsystem for Linux 2でPythonを動かす

1. Windows Subsystem for Linuxをインストール

Windows Power Shellを管理者権限で開き、下記を実行する。

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

WSL 2を使用するため、仮想マシンプラットフォームを有効化する。

Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform

x64 マシン用 WSL2 Linux カーネル更新プログラム パッケージをインストールする。

デフォルトバージョンを2とする。

wsl --set-default-version 2

2. Ubuntu Linuxをインストール

Microsoft StoreからUbuntuをインストールする。

3. WSLのバージョン確認

> wsl -l -v
  NAME      STATE           VERSION
* Ubuntu    Running         2

4. Pythonのバージョン確認

Ubuntuをインストールした場合、Python3が既にインストールされている。

$ python3 --version
Python 3.8.5

Kubernetes環境構築

Raspberry Pi 2上に構築しようと思っていたのだが、メモリが足りなくてまともに使用するのは無理そうなため、取り合えず仮想環境上に構築したメモ。

1. スワップ無効化

無効化しなくても動くらしいが、パフォーマンスを良くするのために、無効化が推奨らしい。

2. dockerインストールに必要なソフトウェアをインストール
sudo apt install apt-transport-https ca-certificates curl software-properties-common
sudo apt update
sudo apt upgrade
3. dockerインストール
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
sudo apt update
apt-cache policy docker-ce
#Kubernetesの動作保証はv19までであるが、今回は最新バージョンで試した
sudo apt install docker-ce
sudo systemctl status docker
#dockerの操作をsudo無しで出来るようにするため、ログインユーザーをdockerグループに追加
sudo usermod -aG docker ユーザーID
4. docker設定
cat <<EOF | sudo tee /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF

sudo mkdir -p /etc/systemd/system/docker.service.d
sudo systemctl daemon-reload
sudo systemctl restart docker
#ブリッジ接続を有効化
sudo sysctl net.bridge.bridge-nf-call-iptables=1

#カーネルオプションに下記を設定し再起動
sudo vi /etc/default/grub
GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"
sudo update-grub
sudo shutdown -r now
5. kubernetesインストール
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF

sudo apt update
sudo apt install kubelet kubeadm kubectl
6. kubernetes設定(マスター)
sudo kubeadm init --apiserver-advertise-address=192.168.xx.xx
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
#Weave Netを設定
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"
7. kubernetes設定(マスター以外)

dockerのインストール、設定共にマスターと同様に実施する。5. kubernetesのインストールまで同様に実施する。

sudo kubeadm join 192.168.xx.xx:6443 --token 「initしたときに表示されたトークン」 --discovery-token-ca-cert-hash 「initしたときに表示されたハッシュコード」
8. 確認
#マスターでノードが追加されたか確認
kubectl get nodes
#ラベルを付ける
kubectl label node 「ノード名」 node-role.kubernetes.io/worker=

Raspberry Pi 2でクラスタ構築 その4(Hadoop編)

前回、HDFS高可用性(HA)機能を設定した。今回はResourceManagerをHA化する。

準備

SDカードの寿命を縮めるため、あまり推奨は出来ないが、本格的にメモリ不足のため、念のためスワップ領域を作成しておく。

sudo mkdir /var/swap
sudo dd if=/dev/zero of=/var/swap/swap0 bs=1M count=2048
sudo chmod 600 /var/swap/swap0
sudo mkswap /var/swap/swap0
sudo swapon /var/swap/swap0
#再起動後も自動的にスワップ領域が適用されるようにする
sudo vi /etc/fstab
#下記の行を追加する
/var/swap/swap0 none swap defaults 0 0

2021/02/25 追記:
SDカードへ書き込みを少なくするため、スワップの頻度が少なくなるように調整する。

#スワップの閾値を確認
cat /proc/sys/vm/swappiness
> 60

sudo vi /etc/sysctl.conf
#下記を追記する(物理メモリが1%以下になったらスワップを開始)
vm.swappiness = 1

#設定を反映
sudo sysctl -p
cat /proc/sys/vm/swappiness
> 1

サーバー構成を検討

NameNodeは奇数で構成する必要があったが、ResourceManagerはそのような制約はないため、下記の2台構成とする。

master1・・・Zookeeper,NameNode(Standby), ResourceManager(Standby)
master2・・・Zookeeper,NameNode(Standby), ResourceManager(Active)
master3・・・Zookeeper,NameNode(Active)
slave1・・・DataNode, NodeManager
slave2・・・DataNode, NodeManager
slave3・・・DataNode, NodeManager
slave4・・・DataNode, NodeManager

ResourceManager高可用性(HA)機能の設定

master1~2のetc/hadoop/yarn-site.xmlを編集する。(メモリ関連の記述は省略)

   <property>
    <name>yarn.resourcemanager.ha.enabled</name>
    <value>true</value>
  </property>
  <property>
    <name>yarn.resourcemanager.cluster-id</name>
    <value>rpcluster_r</value>
  </property>
  <property>
    <name>yarn.resourcemanager.ha.rm-ids</name>
    <value>rm1,rm2</value>
  </property>
  <property>
    <name>yarn.resourcemanager.hostname.rm1</name>
    <value>master1</value>
  </property>
  <property>
    <name>yarn.resourcemanager.hostname.rm2</name>
    <value>master2</value>
  </property>
  <property>
    <name>yarn.resourcemanager.webapp.address.rm1</name>
    <value>master1:8088</value>
  </property>
  <property>
    <name>yarn.resourcemanager.webapp.address.rm2</name>
    <value>master2:8088</value>
  </property>
  <property>
    <name>yarn.resourcemanager.scheduler.class</name>
    <value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler</value>
  </property>
  <property>
    <name>yarn.resourcemanager.recovery.enabled</name>
    <value>true</value>
  </property>
  <property>
    <name>yarn.resourcemanager.store.class</name>
    <value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value>
  </property>
  <property>
    <name>hadoop.zk.address</name>
    <value>master1:2181,master2:2181,master3:2181</value>
  </property>

slave1~4のetc/hadoop/yarn-site.xmlを編集する。(メモリ関連の記述は省略)

   <property>
    <name>yarn.resourcemanager.ha.enabled</name>
    <value>true</value>
  </property>
  <property>
    <name>yarn.resourcemanager.cluster-id</name>
    <value>rpcluster_r</value>
  </property>
  <property>
    <name>yarn.resourcemanager.ha.rm-ids</name>
    <value>rm1,rm2</value>
  </property>
  <property>
    <name>yarn.resourcemanager.hostname.rm1</name>
    <value>master1</value>
  </property>
  <property>
    <name>yarn.resourcemanager.hostname.rm2</name>
    <value>master2</value>
  </property>
  <property>
    <name>yarn.resourcemanager.webapp.address.rm1</name>
    <value>master1:8088</value>
  </property>
  <property>
    <name>yarn.resourcemanager.webapp.address.rm2</name>
    <value>master2:8088</value>
  </property>
  <property>
    <name>yarn.resourcemanager.scheduler.class</name>
    <value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler</value>
  </property>
  <property>
    <name>yarn.resourcemanager.recovery.enabled</name>
    <value>true</value>
  </property>
  <property>
    <name>yarn.resourcemanager.store.class</name>
    <value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value>
  </property>
  <property>
    <name>hadoop.zk.address</name>
    <value>master1:2181,master2:2181,master3:2181</value>
  </property>
  <property>
    <name>yarn.nodemanager.aux-services</name>
    <value>mapreduce_shuffle</value>
  </property>
  <property>
    <name>yarn.nodemanager.env-whitelist</name>
    <value>JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_MAPRED_HOME</value>
  </property>

ResourceManagerの起動

master1で下記コマンドを実行し、ResourceManagerを起動する。

$HADOOP_HOME/sbin/start-yarn.sh

起動が確認できたので、実際に処理を実行してみる。

$HADOOP_HOME/bin/hadoop jar /home/xxx/hadoop-latest/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.2.2.jar pi 10 10000

最終的に下記の結果が表示された。スワップを設定したからか今回は前回に比べて50秒ほど早く処理が終わった。

Job Finished in 256.114 seconds
Estimated value of Pi is 3.14120000000000000000

Raspberry Pi 2でクラスタ構築 その3(Hadoop編)

前回の構成ではNameNodeが単一障害点になっていたため、HDFS高可用性(HA)機能を試してみる。

サーバー構成を検討

スタンバイのNameNodeがSecondary NameNodeの代わりになるため、Secondary NameNodeは不要になる。

master1・・・Zookeeper,NameNode(Active), ResourceManager
master2・・・Zookeeper,NameNode(Standby)
master3・・・Zookeeper,NameNode(Standby)
slave1・・・DataNode, NodeManager
slave2・・・DataNode, NodeManager
slave3・・・DataNode, NodeManager
slave4・・・DataNode, NodeManager

Zookeeperをインストール

master1~3にインストールする。

wget https://downloads.apache.org/zookeeper/zookeeper-3.6.2/apache-zookeeper-3.6.2-bin.tar.gz
tar xvfz apache-zookeeper-3.6.2-bin.tar.gz
ln -nfs /home/xxx/apache-zookeeper-3.6.2-bin zookeeper-latest

master1~3のconf/zoo.cfgを編集する。

tickTime=2000
initLimit=5
syncLimit=2
dataDir=/home/xxx/zookeeper-latest/data
clientPort=2181
server.1=master1:2888:3888
server.2=master2:2888:3888
server.3=master3:2888:3888

master1~3の~/zookeeper-latest/data/myidファイルを作成し、server.xで指定した数字と同じ数字を記入する。
その後、master1~3のZookeeperを順次起動する。

~/zookeeper-latest/bin/zkServer.sh start

HDFS高可用性(HA)機能の設定

master1~3のetc/hadoop/hdfs-site.xmlを編集する。

  <property>
    <name>dfs.nameservices</name>
    <value>rpcluster</value>
  </property>
  <property>
    <name>dfs.ha.namenodes.rpcluster</name>
    <value>nn1,nn2,nn3</value>
  </property>
  <property>
    <name>dfs.namenode.rpc-address.rpcluster.nn1</name>
    <value>master1:8020</value>
  </property>
  <property>
    <name>dfs.namenode.rpc-address.rpcluster.nn2</name>
    <value>master2:8020</value>
  </property>
  <property>
    <name>dfs.namenode.rpc-address.rpcluster.nn3</name>
    <value>master3:8020</value>
  </property>
  <property>
    <name>dfs.namenode.http-address.rpcluster.nn1</name>
    <value>master1:9870</value>
  </property>
  <property>
    <name>dfs.namenode.http-address.rpcluster.nn2</name>
    <value>master2:9870</value>
  </property>
  <property>
    <name>dfs.namenode.http-address.rpcluster.nn3</name>
    <value>master3:9870</value>
  </property>
  <property>
    <name>dfs.namenode.shared.edits.dir</name>
    <value>qjournal://master1:8485;master2:8485;master3:8485/rpcluster</value>
  </property>
  <property>
    <name>dfs.client.failover.proxy.provider.rpcluster</name>
    <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
  </property>
  <property>
    <name>dfs.ha.fencing.methods</name>
    <value>sshfence</value>
  </property>
  <property>
    <name>dfs.ha.fencing.ssh.private-key-files</name>
    <value>/home/ubuntu/.ssh/id_rsa</value>
  </property>
  <property>
    <name>dfs.ha.fencing.methods</name>
    <value>shell(/bin/true)</value>
  </property>
  <property>
    <name>dfs.ha.automatic-failover.enabled</name>
    <value>true</value>
  </property>
  <property>
    <name>ha.zookeeper.quorum</name>
    <value>master1:2181,master2:2181,master3:2181</value>
  </property>
  <property>
    <name>dfs.journalnode.edits.dir</name>
    <value>/home/ubuntu/hadoop-latest/dfs/journalnode</value>
  </property>
  <property>
    <name>dfs.namenode.name.dir</name>
    <value>/home/ubuntu/hadoop-latest/dfs/namenode</value>
  </property>
  <property>
    <name>dfs.replication</name>
    <value>3</value>
  </property>

slave1~4のetc/hadoop/hdfs-site.xmlを編集する。

   <property>
    <name>dfs.nameservices</name>
    <value>rpcluster</value>
  </property>
  <property>
    <name>dfs.ha.namenodes.rpcluster</name>
    <value>nn1,nn2,nn3</value>
  </property>
  <property>
    <name>dfs.namenode.rpc-address.rpcluster.nn1</name>
    <value>master1:8020</value>
  </property>
  <property>
    <name>dfs.namenode.rpc-address.rpcluster.nn2</name>
    <value>master2:8020</value>
  </property>
  <property>
    <name>dfs.namenode.rpc-address.rpcluster.nn3</name>
    <value>master3:8020</value>
  </property>
  <property>
    <name>dfs.namenode.http-address.rpcluster.nn1</name>
    <value>master1:9870</value>
  </property>
  <property>
    <name>dfs.namenode.http-address.rpcluster.nn2</name>
    <value>master2:9870</value>
  </property>
  <property>
    <name>dfs.namenode.http-address.rpcluster.nn3</name>
    <value>master3:9870</value>
  </property>
  <property>
    <name>dfs.namenode.shared.edits.dir</name>
    <value>qjournal://master1:8485;master2:8485;master3:8485/rpcluster</value>
  </property>
  <property>
    <name>dfs.client.failover.proxy.provider.rpcluster</name>
    <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
  </property>
  <property>
    <name>dfs.ha.fencing.methods</name>
    <value>sshfence</value>
  </property>
  <property>
    <name>dfs.ha.fencing.ssh.private-key-files</name>
    <value>/home/ubuntu/.ssh/id_rsa</value>
  </property>
  <property>
    <name>dfs.ha.fencing.methods</name>
    <value>shell(/bin/true)</value>
  </property>
  <property>
    <name>dfs.ha.automatic-failover.enabled</name>
    <value>true</value>
  </property>
  <property>
    <name>ha.zookeeper.quorum</name>
    <value>master1:2181,master2:2181,master3:2181</value>
  </property>
  <property>
    <name>dfs.datanode.data.dir</name>
    <value>/mnt/usbhdd/hadoop/data</value>
  </property>

master1~3、slave1~4のetc/hadoop/core-site.xmlを編集する。

  <property>
    <name>fs.defaultFS</name>
    <value>hdfs://rpcluster</value>
  </property>
  <property>
    <name>ha.zookeeper.quorum</name>
    <value>master1:2181,master2:2181,master3:2181</value>
  </property>

HDFSのフォーマット

master1~3のJournalNodeのみ起動する。

$HADOOP_HOME/bin/hdfs --daemon start journalnode

master1でNameNodeをフォーマットする。

$HADOOP_HOME/bin/hdfs namenode -format
$HADOOP_HOME/bin/hdfs namenode -initializeSharedEdits -force
$HADOOP_HOME/bin/hdfs zkfc -formatZK -force

master1のNameNodeのみ起動する。

$HADOOP_HOME/bin/hdfs --daemon start namenode

master2、3でスタンバイ用のNameNodeを作成する。

$HADOOP_HOME/bin/hdfs namenode -bootstrapStandby -force

HDFSの起動

全てのNameNode、JournalNodeを停止する。

$HADOOP_HOME/bin/hdfs --daemon stop namenode
$HADOOP_HOME/bin/hdfs --daemon stop journalnode

master1で下記コマンドを実行し、HDFSを再起動する。

$HADOOP_HOME/sbin/start-dfs.sh

次回、ResourceManagerをHA化する。

Raspberry Pi 2でクラスタ構築 その2(Hadoop編)

前回、Hadoop環境を構築しStandaloneモードで動作確認出来たため、今度は完全分散モードで動作を確認する。

準備

各サーバーからパスワード無しで接続出来るようにする。この作業を行っておくとマスターの起動とスレーブの起動を1コマンドで実行出来るようになる。

#鍵を生成
ssh-keygen -t rsa
#各サーバーに公開鍵をコピー
ssh-copy-id -i /home/foo/.ssh/id_rsa.pub foo@192.168.0.xxx
#パスワード無しでログイン出来ることを確認
ssh 192.168.0.xxx

データ保存用のUSBハードディスクを起動時にマウントするようにする。

#USBハードディスクのパスを確認
sudo fdisk -l
#UUIDを確認
sudo blkid /dev/sda1 #パスは環境毎に異なる可能性があるため注意
sudo vi /etc/fstab
#下記の1行を追記
UUID=xxx-xxx-xxx-xxx-xxx /mnt/usbhdd ext4 defaults 0 0 #UUIDは機器毎に異なるため注意
#再起動して自動的にマウントされることを確認
sudo shutdown -r now

ホスト名を/etc/hostsに記載する。Hadoopの設定でIPアドレスを使用するとエラーが発生するため、ここでホスト名を設定しておく。

sudo vi /etc/hosts

192.168.0.xx1 master1
192.168.0.xx2 master2
192.168.0.xx3 master3
192.168.0.xx4 slave1
192.168.0.xx5 slave2
192.168.0.xx6 slave3
192.168.0.xx7 slave4

クラスターの構成を検討

手元にには7台のRaspberry Pi 2があり、そのうち4台にはUSBハードディスクを接続している。

Hadoopの構築に必要な機能は以下の通りであることから、

  • NameNode
  • DataNode
  • Secondary NameNode
  • ResourceManager
  • NodeManager
  • WebAppProxy
  • Map Reduce Job History Server

各サーバーに下記の様に割り振ることにした。Zookeeperによる自動フェイルオーバーについては今回は設定しない。

master1・・・NameNode, ResourceManager
master2・・・Secondary NameNode
master3・・・今回は使用しない
slave1・・・DataNode, NodeManager
slave2・・・DataNode, NodeManager
slave3・・・DataNode, NodeManager
slave4・・・DataNode, NodeManager
※slave1~4にはUSBハードディスクが接続されている。

Hadoopデーモンの環境設定

~/.bashrcに下記を追記する。

export JAVA_HOME="$HOME/jdk-11-latest"
export HADOOP_HOME="$HOME/hadoop-latest"
if [ -d "$HADOOP_HOME" ] ; then
    export HADOOP_CONF_DIR="$HADOOP_HOME/etc/hadoop"
fi

全サーバーのetc/hadoop/hadoop-env.shを編集する。

export JAVA_HOME=/home/xxx/jdk-11-latest
export HADOOP_HOME=/home/xxx/hadoop-latest
export HADOOP_HEAPSIZE_MAX=200m

HDFSの設定

全サーバーのetc/hadoop/core-site.xmlを編集する。

<configuration>
  <property>
    <name>fs.defaultFS</name>
    <value>hdfs://master1:9000/</value>
  </property>
</configuration>

master1のetc/hadoop/hdfs-site.xmlを編集する。

<configuration>
  <property>
    <name>dfs.replication</name>
    <value>3</value>
  </property>
  <property>
    <name>dfs.namenode.secondary.http-address</name>
    <value>master2:50090</value>
  </property>
  <property>
    <name>dfs.namenode.name.dir</name>
    <value>/home/xxx/hadoop-latest/name</value>
  </property>
</configuration>

master1のetc/hadoop/workersを編集する。

slave1
slave2
slave3
slave4

slave1~4のetc/hadoop/hdfs-site.xmlを編集する。

<configuration>
  <property>
    <name>dfs.datanode.data.dir</name>
    <value>/mnt/usbhdd/hadoop/data</value>
  </property>
</configuration>

master1で下記を実行する。

$HADOOP_HOME/bin/hdfs namenode -format <cluster_name>
$HADOOP_HOME/sbin/start-dfs.sh

正常動作を確認出来たら、下記を実行し停止する。

$HADOOP_HOME/sbin/stop-dfs.sh

この時下記のWarningが出たため、

OpenJDK Server VM warning: You have loaded library /home/xxx/hadoop-3.2.2/lib/native/libhadoop.so.1.0.0 which might have disabled stack guard. The VM will try to fix the stack guard now.
It's highly recommended that you fix the library with 'execstack -c &lt;libfile>', or link it with '-z noexecstack'.

下記の対応を行った。

sudo apt-get install prelink
sudo execstack -c /home/xxx/hadoop-3.2.2/lib/native/libhadoop.so.1.0.0
sudo execstack -c /home/xxx/hadoop-3.2.2/lib/native/libhdfs.so.0.0.0
sudo execstack -c /home/xxx/hadoop-3.2.2/lib/native/libnativetask.so.1.0.0

YARNの設定

注意:メモリの設定は参考にしないでください。物理メモリが1GBしかないRaspberry Pi 2で無理やり動くように設定したものなので、正しい設定方法ではないです。

master1、slave1~4のetc/hadoop/yarn-site.xmlを編集する。(yarn.scheduler.~はmaster1のみ追記、yarn.nodemanager.~はslave1~4のみ追記)

<configuration>
  <property>
      <name>yarn.resourcemanager.hostname</name>
      <value>master1</value>
  </property>
  <property>
      <name>yarn.scheduler.minimum-allocation-mb</name>
      <value>200</value>
  </property>
  <property>
      <name>yarn.scheduler.maximum-allocation-mb</name>
      <value>800</value>
  </property>
  <property>
    <name>yarn.nodemanager.resource.memory-mb</name>
    <value>200</value>
  </property>
  <property>
    <name>yarn.nodemanager.aux-services</name>
    <value>mapreduce_shuffle</value>
  </property>
  <property>
    <name>yarn.nodemanager.env-whitelist</name>
    <value>JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_MAPRED_HOME</value>
  </property>
</configuration>

master1のetc/hadoop/mapred-site.xmlを編集する。

<configuration>
  <property>
    <name>mapreduce.framework.name</name>
    <value>yarn</value>
  </property>
  <property>
    <name>yarn.app.mapreduce.am.resource.mb</name>
    <value>200</value>
  </property>
  <property>
    <name>mapreduce.map.memory.mb</name>
    <value>200</value>
  </property>
  <property>
    <name>mapreduce.map.java.opts</name>
    <value>-Xmx200M</value>
  </property>
  <property>
    <name>mapreduce.reduce.memory.mb</name>
    <value>200</value>
  </property>
  <property>
    <name>mapreduce.reduce.java.opts</name>
    <value>-Xmx200M</value>
  </property>
  <property>
    <name>yarn.app.mapreduce.am.env</name>
    <value>HADOOP_MAPRED_HOME=/home/xxx/hadoop-latest</value>
  </property>
  <property>
    <name>mapreduce.map.env</name>
    <value>HADOOP_MAPRED_HOME=/home/xxx/hadoop-latest</value>
  </property>
  <property>
    <name>mapreduce.reduce.env</name>
    <value>HADOOP_MAPRED_HOME=/home/xxx/hadoop-latest</value>
  </property>
</configuration>

master1で下記を実行する。

$HADOOP_HOME/sbin/start-dfs.sh
$HADOOP_HOME/sbin/start-yarn.sh

問題なく両方の起動が確認できたので、実際に処理を実行してみる。

$HADOOP_HOME/bin/hadoop jar /home/xxx/hadoop-latest/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.2.2.jar pi 10 10000

下記の様に処理が進んで行き、

2021-02-18 08:12:21,597 INFO mapreduce.Job:  map 0% reduce 0%
2021-02-18 08:13:06,983 INFO mapreduce.Job:  map 19% reduce 0%
2021-02-18 08:13:38,044 INFO mapreduce.Job:  map 31% reduce 0%
2021-02-18 08:13:39,081 INFO mapreduce.Job:  map 38% reduce 0%
2021-02-18 08:14:09,121 INFO mapreduce.Job:  map 44% reduce 0%
2021-02-18 08:14:10,159 INFO mapreduce.Job:  map 56% reduce 0%
2021-02-18 08:14:41,193 INFO mapreduce.Job:  map 69% reduce 0%
2021-02-18 08:14:48,435 INFO mapreduce.Job:  map 69% reduce 23%
2021-02-18 08:15:12,225 INFO mapreduce.Job:  map 81% reduce 23%
2021-02-18 08:15:13,260 INFO mapreduce.Job:  map 81% reduce 27%
2021-02-18 08:15:42,218 INFO mapreduce.Job:  map 88% reduce 27%
2021-02-18 08:15:43,256 INFO mapreduce.Job:  map 94% reduce 27%
2021-02-18 08:15:44,290 INFO mapreduce.Job:  map 94% reduce 31%
2021-02-18 08:16:14,264 INFO mapreduce.Job:  map 100% reduce 31%
2021-02-18 08:16:15,298 INFO mapreduce.Job:  map 100% reduce 67%
2021-02-18 08:16:17,369 INFO mapreduce.Job:  map 100% reduce 100%

最終的に下記の結果が表示された。やはりかなり遅い。

Job Finished in 320.709 seconds
Estimated value of Pi is 3.14157500000000000000

次回、NameNodeをHA化する。