微忘録

好奇心に記憶力がついていかない人のブログ

numpyオブジェクトをjson.dumpできるようにエンコーダーを拡張する方法

最近、友人の運営する大学講義の検索サイトを、機械学習で良い感じにしてます。

地味に悩んだのが「JSON形式でのデータ入出力」の部分。具体的には、「JSON形式で渡されたデータに、推定結果の一部numpyオブジェクトを加えたJSONデータを返す」作業。

Pythonなど動的言語はデータ型をよしなに判断してくれますが、少し例外に触れた時には指定してあげる必要があります。
今回はその例外のためにクラスを自作して指定する方法を備忘録。

問題

何も引数に指定せず、ただ以下の様にjson.dumps()メソッドにnumpy.int64などを渡すと、以下の様なエラーに遭遇する。

json.dumps(numpy.int64(111))
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-12-c21d581d4bed> in <module>()
----> 1 json.dumps(np.int64(111))


/usr/local/var/pyenv/versions/anaconda3-5.0.0/lib/python3.6/json/__init__.py in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
    229         cls is None and indent is None and separators is None and
    230         default is None and not sort_keys and not kw):
--> 231         return _default_encoder.encode(obj)
    232     if cls is None:
    233         cls = JSONEncoder


/usr/local/var/pyenv/versions/anaconda3-5.0.0/lib/python3.6/json/encoder.py in encode(self, o)
    197         # exceptions aren't as detailed.  The list call should be roughly
    198         # equivalent to the PySequence_Fast that ''.join() would do.
--> 199         chunks = self.iterencode(o, _one_shot=True)
    200         if not isinstance(chunks, (list, tuple)):
    201             chunks = list(chunks)


~~~

TypeError: Object of type 'int64' is not JSON serializable

この一文は「JSONモジュールの既存エンコーダーに対応している型じゃないから変換無理です」という話

TypeError: Object of type 'int64' is not JSON serializable

なので既存エンコーダーに、自前のエンコード用クラスを与えて拡張する必要があります。

解決策

以下のような、型の種類ごとに変換を行うクラスを作成して、JSONモジュールに対応する型にします。

  • isinstance(object, class)で、一致する場合はreturn以下で変換して返す。
  • numpy.integerint型へ
  • numpy.floatingfloat型へ
  • numpy.ndarraylist型へ

(今回はint型の変換だけで良いのですが、オマケでその他も加えておきます。)

import numpy
import json

class MyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, numpy.integer):
            return int(obj)
        elif isinstance(obj, numpy.floating):
            return float(obj)
        elif isinstance(obj, numpy.ndarray):
            return obj.tolist()
        else:
            return super(MyEncoder, self).default(obj)

そしてjson.dump()cls引数にクラスを渡してあげます。

json.dumps(numpy.int64(111),cls = MyEncoder)
'111'

解決!

最後に

JSON形式で数値を扱うこと自体ナンセンスかもしれませんが、遭遇しやすい問題かなと思うので載せておきました。 良き分析ライフを御過ごしください。

参考記事

以下記事を参考にしております。先人の知恵に感謝です。

GitHubのセキュリティ設定でやるべきこと

GitHub周りのセキュリティ設定、何回忘れたのかさえ忘れました。 公開鍵でSSH接続して、EmailをPrivateするだけの備忘録です。

※厳密なセキュリティ設定は記述しないので、他の猛者の方々の記事を参考にして下さい。


アカウントと公開鍵の設定

鍵セットの作成

rsa指定で鍵セットを作成。既存がある際は重複削除しないよう注意。

$ cd ~/.ssh #移動して
$ ssh-keygen -t rsa -C mymail@example.com #鍵作成。-C以下任意で自分のメールアドレス

SSH連携

ssh/configに記入し、GitHubとのssh接続を楽にする。

$ vim ~/.ssh/config 

#以下をインサート
Host github.com
  HostName github.com
  IdentityFile ~/.ssh/id_rsa
  User git

公開鍵をクリップボードにコピーして、GitHubアカウントのSSH keysでコピペして登録。

$ pbcopy < ~/.ssh/id_rsa.pub

接続成功したか確認。成功したら以下のような文が出てくる

$ ssh -T git@github.com
Hi wtnVenga! You've successfully authenticated, but GitHub does not provide shell access.

ローカルリポジトリのconfig設定

ローカルリポジトリに移動。config設定を開いて、https://~git@~に変更。

$ cd [local repository]
$ vim .git/config

#'url = https://github.com/[user_name]/[repository_name].git' の部分を以下形式に変更。
url = git@github.com:[user_name]/[repository_name].git

PrivateEmailの利用

GithubのEmail設定をPrivate化するとEmail情報を守れる。
(※リモートリポジトリをcloneされた時にcommitログから漏れない。)

GitHubアカウントの設定

まずはEmail設定の Keep my email address privateBlock command line pushes that expose my email にチェック。 表示される[userid]+[username]@users.noreply.github.com をコピペ。

git configの設定

git configのemail設定を書き換える(--global--local かはお好みで)

$ git config --global user.email [userid]+[username]@users.noreply.github.com

(commitログ書き換え)

最新コミットログの変更。(開いたら:ZZで保存するだけ)

git commit --amend --reset-author

過去コミットログの変更。自分のSHA(コミットハッシュ値から)を指定

git rebase -i [my_SHA] -x "git commit --amend --reset-author -CHEAD"

参考元

git - Meaning of the GitHub message: push declined due to email privacy restrictions - Stack Overflow

How to amend several commits in Git to change author - Stack Overflow

gensimのmodels.TfidfModel()で、引数にSMART notationが使えるようになっていた話

つい先日こんなツイートをしたところ、爆速で公式からリプライが来ました。

「2018年1月2日のgensim3.3.0のリリースに際して、TfidfModelにSMART notation機能が追加されたから宜しくな!」という話で、つまり「models.TfidfModel()コーパスの重み付け手法の選択が、簡単な記法でできるsmartirs引数を新たに用意したよ」という要旨です。今回はこの機能について宣伝と備忘録。

gensimとTF-IDF法

gensimについて

gensimはLSIやLDAなどのトピック分析を行う際に便利なPythonモジュールです。トピック分析の発端である自然言語処理での活用と主しており、文書ごと単語リスト*1(配列)を用意すれば、gensimモジュールの関数を用いて、辞書とコーパスの作成からトピック推定と評価まで可能です。

TF-IDFについて

そして今回備忘録をするmodels.TfidfModel()は、コーパス作成時に単語にTF-IDF法を用いた重み付けをする関数です。

TFは"Term Frequency"つまり文書内単語頻度、IDFは"Inverse Document Frequency"つまり逆文書頻度、そしてTF-IDFはそれら2種の重み付けの積で、「全文書内での出現頻度は低いが、特定文書にて頻繁に用いられる特定単語に、強い重み付けができる」方法です。

そしてgensim3.3.0以前までのmodels.TfidfModel()ではTF-IDF法のみしか指定できず、「TF法だけ」もしくは「IDF法だけ」は自作する必要がありました。*2 この面倒くささを改善したのが、今回のSMART notation機能の導入です。

SMART notationを試す

SMART notationとは

SMART (System for the Mechanical Analysis and Retrieval of Text) notationとは、「文書検索システムにおける重み付けの簡便表記法」になります。

伝統的なベクトル空間モデルを用いた文書検索では、クエリ内容(文書名)に対して、各文書の単語群(Bag-of-Words)の出現頻度にコサイン類似度を用いた計算により、類似性の高い文書を検索結果として表示します。この時の検索結果を左右するのが、TF-IDF法などによる文書内単語群それぞれ対する重み付けです。*3

そしてこの文書内単語群に行われる重み付けを簡便的に表記法する方法が"SMART notation"であり、以下一覧のような表記になります。*4 f:id:wtnVenga:20180208115703p:plain

ここから"単語""文書""正規化"の順に記号を3つ重ねるだけで、どのような重み付けがされるのかが一目瞭然になります。

gensim3.3.0でサンプルコード

そしてmodels.TfidfModel()に"smartirs"引数が追加されたことでSMART notationが利用可能になりました。以下のように重み付けを指定することができます。

from gensim.corpora import Dictionary
from gensim.models import TfidfModel

documents = ["Human machine interface for lab abc computer applications",
              "A survey of user opinion of computer system response time",
              "The EPS user interface management system",
              "System and human system engineering testing of EPS",
              "Relation of user perceived response time to error measurement",
              "The generation of random binary unordered trees",
              "The intersection graph of paths in trees",
              "Graph minors IV Widths of trees and well quasi ordering",
              "Graph minors A survey"]

# 以下の7語をストップワードとして定義
stop_words = set('for a of the and to in'.split())

# 文を単語に分割し、ストップワードを除去した配列を作成
texts = [[word for word in document.lower().split() if word not in stop_words] for document in documents]

#辞書とコーパスの作成
dictionary = Dictionary(texts)
corpus = [dictionary.doc2bow(text) for text in texts]

#"n"(tf法)と、"t"(idf法)で、"c"(コサイン正規化)した重み付け
model = TfidfModel(corpus, id2word=dictionary, smartirs="ntc")
vectorized_corpus = list(model[corpus])

最後に

gensim3.3.0は最新版のAnaconda3-5.0.0では未だ対応していないので、別途"pip install"する必要があります。早く追加されて欲しいところ。

*1:厳密化すると「単語(Term)」よりも、字句解析における「トークン(token)」のことです。形態素解析などを用いても必ずしも意味ある単語にすることは難しく、あくまでも文字のカタマリとして「トークン」と呼ばれます。

*2:L2正規化するか否かの"normalise"引数は既存

*3:https://nlp.stanford.edu/IR-book/html/htmledition/queries-as-vectors-1.html#eqn:cosinescore

*4:https://nlp.stanford.edu/IR-book/html/htmledition/document-and-query-weighting-schemes-1.html

MALLETラッパーを用いた、gensimでの"Gibbs sampler"によるトピック推定を試す

LSIやLDAなどのトピック分析をPythonで実行するなら、gensimモジュールの利用が一般的です。しかしgensimでは変分ベイズ法による推定しか基本的に対応しておらず、その他のギブスサンプリングなどを用いるには少し工夫が必要です。

そこで今回はgensimにあるgensim.models.wrappers.LdaMallet()という、java製のオープンソースソフトであるMALLETのラッパー機能を用いて、Pythonかつgensimの環境でギブスサンプリングによるトピック推定をざっくりと流れだけ試します。

崩壊型ギブスサンプリングはldaモジュールで可能です。現行のscikit-learnモジュールのLDAは変分ベイズのみです。ご注意を。

なお以下の環境で準備します。詳細については適宜ググってください。

MALLETの準備

MALLETをダウンロードして、Apach antでディレクトリをビルドするだけ

  1. MALLETのダウンロード(こちらから Downloading MALLET
  2. MALLETのディレクトリにてantコマンドでビルド
  3. "BUILD SUCCESSFUL"が表示されたらビルド成功
  4. ディレクトリのパスを"Users/~.../mallet-2.0.8/bin/mallet"まで記憶

Pythonの実行

文書ごと単語リストの用意

トピック分析したい文書群(documents)リストを用意し、ストップワード除去を行う。
gensimチュートリアルを引用

documents = ["Human machine interface for lab abc computer applications",
              "A survey of user opinion of computer system response time",
              "The EPS user interface management system",
              "System and human system engineering testing of EPS",
              "Relation of user perceived response time to error measurement",
              "The generation of random binary unordered trees",
              "The intersection graph of paths in trees",
              "Graph minors IV Widths of trees and well quasi ordering",
              "Graph minors A survey"]

# 以下の7語をストップワードとして定義
stop_words = set('for a of the and to in'.split())

# 文を単語に分割し、ストップワードを除去した配列を作成
texts = [[word for word in document.lower().split() if word not in stop_words] for document in documents]

以下のような中身の単語リスト(texts)に格納されました。

    [['human', 'machine', 'interface', 'lab', 'abc', 'computer', 'applications'], ['survey', 'user', 'opinion', 'computer', 'system', 'response', 'time']]

辞書とコーパスの作成

gensimモジュールを呼び出し、文書ごと単語リストを元に辞書とコーパスを作成します。

from gensim import corpora, models
import gensim

#辞書とコーパスを作成
dictionary = corpora.Dictionary(texts)
corpus = [dictionary.doc2bow(text) for text in texts]

#コーパスをMALLET用の形式に変換します。
corpora.malletcorpus.MalletCorpus.serialize("./corpus.mallet", corpus)
mallet_corpus = corpora.malletcorpus.MalletCorpus("./corpus.mallet")

ギブスサンプリングによるトピックモデル推定

ビルドされたMALLETのパス(mallet_path)を指定し、用意した辞書とコーパスで3つのトピックを推定。

mallet_path = "Users/~.../mallet-2.0.8/bin/mallet"
lda = models.wrappers.LdaMallet(mallet_path, mallet_corpus, num_topics=3, id2word=dictionary)

推定されたトピックを確認。各トピック内容の解釈は割愛。

lda.print_topics()
 [(0,
  '0.182*"random" + 0.091*"user" + 0.091*"machine" + 0.091*"binary" + 0.091*"engineering" + 0.091*"paths" + 0.091*"human" + 0.091*"opinion" + 0.091*"minors" + 0.091*"lab"'),
 (1,
  '0.136*"perceived" + 0.136*"interface" + 0.136*"measurement" + 0.045*"survey" + 0.045*"graph" + 0.045*"eps" + 0.045*"time" + 0.045*"trees" + 0.045*"ordering" + 0.045*"testing"'),
 (2,
  '0.176*"quasi" + 0.118*"iv" + 0.118*"computer" + 0.059*"system" + 0.059*"relation" + 0.059*"generation" + 0.059*"management" + 0.059*"time" + 0.059*"response" + 0.059*"human"')]

最後に

今のところ「やってみた」系の記事では変分ベイズでの実装しか見当たらないので備忘録してみました。

gensimには他にも、Biei先生のフォーマットや、GibbsLDA++のフォーマットなどがあります(gensim: Corpora and Vector Spaces)。 そのため「gensimでデータ整形だけして他で実行するぜ!」な強者の方々は、その他の選択肢も是非ご検討ください。

※記事内容の改善点にお気付きの方は、コメントにてご一報いただけますと幸甚です。

2015年度版NLP100本ノック第2章をPython3で解く

前回(2015年度版NLP100本ノック第1章をPython3で解く - 微忘録)の続き。 bashとPython3で。各コードの実行結果は、こちらのリポジトリにて公開しています。

第2章: UNIXコマンドの基礎

hightemp.txtは,日本の最高気温の記録を「都道府県」「地点」「℃」「日」のタブ区切り形式で格納したファイルである.以下の処理を行うプログラムを作成し,hightemp.txtを入力ファイルとして実行せよ.さらに,同様の処理をUNIXコマンドでも実行し,プログラムの実行結果を確認せよ.

10. 行数のカウント

行数をカウントせよ.確認にはwcコマンドを用いよ.

%%bash
wc -l ./datasets/hightemp.txt
with open("./datasets/hightemp.txt","r") as hightemp:
    print(len(hightemp.readlines()))

11. タブをスペースに置換

タブ1文字につきスペース1文字に置換せよ.確認にはsedコマンド,trコマンド,もしくはexpandコマンドを用いよ.

%%bash
sed -e "s/\t/| /g" ./datasets/hightemp.txt
import re
with open("./datasets/hightemp.txt","r") as hightemp:
    lines = hightemp.readlines()
    print(lines,'\n')
    
    print([re.sub(r'\t'," ",hightemp)for hightemp in lines])

12. 1列目をcol1.txtに,2列目をcol2.txtに保存

各行の1列目だけを抜き出したものをcol1.txtに,2列目だけを抜き出したものをcol2.txtとしてファイルに保存せよ.確認にはcutコマンドを用いよ.

%%bash
cut -f 1 ./datasets/hightemp.txt > ./datasets/col1.txt
cut -f 2 ./datasets/hightemp.txt > ./datasets/col2.txt
with open("./datasets/hightemp.txt") as hightemp:
    lines = hightemp.readlines()
    
    #col1.txt
    col1 = []
    for line in lines:
        col1.append(line.split()[0] + "\n") #"\n"で単語ごとに改行
        
    with open("./datasets/col1py.txt" , "w") as split:
            split.writelines(col1)
    
    #col2.txt
    col2 = []
    for line in lines:
        col2.append(line.split()[1] + "\n")
        
    with open("./datasets/col2py.txt" , "w") as split:
            split.writelines(col2)
            
    hightemp.close()

13. col1.txtとcol2.txtをマージ

12で作ったcol1.txtとcol2.txtを結合し,元のファイルの1列目と2列目をタブ区切りで並べたテキストファイルを作成せよ.確認にはpasteコマンドを用いよ

%%bash
paste -d"\t" ./datasets/col1.txt ./datasets/col2.txt
with open("./datasets/col1.txt") as col1_file, open("./datasets/col2.txt") as col2_file:
    col1, col2 = col1_file.readlines(), col2_file.readlines()

with open("./datasets/merged.txt", "w") as writer:
    for a, b in zip(col1, col2):
        writer.write("\t".join([a.rstrip(),b]))
        
for i in open("./datasets/merged.txt"):
    print(i)

14. 先頭からN行を出力

自然数Nをコマンドライン引数などの手段で受け取り,入力のうち先頭のN行だけを表示せよ.確認にはheadコマンドを用いよ.

%%bash
head -n 5 ./datasets/hightemp.txt
def head(n,file):
    with open(file,'r') as hightemp:
        lines = hightemp.readlines()
        for lineNumber in range(n):
            print(lines[lineNumber])
            
head(5, "./datasets/hightemp.txt")

15. 末尾のN行を出力

自然数Nをコマンドライン引数などの手段で受け取り,入力のうち末尾のN行だけを表示せよ.確認にはtailコマンドを用いよ

%%bash
tail -n 5 ./datasets/hightemp.txt
def tail(n,file):
    with open(file,'r') as hightemp:
        lines = hightemp.readlines()
        stopline = len(lines) - n
        for lineNumber in range(stopline,len(lines),1):
            print(lines[lineNumber])
            
tail(5, "./datasets/hightemp.txt")

16. ファイルをN分割する

自然数Nをコマンドライン引数などの手段で受け取り,入力のファイルを行単位でN分割せよ.同様の処理をsplitコマンドで実現せよ.

%%bash
split -l 5 ./datasets/hightemp.txt ./datasets/n-split_
def splitfunc(file,n):
    with open(file) as filed:
        lines = filed.readlines()
    
    for i in range(n):
        with open("./datasets/splitblock%s.txt" % str(i), "w") as split:
            split.writelines(lines[n*i : n*(i +1)])
            
splitfunc("./datasets/hightemp.txt",3)

with open("./datasets/splitblock2.txt","r") as check:
    print(check.readlines())

17. 1列目の文字列の異なり

1列目の文字列の種類(異なる文字列の集合)を求めよ.確認にはsort, uniqコマンドを用いよ.

%%bash
cut -f 1 ./datasets/hightemp.txt |sort | uniq
with open("./datasets/hightemp.txt","r") as hightemp:
    lines = hightemp.readlines()
    
    col1 = []
    for line in lines:
        col1.append(line.split()[0] + "\n") #"\n"で単語ごとに改行

    li = set(col1)
    print(sorted(li))

18. 各行を3コラム目の数値の降順にソート

各行を3コラム目の数値の逆順で整列せよ(注意: 各行の内容は変更せずに並び替えよ).確認にはsortコマンドを用いよ(この問題はコマンドで実行した時の結果と合わなくてもよい).

%%bash
sort -nk3r ./datasets/hightemp.txt
with open("./datasets/hightemp.txt", 'r') as hightemp:
    lines = hightemp.readlines()
    for line in sorted(lines,key=lambda x: x.split()[2], reverse=False):
        print(line)

19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる

各行の1列目の文字列の出現頻度を求め,その高い順に並べて表示せよ.確認にはcut, uniq, sortコマンドを用いよ.

%%bash
cut -f 1 ./datasets/hightemp.txt | sort |uniq -c | sort -r
from collections import Counter

with open("./datasets/hightemp.txt","r") as hightemp:
    lines = hightemp.readlines()
    
    li = []
    for line in lines:
        li.append(line.split()[0])
    
    counted = Counter(li)
    print(counted)

2015年度版NLP100本ノック第1章をPython3で解く

Djangoアプリへの導入などを考えて、Pythonでやり直したいと思い挑戦したので備忘録。
各コードの実行結果はこちらのリポジトリにて公開しています。

第1章: 準備運動

00. 文字列の逆順

文字列"stressed"の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ

word = "stressed"
print(word[::-1])

01. 「パタトクカシーー」

「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ.

#単純に2飛ばしで
word = "パタトクカシーー"
print(word[::2])

02. 「パトカー」+「タクシー」=「パタトクカシーー」

「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ.

#分割する文字列と、格納するリストを作成
word = "パタトクカシーー"
word_li =[]

#特定の順番の文字を分割し、リストに要素として格納する
for i in range(1,len(word),2) :
    word_li.append(word[i])

#リスト内文字をスペース無しで連結する
word_li = "".join(word_li)    
print(word_li)

03. 円周率

"Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."という文を単語に分解し,各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ.

#すでに分かち書きされているので、半角スペースごとに分解してリストに格納。
sentence = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
sentence_li =[]
sentence_li = sentence.split(" ")
#リストを各単語の(アルファベットの)文字数を先頭から出現順に並べる
sorted(sentence_li,key=len)

04. 元素記号

"Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."という文を単語に分解し,1, 5, 6, 7, 8, 9, 15, 16, 19番目の単語は先頭の1文字,それ以外の単語は先頭に2文字を取り出し,取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ.

#単語に分解
sentence = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
sentence_li = sentence.split(" ")

sentence_dict = {}
topword = [1, 5, 6, 7, 8, 9, 15, 16, 19]

for i in sentence_li:
    if sentence_li.index(i) + 1 in topword:
        sentence_dict[i[:1]] = sentence_li.index(i) + 1
    else:
        sentence_dict[i[:2]] = sentence_li.index(i) + 1
        
print(sentence_dict)

05. n-gram

与えられたシーケンス(文字列やリストなど)からn-gramを作る関数を作成せよ.この関数を用い,"I am an NLPer"という文から単語bi-gram,文字bi-gramを得よ.

#n-gram関数作り
def ngram(input, n):
    result = []
    for i in range(0, len(input) - n + 1):
        result.append(input[i:i + n])

    return result

#文章用意
sentence = "I am an NLPer"

#単語n-gram(分かち書き)
sentence_li = sentence.split(" ")
ngram(sentence_li, 2)

#文字n-gram(文字区切り)
ngram(sentence, 2)

06. 集合

"paraparaparadise"と"paragraph"に含まれる文字bi-gramの集合を,それぞれ, XとYとして求め,XとYの和集合,積集合,差集合を求めよ.さらに,'se'というbi-gramがXおよびYに含まれるかどうかを調べよ.

sentence1 = "paraparaparadise"
sentence2 = "paragraph"

#bi-gram結果をset型で重複無し
X = set(ngram(sentence1,2))
Y = set(ngram(sentence2,2))

#和集合
X | Y

#積集合
X & Y

#差集合
X - Y

#seを含むか確認
'se' in X
'se' in Y

07. テンプレートによる文生成

引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ.さらに,x=12, y="気温", z=22.4として,実行結果を確認せよ.

def xyz_def(x,y,z) :
    print(str(x) + "時の" + str(y) + "は" + str(z))
    
xyz_def(x=12, y="気温", z=22.4)
def xyz_def(x, y, z):
    print( '{hour}時の{subject}は{value}'.format(hour=x, subject=y, value=z))

xyz_def(x=12, y="気温", z=22.4)

08. 暗号文

与えられた文字列の各文字を,以下の仕様で変換する関数cipherを実装せよ.

英小文字ならば(219 - 文字コード)の文字に置換 その他の文字はそのまま出力 この関数を用い,英語のメッセージを暗号化・復号化せよ.

def cipher(input) :
    result = ''
    for i in input :
        if i.islower() == True:
            result += chr(219 - ord(i))
        else:
            result += i  
    return result

cipher("oh,myパスタ")

09. Typoglycemia

スペースで区切られた単語列に対して,各単語の先頭と末尾の文字は残し,それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ.ただし,長さが4以下の単語は並び替えないこととする.適当な英語の文(例えば"I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind .")を与え,その実行結果を確認せよ.

import random

def sliceRandom(input) :
    text = input.split(" ")
    result = []
    for i in text:
        if len(i) <= 4:
            result.append(i)
        else:
            ramdomed = list(i[1:-1])
            random.shuffle(ramdomed)
            result.append(i[0] + "".join(ramdomed) + i[-1])
    return "".join(result)

sliceRandom("I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind .")

分類問題の評価指標をおさらいする

流行に敏感なのでインフルエンザB型に罹患しました。鼻奥の痛いあの検査の結果を待つ間に、「これ分類問題のやつだ!」と思い出し、評価指標をおさらいしたので備忘録。

分類問題の評価

分類問題を評価するとき、「真実の分類」と「推定の分類」の差異について考えます。評価の際には、結果をクロス集計表に落とし込んで簡便化することが一般的です。

具体例として、「インフルエンザ検査の診断性能を評価するために、検査結果の分類が真実の分類を正しく捉えているか調べる」という場合について考えます。 f:id:wtnVenga:20180125004936p:plain

全ての患者は以下の(1)~(4)の診断結果が与えられます。

  • (1)真陽性…インフルエンザ罹患者に、陽性と正しく診断
  • (2)偽陰性…インフルエンザ罹患者に、陰性と間違えて診断
  • (3)偽陽性…インフルエンザ以外の罹患者に、陽性と間違えて診断
  • (4)真陰性…インフルエンザ以外の罹患者に、陰性と正しく診断

診断結果(1)・(4)が高い検査は有用である一方で、(2)・(3)が高い検査は無用であると判断できます。

評価指標

評価指標は「正しさ」と「間違い」の2種が用意され、判断したい内容に合わせて評価指標を用いることが大事です。

「正しさ」を評価する指標

  • 適合率(Precision)…陽性と診断した中で、インフルエンザ罹患者の割合

     \dfrac{(1)}{(1)+(3)}

  • 再現率(Recall)…インフルエンザ罹患者に、陽性と診断した割合

     \dfrac{(1)}{(1)+(2)}

  • F値(F-meansure)…(再現率と適合率の調和平均)

     \dfrac{2(1)} {2(1)+(2)+(3)} = \dfrac{2}{\frac{1}{Recall} + \frac{1}{Precision}}

  • 特異度(Specificity)…インフルエンザ以外の罹患者に、陰性と診断した割合

    \dfrac{(4)}{(3)+(4)}

  • 正確性(Accuracy)…全診断結果の中で、インフルエンザ罹患者に陽性、インフルエンザ以外の罹患者に陰性と診断した割合

    \dfrac{(1)+(4)}{(1)+(2)+(3)+(4)}

「間違い」を評価する指標

  • 偽陽性率…インフルエンザ以外の羅漢者に、陽性と診断した割合

     \dfrac{(3)}{(3)+(4)}

  • 偽陰性率…インフルエンザ罹患者に対して、陰性と診断した割合

     \dfrac{(2)}{(1)+(2)}

適合率と再現率の関係

適合率と再現率のトレードオフ関係の対策として、情報検索システムの精度評価に使われるF値が、評価指標として用いられることも一般的です。

陽性診断を多く出すと分子増加により再現率が上昇しますが、分母増加により適合率の割合が低下します。 また陰性診断を多く出すと分母減少により適合率が上昇しますが、分子減少により再現率の割合が低下します。

そこで適合率と再現率の2つの指標それぞれの割合を考慮したF値が活用できます。F値は以下のような式で、2つの指標の調和平均(逆数の算術平均の逆数)を取ることで算出されます。

 \dfrac{2(1)} {2(1)+(2)+(3)} = \dfrac{1}{\frac{1}{2} (\frac{1}{Recall} + \frac{1}{Precision})} = \dfrac{2}{\frac{1}{Recall} + \frac{1}{Precision}}

別の具体例として「同一距離を平均時速5kmと10kmで2回走った時の平均時速は時速6.7kmほどである」というような以下の計算からも、よくある算術平均(=相加平均)との差と有用性についても確認できると思います。

 \dfrac{2}{\frac{1}{5}+ \frac{1}{10}} = \dfrac{1}{\frac{1}{2} (\frac{1}{5} + \frac{1}{10})} = \dfrac{20}{3} \fallingdotseq 6.666....(km/h)

最後に

さっくりとですが分類問題における評価指標について備忘録を認めました。クロス集計表といえば「カイ二乗値の独立性の検定」ですが、まだ全快でないので本記事では割愛します。

おさらいする機会をくれた点では、インフルエンザも悪くないかなって感じです。 今年のインフルエンザ、発熱は低いものの抜群の感染力なので、皆様お身体をお大事になさいませ。

参考元

検査の評価指標(再現率、適合率、特異度、正確度、F値) - 具体例で学ぶ数学