Fuwafuwa's memorandum

Fuwafuwa's memorandum

Data analysis, development, reading, daily feeling.
MENU

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

SQL server: ナイーブベイズ分類器らしきものを実装する

テキストデータから分類できると様々な面で便利であったため。
ベイズを用いるのは実装が簡単そうだから。
テキストマイニングの御多分に漏れず大変計算量が多いため、可能な限り実装を簡素にする。

ちなみにテーブルをランダムサンプリングする方法として下記のようなものがある。
SQL server: 比率を指定してランダムサンプリング

通常であればテキストを形態素解析するところだが、面倒なので辞書データから名詞を検索し単語の生起確率を求める。
辞書データはipadicからNoun.csvを取り出してSQL serverにそのまま格納した。
上記ファイルには下記のカラムが格納されている。

表層形,左文脈ID,右文脈ID,コスト,品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音

このうち「表層形・コスト・品詞・品詞細分類1・原型・読み・発音」のみ格納。
コストは単語が出現する際のコストであり数字が大きいほど出現頻度が少ない見込みとなる。

格納したいデータベースを右クリック→タスク→データのインポート
ウィザードでフラットソースからインポート。

なお、一般名詞辞書はデフォルトでは6万字あり、重そうなのでコストが小さいもののみを活用したい。
ここではコストが平均-標準偏差を下回る単語のみを格納。

DECLARE @avg_noun INT = (
	SELECT AVG(CONVERT(INT,[コスト]))
	FROM [辞書テーブル名] )
DECLARE @stdev_noun INT = (
	SELECT STDEV(CONVERT(INT,[コスト]))
	FROM [辞書テーブル名] )

IF OBJECT_ID('tempdb..#noun') IS NOT NULL
	DROP TABLE #noun

SELECT *
INTO #noun
FROM [dic_noun]
WHERE [コスト] < @avg_noun - @stdev_noun
textからlabelを付与するために下記で分類器らしきものを実装する。 \begin{equation}
P(label, text)
= P(label|text)P(text)
= P(text|label)P(label) \end{equation}

上記から下記が導ける。
\begin{equation}
P(label|text) = P(text|label)P(label) / P(text) \end{equation}

P(text)はどのlabelでも同じなので下記の分子のみを求める。
\begin{equation}
P(text|label)P(label) \end{equation}

下記でP(label)データを格納する。
DECLARE @text_n INT = (
	SELECT COUNT(text)
	FROM #sample )

IF OBJECT_ID('tempdb..#label_p') IS NOT NULL
	DROP TABLE #label_p

SELECT label,1.00*COUNT(text)/@text_n p
INTO #label_p
FROM #sample
GROUP BY label

次にP(text|label)を求めたい。
正確にはわからないため、下記でこれを代用する。 \begin{equation} P(text|label)=P(word|label)*P(word|label)*P(word|label)... \end{equation} また、P(word|label)は下記のように表せる。 \begin{equation} P(word|label) = P(word∩label)/P(label) \end{equation}
下記でP(word∩label)を求める。
--labelにおける単語の出現回数をテーブルに格納する
IF OBJECT_ID('tempdb..#label_noun') IS NOT NULL
	DROP TABLE #label_noun

SELECT label,noun,COUNT(noun) co
INTO #label_noun
FROM (
	SELECT noun.[表層形] noun,label
	FROM #sample data
	INNER JOIN #noun noun
		ON data.text LIKE '%'+[表層形]+'%'
	) tab
GROUP BY label,noun

--単語があるlabelのtextに含まれる確率
IF OBJECT_ID('tempdb..#word_label_p') IS NOT NULL
	DROP TABLE #word_label_p

CREATE TABLE #word_label_p
	( noun NVARCHAR(256)
	, label NVARCHAR(256)
	, p FLOAT )

DECLARE @label NVARCHAR(256)
DECLARE @co INT

DECLARE P_CURSOR CURSOR FOR
	SELECT label,SUM(co) co
	FROM #label_noun
	GROUP BY label
OPEN P_CURSOR
FETCH NEXT FROM P_CURSOR INTO @label,@co
WHILE @@FETCH_STATUS = 0
	BEGIN
		INSERT INTO #word_label_p
		SELECT noun,label,1.00*co/@co p
		FROM #label_noun
		WHERE label = @label
	
		FETCH NEXT FROM P_CURSOR INTO @label,@co
	END
CLOSE P_CURSOR
DEALLOCATE P_CURSOR
RETURN 
煩雑になるのでP(label)を省略する。

P(label)とP(word|label)を前述の公式に当てはめるだけだが、ここでは単語文書行列を作成していないため、乗算するとデータ数の多いlabelの生起確率が過剰に小さく見積もられることになる。かといって行列を作成するのもなんだか面倒なのでここでは単純に和とする。

下記でテストデータを入力する。
IF OBJECT_ID('tempdb..#test_result') IS NOT NULL
	DROP TABLE #test_result

CREATE TABLE #test_result 
	( id INT
	, text NVARCHAR(2048)
	, label NVARCHAR(256)
	, test_label NVARCHAR(256) 
	, prop FLOAT )

DECLARE @id INT
DECLARE @text NVARCHAR(2048)
DECLARE @label NVARCHAR(256)

DECLARE TEST_CURSOR CURSOR FOR
	SELECT id,label
	FROM #test
OPEN TEST_CURSOR
FETCH NEXT FROM TEST_CURSOR INTO @id,@label
WHILE @@FETCH_STATUS = 0
	BEGIN 
		INSERT INTO #test_result
		SELECT TOP 1 id,text,@label,data4.label,text_p+p prop
		FROM (
			SELECT id,text,label,SUM(word_p) text_p
			FROM (
				SELECT id,text,data1.noun,COUNT(data1.noun) wordcount,word.p word_p,word.label
				FROM (
					SELECT id,text,[表層形] noun
					FROM (
						SELECT *
						FROM #test 
						WHERE id = @id
						) data
					INNER JOIN #noun noun
						ON data.text LIKE '%'+noun.[表層形]+'%'
					) data1
				INNER JOIN #word_label_p word
					ON data1.noun = word.noun
				GROUP BY id,text,data1.noun,label,word.p
				) data2
			GROUP BY id,text,label
			) data3
		INNER JOIN #label_p data4
			ON data3.label = data4.label
		ORDER BY prop DESC
		
        FETCH NEXT FROM TEST_CURSOR INTO @id,@label
    END
CLOSE TEST_CURSOR
DEALLOCATE TEST_CURSOR
RETURN
以上。

スポンサーサイト

Python: k-meansのクラスタ数を決定するためにSSE値を図示

ネットをふわふわしていたら懐かしい図を見つけたのでコピペ。
k-meansの最適なクラスター数を調べる方法

import pandas as pd

data = pd.read_csv('file_name.csv')

##クラスタリングに必要なカラムのみarrayに
d_history = data.iloc[:,7:20]
d_history_array = d_history.as_matrix()

from sklearn.cluster import KMeans
import matplotlib.pyplot as plt

distortions = []
for i  in range(1,11):                # 1~10クラスタまで一気に計算 
    km = KMeans(n_clusters=i,
                init='k-means++',     # k-means++法によりクラスタ中心を選択
                n_init=10,
                max_iter=300,
                random_state=0)
    km.fit(d_history_array)                         # クラスタリングの計算を実行
    distortions.append(km.inertia_)   # km.fitするとkm.inertia_が得られる

plt.plot(range(1,11),distortions,marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Distortion')
plt.show()
Pythonでエルボー図書いてる人はじめて見た。

Python: デンドログラムでクラスタ数を同定しk-meansで分類する

サイズが大きくクラスタ数が不明なデータをクラスタリングする時。

import pandas as pd

###データの読み込み
data = pd.read_csv('data.csv')

###ランダムサンプリングで標本1000格納
sample_data = data.sample(1000)

###非類似度の指標とクラスタ連結法を指定し、階層的クラスタリングでデンドログラムを描画
from matplotlib.byplot import show
from scipy.cluster.hierarchy import linkage, dendrogram

result = linkage(sample_data.loc[:,['val0','val1','val2']], metric = 'chebyshev', method = 'average')
dendrogram(result)
show()

###k-meansでデータにクラスタをレイベリングする
###クラスタリングに使用する変数のみ格納しarray型に変換する
data_array = data.loc[:,['val0','val1','val2']].as_matrix()

###クラスタ数と初期値を設定してk-meansを実行する
from sklearn.cluster import KMeans

model = KMeans(n_clusters=5, random_state=20).fit(data_array)
labels = model.labels_

###labelをpandas型に変換する
labels_pandas = pd.DataFrame(labels)

###元データにラベルを結合する
data_labels = pd.concat([data,labels_pandas],axis=1)
data_labels = data_labels.rename(columns={0:'label'})

Python: doc2vecによる類似度の行列を次元縮約しk-meansでクラスタ分析

Doc2Vecとk-meansで教師なしテキスト分類↓
http://qiita.com/shima_x/items/196e8d823412e45680e9
このままではうまくいかなかったので書き換え。
mecabで分かち書きした文書を読み込みます。

from gensim import corpora, models
import numpy as np
from numpy import random
random.seed(555)
from scipy.cluster.vq import vq, kmeans, whiten
from sklearn.decomposition import TruncatedSVD
from collections import defaultdict

article = open('data.txt')
model_basename = './D2V/doc2vec_result/model'
topic_result_basename = './D2V/doc2vec_result/topic'


articles = []
for x in article:
    articles.append(x)

article_list = []
for item in articles:
    itemlist = item.split(' ')
    article_list.append(itemlist)

class LabeledLineSentence(object):
    def __init__(self, filename):
        self.filename = filename
    def __iter(self):
        for uid, line in enumerate(open(filename)):
            yield LabeledSentence(words = line.split(), labels = ['sent_%s' % uid])

article_tag = []
count = 0
for item in article_list:
    model = models.doc2vec.LabeledSentence(words = item, tags = ['sent_%s' % count])
    article_tag.append(model)
    count += 1

model = models.Doc2Vec(alpha = .025, min_alpha =.025, min_count = 1)
model.build_vocab(article_tag)

model.save("my_model.doc2vec")
model_loaded = models.Doc2Vec.load("my_model.doc2vec")

for epoch in range(10):
    model.train(article_tag)
    model.alpha -= 0.002
    model.min_alpha = model.alpha

def create_sim_vec(model,n_sent):
    base_name = 'sent_'
    sim_matrix = []
    sim_matrix_apd = sim_matrix.append
    word_matrix = []
    word_matrix_apd = word_matrix.append
    sim_vec = np.zeros(n_sent)
    for i_sent in range(n_sent):
        word_list = []
        word_list_apd = word_list.append
        # sentが存在しない場合があるので、例外処理を入れておく                               
        try:
            for word, sim_val in model.docvecs.most_similar(base_name+str(i_sent)):
                if 'sent_' in word:
                    _, s_idx = word.split('_')
                    sim_vec[int(s_idx)] = sim_val
                else:
                    word_list_apd(word)
        except:
            pass
        sim_matrix_apd(sim_vec)
        word_matrix_apd(word_list)
    return sim_matrix, word_matrix

n_sent = len(articles)
sent_matrix, word_matrix = create_sim_vec(model, n_sent)

def sent_integrate(sim_matrix, n_class):
    whiten(sim_matrix)
    centroid, destortion = kmeans(sim_matrix, n_class, iter = 100, thresh = 1e-05)
    labels, dist = vq(sim_matrix,centroid)
    return labels

def count_class(labels):
    res_dic = defaultdict(int)
    for label in labels:
        res_dic[label] += 1
    return res_dic

def count_labeled_data(label_data, labels):
    result_dict = {}
    for orig_labels, label in zip(label_data, labels):
        labels = np.array(orig_labels.split(), dtype = np.int64)
        if label not in result_dict:
            result_dict[label] = labels
        else:
            result_dict[label] += labels
    return result_dict

np.savetxt('sent_matrix',np.array(sent_matrix))
dimention = 100
lsa = TruncatedSVD(dimention)
info_matrix = lsa.fit_transform(sent_matrix)
np.savetxt('info_matrix', np.array(info_matrix))

n_class = 10
labels = sent_integrate(np.array(info_matrix), n_class)
np.savetxt('sent_labels2.csv', labels, delimiter = ',', fmt = '%d')

Python: gensimでdoc2vecを実行する

gensimモジュールでdoc2vecを行います。

##https://gist.github.com/balajikvijayan/9f7ab00f9bfd0bf56b14
from gensim import models

sentence0 = models.doc2vec.LabeledSentence(
    words=[u'IT', u'企業', u'エンジニア', u'統計学', u'機械学習'], tags=["sent_0"])
sentence1 = models.doc2vec.LabeledSentence(
    words=[u'大学', u'科学', u'研究', u'解析', '統計学'], tags=["sent_1"])
sentence2 = models.doc2vec.LabeledSentence(
    words=[u'エンジニア', u'データベース', u'解析'], tags=["sent_2"])
sentence3 = models.doc2vec.LabeledSentence(
    words=[u'社会', u'科学', u'心理', u'プログラミング'], tags=["sent_3"])
sentence4 = models.doc2vec.LabeledSentence(
    words=[u'アプリ', u'開発', u'エンジニア', 'プログラミング'], tags=["sent_4"])

sentences = [sentence0, sentence1, sentence2, sentence3, sentence4]

class LabeledLineSentence(object):
    def __init__(self, filename):
        self.filename = filename
    def __iter__(self):
        for uid, line in enumerate(open(filename)):
            yield LabeledSentence(words=line.split(), labels=['sent_%s' % uid])
            
model = models.Doc2Vec(alpha=.025, min_alpha=.025, min_count=1)
model.build_vocab(sentences)

for epoch in range(10):
    model.train(sentences)
    model.alpha -= 0.002 
    model.min_alpha = model.alpha  

model.save("my_model.doc2vec")
model_loaded = models.Doc2Vec.load('my_model.doc2vec')
下記で実行。
print(model.docvecs.most_similar(["SENT_0"]))
結果は下記。
In [17]: print(model_loaded.docvecs.most_similar(["sent_1"]))
[('sent_3', 0.6345847249031067), ('sent_4', -0.2628158926963806), ('sent_0', -0.42180296778678894), ('sent_2', -0.42976364493370056)]
とっても便利!

朝日新聞の投稿欄から100記事を選んで類似した記事を探しました↓
1行ごとに1投稿が格納されたtxtファイルをmecabで分かち書きしたものです。
article = open("asa2007.txt")

articles = []
for x in article:
	articles.append(x)
	
article_list = []
for item in articles:
	itemlist = item.split(' ')
	article_list.append(itemlist)

class LabeledLineSentence(object):
    def __init__(self, filename):
        self.filename = filename
    def __iter__(self):
        for uid, line in enumerate(open(filename)):
            yield LabeledSentence(words=line.split(), labels=['sent_%s' % uid])

article_tag = []
count = 0
for item in article_list:
	model = models.doc2vec.LabeledSentence(words=item, tags=['sent_%s' % count])
	article_tag.append(model)
	count += 1

model = models.Doc2Vec(alpha=.025, min_alpha=.025, min_count=1)
model.build_vocab(article_tag[0:999])
 
for epoch in range(10):
    model.train(article_tag[0:999])
    model.alpha -= 0.002
    model.min_alpha = model.alpha  
 
model.save("my_model.doc2vec")
model_loaded = models.Doc2Vec.load('my_model.doc2vec')

print(model.docvecs.most_similar(['sent_0']))
下記の記事と類似した記事を探します。
外国から帰ってきてほっとするのは、蛇口からほとばしる水を手に受ける瞬間であり、
レストランで出されるコップの水を躊躇(ちゅうちょ)なく口に含む時だ。
安心して飲める水の恩恵をいつ、どこでも受けられる幸せこそ、日本の国のありがたさだ。
清潔な水が十分に供給されるということは、とりもなおさず豊かな水源があるから。
源をたどると、それは大河の流れでもダムが造られているからでもない。
川へ水を注いでいる山、それも落葉樹の茂った緑の山々のお陰だ。
天の恵みの雨を落ち葉まじりの土が受け止めてくれ、静かに木の根に浸透していく。
そして、途方もない歳月を積み重ねて蓄え、土深く守る。
やがて山すそから、細い流れのわき水となり、さらに大きな流れに成長する。
まさに太古からの自然の営みによって私たちは安心して水が使えるのだ。
この仕組みを人間が崩すと、自然からしっぺ返しがくる。
目の前の便利さや都合のために、山の木をむやみに切ってはいけない。
安心して水の飲める国こそ美しい国である。
上記と最も類似度(0.2826824188232422)が高かったのが↓
美しい音楽、絵、花、詩などには感動と癒やされるものがある。それは心のごちそうだ。
2年前に調布市に転入してから、近所にある武者小路実篤公園に時折、夫婦で散歩に行く。
夕方など、武蔵野の面影をわずかに残す雑木林が、夕日に赤く照らされる。
枯れ葉を踏みしめる音、ちょろちょろ流れるわき水の音、遠くでカラスの鳴く声、何かホッとする静寂のひととき。
都会の一角に残された小さな自然、これも心のごちそう、心の栄養と思う。
旧友からお連れ合いの悲報が届く年頃になった。だから余計に夫婦の何げない散歩が大切に思えてくる。
仕事優先の生活を送っている私だが、最近、学習意欲のある視覚障害を持つ友人が何人も出来て良い刺激を受けている。
この友人たちと自然が織りなす芸術を楽しむなど、心のごちそうの共有を願っている。
もちろんおいしい食事は好きだが、いつも腹八分目を心がけている。
どちらも水や山などの自然の美しさに関するものであり
人間が読んでもかなり類似度が高い結果となっております。

該当の記事は見つかりませんでした。
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。