SuprSonicJetBoy's blog

いろいろです。

GPyOpt が最初のサンプルで動かない場合

GPyOpt を試していたのですが、最初のサンプルでいきなりコケたので、その解決方法のメモ。

もくじ

環境

* Windows 10  
* Python 3.5  
* GPyOpt v1.0.3  

エラー内容

とてもシンプルなコードでエラー。
sheffieldml.github.io

C:\>python gpyopt_test.py
Traceback (most recent call last):
  File "gpyopt_test.py", line 11, in <module>
    myBopt.run_optimization(max_iter=15)
  File "C:\Users\ssjb\AppData\Local\Programs\Python\Python35\lib\site-packages\GPyOpt\methods\bayesian_optimization.py", line 458, in run_optimization
    super(BayesianOptimization, self).run_optimization(max_iter = max_iter, max_time = max_time,  eps = eps, verbosity=verbosity, save_models_parameters = save_models_parameters, report_file = report_file, evaluations_file= evaluations_file, models_file=models_file)
  File "C:\Users\ssjb\AppData\Local\Programs\Python\Python35\lib\site-packages\GPyOpt\core\bo.py", line 103, in run_optimization
    self._update_model()
  File "C:\Users\ssjb\AppData\Local\Programs\Python\Python35\lib\site-packages\GPyOpt\core\bo.py", line 198, in _update_model
    self._save_model_parameter_values()
  File "C:\Users\ssjb\AppData\Local\Programs\Python\Python35\lib\site-packages\GPyOpt\core\bo.py", line 209, in _save_model_parameter_values
    if self.model_parameters_iterations == None:
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

解決方法

GPyOpt/core/bo.py の209行目を修正します。

def _save_model_parameter_values(self):
-    if self.model_parameters_iterations == None:
+    if self.model_parameters_iterations is None:
        self.model_parameters_iterations = self.model.get_model_parameters()

None の比較は、== ではなく、 is を使いましょう。

sheffieldml.github.io

Keras / LSTM で自然数列を予測する簡単なサンプル

LSTM (Long short-term memory) の学習のために、Kerasで自然数列を推測しました。
気温や正弦波、株価の予測などをしている記事は多く見かけましたが、最も単純な自然数(正の整数)の数列はなかったので、LSTMの入り口としてコードを書いてみました。

(もっとも機械学習でやる必要がないのでそもそもやる意味がない)
ただ、正誤判断は単純なので、LSTMの触りをやるには丁度いい題材ではないでしょうか。

自然数の数列

数列の中で最も単純な自然数の数列です。
ここでは、0を含めない正の整数をとります。

 1, 2, 3, 4, 5, 6, 7, 8 , 9, 10, ...
(The positive integers, or the natural numbers)

データの準備

自然数なので、準備も簡単です。
sklearn の MinMaxScaler()で正規化を行います。

from sklearn import preprocessing

SERIES_LENGTH = 1000

# Prepare dataset
dataset = np.arange(1, SERIES_LENGTH+1, 1).reshape(SERIES_LENGTH, 1).astype(np.float)

# Transform
scaler = preprocessing.MinMaxScaler()
dataset = scaler.fit_transform(dataset)

# Split dataset into train and test subsets
train_dataset = dataset[0:int(len(dataset)*0.8), :]
test_dataset =  dataset[len(train_dataset):len(dataset), :]

次に、それぞれのデータを入力値と出力値に分けます。
例えば、1つ前を参照するとすると、

X = 1, 2, 3, 4, 5, 6, 7, 8 , 9, 10, ...
Y = 2, 3, 4, 5, 6, 7, 8 , 9, 10, 11, ...

3つ前までだと、

X = [1, 2, 3], [2, 3, 4], [3, 4, 5], ...
Y = 4, 5, 6, ...

のような感じになります。

コードにすると、このような感じです。
look_back の値で、どのくらい前までを参照するかを決めます。

look_back = 1
x, y = [], []
for i in range(len(dataset) - look_back):
    a = i + look_back
    x.append(dataset[i:a, 0])
    y.append(dataset[a, 0])

予測

あとは、予測部分。
(実際のコードは一番下の全文を参考ください)

from keras.models import Sequential
from keras.layers.core import Dense, Activation
from keras.layers.recurrent import LSTM

model = Sequential()
model.add(LSTM(128, input_shape=(1, look_back)))
model.add(Dense(1))
model.add(Activation('linear'))
model.compile(loss='mean_squared_error', optimizer='adam')
model.fit(x, y, batch_size=2, epochs=10, verbose=1)

Keras 簡単すぎます。
(活性化関数のlinearは線形分類なので、なくてもいいのではないかと思います。あっても意味はない?)

あとは、図に描画できるように整形してやります。

予測結果

Epoch 1/10
799/799 [==============================] - 2s - loss: 0.0128
Epoch 2/10
799/799 [==============================] - 1s - loss: 2.5285e-05
Epoch 3/10
799/799 [==============================] - 1s - loss: 1.6923e-05
Epoch 4/10
799/799 [==============================] - 1s - loss: 8.7859e-06
Epoch 5/10
799/799 [==============================] - 1s - loss: 3.0853e-06
Epoch 6/10
799/799 [==============================] - 1s - loss: 6.9152e-07
Epoch 7/10
799/799 [==============================] - 1s - loss: 4.0892e-08
Epoch 8/10
799/799 [==============================] - 1s - loss: 2.6624e-08
Epoch 9/10
799/799 [==============================] - 1s - loss: 1.1367e-07
Epoch 10/10
799/799 [==============================] - 1s - loss: 2.4738e-06
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
lstm_1 (LSTM)                (None, 128)               66560
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 129
_________________________________________________________________
activation_1 (Activation)    (None, 1)                 0
=================================================================
Total params: 66,689
Trainable params: 66,689
Non-trainable params: 0
_________________________________________________________________
None

Train Score: 1.491 RMSE
Test  Score: 0.979 RMSE

Next prediction: 1000.09


Time: 17.5sec

遠目から見ると一致しているように見えますが、拡大すると少しずれています。
しっかり右肩上がりで予測できています。
赤線は、最新のデータを使った次の予測です。

f:id:suprsonicjetboy:20170904170418p:plain f:id:suprsonicjetboy:20170904170423p:plain

参考

Time Series Prediction with LSTM Recurrent Neural Networks in Python with Keras - Machine Learning Mastery

コード

import urllib, urllib.request
import numpy as np
import matplotlib.pyplot as plt
import math
import time
from keras.models import Sequential
from keras.layers.core import Dense, Activation
from keras.layers.recurrent import LSTM
from sklearn import preprocessing
from sklearn.metrics import mean_squared_error


class PredictionLSTM :

    def __init__(self):
        self.look_back = 3
        self.units = 128
        self.epochs = 10
        self.batch_size = 1


    def create_dataset(self, dataset, look_back=1):
        x, y = [], []
        for i in range(len(dataset) - look_back):
            a = i + look_back
            x.append(dataset[i:a, 0])
            y.append(dataset[a, 0])

        return np.array(x), np.array(y)


    def create_model(self):
        model = Sequential()
        model.add(LSTM(self.units, input_shape=(1, self.look_back)))
        model.add(Dense(1))
        model.add(Activation('linear'))
        model.compile(loss='mean_squared_error', optimizer='adam')

        return model


    def train(self, x, y):
        model = self.create_model()
        model.fit(x, y, batch_size=self.batch_size, epochs=self.epochs, verbose=1)

        return model


if __name__ == "__main__":
    START_TIME = time.time()
    SERIES_LENGTH = 1000

    # Prepare dataset
    dataset = np.arange(1, SERIES_LENGTH+1, 1).reshape(SERIES_LENGTH, 1).astype(np.float)

    # Transform
    scaler = preprocessing.MinMaxScaler()
    dataset = scaler.fit_transform(dataset)

    # Split dataset into train and test subsets
    train_dataset = dataset[0:int(len(dataset)*0.8), :]
    test_dataset =  dataset[len(train_dataset):len(dataset), :]



    # LSTM
    prediction_ltsm = PredictionLSTM()

    # Create train dataset
    train_x, train_y = prediction_ltsm.create_dataset(train_dataset, prediction_ltsm.look_back)

    train_x = np.reshape(train_x, (train_x.shape[0], 1, train_x.shape[1]))
    # Create test dataset
    test_x, test_y  = prediction_ltsm.create_dataset(test_dataset, prediction_ltsm.look_back)
    test_x = np.reshape(test_x, (test_x.shape[0], 1, test_x.shape[1]))


    # Create and fit the LSTM network
    model = prediction_ltsm.train(train_x, train_y)
    print(model.summary())


    # Predict train dataset
    train_prediction = model.predict(train_x)
    train_prediction = scaler.inverse_transform(train_prediction)
    train_y = scaler.inverse_transform([train_y])

    # Predict test dataset
    test_prediction = model.predict(test_x)
    test_prediction = scaler.inverse_transform(test_prediction)
    test_y = scaler.inverse_transform([test_y])


    # Calculate RMSE(Root Mean Squared Error)
    train_score = math.sqrt(mean_squared_error(train_y[0], train_prediction[:, 0]))
    test_score = math.sqrt(mean_squared_error(test_y[0], test_prediction[:, 0]))
    print("\nTrain Score: {0:.3f} RMSE".format(train_score))
    print("Test  Score: {0:.3f} RMSE".format(test_score))


    # Predict the next value using the latest data
    latest_x = np.array([test_dataset[-prediction_ltsm.look_back:]])
    latest_x = np.reshape(latest_x, (latest_x.shape[0], 1, latest_x.shape[1]))
    next_prediction = model.predict(latest_x)
    next_prediction = scaler.inverse_transform(next_prediction)
    print("\nNext prediction: {0:.2f}".format(list(next_prediction)[0][0]), "\n"*2)

    print("Time: {0:.1f}sec".format(time.time() - START_TIME))


    # Draw a figure
    placeholder = np.append(dataset, np.zeros((1, dataset.shape[1])), axis=0)
    placeholder[:, :] = np.nan

    correct_dataset_plt = scaler.inverse_transform(dataset)

    train_plt = np.copy(placeholder)
    train_plt[prediction_ltsm.look_back:len(train_prediction)+prediction_ltsm.look_back, :] = train_prediction

    test_plt = np.copy(placeholder)
    test_plt[len(train_prediction)+(prediction_ltsm.look_back*2):len(dataset), :] = test_prediction

    nest_plt = np.copy(placeholder)
    nest_plt[len(placeholder)-2:len(placeholder), :] = np.append(test_prediction[-1], next_prediction.reshape(1)).reshape(2, 1)

    plt.plot(correct_dataset_plt, label='Correct prediction')
    plt.plot(train_plt, label='Train')
    plt.plot(test_plt, label='Test')
    plt.plot(nest_plt, label='Next prediction', c='r')
    plt.legend() 
    plt.show()
    

github.com

Windowsでscikit-learn(sklearn)をインストールしてirisの予測をサクッとするまで

Windows機械学習周りの環境を再構築したので、まっさらな状態から、scikit-learn (sklearn) を導入し、Random Forestでirisを予測するところまでを行います。 github.com

環境

Python のインストール

Pythonインストーラーからサクッとインストールできます。
基本的にはコマンドプロンプトからPythonを実行しますので、インストール時に「Add Python to PATH」にチェックを入れると、インストール完了後、すぐにコマンドプロンプトからPythonが使えるようになります。

最新は、v3.6.2 ですが、後にTensorFlowをインストールするつもりで、TensorFlowはv3.5.xが推奨なので、v3.5.2を入れました。 www.python.org

C:\>python --version
Python 3.5.2

scikit-learn以外のパッケージのインストール

scikit-learnは、NumPyとSciPyが必要なのですが、まっさきにpipでインストールすると、SciPyのインストールでコケてしまいます。
そのため、非公式とはなりますが、パッケージのWindows用のバイナリが配布されていますので、それを使用します。

Python Extension Packages for Windows - Christoph Gohlke

既にpipでインストールしてしまった場合は、削除から。

C:\>pip uninstall numpy
C:\>pip uninstall scipy

上記サイトから必要なバイナリをダウンロードします。
ダウンロードするものは、環境によって決めます。
たとえば、NumPyであれば、Python v3.5.2で64bitの環境なのでnumpy‑1.13.1+mkl‑cp35‑cp35m‑win_amd64.whlをダウンロードします。

一先ず必要なものは、

  • NumPy (numpy+mkl)
  • SciPy の2つで、必ず NumPy を最初にインストールします。

pip installで先程ダウンロードしたファイルを指定します。

C:\>pip install numpy-1.13.1+mkl-cp35-cp35m-win_amd64.whl
C:\>pip install scipy-0.19.1-cp35-cp35m-win_amd64.whl

scikit-learn のインストール

あとは普通にpipで入れます。
ついでに、pandasもいれておきます。

C:\>pip install scikit-learn
C:\>pip install pandas

Random Forest でiris予測

sklearnには、いくつかデータセットが用意されています。
今回は、iris のデータセットを使用して、予測を行っていきます。

sklearn.datasets.load_iris — scikit-learn 0.19.0 documentation
のように簡単に読み込めますが、他のデータにも応用を効かせたいので、直接読み込むのではなく、訓練とテストにぶんりさせて、Pandasで読み込んで実行してみます。

CSVファイルはこちら。
scikit-learn/iris.csv at master · scikit-learn/scikit-learn · GitHub

ヘッダーが

150,4,setosa,versicolor,virginica

となっていますので、特徴名の

sepal_length,sepal_width,petal_length,petal_width,species

とします。
species は、[0,1,2]のいずれかで、名称は[setosa, versicolor, virginica] が対応します。

次に手動で、訓練ファイルとテストファイルに分離します。
ヘッダーを含めて、121行目までを訓練データ、その他をテストデータとします。
テストデータにもヘッダーを入れます。

コードはこんな感じです。

import os
import numpy as np
import pandas as pd
import time
from sklearn import ensemble, externals

def main():
    START_TIME = time.time()

    SPECIES = ['setosa', 'versicolor', 'virginica']
    FEATURES = [
        'sepal_length',
        'sepal_width',
        'petal_length',
        'petal_width'
    ]
    LABEL = 'species'

    TRAIN_FILE = "./dataset/iris_training.csv"
    TEST_FILE = "./dataset/iris_test.csv"

    OUTPUT_DIR = "./models"
    OUTPUT_FILE = "{0}/iris-model.ckpt".format(OUTPUT_DIR)

    # Dataset
    training_dataset = pd.read_csv(TRAIN_FILE)
    test_dataset = pd.read_csv(TEST_FILE)

    # Shuffle test dataset
    #test_dataset= test_dataset.sample(frac=1).reset_index(drop=True)


    # Training data
    train_x = np.array(training_dataset[FEATURES].astype(np.float32))
    train_y = np.array(training_dataset[LABEL].astype(np.float32))

    # Test data
    test_x = np.array(test_dataset[FEATURES].astype(np.float32))
    test_y = np.array(test_dataset[LABEL].astype(np.float32))


    print("\n---------- INFORMATION ----------")
    print("FEATURES       : {0}".format(FEATURES))
    print("LABEL          : {0}".format(LABEL))
    print("TRAINING FILE  : {0}".format(TRAIN_FILE))
    print("TRAINING DATA  : {0}".format(len(training_dataset)))
    print("TEST FILE      : {0}".format(TEST_FILE))
    print("TEST DATA      : {0}".format(len(test_dataset)))
    print("OUTPUT         : {0}".format(OUTPUT_FILE))


    print("\n------------ ALGORITHM ------------")
    print("Randam Forest")

    # Random forest
    model = ensemble.RandomForestClassifier()

    # Reconstruct the model 
    # model = externals.joblib.load(OUTPUT_FILE)

    model.fit(train_x, train_y)
    importances = model.feature_importances_

    print("\n------------ IMPORTANCES ------------")
    for i in range(len(FEATURES)):
        print("{0}: {1} ".format(FEATURES[i].ljust(15), importances[i]))


    result = model.predict_proba(test_x)
    print("\n------------ PREDICTION 1 ------------")
    print(result[:10])


    result = model.predict(test_x)
    print("\n------------ PREDICTION 2 ------------")
    print(result[:10])


    print("\n------------ PREDICTION 3 ------------")
    for i in range(10):
        if int(result[i]) == int(test_y[i]):
            print("True  => {1}   {0}  @@ {2}".format(int(result[i]), int(test_y[i]), SPECIES[int(test_y[i])]))
        else:
            print("False => {1}   {0}  @@ {2}".format(int(result[i]), int(test_y[i]), SPECIES[int(test_y[i])]))


    print("\n------------ SCORE ------------")
    print(model.score(test_x, test_y))


    # Persist the model 
    joblib = externals.joblib
    if not os.path.exists(OUTPUT_DIR):
        os.mkdir(OUTPUT_DIR)
    joblib.dump(model, "{0}".format(OUTPUT_FILE))

    print("\n--------------------------------")
    print("Time: {0} sec".format(round(time.time() - START_TIME, 3)))
    print("--------------------------------")


if __name__ == '__main__':
    main()

このようにデータを分離させて、特徴で指定するようにできると、正規化等必要ですが、タイタニックなどにも使いまわせます。

実行すると、こんな感じになります。

---------- INFORMATION ----------
FEATURES       : ['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
LABEL          : species
TRAINING FILE  : ./dataset/iris_training.csv
TRAINING DATA  : 120
TEST FILE      : ./dataset/iris_test.csv
TEST DATA      : 30
OUTPUT         : ./models/iris-model.ckpt

------------ ALGORITHM ------------
Randam Forest

------------ IMPORTANCES ------------
sepal_length   : 0.03482778065132767
sepal_width    : 0.017052383871736687
petal_length   : 0.3757864060484355
petal_width    : 0.5723334294285002

------------ PREDICTION 1 ------------
[[ 0.   0.2  0.8]
 [ 0.   0.9  0.1]
 [ 1.   0.   0. ]
 [ 1.   0.   0. ]
 [ 0.   1.   0. ]
 [ 0.   0.   1. ]
 [ 0.   1.   0. ]
 [ 0.   1.   0. ]
 [ 0.   1.   0. ]
 [ 0.   0.6  0.4]]

------------ PREDICTION 2 ------------
[ 2.  1.  0.  0.  1.  2.  1.  1.  1.  1.]

------------ PREDICTION 3 ------------
True  => 2   2  @@ virginica
True  => 1   1  @@ versicolor
True  => 0   0  @@ setosa
True  => 0   0  @@ setosa
True  => 1   1  @@ versicolor
True  => 2   2  @@ virginica
True  => 1   1  @@ versicolor
True  => 1   1  @@ versicolor
True  => 1   1  @@ versicolor
False => 2   1  @@ virginica

------------ SCORE ------------
0.833333333333

--------------------------------
Time: 0.056 sec
--------------------------------

github.com

Perl で selenium を使う

Perl から selenium を使う場面があったので、インストールと簡単な使い方。

環境

selenium のインストー

サクッとcpanm で。

$ sudo cpanm Selenium::Remote::Driver

webdriver のインストー

Chromeを使いたいので、Chrome のドライバーを homebrew で入れます。

$ brew install chromedriver

起動

Selenium::Chrome を使えば、selenium-server-standalone がなくても動きます。

$ perl
use Selenium::Chrome;
my $driver = Selenium::Chrome->new;
$driver->get('http://www.google.com');
print $driver->get_title();
$driver->shutdown_binary();
__END__

Google

UIBarButtonItem の width と X座標を取得する

UIBarButtonItem の width とX座標が欲しくて、色々試行錯誤していたのですが、結局取れないことがわかりました。
なぜ、UIBarButtonItem の横幅と位置が欲しかったかということ、こういうことがしたかったから。

f:id:suprsonicjetboy:20170609145137p:plain 左のホームアイコンの下にあるラインのように、UIBarButtonItem と同じ幅のラインを引きたかったのです。

もくじ

結論

UIBarButtonItem の横幅は取れない、というよりも、明示的に指定しない限り、0になってしまいます。
0でも問題ないのは、タイトルでも画像でも、上手いことサイズを自動的に設定してくれるからです。
(0だと自動調整のようです)

仮に値を指定すると、値は設定できます。
しかし、X座標は取得できません。

では、どうするかというと、自分で UIButton を作り、サイズを決め、UIBarButtonItem.customView に渡してやります。

私は StoryBoard を使う派なので、Navigation BarBar Button Item を設置して、それをカスタムしていきます。

import UIKit

class viewController: UIViewController {
    @IBOutlet weak var leftItem: UIBarButtonItem!
    @IBOutlet weak var rightItem: UIBarButtonItem!

    override func viewDidLoad() {
        super.viewDidLoad()


         print(self.leftItem.width)
        // 0

        print(self.rightItem.width)
        // 0


        let leftButton: UIButton = UIButton()
        leftButton.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
        leftButton.setImage(UIImage(named: "home"), for: .normal)

        self.leftItem.customView = leftButton


        let rightButton: UIButton = UIButton()
        rightButton.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
        rightButton.setImage(UIImage(named: "picture"), for: .normal)

        self.rightItem.customView = rightButton



        if let a: UIButton = self.leftItem.customView as? UIButton {
            print(a.frame)
            // (20.0, 7.0, 30.0, 30.0)
        }

        if let a: UIButton = self.rightItem.customView as? UIButton {
            print(a.frame)
            // (364.0, 7.0, 30.0, 30.0)
        }
    }

}

CGRect(x: 0, y: 0, width: 30, height: 30) で作っていますが、customView に渡すとX座標、Y座標は自動的に算出されます。
これで、UIBarButtonItem でも横幅、X座標を取得することができました!
ついでに、高さとY座標も。

Y座標の値が 7 なのは、UINavigationBarの高さ44 の中で、高さ30のUIButton が垂直方向に中央になるように調整されているようです。

初期のサイズはどうするのか

ここで気になるのが customView に渡す UIButton のサイズです。
UIBarButtonItem が自動的に調整されるのであれば、UIButton もなるべく自動的に調整された時のサイズに合わせて作りたくなります。
そうしないと、ページによってサイズが変わってしまい、見た目的にあまりよくありません。
(30だと、通常のUIBarButtonItemよりも小さくなってしまいました)

かといって、すべての UIBarButtonItem の customView を、同じように設定してサイズを固定するのは正直面倒。
なので、UIBarButtonItem のサイズに合うように、各所注意して調整して見ました。

アイコン自体のサイズを揃える

当たり前ですが、アイコンのサイズを揃えます。
Human Interface Guideline に合わせておくと、UIButton のサイズを間違えなければ自ずと画像サイズは揃うはずです。

Navigation bar and toolbar icon size
Recommended 75px × 75px (25pt × 25pt @3x)
50px × 50px (25pt × 25pt @2x)
Maximum 83px × 83px (27.67pt × 27.67pt @3x)
56px × 56px (28pt × 28pt @2x)

高さは44固定

上記コードでは、30を設定していましたが、UINavigationBar が 44 なので、高さ目一杯のを44の固定にします。
また、デフォルトの UIBarButtonItem のタップ反応の領域が、UINavigationBar 分あるのでそれも理由です。
UIBarButtonItem.width があって、UIBarButtonItem.height がないのは、恐らくそういうことだと思います。
(実際にはUIBarButtonItemの上にあるステータスバー部分を触れても反応していますが…)

完成

比較するとこんな感じです。 f:id:suprsonicjetboy:20170609172757p:plain 青い方がデフォルトの UIBarButtonItem で、右の黒い方が customView を使ったものです。
ほんの少しずれていますが、気にならないレベルです。

サイズ、座標がとれ、UIBarButtonItem と同じように表現することができました。

UINavigationBar の UINavigationItem のタイトルと画像を切り替える

よくある、UINavigationBar の UINavigationItem のタイトルと画像を切り替える方法のメモ。
UIImageView を配置して、表示・非表示の切り替えを無理矢理やっていましたが、普通にできました…

import UIKit

class viewController: UIViewController {

    @IBOutlet weak var navItem: UINavigationItem!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.setLogo()
        // self.setTitle(title: "Title")
    }


    /**
      Set the logo image
    */
   func setLogo() {
        if let image: UIImage = UIImage(named: "logo") {
            let logoImageView: UIImageView = UIImageView(image: image)
            self.navItem.titleView = logoImageView
        }
    }


    /**
      Set the title
    */
   func setTitle(title: String) {
        self.navItem.titleView = nil
        self.navItem.title = title
    }

}

適当なタイミングで呼び出します。
UINavigationItem に titleView を設定した後、普通の title に戻したい場合は、titleView に nil を渡します。
特に難しいことはなく、知っておくべきことでした…

CakePHP3をさくらのレンサバにインストールする

さくらのレンタルサーバーで、今まではCakePHP2を問題なく使えていたのですが、CakePHP3になってからは使うことができなくなってしまいました。

CakePHP3からintlモジュールが必要となり、これがさくらのレンサバには入っておらず、CakePHP3を使うことができなくなりました。
(ゴニョればintlをインストールできるようですが…)

CakePHPの2系を使えばいいのですが、そろそろ3系も触っておきたいなと思い、試しにインストールできるかやってみました。

* この記事は、こちらの内容を踏襲していますので、先に一読頂くといいと思います。
CakePHP3 を intl 拡張モジュールなしでインストールする - Qiita

目次

インストールまでの流れ

CakePHP3からは、composer でのインストールが推奨されているので、まずは composer のインストールから始めます。
次に、composer を使って、CakePHP3 のインストール。
CakePHPプラグイン intlless を使用して、intl が無くても CakePHP3 が動くようにする。

と、こんな感じです。

composer をインストー

さくらのレンサバ(スタンダード)でもSSH接続できますので、接続してから composer をインストールします。

$ ssh -p 22 USER@USER.sakura.ne.jp
Welcome to FreeBSD!

% pwd
/home/USER

% echo $PATH
/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/home/USER/bin

% mkdir bin

% curl -sS https://getcomposer.org/installer | php -- --install-dir=bin --filename=composer
All settings correct for using Composer
Downloading...

Composer (version 1.4.2) successfully installed to: /home/USER/bin/composer
Use it: php bin/composer

% composer
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 1.4.2 2017-05-17 08:17:52

無事、composerはインストールできました。

composer の設定

composerを使ってインストールするのですが、intlがないのでインストールでこけます。
なので、composerにあたかもintlが入っているかのように設定を行います。

% composer config --global platform.ext-intl 0.0.0

この platform というのは、PHP拡張機能のバージョンを偽装することができます。

CakePHP3をインストー

実際にインストールしてみます。

% composer create-project --prefer-dist cakephp/app test/app
Installing cakephp/app (3.4.2)
  - Installing cakephp/app (3.4.2): Loading from cache
Created project in test/app
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 35 installs, 0 updates, 0 removals
  - Installing cakephp/plugin-installer (1.0.0): Downloading (100%)
  - Installing psr/http-message (1.0.1): Downloading (100%)
  - Installing zendframework/zend-diactoros (1.4.0): Downloading (100%)
  - Installing aura/intl (3.0.0): Downloading (100%)
  - Installing mobiledetect/mobiledetectlib (2.8.25): Downloading (100%)
  - Installing psr/log (1.0.2): Downloading (100%)
  - Installing cakephp/chronos (1.1.2): Downloading (100%)
  - Installing cakephp/cakephp (3.4.7): Downloading (100%)
  - Installing symfony/yaml (v3.3.0): Downloading (100%)
  - Installing symfony/debug (v3.3.0): Downloading (100%)
  - Installing symfony/polyfill-mbstring (v1.3.0): Downloading (100%)
  - Installing symfony/console (v3.3.0): Downloading (100%)
  - Installing symfony/filesystem (v3.3.0): Downloading (100%)
  - Installing symfony/config (v3.3.0): Downloading (100%)
  - Installing robmorgan/phinx (v0.6.5): Downloading (100%)
  - Installing cakephp/migrations (1.6.7): Downloading (100%)
  - Installing jakub-onderka/php-console-color (0.1): Downloading (100%)
  - Installing jakub-onderka/php-console-highlighter (v0.3.2): Downloading (100%)
  - Installing dnoegel/php-xdg-base-dir (0.1): Downloading (100%)
  - Installing nikic/php-parser (v3.0.5): Downloading (100%)
  - Installing symfony/var-dumper (v3.3.0): Downloading (100%)
  - Installing psy/psysh (v0.8.5): Downloading (100%)
  - Installing jdorn/sql-formatter (v1.2.17): Downloading (100%)
  - Installing symfony/process (v3.3.0): Downloading (100%)
  - Installing symfony/finder (v3.3.0): Downloading (100%)
  - Installing seld/phar-utils (1.0.1): Downloading (100%)
  - Installing seld/jsonlint (1.6.0): Downloading (100%)
  - Installing seld/cli-prompt (1.0.3): Downloading (100%)
  - Installing justinrainbow/json-schema (5.2.1): Downloading (100%)
  - Installing composer/spdx-licenses (1.1.6): Downloading (100%)
  - Installing composer/semver (1.4.2): Downloading (100%)
  - Installing composer/ca-bundle (1.0.7): Downloading (100%)
  - Installing composer/composer (1.4.2): Downloading (100%)
  - Installing cakephp/debug_kit (3.10.2): Downloading (100%)
  - Installing cakephp/bake (1.3.4): Downloading (100%)
cakephp/app suggests installing markstory/asset_compress (An asset compression plugin which provides file concatenation and a flexible filter system for preprocessing and minification.)
cakephp/app suggests installing dereuromark/cakephp-ide-helper (After baking your code, this keeps your annotations in sync with the code evolving from there on for maximum IDE and PHPStan compatibility.)
cakephp/app suggests installing phpunit/phpunit (Allows automated tests to be run without system-wide install.)
cakephp/app suggests installing cakephp/cakephp-codesniffer (Allows to check the code against the coding standards used in CakePHP.)
cakephp/cakephp suggests installing lib-ICU (The intl PHP library, to use Text::transliterate() or Text::slug())
symfony/console suggests installing symfony/event-dispatcher ()
symfony/var-dumper suggests installing ext-symfony_debug ()
psy/psysh suggests installing ext-pcntl (Enabling the PCNTL extension makes PsySH a lot happier :))
psy/psysh suggests installing ext-readline (Enables support for arrow-key history navigation, and showing and manipulating command history.)
psy/psysh suggests installing ext-pdo-sqlite (The doc command requires SQLite to work.)
psy/psysh suggests installing hoa/console (A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit.)
cakephp/debug_kit suggests installing ext-sqlite (DebugKit needs to store panel data in a database. SQLite is simple and easy to use.)
Writing lock file
Generating autoload files
> Cake\Composer\Installer\PluginInstaller::postAutoloadDump
> App\Console\Installer::postInstall
Created `config/app.php` file
Set Folder Permissions ? (Default to Y) [Y,n]?
Permissions set on /home/USER/www/test/app/tmp/cache
Permissions set on /home/USER/www/test/app/tmp/cache/models
Permissions set on /home/USER/www/test/app/tmp/cache/persistent
Permissions set on /home/USER/www/test/app/tmp/cache/views
Permissions set on /home/USER/www/test/app/tmp/sessions
Permissions set on /home/USER/www/test/app/tmp/tests
Permissions set on /home/USER/www/test/app/tmp
Permissions set on /home/USER/www/test/app/logs
Updated Security.salt value in config/app.php

こんな感じで無事インストールができました。

インストールが完了したら、今後に影響がないように、intlの設定を戻します。

% composer config --global --unset platform.ext-intl

ちなみに、intlが無い場合は、このようにコケます。

% composer create-project --prefer-dist cakephp/app test/app
Installing cakephp/app (3.4.2)
  - Installing cakephp/app (3.4.2): Downloading (100%)
Created project in test/app
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - cakephp/cakephp 3.4.7 requires ext-intl * -> the requested PHP extension intl is missing from your system.
    - cakephp/cakephp 3.4.6 requires ext-intl * -> the requested PHP extension intl is missing from your system.
    - cakephp/cakephp 3.4.5 requires ext-intl * -> the requested PHP extension intl is missing from your system.
    - cakephp/cakephp 3.4.4 requires ext-intl * -> the requested PHP extension intl is missing from your system.
    - cakephp/cakephp 3.4.3 requires ext-intl * -> the requested PHP extension intl is missing from your system.

プラグイン intlless をインストー

このままアクセスすると、案の定エラーがでます。

Fatal error: You must enable the intl extension to use CakePHP. in /home/USER/www/test/app/config/bootstrap.php on line 25

なので、intlを使わなくてもCakePHP3が使えるようになる、intlless をインストールします。
github.com

composerで、intllessのインストール。

% cd test/app
% composer require --prefer-dist chinpei215/cakephp-intlless
Using version ^1.0 for chinpei215/cakephp-intlless
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Installing chinpei215/cakephp-intlless (1.0.1): Loading from cache
Writing lock file
Generating autoload files
> Cake\Composer\Installer\PluginInstaller::postAutoloadDump

最後に、 config/bootstrap.php でintllessを読み込み、intl関連の記述をコメントアウトします。
詳細は、intllessのgithubページを参照してください。

完了

f:id:suprsonicjetboy:20170601172225p:plain

Your version of PHP does NOT have the intl extension loaded. と正しい警告が。
これで、intlを使わずに、無事CakePHP3を表示できました!
intlless の開発者の @chinpei215 さんには感謝しかありません。

* intlを使わないことで少なからず制限があるので、その辺りは留意しましょう。

参考