読者です 読者をやめる 読者になる 読者になる

Keras のテキスト分類の結果をscikit learn のmetricsで評価

機械学習-DeepLearning

前回、scikit-learnの GridSearch をおさらいした。今回は、前々回のコードを修正し、同じscikit-learnのデータを使ってKeras(Tensolflowバックエンド)での標準的実装で精度を出した。精度算出のメトリックを合わせるため、scikit-learnで提供されているmetrics系の関数を使って計算している。

結論からいうと、scikit-learnのlinear svmと、KerasでのMLPの精度は、全体平均精度は変わらず。
というか、今回は、全結合1層+活性化層1層+Dropoutしかないので、Deep learningじゃない。なので、scikit-learnのパーセプトロン実装にDropoutを加えただけという感じ。そういう意味では、パーセプトロンと比較して、Dropoutの効果として見るべきかもしれない。
もう一つの見方として、全結合で512まで一度落としているので、ここで、うまく特徴抽出が働いていて、SVMのGridSearchと同じくらいの性能は出せたと考えるべきかな。

結果

実装 特徴量 precision recall f1-score
Keras(DeepLearning-512-ReLU-Dropout0.5, batch=1024) 単語カウント(記号除去) 0.71 0.70 0.70
Scikit-Learn(LinearSVM-GridSearch(2乗誤差-L2正則化)) TFIDF+正規化(ストップワード除去) 0.71 0.70 0.70
Scikit-Learn(Perceptron-50イテレーション) 単語カウント(ストップワード除去) 0.65 0.64 0.64

入力データ

20 newsgroups dataset

  • 11314 documents - 13.782MB (training set)
  • 7532 documents - 8.262MB (test set)

特徴数

  • Keras:105373 (KerasのTokenizerはデフォルトで半角の記号群を除去)
  • Scikit-Learn:101322(TfidfVectorizerでstopword=englishを指定)

実行時に、ヘッダを削除(過学習を抑えるため)。

ソースコード

# encoding: utf-8
'''
 -- Keras example text classification with scikit leran metrics

Created on 2017/02/23

@author:     mzi

@copyright:  2017 mzi. All rights reserved.

@license:    Apache Licence 2.0

'''
from __future__ import print_function
import sys
import os

from optparse import OptionParser
from time import time

import numpy as np

from sklearn.datasets import fetch_20newsgroups
from sklearn import metrics

from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation
from keras.utils import np_utils
from keras.preprocessing.text import Tokenizer

__all__ = []
__version__ = 0.1
__date__ = '2017-02-23'
__updated__ = '2017-02-23'

TESTRUN = 0
PROFILE = 0

batch_size = 1024
nb_epoch = 5

def size_mb(docs):
    return sum(len(s.encode('utf-8')) for s in docs) / 1e6

def trim(s):
    """Trim string to fit on terminal (assuming 80-column display)"""
    return s if len(s) <= 80 else s[:77] + "..."


def main(argv=None):
    '''Command line options.'''
    program_name = os.path.basename(sys.argv[0])
    program_version = "v%f" %  __version__
    program_build_date = "%s" % __updated__

    program_version_string = '%%prog %s (%s)' % (program_version, program_build_date)
    program_longdesc = 'GridSearh for scikit learn - LinearSVC with TextData' 
    program_license = "Copyright 2017 mzi                                            \
                Licensed under the Apache License 2.0\nhttp://www.apache.org/licenses/LICENSE-2.0"

    if argv is None:
        argv = sys.argv[1:]

    # setup option parser
    op = OptionParser(version=program_version_string, epilog=program_longdesc, description=program_license)
    op.add_option("--report",
                  action="store_true", dest="print_report",
                  help="Print a detailed classification report.")
    op.add_option("--confusion_matrix",
                  action="store_true", dest="print_cm",
                  help="Print the confusion matrix.")
    op.add_option("--all_categories",
                  action="store_true", dest="all_categories",
                  help="Whether to use all categories or not.")
    op.add_option("--filtered",
                  action="store_true",
                  help="Remove newsgroup information that is easily overfit: "
                       "headers, signatures, and quoting.")

    # process options
    (opts, args) = op.parse_args(argv)

    #Categories
    if opts.all_categories:
        categories = None
    else:
        categories = [
            'alt.atheism',
            'talk.religion.misc',
            'comp.graphics',
            'sci.space',
            ]

    #Remove headers
    if opts.filtered:
        remove = ('headers', 'footers', 'quotes')
    else:
        remove = ()

    print(__doc__)
    op.print_help()
    print()
    
    # MAIN BODY #
    print("Loading 20 newsgroups dataset for categories:")
    print(categories if categories else "all")
    
    data_train = fetch_20newsgroups(subset='train', categories=categories,
                                    shuffle=True, random_state=42,
                                    remove=remove)
    
    data_test = fetch_20newsgroups(subset='test', categories=categories,
                                   shuffle=True, random_state=42,
                                   remove=remove)
    print('data loaded')

    # order of labels in `target_names` can be different from `categories`
    target_names = data_train.target_names

    data_train_size_mb = size_mb(data_train.data)
    data_test_size_mb = size_mb(data_test.data)
    print("%d documents - %0.3fMB (training set)" % (
        len(data_train.data), data_train_size_mb))
    print("%d documents - %0.3fMB (test set)" % (
        len(data_test.data), data_test_size_mb))
    print()
    nb_classes = np.max(data_train.target) + 1
    print(nb_classes, 'classes')

    print('Vectorizing sequence data...')
    tokenizer = Tokenizer(nb_words=None, lower=True, split=' ', char_level=False)
    tokenizer.fit_on_texts(data_train.data)
    X_train = tokenizer.texts_to_matrix(data_train.data, mode='count')
    X_test = tokenizer.texts_to_matrix(data_test.data, mode='count')
    print('X_train shape:', X_train.shape)
    print('X_test shape:', X_test.shape)

    print('Convert class vector to binary class matrix (for use with categorical_crossentropy)')
    Y_train = np_utils.to_categorical(data_train.target, nb_classes)
    Y_test = np_utils.to_categorical(data_test.target, nb_classes)
    print('Y_train shape:', Y_train.shape)
    print('Y_test shape:', Y_test.shape)

    max_words=X_train.shape[1]
    print('Building model...')
    model = Sequential()
    model.add(Dense(512, input_shape=(max_words,)))
    model.add(Activation('relu'))
    model.add(Dropout(0.5))
    model.add(Dense(nb_classes))
    model.add(Activation('softmax'))

    model.compile(loss='categorical_crossentropy',
                  optimizer='adam',
                  metrics=['accuracy'])
    
    t0 = time()
    history = model.fit(X_train, Y_train,
                        nb_epoch=nb_epoch, batch_size=batch_size,
                        verbose=1, validation_split=0.1)
    train_time = time() - t0
    print("train time: %0.3fs" % train_time)

    t0 = time()
    #score = model.evaluate(X_test, Y_test,
    #                       batch_size=batch_size, verbose=1)
    #print('Test score:', score[0])
    #print('Test accuracy:', score[1])
    #Y_pred = model.predict(X_test, batch_size=batch_size, verbose=1)
    y_pred = model.predict_classes(X_test, batch_size=batch_size, verbose=1)
    train_time = time() - t0
    print("test time: %0.3fs" % train_time)

    if opts.print_report:
        print("classification report:")
        print(metrics.classification_report(data_test.target, y_pred,
                                            target_names=target_names))
    if opts.print_cm:
        print("confusion matrix:")
        print(metrics.confusion_matrix(data_test.target, y_pred))

    return
    
if __name__ == "__main__":
    if TESTRUN:
        import doctest
        doctest.testmod()
    if PROFILE:
        import cProfile
        import pstats
        profile_filename = '_profile.txt'
        cProfile.run('main()', profile_filename)
        statsfile = open("profile_stats.txt", "wb")
        p = pstats.Stats(profile_filename, stream=statsfile)
        stats = p.strip_dirs().sort_stats('cumulative')
        stats.print_stats()
        statsfile.close()
        sys.exit(0)
    sys.exit(main())
    

コマンドライン

mlp_textclf.py --report --confusion_matrix   --filtered --all_categories

出力

Using TensorFlow backend.

 -- Keras example text classification with scikit leran metrics

Created on 2017/02/23

@author:     mzi

@copyright:  2017 mzi. All rights reserved.

@license:    Apache Licence 2.0


Usage: mlp_textclf.py [options]

Copyright 2017 mzi
Licensed under the Apache License 2.0
http://www.apache.org/licenses/LICENSE-2.0

Options:
  --version           show program's version number and exit
  -h, --help          show this help message and exit
  --report            Print a detailed classification report.
  --confusion_matrix  Print the confusion matrix.
  --all_categories    Whether to use all categories or not.
  --filtered          Remove newsgroup information that is easily overfit:
                      headers, signatures, and quoting.

GridSearh for scikit learn - LinearSVC with TextData

Loading 20 newsgroups dataset for categories:
all
data loaded
11314 documents - 13.782MB (training set)
7532 documents - 8.262MB (test set)

20 classes
Vectorizing sequence data...
X_train shape: (11314, 105373)
X_test shape: (7532, 105373)
Convert class vector to binary class matrix (for use with categorical_crossentropy)
Y_train shape: (11314, 20)
Y_test shape: (7532, 20)
Building model...
Train on 10182 samples, validate on 1132 samples
Epoch 1/5

 1024/10182 [==>...........................] - ETA: 95s - loss: 3.0274 - acc: 0.0518
 2048/10182 [=====>........................] - ETA: 62s - loss: 3.0016 - acc: 0.0894
 3072/10182 [========>.....................] - ETA: 48s - loss: 2.9685 - acc: 0.1387
 4096/10182 [===========>..................] - ETA: 39s - loss: 2.9335 - acc: 0.1799
 5120/10182 [==============>...............] - ETA: 31s - loss: 2.8859 - acc: 0.2254
 6144/10182 [=================>............] - ETA: 24s - loss: 2.8427 - acc: 0.2664
 7168/10182 [====================>.........] - ETA: 18s - loss: 2.7968 - acc: 0.3040
 8192/10182 [=======================>......] - ETA: 12s - loss: 2.7480 - acc: 0.3346
 9216/10182 [==========================>...] - ETA: 5s - loss: 2.6972 - acc: 0.3655 
10182/10182 [==============================] - 69s - loss: 2.6638 - acc: 0.3841 - val_loss: 2.1918 - val_acc: 0.6670
Epoch 2/5

 1024/10182 [==>...........................] - ETA: 57s - loss: 2.0440 - acc: 0.7549
 2048/10182 [=====>........................] - ETA: 47s - loss: 1.9434 - acc: 0.7656
 3072/10182 [========>.....................] - ETA: 44s - loss: 1.9161 - acc: 0.7614
 4096/10182 [===========>..................] - ETA: 35s - loss: 1.8767 - acc: 0.7666
 5120/10182 [==============>...............] - ETA: 29s - loss: 1.8473 - acc: 0.7637
 6144/10182 [=================>............] - ETA: 24s - loss: 1.8071 - acc: 0.7651
 7168/10182 [====================>.........] - ETA: 17s - loss: 1.7737 - acc: 0.7662
 8192/10182 [=======================>......] - ETA: 11s - loss: 1.7445 - acc: 0.7670
 9216/10182 [==========================>...] - ETA: 5s - loss: 1.7181 - acc: 0.7666 
10182/10182 [==============================] - 64s - loss: 1.6931 - acc: 0.7682 - val_loss: 1.6281 - val_acc: 0.7102
Epoch 3/5

 1024/10182 [==>...........................] - ETA: 53s - loss: 1.3333 - acc: 0.8359
 2048/10182 [=====>........................] - ETA: 45s - loss: 1.2573 - acc: 0.8413
 3072/10182 [========>.....................] - ETA: 38s - loss: 1.2245 - acc: 0.8480
 4096/10182 [===========>..................] - ETA: 33s - loss: 1.2217 - acc: 0.8459
 5120/10182 [==============>...............] - ETA: 27s - loss: 1.2054 - acc: 0.8443
 6144/10182 [=================>............] - ETA: 21s - loss: 1.1911 - acc: 0.8433
 7168/10182 [====================>.........] - ETA: 16s - loss: 1.1727 - acc: 0.8470
 8192/10182 [=======================>......] - ETA: 11s - loss: 1.1616 - acc: 0.8478
 9216/10182 [==========================>...] - ETA: 5s - loss: 1.1485 - acc: 0.8477 
10182/10182 [==============================] - 62s - loss: 1.1331 - acc: 0.8494 - val_loss: 1.3319 - val_acc: 0.7438
Epoch 4/5

 1024/10182 [==>...........................] - ETA: 56s - loss: 0.9577 - acc: 0.8896
 2048/10182 [=====>........................] - ETA: 46s - loss: 0.8776 - acc: 0.8955
 3072/10182 [========>.....................] - ETA: 40s - loss: 0.8612 - acc: 0.8916
 4096/10182 [===========>..................] - ETA: 34s - loss: 0.8486 - acc: 0.8955
 5120/10182 [==============>...............] - ETA: 28s - loss: 0.8433 - acc: 0.8943
 6144/10182 [=================>............] - ETA: 22s - loss: 0.8497 - acc: 0.8914
 7168/10182 [====================>.........] - ETA: 16s - loss: 0.8415 - acc: 0.8923
 8192/10182 [=======================>......] - ETA: 11s - loss: 0.8343 - acc: 0.8940
 9216/10182 [==========================>...] - ETA: 5s - loss: 0.8357 - acc: 0.8939 
10182/10182 [==============================] - 62s - loss: 0.8253 - acc: 0.8942 - val_loss: 1.2023 - val_acc: 0.7429
Epoch 5/5

 1024/10182 [==>...........................] - ETA: 68s - loss: 0.7332 - acc: 0.9023
 2048/10182 [=====>........................] - ETA: 61s - loss: 0.7494 - acc: 0.9019
 3072/10182 [========>.....................] - ETA: 50s - loss: 0.7110 - acc: 0.9082
 4096/10182 [===========>..................] - ETA: 39s - loss: 0.6886 - acc: 0.9141
 5120/10182 [==============>...............] - ETA: 31s - loss: 0.6811 - acc: 0.9129
 6144/10182 [=================>............] - ETA: 25s - loss: 0.6698 - acc: 0.9157
 7168/10182 [====================>.........] - ETA: 18s - loss: 0.6632 - acc: 0.9157
 8192/10182 [=======================>......] - ETA: 12s - loss: 0.6540 - acc: 0.9163
 9216/10182 [==========================>...] - ETA: 5s - loss: 0.6445 - acc: 0.9173 
10182/10182 [==============================] - 64s - loss: 0.6358 - acc: 0.9181 - val_loss: 1.1015 - val_acc: 0.7562
train time: 326.131s

1024/7532 [===>..........................] - ETA: 115s
2048/7532 [=======>......................] - ETA: 57s 
3072/7532 [===========>..................] - ETA: 34s
4096/7532 [===============>..............] - ETA: 22s
5120/7532 [===================>..........] - ETA: 13s
6144/7532 [=======================>......] - ETA: 7s 
7168/7532 [===========================>..] - ETA: 1s
7532/7532 [==============================] - 40s    
test time: 55.487s
classification report:
                          precision    recall  f1-score   support

             alt.atheism       0.53      0.46      0.49       319
           comp.graphics       0.68      0.73      0.71       389
 comp.os.ms-windows.misc       0.68      0.62      0.65       394
comp.sys.ibm.pc.hardware       0.69      0.62      0.65       392
   comp.sys.mac.hardware       0.71      0.70      0.71       385
          comp.windows.x       0.86      0.75      0.80       395
            misc.forsale       0.80      0.81      0.80       390
               rec.autos       0.81      0.74      0.77       396
         rec.motorcycles       0.74      0.79      0.76       398
      rec.sport.baseball       0.53      0.92      0.67       397
        rec.sport.hockey       0.96      0.86      0.91       399
               sci.crypt       0.74      0.74      0.74       396
         sci.electronics       0.63      0.64      0.63       393
                 sci.med       0.82      0.76      0.79       396
               sci.space       0.70      0.74      0.72       394
  soc.religion.christian       0.67      0.81      0.73       398
      talk.politics.guns       0.60      0.71      0.65       364
   talk.politics.mideast       0.87      0.73      0.80       376
      talk.politics.misc       0.59      0.43      0.50       310
      talk.religion.misc       0.51      0.29      0.37       251

             avg / total       0.71      0.70      0.70      7532

confusion matrix:
[[147   1   1   0   1   3   0   5   7  18   1   4   5   3  17  46   7  12
    8  33]
 [  6 284  15   8  12  16   4   1   4  10   0  11   8   1   9   0   0   0
    0   0]
 [  2  24 243  35  16  16   3   1   4  20   0   5   1   4  12   1   2   0
    4   1]
 [  0  12  38 243  38   4   9   4   0   8   1   4  29   0   1   0   0   0
    0   1]
 [  0   7  10  22 270   3   8   6   6  16   0   5  24   3   4   1   0   0
    0   0]
 [  0  32  25   8   3 295   7   1   0  10   0   3   5   2   2   1   0   0
    1   0]
 [  1   1   3  13  13   0 316   6   6  12   0   1   8   0   5   0   4   0
    0   1]
 [  3   0   0   1   2   0  12 292  21  28   0   2  16   1   9   3   3   0
    3   0]
 [  2   1   0   0   1   2   7  16 313  18   1   1  11   4   9   1   6   0
    5   0]
 [  2   3   0   0   1   0   3   0   5 364   4   0   1   1   1   7   0   0
    5   0]
 [  5   1   0   0   0   0   2   1   2  32 343   2   0   2   3   1   3   1
    0   1]
 [  3   9   4   2   3   2   1   1   3  26   0 292  14   2   7   1  17   3
    5   1]
 [  1  11   8  18  15   1  12   7  10  17   0  19 253  10   8   0   1   1
    1   0]
 [  7   8   3   2   1   0   6   3  11  15   2   1   8 302   5  10   4   5
    3   0]
 [  5  13   1   0   1   0   2   4   4  21   1   5  11   9 293   6   4   1
   13   0]
 [ 18   3   3   0   0   0   0   1   1  17   1   1   2   4   4 321   1   1
    3  17]
 [  8   0   2   0   1   0   2   4   6  22   0  15   2   4   8   7 257   2
   16   8]
 [ 21   1   1   1   0   1   1   3   7  15   0   5   2   2   5   8  10 276
   16   1]
 [ 15   1   0   0   0   0   1   1   9  10   3  14   3   5   8   5  88   8
  133   6]
 [ 34   4   2   1   0   0   1   3   4  11   1   3   1  11   6  61  22   6
    8  72]]

pythonのscikit-learnでgrid search(テキスト分類)

機械学習-DeepLearning

scikit-learnの復習のため、グリッドサーチしてみた。
テキスト分類です。

タスク

テキストのマルチクラス(20クラス)問題

方法:

  • TFIDFのBoWでベクトル作成
  • 線形分類問題として20クラス分類(one vs the rest)

グリッドサーチのパタン

  • TF-IDF:1-gram or 1-gram+2-gram、単語頻度 or 単語出現(Binary)、idfを使う or 使わない、正規化しない or L1
  • 線形分類:損失がhinge or 2乗誤差、正則化 L1 or L2

結果

経験則から以下のパラメタがよさそうと思っていた。

  • TF-IDF:1-gramでBinaryでidfなしで正規化をL1
  • 線形分類:2乗誤差で正則化L1

でも、、、

  • TF-IDF:1-gram+2-gram、頻度、idfあり、正規化をL2
  • 線形分類:2乗誤差、正則化L2

他の結果とも比較すると、線形分類のパラメタは感覚とあう。
ただ、単語頻度+IDFが全体的には良い結果になっている。ここはちょっと感覚と違う。

計算時間

64パタンを10分割したので640モデルを作成したことになる。
うちのマシンでは10時間くらいかかった、、、。

感想

パラメタによって結果がかなり変わっているのが分かる。一番良かったものと悪かったものの差は20ポイントもある。
ちゃんと測ってあげないとだめだね。

コード

#!/usr/bin/python
# encoding: utf-8
'''
 -- GridSearh for scikit learn - LinearSVC with TextData

@author:     mzi

@copyright:  2017 mzi. All rights reserved.

@license:    Apache Licence 2.0

'''
from __future__ import print_function

import sys
import os

import numpy as np
from optparse import OptionParser
from time import time

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer,CountVectorizer
from sklearn.svm import LinearSVC
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV
from sklearn.utils.extmath import density
from sklearn import metrics


__all__ = []
__version__ = 0.1
__date__ = '2017-02-11'
__updated__ = '2017-02-11'

TESTRUN = 0
PROFILE = 0

def size_mb(docs):
    return sum(len(s.encode('utf-8')) for s in docs) / 1e6

def trim(s):
    """Trim string to fit on terminal (assuming 80-column display)"""
    return s if len(s) <= 80 else s[:77] + "..."


def benchmark(clf, train_data, train_target, test_data, test_target, target_names, opts):
    print('_' * 80)
    print("Training: ")
    print(clf)
    t0 = time()
    clf.fit(train_data, train_target)
    train_time = time() - t0
    print("train time: %0.3fs" % train_time)

    t0 = time()
    pred = clf.predict(test_data)
    test_time = time() - t0
    print("test time:  %0.3fs" % test_time)

    score = metrics.accuracy_score(test_target, pred)
    print("accuracy:   %0.3f" % score)

    feature_names = None
    if isinstance(clf, GridSearchCV):
        clf = clf.best_estimator_
    if isinstance(clf, Pipeline):
        vect = clf.steps[0][1]
        if isinstance(vect, TfidfVectorizer) or isinstance(vect, CountVectorizer):
            feature_names = np.asarray(vect.get_feature_names())
        clf = clf.steps[-1][1]
    if hasattr(clf, 'coef_'):
        print("dimensionality: %d" % clf.coef_.shape[1])
        print("density: %f" % density(clf.coef_))

        if opts.print_top10 and feature_names is not None:
            print("top 10 keywords per class:")
            for i, label in enumerate(target_names):
                top10 = np.argsort(clf.coef_[i])[-10:]
                print(trim("%s: %s" % (label, " ".join(feature_names[top10]))))
        print()

    if opts.print_report:
        print("classification report:")
        print(metrics.classification_report(test_target, pred,
                                            target_names=target_names))

    if opts.print_cm:
        print("confusion matrix:")
        print(metrics.confusion_matrix(test_target, pred))

    print()
    clf_descr = str(clf).split('(')[0]
    return clf_descr, score, train_time, test_time

def main(argv=None):
    '''Command line options.'''

    program_name = os.path.basename(sys.argv[0])
    program_version = "v%f" %  __version__
    program_build_date = "%s" % __updated__

    program_version_string = '%%prog %s (%s)' % (program_version, program_build_date)
    program_longdesc = 'GridSearh for scikit learn - LinearSVC with TextData' 
    program_license = "Copyright 2017 mzi                                            \
                Licensed under the Apache License 2.0\nhttp://www.apache.org/licenses/LICENSE-2.0"

    if argv is None:
        argv = sys.argv[1:]

    # setup option parser
    op = OptionParser(version=program_version_string, epilog=program_longdesc, description=program_license)
    op.add_option("--report",
                  action="store_true", dest="print_report",
                  help="Print a detailed classification report.")
    op.add_option("--confusion_matrix",
                  action="store_true", dest="print_cm",
                  help="Print the confusion matrix.")
    op.add_option("--top10",
                  action="store_true", dest="print_top10",
                  help="Print ten most discriminative terms per class"
                       " for every classifier.")
    op.add_option("--all_categories",
                  action="store_true", dest="all_categories",
                  help="Whether to use all categories or not.")
    op.add_option("--filtered",
                  action="store_true",
                  help="Remove newsgroup information that is easily overfit: "
                       "headers, signatures, and quoting.")

    # process options
    (opts, args) = op.parse_args(argv)


    print(__doc__)
    op.print_help()
    print()

    #Categories
    if opts.all_categories:
        categories = None
    else:
        categories = [
            'alt.atheism',
            'talk.religion.misc',
            'comp.graphics',
            'sci.space',
            ]

    # MAIN BODY #

    #Remove headers
    if opts.filtered:
        remove = ('headers', 'footers', 'quotes')
    else:
        remove = ()

    print("Loading 20 newsgroups dataset for categories:")
    print(categories if categories else "all")
    
    data_train = fetch_20newsgroups(subset='train', categories=categories,
                                    shuffle=True, random_state=42,
                                    remove=remove)
    
    data_test = fetch_20newsgroups(subset='test', categories=categories,
                                   shuffle=True, random_state=42,
                                   remove=remove)
    print('data loaded')

    # order of labels in `target_names` can be different from `categories`
    target_names = data_train.target_names

    data_train_size_mb = size_mb(data_train.data)
    data_test_size_mb = size_mb(data_test.data)
    print("%d documents - %0.3fMB (training set)" % (
        len(data_train.data), data_train_size_mb))
    print("%d documents - %0.3fMB (test set)" % (
        len(data_test.data), data_test_size_mb))
    print()

    # Linear Classification GridSearch
    # - Term Vector(n-gram, tfidf or binary, normarize instanse wize(l1) or none
    # - Linear LinerSVC(loss=(l1 or l2)
    results = []
    print('=' * 80)
    print("GridSearch LinearSVC with L2")
    print("TermVector(n-gram=(1 to 2), binary=(true or not), idf=(true or not), normlized=(true or not))")
    print("LinerSVC(loss=(l1 or l2))")
    text_clf = Pipeline([
        ('vect', TfidfVectorizer(stop_words='english')),
        ('clf', LinearSVC())
        ])
    parameters =[
        {'vect__ngram_range': [(1, 1), (1, 2)],
         'vect__binary': [True, False],
         'vect__use_idf': [True, False],
         'vect__norm': [None, 'l2'],
         'clf__loss': ['squared_hinge'],
         'clf__penalty': ['l1'],
         'clf__dual': [False] 
         },
        {'vect__ngram_range': [(1, 1), (1, 2)],
         'vect__binary': [True, False],
         'vect__use_idf': [True, False],
         'vect__norm': [None, 'l2'],
         'clf__loss': ['hinge'],
         'clf__penalty': ['l2'],
         'clf__dual': [True]},
        {'vect__ngram_range': [(1, 1), (1, 2)],
         'vect__binary': [True, False],
         'vect__use_idf': [True, False],
         'vect__norm': [None, 'l2'],
         'clf__loss': ['squared_hinge'],
         'clf__penalty': ['l2'],
         'clf__dual': [True, False]}
                 ]
    
    gs_clf = GridSearchCV(text_clf, parameters, n_jobs=-1,cv=10,verbose=1)

    results.append(benchmark(gs_clf, data_train.data, data_train.target, data_test.data, data_test.target, target_names, opts))

    print("Best score: %0.3f" % gs_clf.best_score_)
    print("Best parameters set:")
    best_parameters = gs_clf.best_estimator_.get_params()
    for param_name in ['vect__ngram_range', 'vect__binary', 'vect__use_idf', 'vect__norm', 'clf__loss', 'clf__penalty', 'clf__dual']:
        print("\t%s: %r" % (param_name, best_parameters[param_name]))
    print("Av. score for each parameters")
    means = gs_clf.cv_results_['mean_test_score']
    stds = gs_clf.cv_results_['std_test_score']
    for mean, std, params in zip(means, stds, gs_clf.cv_results_['params']):
        print("%0.3f (+/-%0.03f) for %r"
          % (mean, std * 2, params))


if __name__ == "__main__":
    if TESTRUN:
        import doctest
        doctest.testmod()
    if PROFILE:
        import cProfile
        import pstats
        profile_filename = '_profile.txt'
        cProfile.run('main()', profile_filename)
        statsfile = open("profile_stats.txt", "wb")
        p = pstats.Stats(profile_filename, stream=statsfile)
        stats = p.strip_dirs().sort_stats('cumulative')
        stats.print_stats()
        statsfile.close()
        sys.exit(0)
    sys.exit(main())
    

コマンドライン

python3 scikit_gridsearch.py --top10  --report --confusion_matrix --all_categories  --filtered

結果出力

 -- GridSearh for scikit learn - LinearSVC with TextData

@author:     mzi

@copyright:  2017 mzi. All rights reserved.

@license:    Apache Licence 2.0


Usage: scikit_gridsearch.py [options]

Copyright 2017 mzi
Licensed under the Apache License 2.0
http://www.apache.org/licenses/LICENSE-2.0

Options:
  --version           show program's version number and exit
  -h, --help          show this help message and exit
  --report            Print a detailed classification report.
  --confusion_matrix  Print the confusion matrix.
  --top10             Print ten most discriminative terms per class for every
                      classifier.
  --all_categories    Whether to use all categories or not.
  --filtered          Remove newsgroup information that is easily overfit:
                      headers, signatures, and quoting.

GridSearh for scikit learn - LinearSVC with TextData

Loading 20 newsgroups dataset for categories:
all
data loaded
11314 documents - 13.782MB (training set)
7532 documents - 8.262MB (test set)

================================================================================
GridSearch LinearSVC with L2
TermVector(n-gram=(1 to 2), binary=(true or not), idf=(true or not), normlized=(true or not))
LinerSVC(loss=(l1 or l2))
________________________________________________________________________________
Training: 
GridSearchCV(cv=10, error_score='raise',
       estimator=Pipeline(steps=[('vect', TfidfVectorizer(analyzer='word', binary=False, decode_error='strict',
        dtype=<class 'numpy.int64'>, encoding='utf-8', input='content',
        lowercase=True, max_df=1.0, max_features=None, min_df=1,
        ngram_range=(1, 1), norm='l2', preprocessor=None, smooth_idf=True,
  ...ax_iter=1000,
     multi_class='ovr', penalty='l2', random_state=None, tol=0.0001,
     verbose=0))]),
       fit_params={}, iid=True, n_jobs=-1,
       param_grid=[{'vect__norm': [None, 'l2'], 'vect__binary': [True, False], 'clf__penalty': ['l1'], 'vect__use_idf': [True, False], 'clf__loss': ['squared_hinge'], 'clf__dual': [False], 'vect__ngram_range': [(1, 1), (1, 2)]}, {'vect__norm': [None, 'l2'], 'vect__binary': [True, False], 'clf__penalty': ['... 'clf__loss': ['squared_hinge'], 'clf__dual': [True, False], 'vect__ngram_range': [(1, 1), (1, 2)]}],
       pre_dispatch='2*n_jobs', refit=True, return_train_score=True,
       scoring=None, verbose=1)
Fitting 10 folds for each of 64 candidates, totalling 640 fits
[Parallel(n_jobs=-1)]: Done  34 tasks      | elapsed:  2.6min
[Parallel(n_jobs=-1)]: Done 184 tasks      | elapsed: 18.7min
[Parallel(n_jobs=-1)]: Done 434 tasks      | elapsed: 56.7min
[Parallel(n_jobs=-1)]: Done 640 out of 640 | elapsed: 566.4min finished
train time: 34000.658s
test time:  2.164s
accuracy:   0.704
dimensionality: 943737
density: 0.546218
top 10 keywords per class:
alt.atheism: punishment atheist deletion islamic motto bobby atheists religio...
comp.graphics: format cview polygon animation tiff images pov 3d image graphics
comp.os.ms-windows.misc: ax fonts w4wg driver risc win3 ini file cica windows
comp.sys.ibm.pc.hardware: pc bus irq scsi 486 vlb bios gateway ide controller
comp.sys.mac.hardware: lciii c650 duo lc powerbook se centris quadra apple mac
comp.windows.x: application xlib widgets mit x11r5 xterm widget window server...
misc.forsale: interested 00 email make offer asking condition sell offer ship...
rec.autos: wagon gt vw toyota oil engine dealer ford cars car
rec.motorcycles: harley dog bmw motorcycle riding helmet ride bikes dod bike
rec.sport.baseball: hit year runs alomar pitching cubs braves phillies stadiu...
rec.sport.hockey: espn playoff puck season leafs playoffs game team nhl hockey
sci.crypt: pgp des privacy crypto security keys key nsa clipper encryption
sci.electronics: output uv dial circuits power ground 8051 voltage electronic...
sci.med: medicine diet cancer patients pain treatment medical disease doctor msg
sci.space: solar earth lunar shuttle spacecraft moon nasa launch orbit space
soc.religion.christian: marriage faith resurrection easter scripture christ c...
talk.politics.guns: batf bd firearm nra weapon fbi firearms weapons guns gun
talk.politics.mideast: turkey loser arabs armenian turkish jews armenians ara...
talk.politics.misc: government _too_ president taxes jobs libertarians trial ...
talk.religion.misc: sure wrong rosicrucian cockroaches lunacy critus hudson o...

classification report:
                          precision    recall  f1-score   support

             alt.atheism       0.53      0.47      0.50       319
           comp.graphics       0.67      0.72      0.69       389
 comp.os.ms-windows.misc       0.65      0.64      0.64       394
comp.sys.ibm.pc.hardware       0.67      0.67      0.67       392
   comp.sys.mac.hardware       0.73      0.70      0.72       385
          comp.windows.x       0.83      0.72      0.77       395
            misc.forsale       0.72      0.80      0.76       390
               rec.autos       0.79      0.71      0.75       396
         rec.motorcycles       0.83      0.76      0.80       398
      rec.sport.baseball       0.54      0.85      0.66       397
        rec.sport.hockey       0.88      0.90      0.89       399
               sci.crypt       0.84      0.71      0.77       396
         sci.electronics       0.65      0.60      0.62       393
                 sci.med       0.78      0.79      0.79       396
               sci.space       0.75      0.76      0.76       394
  soc.religion.christian       0.65      0.82      0.72       398
      talk.politics.guns       0.60      0.67      0.63       364
   talk.politics.mideast       0.85      0.76      0.80       376
      talk.politics.misc       0.60      0.48      0.53       310
      talk.religion.misc       0.49      0.27      0.35       251

             avg / total       0.71      0.70      0.70      7532

confusion matrix:
[[149   1   3   1   2   2   5   4   1  12   4   2   5   6  12  60   7  12
    7  24]
 [  5 280  21   7   7  17  10   3   1  12   0   6   6   2   7   2   1   1
    1   0]
 [  2  18 251  39  16  11   5   2   2  16   1   3   0   7  11   1   0   2
    4   3]
 [  0  14  32 262  27   6  14   1   0  10   2   2  20   0   1   0   0   0
    1   0]
 [  2   5  12  27 270   3  16   3   1  15   1   2  17   4   4   0   1   0
    0   2]
 [  0  42  35   4   5 284   2   0   1   9   0   3   4   0   4   0   1   0
    0   1]
 [  0   3   2  17  16   0 313   5   2  10   1   1   8   1   3   2   1   2
    2   1]
 [  3   0   1   1   3   1  16 282  15  29   4   1  20   4   5   1   2   4
    4   0]
 [  4   3   1   2   2   0   6  18 304  20   0   0  10   5   7   3   3   0
    9   1]
 [  2   3   0   0   0   2   6   2   4 339  21   0   1   5   1   3   2   1
    4   1]
 [  1   1   0   0   1   1   0   1   2  21 359   0   1   3   1   0   5   0
    1   1]
 [  2   8   6   3   8   3   8   2   5  20   1 283   9   2   3   5  12   2
   12   2]
 [  3  10  12  25   7   7  19   8   7  17   3  12 237  10   8   2   2   1
    2   1]
 [  6   7   1   1   1   0   6   6   3  16   5   0   8 314   2   5   4   2
    6   3]
 [  5  11   3   0   2   0   3   8   3  20   2   1  10  11 301   3   4   1
    5   1]
 [ 17   3   3   0   0   1   0   0   2  14   0   0   1   6   4 327   2   3
    4  11]
 [  7   3   2   1   1   2   3   5   6  14   0  13   2   5   9   7 244   9
   19  12]
 [ 24   2   2   0   0   1   1   2   4  11   0   3   2   2   2  13   7 286
   12   2]
 [ 14   0   0   0   0   1   1   5   2  12   3   2   3   6  11   3  87   6
  148   6]
 [ 33   5   2   2   1   1   2   2   1   9   1   1   2   8   6  69  24   6
    7  69]]

Best score: 0.780
Best parameters set:
	vect__ngram_range: (1, 2)
	vect__binary: False
	vect__use_idf: True
	vect__norm: 'l2'
	clf__loss: 'squared_hinge'
	clf__penalty: 'l2'
	clf__dual: True
Av. score for each parameters
0.682 (+/-0.024) for {'vect__ngram_range': (1, 1), 'vect__norm': None, 'vect__binary': True, 'clf__penalty': 'l1', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': True}
0.691 (+/-0.029) for {'vect__ngram_range': (1, 1), 'vect__norm': None, 'vect__binary': True, 'clf__penalty': 'l1', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': False}
0.723 (+/-0.021) for {'vect__ngram_range': (1, 1), 'vect__norm': 'l2', 'vect__binary': True, 'clf__penalty': 'l1', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': True}
0.708 (+/-0.023) for {'vect__ngram_range': (1, 1), 'vect__norm': 'l2', 'vect__binary': True, 'clf__penalty': 'l1', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': False}
0.702 (+/-0.026) for {'vect__ngram_range': (1, 2), 'vect__binary': True, 'clf__penalty': 'l1', 'vect__use_idf': True, 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__norm': None}
0.699 (+/-0.020) for {'vect__norm': None, 'vect__binary': True, 'clf__penalty': 'l1', 'vect__use_idf': False, 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__ngram_range': (1, 2)}
0.703 (+/-0.024) for {'vect__ngram_range': (1, 2), 'vect__norm': 'l2', 'vect__binary': True, 'clf__penalty': 'l1', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': True}
0.700 (+/-0.026) for {'vect__ngram_range': (1, 2), 'vect__norm': 'l2', 'vect__binary': True, 'clf__penalty': 'l1', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': False}
0.673 (+/-0.027) for {'vect__ngram_range': (1, 1), 'vect__norm': None, 'vect__binary': False, 'clf__penalty': 'l1', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': True}
0.693 (+/-0.022) for {'vect__norm': None, 'vect__binary': False, 'clf__penalty': 'l1', 'vect__use_idf': False, 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__ngram_range': (1, 1)}
0.730 (+/-0.025) for {'vect__ngram_range': (1, 1), 'vect__norm': 'l2', 'vect__binary': False, 'clf__penalty': 'l1', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': True}
0.717 (+/-0.026) for {'vect__norm': 'l2', 'vect__binary': False, 'clf__penalty': 'l1', 'vect__use_idf': False, 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__ngram_range': (1, 1)}
0.698 (+/-0.023) for {'vect__ngram_range': (1, 2), 'vect__binary': False, 'clf__penalty': 'l1', 'vect__use_idf': True, 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__norm': None}
0.695 (+/-0.031) for {'vect__ngram_range': (1, 2), 'vect__norm': None, 'vect__binary': False, 'clf__penalty': 'l1', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': False}
0.716 (+/-0.022) for {'vect__ngram_range': (1, 2), 'vect__norm': 'l2', 'vect__binary': False, 'clf__penalty': 'l1', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': True}
0.710 (+/-0.025) for {'vect__norm': 'l2', 'vect__binary': False, 'clf__penalty': 'l1', 'vect__use_idf': False, 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__ngram_range': (1, 2)}
0.610 (+/-0.022) for {'vect__ngram_range': (1, 1), 'vect__binary': True, 'clf__penalty': 'l2', 'vect__use_idf': True, 'clf__loss': 'hinge', 'clf__dual': True, 'vect__norm': None}
0.664 (+/-0.024) for {'vect__norm': None, 'vect__binary': True, 'clf__penalty': 'l2', 'vect__use_idf': False, 'clf__loss': 'hinge', 'clf__dual': True, 'vect__ngram_range': (1, 1)}
0.768 (+/-0.022) for {'vect__norm': 'l2', 'vect__binary': True, 'clf__penalty': 'l2', 'vect__use_idf': True, 'clf__loss': 'hinge', 'clf__dual': True, 'vect__ngram_range': (1, 1)}
0.739 (+/-0.025) for {'vect__norm': 'l2', 'vect__binary': True, 'clf__penalty': 'l2', 'vect__use_idf': False, 'clf__loss': 'hinge', 'clf__dual': True, 'vect__ngram_range': (1, 1)}
0.585 (+/-0.033) for {'vect__ngram_range': (1, 2), 'vect__binary': True, 'clf__penalty': 'l2', 'vect__use_idf': True, 'clf__loss': 'hinge', 'clf__dual': True, 'vect__norm': None}
0.680 (+/-0.032) for {'vect__norm': None, 'vect__binary': True, 'clf__penalty': 'l2', 'vect__use_idf': False, 'clf__loss': 'hinge', 'clf__dual': True, 'vect__ngram_range': (1, 2)}
0.773 (+/-0.021) for {'vect__norm': 'l2', 'vect__binary': True, 'clf__penalty': 'l2', 'vect__use_idf': True, 'clf__loss': 'hinge', 'clf__dual': True, 'vect__ngram_range': (1, 2)}
0.748 (+/-0.026) for {'vect__ngram_range': (1, 2), 'vect__norm': 'l2', 'vect__binary': True, 'clf__penalty': 'l2', 'clf__loss': 'hinge', 'clf__dual': True, 'vect__use_idf': False}
0.599 (+/-0.028) for {'vect__norm': None, 'vect__binary': False, 'clf__penalty': 'l2', 'vect__use_idf': True, 'clf__loss': 'hinge', 'clf__dual': True, 'vect__ngram_range': (1, 1)}
0.655 (+/-0.025) for {'vect__norm': None, 'vect__binary': False, 'clf__penalty': 'l2', 'vect__use_idf': False, 'clf__loss': 'hinge', 'clf__dual': True, 'vect__ngram_range': (1, 1)}
0.770 (+/-0.020) for {'vect__ngram_range': (1, 1), 'vect__norm': 'l2', 'vect__binary': False, 'clf__penalty': 'l2', 'clf__loss': 'hinge', 'clf__dual': True, 'vect__use_idf': True}
0.743 (+/-0.025) for {'vect__ngram_range': (1, 1), 'vect__binary': False, 'clf__penalty': 'l2', 'vect__use_idf': False, 'clf__loss': 'hinge', 'clf__dual': True, 'vect__norm': 'l2'}
0.600 (+/-0.026) for {'vect__ngram_range': (1, 2), 'vect__norm': None, 'vect__binary': False, 'clf__penalty': 'l2', 'clf__loss': 'hinge', 'clf__dual': True, 'vect__use_idf': True}
0.679 (+/-0.027) for {'vect__norm': None, 'vect__binary': False, 'clf__penalty': 'l2', 'vect__use_idf': False, 'clf__loss': 'hinge', 'clf__dual': True, 'vect__ngram_range': (1, 2)}
0.777 (+/-0.024) for {'vect__ngram_range': (1, 2), 'vect__norm': 'l2', 'vect__binary': False, 'clf__penalty': 'l2', 'clf__loss': 'hinge', 'clf__dual': True, 'vect__use_idf': True}
0.752 (+/-0.023) for {'vect__ngram_range': (1, 2), 'vect__norm': 'l2', 'vect__binary': False, 'clf__penalty': 'l2', 'clf__loss': 'hinge', 'clf__dual': True, 'vect__use_idf': False}
0.619 (+/-0.024) for {'vect__ngram_range': (1, 1), 'vect__norm': None, 'vect__binary': True, 'clf__penalty': 'l2', 'clf__loss': 'squared_hinge', 'clf__dual': True, 'vect__use_idf': True}
0.670 (+/-0.025) for {'vect__ngram_range': (1, 1), 'vect__binary': True, 'clf__penalty': 'l2', 'vect__use_idf': False, 'clf__loss': 'squared_hinge', 'clf__dual': True, 'vect__norm': None}
0.768 (+/-0.027) for {'vect__norm': 'l2', 'vect__binary': True, 'clf__penalty': 'l2', 'vect__use_idf': True, 'clf__loss': 'squared_hinge', 'clf__dual': True, 'vect__ngram_range': (1, 1)}
0.743 (+/-0.028) for {'vect__ngram_range': (1, 1), 'vect__norm': 'l2', 'vect__binary': True, 'clf__penalty': 'l2', 'clf__loss': 'squared_hinge', 'clf__dual': True, 'vect__use_idf': False}
0.594 (+/-0.030) for {'vect__norm': None, 'vect__binary': True, 'clf__penalty': 'l2', 'vect__use_idf': True, 'clf__loss': 'squared_hinge', 'clf__dual': True, 'vect__ngram_range': (1, 2)}
0.680 (+/-0.022) for {'vect__ngram_range': (1, 2), 'vect__norm': None, 'vect__binary': True, 'clf__penalty': 'l2', 'clf__loss': 'squared_hinge', 'clf__dual': True, 'vect__use_idf': False}
0.775 (+/-0.023) for {'vect__ngram_range': (1, 2), 'vect__binary': True, 'clf__penalty': 'l2', 'vect__use_idf': True, 'clf__loss': 'squared_hinge', 'clf__dual': True, 'vect__norm': 'l2'}
0.747 (+/-0.025) for {'vect__ngram_range': (1, 2), 'vect__norm': 'l2', 'vect__binary': True, 'clf__penalty': 'l2', 'clf__loss': 'squared_hinge', 'clf__dual': True, 'vect__use_idf': False}
0.605 (+/-0.024) for {'vect__ngram_range': (1, 1), 'vect__norm': None, 'vect__binary': False, 'clf__penalty': 'l2', 'clf__loss': 'squared_hinge', 'clf__dual': True, 'vect__use_idf': True}
0.664 (+/-0.025) for {'vect__ngram_range': (1, 1), 'vect__norm': None, 'vect__binary': False, 'clf__penalty': 'l2', 'clf__loss': 'squared_hinge', 'clf__dual': True, 'vect__use_idf': False}
0.769 (+/-0.021) for {'vect__ngram_range': (1, 1), 'vect__binary': False, 'clf__penalty': 'l2', 'vect__use_idf': True, 'clf__loss': 'squared_hinge', 'clf__dual': True, 'vect__norm': 'l2'}
0.745 (+/-0.026) for {'vect__norm': 'l2', 'vect__binary': False, 'clf__penalty': 'l2', 'vect__use_idf': False, 'clf__loss': 'squared_hinge', 'clf__dual': True, 'vect__ngram_range': (1, 1)}
0.605 (+/-0.024) for {'vect__ngram_range': (1, 2), 'vect__norm': None, 'vect__binary': False, 'clf__penalty': 'l2', 'clf__loss': 'squared_hinge', 'clf__dual': True, 'vect__use_idf': True}
0.680 (+/-0.025) for {'vect__ngram_range': (1, 2), 'vect__norm': None, 'vect__binary': False, 'clf__penalty': 'l2', 'clf__loss': 'squared_hinge', 'clf__dual': True, 'vect__use_idf': False}
0.780 (+/-0.021) for {'vect__ngram_range': (1, 2), 'vect__binary': False, 'clf__penalty': 'l2', 'vect__use_idf': True, 'clf__loss': 'squared_hinge', 'clf__dual': True, 'vect__norm': 'l2'}
0.756 (+/-0.027) for {'vect__norm': 'l2', 'vect__binary': False, 'clf__penalty': 'l2', 'vect__use_idf': False, 'clf__loss': 'squared_hinge', 'clf__dual': True, 'vect__ngram_range': (1, 2)}
0.628 (+/-0.027) for {'vect__norm': None, 'vect__binary': True, 'clf__penalty': 'l2', 'vect__use_idf': True, 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__ngram_range': (1, 1)}
0.670 (+/-0.025) for {'vect__ngram_range': (1, 1), 'vect__norm': None, 'vect__binary': True, 'clf__penalty': 'l2', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': False}
0.768 (+/-0.027) for {'vect__norm': 'l2', 'vect__binary': True, 'clf__penalty': 'l2', 'vect__use_idf': True, 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__ngram_range': (1, 1)}
0.743 (+/-0.028) for {'vect__norm': 'l2', 'vect__binary': True, 'clf__penalty': 'l2', 'vect__use_idf': False, 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__ngram_range': (1, 1)}
0.614 (+/-0.025) for {'vect__norm': None, 'vect__binary': True, 'clf__penalty': 'l2', 'vect__use_idf': True, 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__ngram_range': (1, 2)}
0.681 (+/-0.023) for {'vect__ngram_range': (1, 2), 'vect__norm': None, 'vect__binary': True, 'clf__penalty': 'l2', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': False}
0.775 (+/-0.023) for {'vect__norm': 'l2', 'vect__binary': True, 'clf__penalty': 'l2', 'vect__use_idf': True, 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__ngram_range': (1, 2)}
0.747 (+/-0.025) for {'vect__ngram_range': (1, 2), 'vect__norm': 'l2', 'vect__binary': True, 'clf__penalty': 'l2', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': False}
0.679 (+/-0.027) for {'vect__ngram_range': (1, 1), 'vect__norm': None, 'vect__binary': False, 'clf__penalty': 'l2', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': True}
0.676 (+/-0.026) for {'vect__norm': None, 'vect__binary': False, 'clf__penalty': 'l2', 'vect__use_idf': False, 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__ngram_range': (1, 1)}
0.769 (+/-0.021) for {'vect__ngram_range': (1, 1), 'vect__norm': 'l2', 'vect__binary': False, 'clf__penalty': 'l2', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': True}
0.745 (+/-0.026) for {'vect__ngram_range': (1, 1), 'vect__norm': 'l2', 'vect__binary': False, 'clf__penalty': 'l2', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': False}
0.694 (+/-0.018) for {'vect__norm': None, 'vect__binary': False, 'clf__penalty': 'l2', 'vect__use_idf': True, 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__ngram_range': (1, 2)}
0.690 (+/-0.027) for {'vect__norm': None, 'vect__binary': False, 'clf__penalty': 'l2', 'vect__use_idf': False, 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__ngram_range': (1, 2)}
0.779 (+/-0.022) for {'vect__ngram_range': (1, 2), 'vect__norm': 'l2', 'vect__binary': False, 'clf__penalty': 'l2', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': True}
0.756 (+/-0.027) for {'vect__ngram_range': (1, 2), 'vect__norm': 'l2', 'vect__binary': False, 'clf__penalty': 'l2', 'clf__loss': 'squared_hinge', 'clf__dual': False, 'vect__use_idf': False}

pythonでテキスト分類の復習

機械学習-DeepLearning

scikitlearnでテキスト分類を復習です。

KerasでのDeep Learning結果との精度比較を行うためにもベース知識として、個人的な備忘録です。

内容

scikit-learnにもともと付属している 20 news groupデータセットを読み込み、各種手法で分類するサンプルです。

ソースコードの大部分は、Classification of text documents using sparse features — scikit-learn 0.18.1 documentationがほとんどで、
一部、scikit-learn v18 に合わせてパラメタの名前を調整しています。

実行するとき、"--all_categories"を指定すると、KNeighborsClassifierが落ちてしまう(私のメモリ1Gという心もとない環境のためと思います)ので、ひとまずコメントアウトしています。

grid searchがまだ仕上がっていないのでちょっと待ってね。

全体

#!/usr/local/bin/python2.7
# encoding: utf-8
'''
Created on 2017/02/03

based on http://scikit-learn.org/stable/auto_examples/text/document_classification_20newsgroups.html

@author: mzi
'''
from __future__ import print_function

import sys
import os

import logging
import numpy as np
from optparse import OptionParser
from time import time
import matplotlib.pyplot as plt
import gc

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_extraction.text import HashingVectorizer
from sklearn.feature_selection import SelectKBest, chi2
from sklearn.linear_model import RidgeClassifier
from sklearn.pipeline import Pipeline
from sklearn.svm import LinearSVC
from sklearn.linear_model import SGDClassifier
from sklearn.linear_model import Perceptron
from sklearn.linear_model import PassiveAggressiveClassifier
from sklearn.naive_bayes import BernoulliNB, MultinomialNB
from sklearn.neighbors import KNeighborsClassifier
from sklearn.neighbors import NearestCentroid
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LassoCV
from sklearn.feature_selection import SelectFromModel
from sklearn.model_selection import GridSearchCV
from sklearn.utils.extmath import density
from sklearn import metrics

def size_mb(docs):
    return sum(len(s.encode('utf-8')) for s in docs) / 1e6

def trim(s):
    """Trim string to fit on terminal (assuming 80-column display)"""
    return s if len(s) <= 80 else s[:77] + "..."

def benchmark(clf):
    print('_' * 80)
    print("Training: ")
    print(clf)
    t0 = time()
    clf.fit(X_train, y_train)
    train_time = time() - t0
    print("train time: %0.3fs" % train_time)

    t0 = time()
    pred = clf.predict(X_test)
    test_time = time() - t0
    print("test time:  %0.3fs" % test_time)

    score = metrics.accuracy_score(y_test, pred)
    print("accuracy:   %0.3f" % score)

    if hasattr(clf, 'coef_'):
        print("dimensionality: %d" % clf.coef_.shape[1])
        print("density: %f" % density(clf.coef_))

        if opts.print_top10 and feature_names is not None:
            print("top 10 keywords per class:")
            for i, label in enumerate(target_names):
                top10 = np.argsort(clf.coef_[i])[-10:]
                print(trim("%s: %s" % (label, " ".join(feature_names[top10]))))
        print()

    if opts.print_report:
        print("classification report:")
        print(metrics.classification_report(y_test, pred,
                                            target_names=target_names))

    if opts.print_cm:
        print("confusion matrix:")
        print(metrics.confusion_matrix(y_test, pred))

    print()
    clf_descr = str(clf).split('(')[0]
    return clf_descr, score, train_time, test_time

if __name__ == "__main__":

    # Display progress logs on stdout
    logging.basicConfig(level=logging.INFO,
                        format='%(asctime)s %(levelname)s %(message)s')

    # parse commandline arguments
    op = OptionParser()
    op.add_option("--report",
                  action="store_true", dest="print_report",
                  help="Print a detailed classification report.")
    op.add_option("--chi2_select",
                  action="store", type="int", dest="select_chi2",
                  help="Select some number of features using a chi-squared test")
    op.add_option("--confusion_matrix",
                  action="store_true", dest="print_cm",
                  help="Print the confusion matrix.")
    op.add_option("--top10",
                  action="store_true", dest="print_top10",
                  help="Print ten most discriminative terms per class"
                       " for every classifier.")
    op.add_option("--all_categories",
                  action="store_true", dest="all_categories",
                  help="Whether to use all categories or not.")
    op.add_option("--use_hashing",
                  action="store_true",
                  help="Use a hashing vectorizer.")
    op.add_option("--n_features",
                  action="store", type=int, default=2 ** 16,
                  help="n_features when using the hashing vectorizer.")
    op.add_option("--filtered",
                  action="store_true",
                  help="Remove newsgroup information that is easily overfit: "
                       "headers, signatures, and quoting.")

    (opts, args) = op.parse_args()
    if len(args) > 0:
        op.error("this script takes no arguments.")
        sys.exit(1)
    
    print(__doc__)
    op.print_help()
    print()

    #Categories
    if opts.all_categories:
        categories = None
    else:
        categories = [
            'alt.atheism',
            'talk.religion.misc',
            'comp.graphics',
            'sci.space',
        ]
    
    #Remove headers
    if opts.filtered:
        remove = ('headers', 'footers', 'quotes')
    else:
        remove = ()
    
    print("Loading 20 newsgroups dataset for categories:")
    print(categories if categories else "all")
    
    data_train = fetch_20newsgroups(subset='train', categories=categories,
                                    shuffle=True, random_state=42,
                                    remove=remove)
    
    data_test = fetch_20newsgroups(subset='test', categories=categories,
                                   shuffle=True, random_state=42,
                                   remove=remove)
    print('data loaded')

    # order of labels in `target_names` can be different from `categories`
    target_names = data_train.target_names

    data_train_size_mb = size_mb(data_train.data)
    data_test_size_mb = size_mb(data_test.data)
    print("%d documents - %0.3fMB (training set)" % (
        len(data_train.data), data_train_size_mb))
    print("%d documents - %0.3fMB (test set)" % (
        len(data_test.data), data_test_size_mb))
    print()

    # split a training set and a test set
    y_train, y_test = data_train.target, data_test.target
    
    print("Extracting features from the training data using a sparse vectorizer")
    t0 = time()
    if opts.use_hashing:
        vectorizer = HashingVectorizer(stop_words='english', non_negative=True,
                                       n_features=opts.n_features)
        X_train = vectorizer.transform(data_train.data)
    else:
        vectorizer = TfidfVectorizer(sublinear_tf=True, max_df=0.5,
                                     stop_words='english')
        X_train = vectorizer.fit_transform(data_train.data)
    duration = time() - t0
    print("done in %fs at %0.3fMB/s" % (duration, data_train_size_mb / duration))
    print("n_samples: %d, n_features: %d" % X_train.shape)
    print()

    print("Extracting features from the test data using the same vectorizer")
    t0 = time()
    X_test = vectorizer.transform(data_test.data)
    duration = time() - t0
    print("done in %fs at %0.3fMB/s" % (duration, data_test_size_mb / duration))
    print("n_samples: %d, n_features: %d" % X_test.shape)
    print()

    # mapping from integer feature name to original token string
    if opts.use_hashing:
        feature_names = None
    else:
        feature_names = vectorizer.get_feature_names()

    # Extract features by chi2
    if opts.select_chi2:
        print("Extracting %d best features by a chi-squared test" %
              opts.select_chi2)
        t0 = time()
        ch2 = SelectKBest(chi2, k=opts.select_chi2)
        X_train = ch2.fit_transform(X_train, y_train)
        X_test = ch2.transform(X_test)
        if feature_names:
            # keep selected feature names
            feature_names = [feature_names[i] for i
                             in ch2.get_support(indices=True)]
        print("done in %fs" % (time() - t0))
        print()
    
    if feature_names:
        feature_names = np.asarray(feature_names)
    
    # Classification
    results = []
    for clf, name in (
            (RidgeClassifier(tol=1e-2, solver="sag"), "Ridge Classifier"),
            (Perceptron(n_iter=50), "Perceptron"),
            (PassiveAggressiveClassifier(n_iter=50), "Passive-Aggressive"),
            #(KNeighborsClassifier(n_neighbors=10), "kNN"),
            (RandomForestClassifier(n_estimators=100), "Random forest")):
        print('=' * 80)
        print(name)
        results.append(benchmark(clf))
    
    for penalty in ["l2", "l1"]:
        print('=' * 80)
        print("%s penalty" % penalty.upper())
        # Train Liblinear model
        results.append(benchmark(LinearSVC(loss='squared_hinge', penalty=penalty,
                                                dual=False, tol=1e-3)))
    
        # Train SGD model
        results.append(benchmark(SGDClassifier(alpha=.0001, n_iter=50,
                                               penalty=penalty)))
    
    # Train SGD with Elastic Net penalty
    print('=' * 80)
    print("Elastic-Net penalty")
    results.append(benchmark(SGDClassifier(alpha=.0001, n_iter=50,
                                           penalty="elasticnet")))
    
    # Train NearestCentroid without threshold
    print('=' * 80)
    print("NearestCentroid (aka Rocchio classifier)")
    results.append(benchmark(NearestCentroid()))
    
    # Train sparse Naive Bayes classifiers
    print('=' * 80)
    print("Naive Bayes")
    results.append(benchmark(MultinomialNB(alpha=.01)))
    results.append(benchmark(BernoulliNB(alpha=.01)))
    
    print('=' * 80)
    print("LinearSVC with L1-based feature selection")
    # The smaller C, the stronger the regularization.
    # The more regularization, the more sparsity.
    clf = LinearSVC(penalty="l1", dual=False, tol=1e-3)
    results.append(benchmark(Pipeline([
      ('feature_selection', SelectFromModel(clf)),
      ('classification', LinearSVC())
    ])))
    
    # make some plots
    indices = np.arange(len(results))
    
    results = [[x[i] for x in results] for i in range(4)]
    
    clf_names, score, training_time, test_time = results
    training_time = np.array(training_time) / np.max(training_time)
    test_time = np.array(test_time) / np.max(test_time)
    
    plt.figure(figsize=(12, 8))
    plt.title("Score")
    plt.barh(indices, score, .2, label="score", color='navy')
    plt.barh(indices + .3, training_time, .2, label="training time",
             color='c')
    plt.barh(indices + .6, test_time, .2, label="test time", color='darkorange')
    plt.yticks(())
    plt.legend(loc='best')
    plt.subplots_adjust(left=.25)
    plt.subplots_adjust(top=.95)
    plt.subplots_adjust(bottom=.05)
    
    for i, c in zip(indices, clf_names):
        plt.text(-.3, i, c)
    
    plt.show()

    

今年の事始めはkerasから

機械学習-DeepLearning

Deep Learningが流行していますね。流行している理由は2つ。

  • 研究的に良い精度が出る報告がされていること
  • 使えるSDKなどの基盤がそろってきたこと

これらが相まって、以下のような好循環が生まれています。

  1. 研究者が良い報告がされた論文を発表する
  2. 誰か(または本人)がSDKを使って実装し公開する
  3. これを使って試して自分のタスクに適用してみる
  4. うまくいかなかったところを改善する
  5. 論文にして発表する

まだまだ、研究の領域を出ていないとは思いますが、こんな感じで好循環が生まれています。
この辺の話はまた別の機会にでも。

で、いろんな基盤が出てきてます。
ざっくりまとめるとこんな感じ。まだまだたくさんありますが、最近はこんなところに集約されてる気がする(日本では)。

行列計算ライブラリ Tensorflow(by Google), Theano(by モントリオール大学)
SDK Chainer(by Preferred Networks, inc.), Keras(by Googleになるかも), The Microsoft Cognitive Toolkit(旧CNTK by Microsoft)

この他にもありますので"deep learning フレームワーク"で検索をかけてみてください。また、Wikipediaにもこんなエントリがあります。
Comparison of deep learning software - Wikipedia

よく言われる TensorFrowは分散処理に優れた行列計算ライブラリであって、実際はDeepLearning専用ではありません。なので、高次元のSDKがほしくなります。
高次元のSDKとして有名なのは、Chainer。日本人が主に作っているので日本語ドキュメントも充実していてとっつきやすいです。

Kerasってなに

ひとことで言うと、行列計算ライブラリを使いやすい形で提供する高次APIです。

バックエンドの行列計算ライブラリは、TensorflowとTheanoです。これらを切り替えて使うことができ、これぞれの分散処理の恩恵やGPUを利用した高速化の恩恵を受けることができます。
日本語ドキュメントも作られています。

Keras Documentation

始めるまでのおすすめの手順

個人的な感想ですが、Kerasは、実際使ってみると、Chainerに似ていてプログラマはとっつきやすいです。しかし、DeepLearningの基本構成を知らないと訳が分からないとは思います。ですので、おすすめ手順は以下。

ニューラルネットの基本を知る

まずは、以下を読んで、ニューラルネットの基本をPythonの実装として体感しましょう。でも、いきなり数式から入るとめっちゃ難しくなってしまいます。そこで、以下の本がおすすめです。

  • 実例を含めてPythonソースコードがかいてある。
  • 数式も載っているけど、微分とか行列演算から書いてあるので、高校数学の知識がなくても最悪読めてしまう。
  • また、実装形態も、KerasやChainerが目指している高次APIの方法に近い。

DeepLearningもちょっとかじりますが、これ以上難しくなると実装は大変な領域に入ります。ちょうどいいところで止めてあるのも、この本のよいところで、わかった気にさせてくれます。聞きかじりで手を出せてしまう危険さはありますが、、、。

わかってる人も復習もかねて見ておくと勉強になります。知ってる人なら1weekあれば読み終わってしまえます。

DeepLearningでできることを知る

次に読むか、並行して読むとよいなと思ったのはこれ。ニューラルネットの理解を進めてできることは何かを知っておくと、モチベーションにつながります。

  • DeepLearingでできることの紹介
  • それぞれのいろんな実装形態の紹介
  • これらを使ったサンプルプログラムの紹介

新しい事例も交えて書いてあります。半面、少し時間がたつと、過去の事例になってしまうかもしれないので、読むなら今です。

サンプルプログラムも載っているので、ノードやバックプロパゲーションの実装の方法や、図の表示方法なども書いてあり、役立ちます。

この辺まで大体ザクッと読んでみてからだと、すごく理解が早いと思います。

Kerasをインストール

ニューラルネットやDeepLearningの基本が大体わかったら、Kerasを触ってみましょう。

インストールですが、ドキュメント通りで大体終わります。

Keras Documentation

Ubuntu 14.04LTSの環境で少し詰まったのは、numpy, scipy, pyyaml。numpy, scipy にはBLATが必要なので、pipで入れるよりも apt-get で入れてしまったほうが、依存関係がはっきりしてよいでしょう。また、pyyamlもlibyamlが必要なので、apt-getで。同じくh5pyも。

sudo apt-get install python-numpy python-scipy python-yaml python-h5py

cuDNNは今回入れませんでした。

また、バックエンドにTensolFlowを使いたかったので、Tensolflowのみ入れてみました。

Download and Setup

pipでインストール完了。

サンプルを実行してみる

ここで、Kerasのサンプルがexampleディレクトリにいくつかあります。

keras/examples at master · fchollet/keras · GitHub

わたしは、テキストデータ処理に興味があったので、とりあえず、動くかどうかだけ見てみようと思い以下を試してみます。

reuters_mlp.py Trains and evaluate a simple MLP on the Reuters newswire topic classification task.

タスクとしては、ニュース記事を46カテゴリに分類に分類するタスクですね。

python reuters_mlp.py
Using TensorFlow backend.
Loading data...
8982 train sequences
2246 test sequences
46 classes
Vectorizing sequence data...
X_train shape: (8982, 1000)
X_test shape: (2246, 1000)
Convert class vector to binary class matrix (for use with categorical_crossentropy)
Y_train shape: (8982, 46)
Y_test shape: (2246, 46)
Building model...
Train on 8083 samples, validate on 899 samples
Epoch 1/5
8083/8083 [==============================] - 5s - loss: 1.4141 - acc: 0.6819 - val_loss: 1.0555 - val_acc: 0.7686
Epoch 2/5
8083/8083 [==============================] - 4s - loss: 0.7821 - acc: 0.8175 - val_loss: 0.9347 - val_acc: 0.7942
Epoch 3/5
8083/8083 [==============================] - 4s - loss: 0.5468 - acc: 0.8644 - val_loss: 0.8641 - val_acc: 0.8109
Epoch 4/5
8083/8083 [==============================] - 4s - loss: 0.4141 - acc: 0.8978 - val_loss: 0.8620 - val_acc: 0.8120
Epoch 5/5
8083/8083 [==============================] - 4s - loss: 0.3244 - acc: 0.9180 - val_loss: 0.8896 - val_acc: 0.8120
2208/2246 [============================>.] - ETA: 0sTest score: 0.86964678913
Test accuracy: 0.793855743544

まあ、精度は感覚的にはこんなもんなんかな。SVMと比較してみたくなりますね。あとでやってみます。


そして、めっちゃソースコードがシンプル
ドキュメントを読んでみると、このデータは、データセットが既に用意されていて、簡単にデータをロードできるようになっている。そして、単語区切りをしてベクトル作成するクラスも実装されてる。この辺を参考にしながら、日本語版を作ればよさそうですね。

とりあえず、今日はこの辺で終了。

はじめてのブルベ完走!

自転車

もうかなりたってしまいましたが、BRM200を完走できました。

メダルももちろんもらいました。

f:id:mzi:20150718161425j:plain

完走したのはこれ。

AJ群馬 Audax Randonneurs Gunma: BRM517群馬200km 初めてのブルベ

BRM517群馬200は初心者向け

ブルベ名にもあるように、「初めてのブルベ」なので、非常に走りやすかったです。
主催者の皆さんに感謝です。

  • ほとんど平地を走る。峠は一か所だけ、今考えると、そんなにきつくなかった。
  • PC(チェックポイント)も無人で、コンビニ2か所と写真撮影だけ。
  • 走る道はだいたい幹線道路の近くだったり有名なサイクリングロード利用なので、トラブルがあっても自己解決しやすいし、コンビニもたくさんある。

平地ばかりだからかわかりませんが、結構広い範囲を走った満足感

私ははじめてブルベに参加したので、ほかのブルベは知りませんが、200kmでこんなにたくさんの県をまたぐとは思いませんでした。一部しか走っていいない県ももちろんありますが、初心者的には満足感ありでした。

群馬県→栃木県→茨城県→千葉県→埼玉県→群馬県

完走ルート
connect.garmin.com

ほとんど利根川沿いでしたが、流域面積日本最大がわかるこれだけの県をまがって走るのもなかなかないでしょう。

坂東大橋道標にて撮影。わかりにくい!

f:id:mzi:20150517180332j:plain

スタートとゴールの温泉もよし

体力を回復させてくれたのは温泉でした。
よしおか温泉で汗を流し、ゆっくりつかって、足や腕をゆっくりマッサージ、背中などをストレッチ。十分回復できました。

走行中の会話も楽しめました。

途中、利根川自転車道で、ロードバイクに乗ったおじいちゃんと会話。あちらから声をかけていただきました。まあ、真昼間から反射ベストを着て走っている変な人で目立ちますからね。

ほとんど平地を200Km走っていることを話すと、
「ずっと平地なの?それじゃつまらないでしょ。」とのこと。

この方、定年後に自転車をはじめ、現在70才、ほぼ毎日走っているとのこと。山が好きでよく峠越えに行ったり、レースを見に行ったりするそうです。

山登りのコツはなんですかと聞くと、
引き足を使うことかなと、「あと、あきらめないこと」とのこと。
奥が深い。

というわけで、一般の自転車道を走るので、こんな会話もできます。

個人的な良かった点と反省点

荷物系とコックピットは満足

初参加だったので、どんな感じにしたら良いのかわからず、いろいろ調べてきました。

荷物は、トップチューブバックはROSWHEELのMサイズ、サドルバックはORTLIEBのLサイズ。

ROSWHEELのトップチューブバックは、平地走行ではぎりぎりで足に当たらないサイズ。快適でした。しかし、山道でダンシングするときさすがにあたります。私はシッティングで登っていましたので、ほとんど気にならなかったですが、ダンシングの方はやめたほうがいいでしょう。

ORTLIEBのバックは、見た目のわりにかなり入ると噂で買いました。その通りで、着替えや雨具までいれても大丈夫。重さで走行時に自転車が左右に振られるのではと思ってましたが、そんなに高速走行するわけでもないので、気になりませんでした。

ORTLIEB(オルトリーブ) サドルバッグ L スレート

ORTLIEB(オルトリーブ) サドルバッグ L スレート

コックピットは、全部百均で揃えました。

  • B5のバインダー
  • ヘアゴム
  • 大型クリップ

これだけ。

B5のバインダーをステムに取り付ける。そのために、バインダーに穴をあけ、ヘアゴムを通し、ステムに縛り付ける。初めは、結束バンドも考えたのですが、ステムはねじ穴などで微妙に凹凸があるので、きっちり括り付けるとバインダーが曲がるし、緩いとがたがたする。そこで、ゴムで付けることにしました。あとはこれに、コマ図を挟んで押しまい。

f:id:mzi:20150509085126j:plain

難点は、目線が手元に行ってしまうので、走行が危うい時がある点。向きを横に取り付けるか、コマ図の作り方を工夫してもう少し上につけるのがよいかな。

日焼けしまくり

5月の日差しをなめてました。

日焼け止め系のグッズを何も持たず出発。上はインナーを着ていましたが、下はサイクリングパンツのみで走ってました。

見事に焼けて、昼ごろには、膝頭が真っ赤。
しかたないので、寒さ対策のために持って行ったタイツを履き、時々上から水をかけながら走りました。これでなんとかしのぎました。

春でも日焼け対策は必須ですね。

着替えはかなり良かった

パッド付きのパンツの変えを持っていきました。本当は荷物になるので必要ないと思っていたのですが、雨が降ったりしたとき取り換えがあったほうがいいと考えていたためです。

実際は別の使い方でした。

200Kmは初めてだったので、100 - 150km でかなり疲れていたし、おまけに、結構向かい風区間が多くて心が折れそうでした。で、リフレッシュするにはどうするか考えていました。着替えがあることに気づき、途中のコンビニでレーパンだけ着替えました。これ、かなりリフレッシュできすごく良かったです。

まとめ

というわけで、はじめてのブルベを完走できました。

次は300走るかな。もう一度200を走っておいて長距離に慣れておくかな。迷い中。

Garmin Edge 500 コース作成と転送の方法

自転車

私のサイコンはGarmin500。こいつのコース機能は結構使えることは、わかってきました。
コースで使いたい機能は二つ。

  1. ルートを表示すること
  2. ポイント情報(ウェイポイント)を表示すること

あとは、コースの作成と転送方法です。

作成と転送の方法の候補

作成方法の候補

  • Bike Route Toasterで作成する → ◎
  • Garmin Training Centerで作成する → △:マップが貧弱で作成しにくい
  • Yahoo ルートラボで作成する → ×:ウェイポイント作成できず
  • Garmin Connectで作成する → ×:ウェイポイント作成できず

YahooルートラボGarmin Connectは、ウェイポイントを作成できない。
でも、このサイトは、たくさんのルート情報があるので、非常に参考になる。これは何とか使いたい。

私にとっての選択肢は、Garmin Training CenterとBike Route Toaster。

Training Centerの使いにくさはものすごいです。地図が貧弱だし、マウス操作が非常によくない。

Bike Route Toasterは、さまざまな地図を切り替えながら見ることができ、かなりよい。

転送方法の候補

  • Garmin Training Centerで転送する → ○:すごく遅い
  • Bike Route Toasterで転送する → △:うまくいかないこともある
  • Garmin Connect で転送する → ×:ウェイポイントを転送できない
  • TCXファイルをGarminEdgeにコピーする → ?:試してない

Gamin Connectで転送するのは、結局ウェイポイントを転送できないので私の目的には合わない。

いろんな記事で、Yahooルートラボで作成しTCXファイルをGarminEdge500にコピーして転送する方法でうまくいっていない人の記事を見ると、ウェイポイントの数が問題らしい。これって結構問題なのではないかなぁ。

実際一番簡単なのは、先のBike Rote Toasterで転送。
これでうまくいかなかったら、Garmin Training Centerで転送するしかない。

Bike route toasterで作成と転送するのがベスト。実際にどうやる。

BRM517群馬200のルートの作成方法で説明します。

AJ群馬 Audax Randonneurs Gunma: BRM517群馬200km 初めてのブルベ

Yahooルートラボで参考になりそうなルートを探す

ブルベやロングライドイベントでは、ルートラボでルートを公開しているものがあるので、これを参考にするとよいはず。

今回のブルベでは、主催者が公開したものはないので、参考になりそうなルートを検索します。昨年度も同じタイトルでブルベをやっているので、きっと去年走った人がルートを公開しているはず。地図で検索だとなぜかうまくいかないので、名前で検索してみる。

ありました。

ルートを見る - ルートラボ - LatLongLab

たぶん去年のやつですね。先人に敬意をはらいつつ、参考にさせていただきます。
ここからGPXでダウンロードします。

Bike Route Toaster でインポート

Bike Route Toaster - Welcome to BikeRouteToaster.com

ユーザを作成しなくても使うことはできます。ですが、お勧めはユーザを作っておくことです。データをアカウントに保存できるので、作業途中のものを置いておけます。

上の[Editor]を押すと編集画面になります

インポート後こんな感じになります。
f:id:mzi:20150613210050p:plain

Bike Route Toaster で編集する

編集は、キューシートやコマ図を見ながらウェイポイントをチェックという地道な編集作業になります。これ結構時間がかかります。全く走ったことがない土地なので。私の場合頑張って一日つぶすぐらいかかりました。

編集方法を簡単に説明します。以下が基本です。もっと便利な機能もありそうですが、基本これだけ。

自動でルート設定を消す

今回はすでにルートがあることを前提に編集するので、右の「Routing」のAuto Routing のチェックを外す。

これをやっておかないと、面倒です。

アカウントに保存する

上の[Save]を押す。名前を付けてOKを押す。

アカウントに保存したコースを呼ぶ

上の[Load] を押す。保存した名前のコースを選択する

ポイントを移動する。

ルートの上にマウスを置くと「×」が出てくるので、ドラッグして移動。

ポイントを削除する。

ルートの上にマウスを置くと「×」が出てくるので、クリックして選択。左側ペインの「Editing」の中のPointのDeleteをクリック。

ポイントを追加する。

追加したいポイントの直前のポイント「x」を選択。「Editing」の中のInsertセクションのAfter Selected Pointを選択。追加したいポイントをクリック。

操作をやり直す

あわてずに 「Editing」のUndoを押す。ポイント追加と移動と削除が取り消されます。下記のコマ図などの情報の追加は取り消されません。

ポイントにコマ図やキューシートの指示を書き込む

書き込みたいポイント「x」を選択。左側ペインの「Editing」の中の「Point」セクションの中からPoint typeを変更します。

f:id:mzi:20150621070645p:plain

LeftやRightなどを選択します。これと同じものがGarminでも表示されます。非常にわかりやすい。
私は、Nameのところに、キューシートのポイント番号とLやRを書き込みます。

さらに、地図表示している上の[Cue Sheet]を選択すると、これらの情報をもとにキューシートが表示される親切設計。配布されているキューシートと比較しながらポイントが正しいか確認できます。

自分でルートを作成したいとき

いろいろやってみましたが、オス勧めは以下の設定。

地図を Open Cycle Mapに変更。Routing の Auto Routingにチェック、Route TypeをBike、Favor Bike Path を 1、Avoid High Wayのチェックを外す。

地図がGoogleだと。自動的なルーティングがうまくいかない。Avoid High Wayにチェックを入れると、国道すら通らなくなるので、変なルートになる。

Bike Route Toaster でデータをGarminに転送する

終わるとこんな感じになる。

f:id:mzi:20150621070905p:plain

上の[Export]を押すといろんな形式(TCX,GPX,KML,CSV,XML)のファイル保存などのメニューが出てくる。
このうち最後の Garmin を選ぶ。

ここで、実は事前準備がいる。GCP - Garmin Communicatorプラグインが必要。
あらかじめインストールしておく。

これで終了

柏~筑波サイクリング 小貝川に沿って

自転車

こちらもだいぶ時間が経過してしまいましたが、4月12日に千葉サイクリング協会主催のサイクリングイベントに参加しました。

www.chiba-cycling.org

千葉県柏市から茨城県つくば市までの往復120kmを走りました。
柏市つくば市も、私にとってそれなりに縁のある土地。つくば市は6年間ぐらい住んでいました。

また、初めてのサイクリングイベント参加&初めての集団走行体験でした。
募集定員300人ということで、どのように300人もの人数が走るのか想像がつきませんでした。
しかし、実際に走ってみるとスタート時間を少しずつずらし、20人くらいのグループを作って走行するようになってました。この人数でも結構走れるもんなんですね。

もう二つ試したいことが、それは、Garmin500のコース機能を試すこと、あとコマ図を見るための装備を試すこと。これらは、かなりいい感じでした。最後にレポートします。

スタート地点 柏の葉キャンパス駅

いわゆるウェーブスタートというやつで、すこしづつスタート、大体10グループぐらいでしょうか?私は遅いので、一番最後のグループに参加。

そして堤防。菜の花万満開。

f:id:mzi:20150412083956j:plain

途中のエイドステーションで、アンパンとクリームパンをもらいました。
さすがに最終グループでは少し遅かったので、ちょっと先行させていただきました。

折り返し地点 つくば市北条の平沢官衙遺跡(ひらさわかんがいせき)

ここでお昼ご飯をいただきました。お弁当とお茶とをいただきゆっくり休養をとり再スタート

今回は第3グループぐらいに参加。かなり早めにスタートしました。
ここでハプニング。船頭いただいた方がコースミスで、5kmぐらいボーナスステージをこなしました。これも集団走行の醍醐味ですね。

帰りのエイドステーションでもアンパンをいただきました。

100km越えたあたりで足がとまってしまいました。ここで、個人的に休養。控えでもっていたスポーツゼリーを飲む。基礎体力をつけないとだめだな、やっぱ。

ゴール地点も柏の葉キャンパス駅

やっと到着しました。まあまあまだいけそうな感じ。

f:id:mzi:20150412151608j:plain

まとめ

connect.garmin.com

120kmで、休憩込で7時間くらい。目標タイムぎりぎりでした。
休憩のとり方は結構大事。無理せず休む。自転車の上で休めればいいんだけどなぁ、、、。

Garmin500のコース機能は使える

地図はないので、画面にコースの線しか出てきませんが、結構使えることがわかりました。
どっち方向に行くか、道から外れたかはわかります。
コマ図に出ているポイントを登録しておくと、そこまでの距離も出てくるので、疲れているときには、少しずつ進んでいくのがわかりよい感じです。

コマ図を見る方法

今回、初めてコマ図を見るための装備を導入しました。装備といっても100円ショップで買った、A5サイズのバインダー、ヘアバンドゴム、透明ビニールポーチを組み合わせたもの。

A5バインダに穴をあけ、ヘアゴムでハンドルとステムの上に括り付けるだけ。
コマ図の紙をビニールポーチに入れてA5バインダに挟む。

コマ図の切り替えは、手動でやるので、少し手間ではありますが、休憩するときにやれば全然問題なし。

難点は、目線が下に向くこと。紙の印刷方法を工夫してみることにしようかな。