Expo (React Native)アプリでAndroid端末のGoogle認証できるように設定してみた

Expo.io、Google公式のドキュメント()を見ながら設定をしてみました。 ExpoでAndroid端末のGoogle認証を実装しようとされている方は参考にしてみてください。

実行環境

expo-cliのインストールは公式サイトをご参照ください。

一連の流れ

本作業の一連の流れは以下の通りです。

  • ①ローカル環境でExpoアプリ作成
  • ②設定
    • Google developers consoleで認証情報作成
    • Firebase consoleで設定(認証機能有効化・Android認証設定)
  • ③ ①で作ったExpoアプリの動作確認

①ローカル環境でExpoアプリ作成

expo-cliで初期プロジェクトを作成

$expo init GoogleAuthWithAndroid

Choose a template:でblankを選択(そのままEnterを押すだけ)

Extracting project files...と表示されるので、1分くらい待つとブランクプロジェクトが作成されます。作成後プロジェクトのディレクトリに移動します。

$cd GoogleAuthWithAndroid

Expoアプリの修正

firebaseのインストール

以下のコマンドを実行し、package.jsonにfirebaseを追加します。

$npm install firebase -S

次にpackage.jsonを修正します。最新のexpoバージョン(30.0.1)を使用すると、依存しているwhatwg-fetchが新しくなりすぎるので手動でdependenciesの箇所にwhatwg-fetchの行を追記。

"dependencies": {
  "expo": "^30.0.1",
  "firebase": "^5.5.5",
  "react": "16.3.1",
  "react-native": "https://github.com/expo/react-native/archive/sdk-30.0.0.tar.gz",
  "whatwg-fetch": "^2.0.4"
}

※詳細はこちらの記事を参照。今後expoのバージョンが上がるとこの修正は不要になりそう。

npmインストール実行します。

$npm install
Google認証処理の追加・画面修正

expo initコマンドで自動作成されたソースコードApp.jsを以下のように修正。

import React from 'react';
import { StyleSheet, Button, View } from 'react-native';
import firebase from 'firebase';
import Expo from 'expo';

export default class App extends React.Component {
  state = { loggedIn: null, };

  async onLoginButtonPress() {
    // ①Expoの機能でGoogleにログインする(トークン取得のため)
    try {
      const result = await Expo.Google.logInAsync({
         androidClientId:"ここに後にGoogle developers consoleで取得するAndroid用のクライアントIDを入れる",
        scopes: ["profile", "email"]
      });
    } catch (err) {
      console.log("Googleトークン取得エラー");
      return;
    }

    // ②トークン取得できたら、firebaseでGoogle認証する
    try {
      if (result.type === "success") {
        const { idToken, accessToken } = result;
        const credential = firebase.auth.GoogleAuthProvider.credential(idToken, accessToken);
        const response = firebase .auth() .signInAndRetrieveDataWithCredential(credential);
        // ログイン後の処理
      }
    } catch (err) {
      console.log("firebase Google認証エラー");
    }
  }

  // ③アプリが起動したらfirebaseに接続するように
  componentWillMount() {
    firebase.initializeApp({
      apiKey: '後にFirebase Consoleで取得する',
      authDomain:  '後にFirebase Consoleで取得する',
      databaseURL: '後にFirebase Consoleで取得する',
      projectId: '後にFirebase Consoleで取得する',
      storageBucket: '後にFirebase Consoleで取得する',
      messagingSenderId: '後にFirebase Consoleで取得する'
    });
 
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        this.setState({ loggedIn: true });
      } else {
        this.setState({ loggedIn: false });
      }
    })
  }

  render() {
    return (
      <View style={styles.container}>
        { /* ④Google認証するボタンを表示し、作成したGoogle認証処理をonPressに設定する */ }
        <Button title="Google認証" onPress={this.onLoginButtonPress.bind(this)}/>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

②設定

Google認証情報作成(Google developers consoleで認証情報を作成)

認証情報作成

Google developers consoleで任意のプロジェクトを作成する。

次に、認証情報画面を開き、認証情報を作成でOAuthクライアントIDを作成する。

f:id:graboros-dev:20181022004008p:plain

f:id:graboros-dev:20181022003759p:plain

ローカルで以下のコマンドを実行し、SHA-1名証明書フィンガープリントを発行する。

$openssl rand -base64 32 | openssl sha1 -c

以下のように設定して、作成ボタンを押す。 f:id:graboros-dev:20181022004646p:plain

発行したクライアントIDをプログラムソースコードに設定する

認証情報が作成すると、認証情報画面にOAuth 2.0クライアント一覧にIDが表示される。 f:id:graboros-dev:20181022005332p:plain 先程修正したソースコードApp.js①のandroidClientIdの箇所に発行したクライアントIDを入れる。

Firebase設定 (Firebase Consoleで認証機能有効化・Android端末の認証設定)

発行したFirebase設定情報をプログラムソースコードに設定する

Firebase Consoleで任意のプロジェクトを作成し、ダッシュボード画面でWebアプリのアイコンを選択。 f:id:graboros-dev:20181022022445p:plain 先程修正したソースコードApp.js③のfirebase.initializeAppの引数に表示されている設定情報を入れる。

認証機能有効化

次にAuthenticationの箇所からログイン方法を選択し、Googleログインを有効にする。 f:id:graboros-dev:20181022010423p:plain

Android端末の認証設定

Firebase Consoleのダッシュボード画面でAndroid端末の設定画面を開く f:id:graboros-dev:20181022010944p:plain

Google developers consoleで設定した内容を入力する。 f:id:graboros-dev:20181022010929p:plain

「②設定ファイルのダウンロード」、「③Firebase SDK の追加」、p④アプリを実行してインストールを確認」は何もせずに進めてOK

③動作確認

Android studioでシミュレータを起動

作成したExpoアプリケーションを読み込むためのAndroidシミュレータをローカルで起動しておく。

Expoアプリケーションの起動

以下のコマンドを実行

$expo start

すると、デフォルトのブラウザに以下の画面が表示されるので、「Run on Android device/emulator」を選択。 f:id:graboros-dev:20181022011513p:plain

確認

Expoアプリがインストールされ、作成した「Google認証」ボタンだけの画面が表示される。

f:id:graboros-dev:20181022011832p:plain

ボタンを押すと、Google認証が行われることを確認できる。

f:id:graboros-dev:20181022012317p:plain

Expo (React native)でエラーー(Can't find variable: self)が発生する場合

firebaseを使おうと思って、ドキュメント通りにnpm installしてもうまくいかず、少しはまってしまったのでメモ。

package-lock.jsonを見るとwhatwg-fetch3.0.0になっていたため、以下のエラー(Can't find variable: self)が発生していた。

エラーが発生したバージョンは、react-nativesdk-30.0.0、expoは30.0.1 f:id:graboros-dev:20181021195127p:plain

以下のようにwhatwg-fetchバージョンを指定し、npm installでインストールするとエラーが発生しなくなった。

  "dependencies": {
    "@expo/samples": "2.1.1",
    "expo": "^30.0.1",
    "firebase": "^5.5.5",
    "react": "16.3.1",
    "react-native": "https://github.com/expo/react-native/archive/sdk-30.0.0.tar.gz",
    "react-navigation": "^2.16.0",
    "whatwg-fetch": "^2.0.4"
  }

この問題はStackoverflow、expo.ioのforumでも記載があった。 stackoverflow.com forums.expo.io

expo 30.0.1で修正されたと書いてあるけど、まだ現象が発生しているみたいなので、お気をつけください。。

IPython Notebook 5 ( Jupyter notebook )で インデント スペース 数を4つから2つ変更した

IPython Notebook 5.4.1での、インデントの変更方法。

~/.jupyter/nbconfigに以下の2つのファイルをおけばインデント2つに変更できた。

edit.json:

{
  "Editor": {
    "codemirror_options": {
      "indentUnit": 2,
      "vimMode": false, 
      "keyMap": "default"
    }
  }
}

notebook.json:

{
  "CodeCell": {
    "cm_config": {
      "indentUnit": 2
    }
  }
}

参考 stackoverflow.com

Rubymine と dockerを 連携して デバッグ できるようにしてみた

Railsのプロジェクトにいくつか関わっているとRMagicとかのネイティブインストールするライブラリのバージョンに違いが出てきたりして、動かずハマるということがしばしばある。 そろそろ開発環境をプロジェクト毎に分けたいなー、と思い始めてきたのでDockerを導入してみた。

導入した環境はUbuntu 17.10、Rubymine 2018.1。(おそらくMacとか他の環境でもほとんど同じだと思う。)

設定はjetbrainsのマニュアル通りにすれば大丈夫だったけど、一応手順を記載。

1. Gemの追加

Gemfileに以下のGemを追加し、Rubymineからデバッグ実行できるように

gem 'debase'
gem 'ruby-debug-ide'

2. Dockerfile、docker-compose.ymlをRails.rootに作成

プロジェクト毎の設定を記載する。

  • Dockerfile
FROM ruby:2.3.3
RUN apt-get update -qq && \
    apt-get install -y build-essential libpq-dev nodejs
RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile .
ADD Gemfile.lock .
ENV BUNDLE_JOBS=4 \
    BUNDLE_PATH=/bundle
RUN bundle install
ADD . /myapp

Rubymine設定前に、以下のようなコマンドを実行してDocker Imageが作成するか確認しておくこと。

# Dockerfileをビルドして、Docker Imageを作る
docker build -t developer-name/project-name --no-cache=true .

# Docker Imageができたか確認する
docker images

同様にdocker-composeも設定する。

  • docker-compose.yml
version: '2'
services:
  app:
    build: .
    command: bundle exec rails s -p 3000 -b 0.0.0.0
    environment:
      RAILS_ENV: development
    ports:
      - '3000:3000'
    volumes:
      - .:/myapp

(version: '3'にすると動かなくなったので、version: '2'にしている)

docker-compose も動くか確認

# Dockerイメージを作成
docker-compose build

# コンテナの作成と開始
docker-compose up -d

# コンテナの停止
docker-compose down

3. Docke設定を追加

メニュー[File] => [Settings] => [Build, Execution, Development] => [Docker]に設定を追加。 f:id:graboros-dev:20180504221056p:plain 「Docker」等わかりやすい名前をつけて、「Unix socket」を選択。

Docker Deamonに繋がれば、添付のようにConnection Successfulと表示される。

4. Docker環境のRubyを使うように

もしハマるところがあるとすれば多分ここだけ。

メニュー[File] => [Settings] => [Ruby SDK and Gems]を選択。 [+]を選択して、[New Remote]を選択。

f:id:graboros-dev:20180504214908p:plain Serverに2. で作ったDockeを選択。 Configuration filesに1. で作ったdocker-compose.ymlを選択。 Serviceにdocker-compose.ymlで指定したサービス名を指定。

この段階でdocker-composeが動かない場合、Ruby interpriter pathの先が見つからず「Unable to detect full path for ruby」というようなエラーが表示されて???となるので、事前に手動で動くことを確認しておくこと。

5. デバッグ実行

f:id:graboros-dev:20180504225405p:plain

デバッグ実行してlocalhost:3000にアクセスする。ちゃんとブレークポイントに止まる。素晴らしい! ※最初からデバッグ実行したときにうまく動かないときがあるので、先に普通にRunしたほうが良い

補足

  1. Rubymineから初めてdebug実行したとき以下のようなエラーが表示されていた。 f:id:graboros-dev:20180517082341p:plain 調べてみるとdocoker-composeのバージョンが古いときに発生するエラーらしいので、試しにdocker-composeのバージョンを上げてみたら直った。

  2. ちなみに、Rubymineでデバッグ実行の設定する際にDockerコンテナがいくつかできていたので、以下のようにimageを元にdocker rmするコマンドを作ってみた。 まだあまり使っていないけど、便利そうな気がする。

docker rm $(docker ps -a | grep "docker-image-name" | awk '{print $1}')

(この行を消して、ここに「わたしの転機」について書いてください)

#わたしの転機

りっすん×はてなブログ特別お題キャンペーン「りっすんブログコンテスト〜わたしの転機〜」
Sponsored by イーアイデム

【Mac OS】巨大なデータをpickleで読み書きしたときにはまったこと

あまりデータサイズが大きくない訓練データセットを読み込む時は普通にpandasのread_csvを使って

import pandas as pd
df = pd.read_csv('train.csv')

で大丈夫だけど、pandasのread_csvは遅いので、データサイズが大きい時は他の方法を使いたくなる。 (実行環境:MacBook Pro (Early 2015)、CPU 2.9 GHz Intel Core i5、メモリ 8 GB 1867 MHz DDR3、データCSV:7.0GB)

%time df = pd.read_csv('train.csv')
CPU times: user 2min 31s, sys: 58.8 s, total: 3min 29s
Wall time: 4min 2s

遅い。。より良い方法がないか、と調べているとpickleでシリアライズ読み書きすれば良いとわかるが、以下のコードだとうまくいかない。

import pickle
pickle.dump(df, open('train.csv.pkl', 'wb'), protocol=4)

OSError Traceback (most recent call last)
<ipython-input-10-aa4f708bf30d> in <module>()
 1 import pickle
----> 2 pickle.dump(df, open('train.csv.pkl', 'wb'), protocol=4)

OSError: [Errno 22] Invalid argument

このエラーをStackover flowで調べてみると長い間解決されていない、Mac OSでは2GBより大きいデータ書き込み時にエラーが発生する問題が原因だということが分かる。 手っ取り早い解決策として、以下のコードのように2の31乗バイトに分けてファイル出力するとエラーがでなくなるらしい。

import sys
import os

def save_as_pickled_object(obj, filepath):
    max_bytes = 2**31 - 1
    bytes_out = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL)
    n_bytes = sys.getsizeof(bytes_out)
    with open(filepath, 'wb') as f_out:
        for idx in range(0, n_bytes, max_bytes):
            f_out.write(bytes_out[idx:idx+max_bytes])


def try_to_load_as_pickled_object_or_None(filepath):
    max_bytes = 2**31 - 1
    try:
        input_size = os.path.getsize(filepath)
        bytes_in = bytearray(0)
        with open(filepath, 'rb') as f_in:
            for _ in range(0, input_size, max_bytes):
                bytes_in += f_in.read(max_bytes)
        obj = pickle.loads(bytes_in)
    except:
        return None
    return obj
# 書き込み
save_as_pickled_object(df, 'train.csv.pkl')

# 読み込み
%time df = try_to_load_as_pickled_object_or_None('train.csv.pkl')
CPU times: user 27.8 s, sys: 1min 26s, total: 1min 53s
Wall time: 3min 7s

pickleで読み書きできるようにはなったが、結局1分くらいしか早くなっていないので、pickleからcPickle(python 3.xでbundleされているpickleのC言語実装)にしたところ少し早くなった。

import _pickle as cPickle
import sys
import os

def try_to_load_as_pickled_object_or_None(filepath):
    max_bytes = 2**31 - 1
    try:
        input_size = os.path.getsize(filepath)
        bytes_in = bytearray(0)
        with open(filepath, 'rb') as f_in:
            for _ in range(0, input_size, max_bytes):
                bytes_in += f_in.read(max_bytes)
        obj = cPickle.loads(bytes_in)
    except:
        return None
    return obj
%time df = try_to_load_as_pickled_object_or_None('talkingdata_train.csv.pkl')
CPU times: user 25 s, sys: 1min 13s, total: 1min 38s
Wall time: 2min 21s

いまさらNBSVMを調べてみた

kaggleをやっていてテキスト分類のコンペのkernelでNBSVMが使われていたので、勉強のために今更ながらNBSVMに関して調べてみた。

NBSVMはテキストデータのトピック分類や感情分類で比較的に精度が良いことを確認されているアルゴリズムで、このようなジャンルの機械学習モデルの精度基準に使われたりするらしい。kaggleコンペのベースラインkernelでも使われていたりする

NBSVMとは

Naive Bayes素性を利用したSVM(Support Vector Machine)のことである。と聞いてもあまりピンとこないが、ベイズの定理を使ってある分類に分類されやすいかどうかということを計算に変換してから、SVMのような分類器で学習させましょうということらしい。 例えば、機会学習アルゴリズムを用いて、投稿されたニュース記事を「経済」、「エンタメ」、「スポーツ」いずれかのニューストピックに分類したいときにNBSVMを使うことができる。

これからあるニュース記事のトピックを「経済」か「経済」以外かに分類するような機械学習モデルを作成することを想定し、既存の以下の記事(エンタメ記事 2件、経済記事 2件)を学習させる例を考えてみる。

・記事1(トピック:エンタメ)
ローラがファッション分野で引っ張りだこで、雑誌での露出が増えている。 
 
・記事2(トピック:経済)
新卒は電話対応が苦手らしい。  

・記事3(トピック:経済)
電話対応は新卒の仕事だ。  

・記事4(トピック:エンタメ)
新海誠が監督、脚本を務めた映画「君の名は。」が国内興行収入250億円を突破した。

ここで文章を以下のような単語の出現回数のベクトルに変換する。 (説明をわかりやすくするために助詞などトピックの種類とは関連がない単語を省いている。)

・記事1(トピック:エンタメ)
{ファッション: 1、雑誌: 1、露出: 1、新卒: 0、電話対応: 0、苦手: 0、
 監督: 0、映画: 0、興行収入: 0}  

・記事2(トピック:経済)
{ファッション: 0、雑誌: 0、露出: 0、新卒: 1、電話対応: 1、苦手: 1、
 監督: 0、映画: 0、興行収入: 0}  

・記事3(トピック:経済)
{ファッション: 0、雑誌: 0、露出: 0、新卒: 1、電話対応: 1、苦手: 0、
 監督: 0、映画: 0、興行収入: 0}    

・記事4(トピック:エンタメ)
{ファッション: 0、雑誌: 0、露出: 0、新卒: 0、電話対応: 0、苦手: 0、
 監督: 1、映画: 1、興行収入: 1}

次に、条件付確率を考慮して、このベクトルを変換する。(この変換がNBSVMという名前のNB(Naive Bayes)の部分)

記事が「経済記事」かどうかを判別したいこのケースで考慮に入れたいのは、「「経済」記事内のその単語の出現割合」と「「経済」ではない記事内のその単語の出現割合」なので、文章毎の単語の出現回数に  \displaystyle \frac{「経済」記事内のその単語の出現割合}{「経済」ではない記事内のその単語の出現割合} をかけてあげれば、「経済」記事の単語の重みが大きくなり、他のトピック記事に頻出する単語の重みを小さくすることができる。(ベイズの定理との関係は補足に記載)

しかし、ここで「「経済」ではない記事内のその単語の出現割合」が0の場合に割り算ができなくなるという不都合が起こる。なので、分母・分子それぞれに1を足して計算を行い、0割りを回避する。

全単語の出現割合を算出する必要があるが、ここでは「新卒」という単語の出現割合の計算式を確認する。
 「経済」記事内のその単語の出現割合  = \displaystyle \frac{「経済」記事にその単語が出現する回数 + 1( 2 + 1 =3)}{「経済」のドキュメントの数 + 1(2 + 1 =3)} = 1

 「経済」ではない記事内のその単語の出現割合  =\displaystyle \frac{「経済」ではない記事にその単語が出現回数 + 1( 0 + 1 =1)}{「経済」ではないドキュメントの数 + 1(2 + 1 =3)} = 1/3

上の2式より  \displaystyle \frac{「経済」記事内のその単語の出現割合}{「経済」ではない記事内のその単語の出現割合} = \displaystyle \frac{1}{1/3} = 3

「新卒」という単語以外でも同様にして計算を行い、文章毎の単語の出現回数に掛けると

・記事1(トピック:エンタメ)
{ファッション: 0.5、雑誌: 0.5、露出: 0.5、新卒: 0、電話対応: 0、苦手: 0、
 監督: 0、映画: 0、興行収入: 0}  

・記事2(トピック:経済)
{ファッション: 0、雑誌: 0、露出: 0、新卒: 3、電話対応: 3、苦手: 2、
 監督: 0、映画: 0、興行収入: 0}  

・記事3(トピック:経済)
{ファッション: 0、雑誌: 0、露出: 0、新卒: 3、電話対応: 3、苦手: 0、
 監督: 0、映画: 0、興行収入: 0}    

・記事4(トピック:エンタメ)
{ファッション: 0、雑誌: 0、露出: 0、新卒: 0、電話対応: 0、苦手: 0、
 監督: 0.5、映画: 0.5、興行収入: 0.5}

となり、「経済」の記事に出現する単語の重みが大きくなり、その他の記事の単語の重みが小さくなっていることが分かる。

このベクトルをSVNのような分類器で学習させると、新しい記事がどのトピックの記事であるかどうかを判別するモデルができる。 同様に他のトピックに関しても計算をしてトピック毎に分類器のモデルを作れば、新たな記事がどのトピックの記事かを判別するモデルができる。

このように単語の出現回数だけではなくて、特定のトピックであることを前提とした単語の出現割合を考慮することで、機械学習の判別の精度をあげることができる。

実装に関しては、SVMはscikit-learnにあるから良いとして、NBの部分は前述のkernelを見ると、

 r = np.log(pr(1,y) / pr(0,y))

となり、Pythonで結構シンプルに書けることが分かる。

注意点

①アンダーフロー回避

単語の出現回数に、 \displaystyle \frac{「経済」記事内のその単語の出現割合}{「経済」ではない記事内のその単語の出現割合}を掛けたが、この方法ではアンダーフローが発生する可能性があるので、実際にはこれを対数化したものを掛ける。

アンダーフロー: 0.0001 * 0.0001の計算結果が0.0000001のように有効桁が下がり、コンピュータで例えば小数点第4桁までしか計算できない場合には結果は0になってしまい精度が下がることをいう。

 \log 0.0001 * 0.0001 = \log 0.0001 * \log 0.0001
 = \log 10^{-4} +  \log 10^{-4} = (-4) + (-4) = -8
のように対数化すれば、掛け算・割り算を足し算・引き算に変換できるので、有効桁が下がらない。

②トピックに共通する単語の除外

上記の例では、恣意的に共通する単語を除外したが、TfidfVectorizerのようなアルゴリズムを使って頻出の単語の重要度を下げることによって機械的に除外できる。

補足

ベイズの定理出てこなかったので補足。こんな感じで分母と分子のそれぞれで使われているらしい。
 \displaystyle \frac{P(経 | 単)}{P(not経 | 単)} = \displaystyle \frac{P(単) P(単 | 経)}{P(単) P(単 | not経)} = \displaystyle \frac{P(単 | 経)}{P(単 | not経)}

参考文献

TfidfVectorizerのよく使いそうなオプションまとめ

TfidfVectorizerとは

文章内に出現する単語の出現頻度と希少性を掛け合わせた値Tfidfを算出するアルゴリズム。文章を特徴づける単語を探したりできる。

例えば、Twitterの投稿テキストを読み込んで、気分が「Happy」か「Unhappy」かを判別する機械学習モデルを作ろうと思った時に、テキスト内の単語の出現頻度を元に判別するという方法が考えられる。
しかし、どの投稿でもよく出現する助詞などの単語の出現頻度によって「Happy」か「Unhappy」かの判別基準が変わるのは望ましくない。
出現頻度だけではなく、その希少性も考慮に入れて判別を行いたいはずで、そのような場合にTfidVectorizerというアルゴリズムを使うと良い。

TfidfVectorizerの使用方法

Pythonのscikit-learnからクラスを使う時、以下のようにすれば使える。

# scikit-learnからTfidfVectorizerを読み込む
from sklearn.feature_extraction.text import TfidfVectorizer

# 文章内に10回まで出現する単語のTfidf値を求めるためのTfidfVectorizerを作る
vec = TfidfVectorizer(max_df=10) # ここでオプションを指定

# 単語の出現回数を数える文章を作成
docs = ["I have a pen", "I have an apple", "Ugh, apple pen", "I have a pen", "I have an pineapple", "Ugh, Pineapple pen"]

# 文章内の単語のTfidf値を取得
term_doc = vec.fit_transform(docs)

# 単語に割り当てられている序数を表示
vec.vocabulary_
=> {'an': 0, 'apple': 1, 'have': 2, 'pen': 3, 'pineapple': 4, 'ugh': 5}

# 単語毎のtfidf値を取得
term_doc.toarray()
=>
array([[ 0.             ,  0.             ,  0.70710678,  0.70710678,  0.            ,  0.        ],
       [ 0.62951441,  0.62951441,  0.4554374 ,  0.             ,  0.            , 0.        ],
       [ 0.             ,  0.62951441,  0.            ,  0.4554374 ,  0.             ,  0.62951441],
       [ 0.             ,  0.             ,  0.70710678,  0.70710678,  0.            ,  0.        ],
       [ 0.62951441,  0.                 ,  0.4554374 ,  0.             ,  0.62951441, 0.        ],
       [ 0.             ,  0.             ,  0.             ,  0.4554374 ,  0.62951441, 0.62951441]])

この結果を見ると、配列の1行目(1つ目の文章"I have a pen")は3列目(have)と4列目(pen)のTfidf値が高く、 6行目(5つ目の文章"Ugh, Pineapple pen")は4列目(pineapple)と5列目(pen)、6列目(ugh)のTdidf値が高くなっていることがわかります。 頻出のIanといった単語はTdidf値が0で重要度が低いことがわかり、Tdidf値の高い単語が文章を特徴づける重要な単語としてみなすことができます。

tokenizer

トークン化するためのコールバックを指定する。 例えば以下のようにすれば、!"#$%&\'(+)*,-./:;<=>?@\\\[\]^_{|}~“”¨«»®´·º½¾¿¡§£₤‘’`のような単語を区切り文字に使える。

import re
def tokenize(s): return re.split('[ !"#$%&\'(+)*,-./:;<=>?@\\\[\]^_`{|}~“”¨«»®´·º½¾¿¡§£₤‘’]', s)
TfidfVectorizer(tokenizer=tokenize)

ngram_range

n-gramの最小値と最大値をタプルで指定します。例えば、1gramと2gramの頻出を調べる場合には(1, 2)のように指定します。 上記の例でオプションにngram_range=(1, 2)を指定すると、以下の単語(単語列)のTdidf値を算出します。

vec.vocabulary_
=> {'an': 0, 'an apple': 1, 'an pineapple': 2, 'apple': 3, 'apple pen': 4, 'have': 5, 'have an': 6, 'have pen': 7, 'pen': 8, 'pineapple': 9,'pineapple pen': 10, 'ugh': 11, 'ugh apple': 12, 'ugh pineapple': 13}

max_features

抽出する単語数を指定します。tfidf値の降順で単語が返ってくる。 デフォルトはNoneなので、指定しない場合は全件取得する。

max_df

intで指定された場合には、指定された出現回数までの単語のみを抽出。
floatで指定された場合には、指定された出現割合までの単語のみを抽出。
デフォルトは1.0なので、指定しない場合全ての文章にこの単語があっても抽出する。

min_df

intで指定された場合には、指定された出現回数以上の単語のみを抽出。
floatで指定された場合には、指定された出現割合以上の単語のみを抽出。
デフォルトは1なので、指定しない場合1回以上出現した単語を抽出。

strip_accents

前処理の段階でアクセント記号があれば、アクセント(è、ò、ù ⇦ こういうの)を取り除く。 'ascii'が指定されている場合はASCIIコードマッピングで取り除き、‘unicode’、Unicodeのコードマッピングで取り除く。 Noneが指定されている場合には取り除かない。

アクセント記号って日本人だと普段使わないから意識しないけど、ASCIIコードの中にもあるんですよね。

smooth_idf

単語の出現回数に1足すかどうかを指定する。idf値の計算式の分母で単語の出現回数を使っていて、0になる場合は0割が発生するので、それを防ぐために入れる。 デフォルトはTrueのため、0割防止のために1を足している。

あまり出現回数にばらつきがないような文章でそれぞれ出現回数が少ない場合には全部に1が足されてしまうので、多くの単語の優位性が上がってしまう気がした。

stop_words

Tfidf値を求めなたくない単語のリストを指定する。 例えば上記の例では、Ihave, anといった単語が文書を特徴づけるものではないとわかっている単語を指定して、不要な項目の計算を省くことができる。

sublinear_tf

単語の出現頻度(Term Freequency)に対数変換するどうかを指定する。 Trueにするとtfを1 + log(tf)に置き換える。 デフォルトはFalseのため、対数変換しない。

あまりにも出現頻度が高い単語があった時に他の単語よりtfidf値が上がってしまうので、あまり影響を受けないようにすることができそう。

sklearn.feature_extraction.text.TfidfVectorizer — scikit-learn 0.19.1 documentation