Google Cloud Platform - Cloud Machine Learning Engine (Cloud ML Engine) + ローカルWindowsで試す

GCP の Cloud ML Engine の使い方を勉強中です。 ローカルの開発環境をWindowsにしても、大丈夫か試してみました。

目的

Cloud ML Engineのローカル開発環境としてWindowsが使えるか試すこと。

結論

完全には難しい。リスクを抱えて行うにはいいけどね。 原因は、WindowsでTensorflow1.2+Python2.xの組みあわせが動かないこと。

環境 OS Python Tensorflow
GCP Cloud ML Ubuntu 2.7 1.2.x
ローカル開発環境 Windows 10 3.6 1.4.0

ローカル開発環境を、Windows + Python2.7 + Tensorflow1.2 にしたいが、 Windows + Python2.7 で Tensorflow は動かない。

といいわけで、やればできるけど、 バージンの違い(Python 2と3)を吸収するコードを書くこと、 もし動かなかったときJobが止まっちゃうので後戻りがかなり発生すること、 を許容できればいい。

GCP Cloud ML セットアップ手順

はじめに  |  Cloud Machine Learning Engine (Cloud ML Engine)  |  Google Cloud Platform を参照して実行。

Cloud ML を使う基本的なフロー

今回は、以下のうち、1と2までがWindowsでできるか検証。結果的には1だけでで断念。

  1. TensorFlow トレーナーを作成し、それをローカルで検証します。
  2. クラウド内の 1 つのワーカー インスタンスでトレーナーを実行します。
  3. クラウド内の分散トレーニング ジョブとしてトレーナーを実行します。
  4. ハイパーパラメータ チューニングを使用して、ハイパーパラメータを最適化します。
  5. 予測をサポートするモデルをデプロイします。
  6. オンライン予測をリクエストし、レスポンスを確認します。
  7. バッチ予測をリクエストします。

準備

Quickstart Using the Command-Line  |  Cloud Machine Learning Engine (Cloud ML Engine)  |  Google Cloud Platformを参照して準備。

  • プロジェクト作成
  • プロジェクトを課金対象にする
  • Cloud Machine Learning Engine and Compute Engine API(複数)を有効
Cloud SDK をインストールして初期化

Quickstart for Windows  |  Cloud SDK Documentation  |  Google Cloud Platformを参照してインストール。

  • Cloud SDK  |  Google Cloud PlatformSDKをダウンロード。
  • インストールする。この時、gcloudのみのPythonランタイムをインストールすることを忘れずに。
  • gcloud init を実行して初期化

念のためアップデート

gcloud components update
Cloud SDK インストール済みですでにデフォルト設定がある場合は、プロジェクトの設定
gcloud config set project [project-id]
TensorFlow をインストール

ここで、ローカルはWindows 10 なので、Anaconda + python3.5 。 あらかじめ環境を仮想環境を構築(python3.5)してある。 以下のコマンドでとりあえずインストール。また、当方の環境はPowerShellです。

activate.ps1 google-cloud-platform
pip install --ignore-installed --upgrade tensorflow

サンプルプログラムをローカルにダウンロード

git clone https://github.com/GoogleCloudPlatform/cloudml-samples.git
cd .\cloudml-samples\census\estimator\

トレーナーをローカルで開発して検証する

データをダウンロードしてパス設定。ちなみに、当方の環境はPowerShellです。

mkdir data
gsutil -m cp gs://cloudml-public/census/data/* data/
$TRAIN_DATA = "${PWD}\data\adult.data.csv"
$EVAL_DATA = "${PWD}\data\adult.test.csv"

依存関係モジュールをインストールする

pip install -r ../requirements.txt

実行してみる。

$MODEL_DIR = "output"
mkdir $MODEL_DIR
rm $MODEL_DIR/* -Recurse -Force
gcloud ml-engine local train `
    --module-name trainer.task `
    --package-path trainer/ `
    -- `
    --train-files $TRAIN_DATA `
    --eval-files $EVAL_DATA `
    --train-steps 1000 `
    --job-dir $MODEL_DIR `
    --eval-steps 100 `
    --verbosity DEBUG

結果エラー

...
  File "D:\home\hironori\Documents\edu\google-cloud-platform\cloudml-samples\census\estimator\trainer\task.py", line 4,
in <module>
    import model
ModuleNotFoundError: No module named 'model'

しょうがない、Python3なんだもん。 ということで、 task.pyimport model 部分を from . import model に変更。

もう一回実行。

gcloud ml-engine local train `
    --module-name trainer.task `
    --package-path trainer/ `
    -- `
    --train-files $TRAIN_DATA `
    --eval-files $EVAL_DATA `
    --train-steps 1000 `
    --job-dir $MODEL_DIR `
    --eval-steps 100 `
    --verbosity DEBUG

やっぱだめだわ。

...
  File "D:\home\hironori\Documents\edu\google-cloud-platform\cloudml-samples\census\estimator\trainer\model.py", line 23
7, in json_serving_input_fn
    for key, tensor in inputs.iteritems()
AttributeError: 'dict' object has no attribute 'iteritems'

うーんやっぱPython2.7用のコードなんですね。 うーん、困った。

この辺でやめとこうかな。

Outlook データを Python で読む (win32com)

データ分析、特に、テキスト分析の案件では、大量にあるデータとして、よく出てくるのが社内文書と電子メール。電子メールは特に会社の指定などでツールが決まっていることが多い。中でも厄介なのは、Microsoft Outlookの中だ。

このデータを取得する手法として、エキスポート機能が使えると思いきや、メールヘッダ情報を含んでいなかったり、メールアドレス情報に余計なものが含まれていたり、そもそもCSVデータ出力できず、MSG形式になったり、特定フォルダの下を再帰的に取得できなかったりと結構面倒。

そこで、Pythonで直接Outlookデータから、From,To,CC,サブジェクト,本文,ヘッダを出力するプログラムを書いてみた。今思うと、VBAのほうが素直だった気がするが、あまり気にしない。

以下に公開しているので参照のこと。

github.com

一番わからなかったのは、定数を使えるようにすることと、プロパティメソッドの利用方法。 皆さん同じことで困っているようで、結構すぐに見つかった。

定数を使えるようにするには、win32comでインタフェース用のライブラリを作成する必要がある。 そのために、python makepy.pyを実行する

ヘッダ文字列取得のために、COMオブジェクトを直接呼出しが必要だった。 ここは、仕方ないのでVBAの方法と同じ方法をそのまま利用している。

ヘッダ情報は、Internet Mail Headerがそのまま取得されているので、一般的なパーサーを利用できる。このサンプルも上記に乗せている。

では、データ取得を楽しんでください。

国内AI市場調査ってどうなんでしょうね

今週はIDC Japanがこんな発表をしました。

ニュース - 国内AIシステム市場は5年で16倍に急成長、IDC Japanが予測:ITpro

そうね。『2016~2017年は弁護士や医師といった「ナレッジワーカー向け」、製造業向け「品質管理」。2018年以降は金融での「詐欺検出/分析」、コールセンターの自動応答など「自動顧客サービス」への適用。』

2017/11/17 08:07

他の調査会社さんはどうなんでしょうと思い、少し調べてみることにしました。

各社さん調査はまちまちで、市場規模もすごく違う。 本当は全部横並びにしようと画策してましたが、調査対象が全然違うので、危険と思いやめました。 そんな中ですが、各社さん違いが見えて面白いです。

  • IDC Japanさんは、企業での活用シーンの定義から入っている。 市場規模は小さくなりがち。
  • 富士キメラ総研さんは、ユーザ事例をまとめた資料から推計しているのかな。 市場規模は大きくなりがち(調査会社が事例を広く集めるので)。
  • ミック経済研究所さんは、ITベンダーが提供するソリューションベースでまとめている。 市場規模が大きくなりがち(ベンダーは未来があると思っているので)。

目的に応じて使い分けるといいですね。 * ベンダー比較ならミック経済研究所 * ユーザ事例調査なら富士キメラ総研 * AI市場の定義やまとめなら IDC Japan

IDC Japan

国内コグニティブ/AIシステム市場予測を発表

2017年11月15日のプレスリリースです。

2016年(ユーザ支出) 2021年 CAGR
158億 2501億 73.6

IDCではコグニティブ/AIシステム市場を、自然言語処理と言語解析を使用して質問に応答し、機械学習をベースとしたリコメンデーションとディレクションを提供する技術として定義しています。同市場は、ハードウェア/ソフトウェア/サービスのテクノロジーによる分類と、ビジネスでの利用方法(ユースケース)に即した分類方法で分析しています。この分類方法は、IDCの「Worldwide Semiannual Cognitive/Artificial Intelligence(AI) Systems Spending Guide」で主要なユースケースを抽出し、適用モデルを作成しています。

というように市場規模を測るときに適用モデルを決めて推計しているようです。

富士キメラ総研

プレスリリース:『2016 人工知能ビジネス総調査』まとまる(2016/11/28発表 第16095号)

2016年11月28日「2016 人工知能ビジネス総調査」が出ています。 これのプレスリリースを見ると市場規模がでてます。

業種別

2015年度 2020年度予測 2030年度予測
製造 315億円 1,680億円 3,340億円
流通/サービス 150億円 985億円 2,120億円
金融 495億円 2,820億円 5,860億円
情報通信 270億円 1,720億円 3,680億円
医療/ライフサイエンス 100億円 500億円 1,030億円
公共/社会インフラ 155億円 2,015億円 4,520億円
その他業種 15億円 300億円 650億円
合計 1,500億円 1兆20億円 2兆1,200億円

注目市場別抜粋

2015年度 2020年度予測 2030年度予測
需要予測 370億円 825億円 2,015億円
コールセンター 104億円 730億円 1,870億円
映像監視 13億円 98億円 1,600億円
コミュニケーションロボット 5億円 160億円 600億円
ネットワークセキュリティ 40億円 265億円 390億円

その他

2017年11月17日「2018 人工知能ビジネス総調査」が出ています。 まだプレスがないので、市場規模などが分かりませんが、12業種で活用方法の調査をしいているものです。

2018 人工知能ビジネス総調査 (市場調査レポート)

2017 情報セキュリティビジネスにおける人工知能(AI)活用モデルの将来展望 (マルチクライアント調査レポート)

ミック経済研究所

AIエンジン&AIソリューション市場の現状と将来展望 | ミック経済研究所

2017 2021
AIエンジン市場 746億円 2080億円
AIソリューション市場 3077億円 8250億円

ITベンダーが持っているAIソリューションをもとに、この活用先を調べ推計するという方法ですね。 調査の14業種と32適応分野に限定して調べている。

ベンダー目線でまとまった比較資料として結構いい感じです。

矢野経済研究所

人工知能やコグニティブのみにフォーカスした市場規模予測は見当たりませんでした。 ご存知の方教えてください。

当然といえば、当然で、人工知能は技術軸なので、どの市場に技術適用されるか調べなければ、 どの市場を調査するかきまりません。

2015.11.24のレポートですが、中長期的な技術適用先市場はありました。 これによると、規模は不明ですが医療と金融市場とのことです。

2017国内ICT全般の市場レポートには活用先がのているようです。 (図表52 人工知能(AI)活用の中長期予測)

参照先

2017 国内企業のIT投資実態と予測 - 市場調査とマーケティングの矢野経済研究所 人工知能(AI)活用の中長期予測 | 市場調査の矢野経済 ICTユニット

株式会社富士経済

住宅分野、業務分野、エネルギー分野向けAI搭載機器、AI活用サービスの国内市場を調査

AI搭載機器、AI活用サービスの国内市場を調査があります。 これは、住宅分野、業務分野、エネルギー分野への活用のみを合計したもの。

AI搭載機器、AI活用サービスの国内市場

2017年見込 2025年予測 2017年見込比
AI搭載機器 27億円 1,861億円 68.9倍
AI活用サービス 1億円 169億円 169.0倍

その他

空調・エネルギー制御分野における人工知能(AI)/IoTの活用・R&D実態と今後の展望

生体情報センサ応用システムの AI・IoT活用 /研究開発動向と方向性

2017年 メディカルIoT・AI関連市場の最新動向と将来展望

総務省

総務省|平成29年版 情報通信白書|市場規模と実質GDPの推計

情報通信白書平成29年に「図表3-5-2-7 2030年までのIoT・AIの経済成長へのインパクト(市場規模)」があります。

16年 20年 25年 30年
市場規模(ベースシナリオ) 1,070 1,129 1,180 1,224
市場規模(経済成長シナリオ) 1,070 1,170 1,315 1,495

どういう市場を調べたのかまったくのっていません。 そこで、元々の調査がどこかのベンダーさんがやられたもののはずなので、元資料を探してみました。

http://www.soumu.go.jp/johotsusintokei/linkdata/h29_04_houkoku.pdf

「IoT時代におけるICT経済の諸課題に関する調査研究」(H29年)で情報通信総合研究所さんによる調査ですね。 残念ながら、定義はなく情報産業全般の調査です。

今週のブックマーク

スマートスピーカの認知度は約3割--知名度はGoogle、Amazon、LINEの順に - CNET Japan

調査期間は、2017年10月25〜31日。調査対象は、15歳から69歳の男女。有効回答は、2155人

2017/11/13 07:58

Eight years of Go - The Go Blog

もうそんなになるかね

2017/11/13 08:05

ICOで地域活性化!岡山県西粟倉村がICO導入に向けた共同研究を開始!! - 改革!地方創生地域活性化

こんな方法もあるのね

2017/11/13 08:20

www.itmedia.co.jp

blog.livedoor.jp

ニュース - 三井住友FGと三井住友銀、RPA活用で40万時間分の業務量削減を発表:ITpro

ほんまかぁ。さておき、コンサル会社をこれだけ使いこなすのがすごい。

2017/11/14 12:30

スマホ動画広告、半数が「最後まで見た」 サイバー子会社が調査 - ITmedia NEWS

調査対象は15~49歳の男女1000人。10月6日~8日にインターネット調査を実施した。

2017/11/14 12:35

yro.srad.jp

japan.cnet.com

自腹切っても「楽しいから」……アキバで“流通”する仮想通貨「モナコイン」の謎 (1/4) - ITmedia NEWS

こんな世界があるんだねぇ。

2017/11/14 18:08

smhn.info

japan.cnet.com

インスタ、実際どのくらい使われている?18-29歳と30代の間に大きな差が出る3大SNSの利用状況 (1/2):MarkeZine(マーケジン)

LINEの利用率は全体で8割を超えており、TwitterFacebookInstagramはおよそ3~4割という状況。

2017/11/15 08:31

wired.jp

ウィーチャット1日あたり9億200万人が使用 60%がビジネス向けへシフト 写真1枚 国際ニュース:AFPBB News

チャットサービスがはまる業態ってあるよね『ウィーチャットを使っている業界は200業種を超えている。月平均のアクセス人数が最も多い業界は、交通・旅行、Eコマース、生活サービスなどだった。』

2017/11/15 08:41

RPA導入の現場でいま何が起きているのか――机上検証の進め方、効果を示す数値の導き方:RPA導入ガイド(3) - @IT

この効果数値大事なんだけど。この数字の算出方法に行き着くまでに時間がかかりすぎると本末転倒。

2017/11/15 12:02

ドコモAIエージェントが描く新しい未来

ジョブス時代のappleみたい

2017/11/15 12:16

japan.cnet.com

「メディアビジネスは今すぐやめましょう」 | KOMUGI

たしかに。でもプラットフォームもコンテンツがないので初期立ち上げはきびしいんだよね。

2017/11/16 18:13

自動運転:全国初 レベル4の「無人」を実証実験 - 毎日新聞

乗りたいね

2017/11/16 18:15

O'Reilly Japan - 仕事ではじめる機械学習

“仕事ではじめる機械学習

2017/11/17 06:47

ニュース - 国内AIシステム市場は5年で16倍に急成長、IDC Japanが予測:ITpro

そうね。『2016~2017年は弁護士や医師といった「ナレッジワーカー向け」、製造業向け「品質管理」。2018年以降は金融での「詐欺検出/分析」、コールセンターの自動応答など「自動顧客サービス」への適用。』

2017/11/17 08:07

「世界最速」機械学習技術、ヤフーがOSSで公開 - ITmedia NEWS

やりおった。

2017/11/17 08:11

MicrosoftがMariaDB Foundationに参加してAzure Database for MariaDBをローンチ | TechCrunch Japan

なにか起こりそうな予感。

2017/11/17 08:17

中国でAIロボットが医師試験に合格「医療過疎地での初期診断も可能」 | ROBOTEER

できちゃうよね。決まった診断なら、

2017/11/17 08:22

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

前回、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(テキスト分類)

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でテキスト分類の復習

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から

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と比較してみたくなりますね。あとでやってみます。


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

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