「text2vec」パッケージを使ってSCDVのようなことをやってみる

  • word2vecの代わりに「text2vec」パッケージのGloveを使用
  • GMMは「ClusterR」パッケージを使用

GloVeでワードベクトル作成まで

library(text2vec)

Absts <- readLines("Absts_for_text2vec.txt")

number_of_docs <- length(Absts)

it <- itoken(Absts, tolower, word_tokenizer)

#text2vec_vocabularyの作成
voc <- create_vocabulary(it)
voc <- prune_vocabulary(voc, doc_count_min = 3L)

library(tm)
new_stopwords <- c(stopwords("en"), "can", "could", "may", "might", "also", "however")

voc <- voc[!(voc$term %in% new_stopwords),]

#TCM(term-co-occurence matrix)の作成
tcm <- create_tcm(it, vocab_vectorizer(voc), skip_grams_window = 10L)

#ワードベクトルの作成
glove <- GloVe$new(word_vectors_size = 100, vocabulary = voc, x_max = 100)
main <- glove$fit_transform(tcm, n_iter = 50)
context <- glove$components
word_vectors <- main + t(context)

混合ガウスモデルクラスタリング

まずはクラスター数をいくつにするかの検討

library(ClusterR)
Optimal_Clusters_GMM(word_vectors, 100, criterion = "BIC", "maha_dist", "random_subset", km_iter = 10, em_iter = 10, plot_data = TRUE)

f:id:touch-sp:20180223120223p:plain:w600
元文献の通り60くらいが妥当なのかな?

gmm <- GMM(word_vectors, 60, "maha_dist", "random_subset", km_iter = 10, em_iter = 10)
pr <- predict_GMM(word_vectors, gmm$centroids, gmm$covariance_matrices, gmm$weights)
proba <- pr$cluster_proba

idfの計算

idf <- log((Number_of_docs + 1) / (voc$doc_count + 1)) + 1

この記事は書きかけです。

RとfastTextを使ってSCDVのようなことをやってみる

Ubuntu上でfastTextを実行(word2vecの代わり)

touch-sp.hatenablog.com

その結果をRで拾い上げ、その後を実行

library(text2vec)

#fastTextデータを読み込む
word_vec <- read.table("model3.vec", header = F, sep = " ", skip = 1, row.names = 1)
word_vec <- word_vec[, -101]
word_vec <- as.matrix(word_vec)

#text2vec用に用意した訓練データを読み込む
Absts <- readLines("Absts_post_stem_for_text2vec.txt", encoding = "UTF-8")

Number_of_docs = length(Absts)

it <- itoken(Absts, tolower, word_tokenizer)

voc <- create_vocabulary(it)

#vocとワードベクトルをそろえる

both <- intersect(voc$term, rownames(word_vec))
Number_of_words <- length(both)

voc_after <- voc[(voc$term %in% both),]
word_vec_after <- word_vec[(rownames(word_vec) %in% both),]

voc_after_after <- voc_after[order(voc_after$term),]
word_vec_after_after <- word_vec_after[order(rownames(word_vec_after)),]

#idfを計算する
idf <- log((Number_of_docs + 1) / (voc_after_after$doc_count + 1)) + 1

#Gaussian Mixture Modelling
library(ClusterR)
gmm <- GMM(word_vec_after_after, 30, "maha_dist", "random_subset", 10, 10)
pr <- predict_GMM(word_vec_after_after, gmm$centroids, gmm$covariance_matrices, gmm$weights)
proba <- pr$cluster_proba

new_vec <- matrix(numeric(Number_of_words * 100 * 30), nrow = Number_of_words)
for (i in 0:29) {
    new_vec[, (i * 100 + 1):((i + 1) * 100)] <- word_vec_after_after * proba[, i + 1]
}

new_vec <- new_vec * idf

rownames(new_vec) <- rownames(word_vec_after_after)

saveRDS(new_vec, "D:/rworks/new_vec.RDS")

WindowsからSCDVを使ってみる

参考にさせて頂いたページ
qiita.com
2018年2月現在SCDVに関して日本語で書かれたページはここしか見つからなかった。
SCDVを勉強したいが元論文は英語で書かれていてもちろん読めるわけがない。
Githubスクリプトを眺めてもなにがなんだかわからない。
そういった素人の自分が実際に動かしながらSCDVを理解しようと試みた。
そのため、Pythonスクリプトはほぼそのまま使用させて頂いた。

環境

Windows10 pro 64bit
UbuntuWindowsストアからインストール)
Python 3.5.2(Ubuntuに最初からインストールされている)

下準備

  • pip3のインストール
sudo apt-get install python3-pip
  • pip3のアップデート
sudo pip3 install --upgrade pip
  • Jupyter Notebookのインストール(必須ではない)
sudo pip3 install jupyter

普通にJupyter Notebookが使用できることに驚いた。

パッケージのインストール

「pandas」を入れると「numpy」も同時に入る

sudo pip3 install pandas
sudo pip3 install scipy
sudo pip3 install gensim
sudo pip3 install scikit-learn

MeCabの導入

sudo apt-get install mecab
sudo apt-get install libmecab-dev
sudo apt-get install mecab-ipadic
sudo apt-get install mecab-ipadic-utf8
sudo pip3 install mecab-python3

テキストファイルの準備

(注意)エンコードは「UTF-8

wget http://www.rondhuit.com/download/ldcc-20140209.tar.gz
tar axvf ldcc-20140209.tar.gz

「text」フォルダが作成され、その中には9個のフォルダが存在する。
フォルダを一つずつあけて、「LICENSE.txt」を削除していく。
(もちろん削除する前に中身はきちんと読みましょう)

テキストファイルからデータフレームを作成

import pandas as pd
import glob
import pickle

dirlist = ["dokujo-tsushin","it-life-hack","kaden-channel","livedoor-homme",
           "movie-enter","peachy","smax","sports-watch","topic-news"]

df = pd.DataFrame(columns=["class","news"])

for i in dirlist:
    path = "./text/"+i+"/*.txt"
    files = glob.glob(path)
    for j in files:
        f = open(j)
        data = f.read() 
        f.close()
        t = pd.Series([i,"".join(data.split("\n")[3:])],index = df.columns)
        df  = df.append(t,ignore_index=True)
        
pickle.dump(df,open("textdata","wb"))
>>> type(df)
<class 'pandas.core.frame.DataFrame'>
>>> df.shape
(7367, 2)

7367行のデータフレームを「textdata」という名前で保存した。

単語ベクトルの作成(word2vec)

あらかじめ「model」というフォルダを作成しておく。

import logging
from gensim.models import Word2Vec
import MeCab
import re
import pickle

df = pickle.load(open("textdata","rb"))

tokenizer =  MeCab.Tagger("-Owakati")  
sentences = []

print ("Parsing sentences from training set...")
# Loop over each news article.
for review in df["news"]:
    try:
        # Split a review into parsed sentences.
        result = tokenizer.parse(review).replace("\u3000","").replace("\n","")
        result = re.sub(r'[01234567890123456789!@#$%^&\-|\\*\“()_■×※⇒—●(:〜+=)/*&^%$#@!~`){}…\[\]\"\'\”:;<>?<>?、。・,./『』【】「」→←○]+', "", result)
        h = result.split(" ")
        h = list(filter(("").__ne__, h))
        sentences.append(h)
    except:
        continue

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s',\
    level=logging.INFO)

print ("Training Word2Vec model...")
# Train Word2Vec model.
model = Word2Vec(sentences, workers=40, hs = 0, sg = 1, negative = 10, iter = 25,\
            size=200, min_count = 20, \
            window = 10, sample = 1e-3, seed=1)

model_name = "word2vec"
model.init_sims(replace=True)

print ("Saving Word2Vec model...")
# Save Word2Vec model.
model.save("./model/"+model_name)
>>> type(model)
<class 'gensim.models.word2vec.Word2Vec'>
>>> type(model.wv.index2word)
<class 'list'>
>>> model.wv.index2word[0:10]
['の', 'に', 'を', 'が', 'は', 'て', 'で', 'た', 'と', 'し']
>>> len(model.wv.index2word)
11856
>>> type(model.wv.vectors)
<class 'numpy.ndarray'>
>>> model.wv.vectors.shape
(11856, 200)

「model」フォルダに「word2vec」という名前で保存した。

単語ベクトルのクラスタリング

import pickle
from gensim.models import Word2Vec
from sklearn.mixture import GaussianMixture

model = Word2Vec.load("./model/word2vec")
word_vectors = model.wv.vectors

clf =  GaussianMixture(n_components=60,
                       covariance_type="tied", init_params='kmeans', max_iter=50)
clf.fit(word_vectors)
idx_proba = clf.predict_proba(word_vectors)

idx_proba_dict = dict(zip( model.wv.index2word, idx_proba ))

pickle.dump(idx_proba_dict, open("./model/idx_proba_dict.pkl", "wb"))

print ("Clustering Done...")

#idx = clf.predict(word_vectors)
#idx_dict = dict(zip( model.wv.index2word, idx ))
#pickle.dump(idx_dict, open("./model/idx_dict.pkl", "wb"))

「idx」はそれぞれの単語がどのクラスタに属するかを表した配列
「idx_proba」はそれぞれの単語が各クラスタに属する確率

>>> type(idx)
<class 'numpy.ndarray'>
>>> idx.shape
(11856,)
>>> type(idx_proba)
<class 'numpy.ndarray'>
>>> idx_proba.shape
(11856, 60)
>>> sum(idx_proba[0,])
1.0
>>> sum(idx_proba[100,])
1.0000000000000042
>>> sum(idx_proba[200,])
0.99999999999999056
>>> sum(idx_proba[300,])
1.0000000000000282
>>> idx[100:110,]
array([56, 11,  9, 11, 48, 56, 57, 56, 56, 56])
>>> idx_proba[100:110,].argmax(axis=1)
array([56, 11,  9, 11, 48, 56, 57, 56, 56, 56])

「idx_proba」を単語に紐づけした辞書型「idx_proba_dict」 として保存した。
「idx」はこの先使用しない。

TF-IDFを計算(必要なのはIDFのみ)

import pickle
import numpy as np
import MeCab
import re
from sklearn.feature_extraction.text import TfidfVectorizer,HashingVectorizer

df = pickle.load(open("textdata","rb"))

tokenizer =  MeCab.Tagger("-Owakati")  
traindata = []

for review in df["news"]:
    result = tokenizer.parse(review).replace("\u3000","").replace("\n","")
    result = re.sub(r'[01234567890123456789!@#$%^&\-|\\*\“()_■×※⇒—●(:〜+=)/*&^%$#@!~`){}…\[\]\"\'\”:;<>?<>?、。・,./『』【】「」→←○]+', "", result)
    h = result.split(" ")
    h = filter(("").__ne__, h)
    traindata.append(" ".join(h))

tfv = TfidfVectorizer(dtype=np.float32)
tfidfmatrix_traindata = tfv.fit_transform(traindata)
featurenames = tfv.get_feature_names()
idf = tfv._tfidf.idf_

word_idf_dict = dict(zip(featurenames, idf))

pickle.dump(word_idf_dict, open("./model/word_idf_dict.pkl", "wb"))

単語に紐づけした辞書型「word_idf_dict」として保存した。
ここでの計算式は「log( (文章の総数+1) / (単語を含む文書数+1) ) + 1」で計算されている(自然対数)。

新しい単語ベクトルを求める

import numpy as np
import pickle
from gensim.models import Word2Vec

model = Word2Vec.load("./model/word2vec")
idx_proba_dict = pickle.load(open("./model/idx_proba_dict.pkl","rb"))
word_idf_dict = pickle.load(open("./model/word_idf_dict.pkl","rb"))

prob_wordvecs = {}
for word in idx_proba_dict:
    prob_wordvecs[word] = np.zeros( 60 * 200, dtype="float32" )
    for index in range(0, 60):
        try:
            prob_wordvecs[word][index*200:(index+1)*200] = model[word] * idx_proba_dict[word][index] * word_idf_dict[word]
        except:
            continue
            
pickle.dump(prob_wordvecs,open("./model/prob_wordvecs.pkl","wb"))
>>> type(prob_wordvecs)
<class 'dict'>
>>> len(prob_wordvecs)
11856
>>> prob_wordvecs["明日"].shape
(12000,)

単語ベクトルが12000次元になっていることがわかる。

WindowsからfastTextを使ってみる

参考にさせて頂いたページ
tadaoyamaoka.hatenablog.com

環境

Windows10 pro 64bit
UbuntuWindowsストアからインストール)
Python 3.5.2(Ubuntuに最初からインストールされている)
Perl 5.22.1(Ubuntuに最初からインストールされている)

下準備

  • unzipのインストール
sudo apt-get install unzip
  • pip3のインストール
sudo apt-get install python3-pip
  • 「numpy」「scipy」「gensim」のインストール
sudo pip3 install numpy
sudo pip3 install scipy
sudo pip3 install gensim

fastTextの導入

wget https://github.com/facebookresearch/fastText/archive/v0.1.0.zip
unzip v0.1.0.zip
cd fastText-0.1.0
make

テキストファイルの準備

(注)用意するテキストファイルはUTF-8で。必要に応じて下記を実行。

sudo apt-get install nkf
nkf -w Absts.txt > Absts_utf8.txt

fastTextの実行

./fasttext skipgram -input Absts_utf8.txt -output model

「model.bin」「model.vec」が作成される。

gensimを使って評価

ここからはPythonを使用。
(注)UbuntuPythonを起動する時は「python3」と入力。

from gensim.models.wrappers.fasttext import FastText
model = FastText.load_fasttext_format("model")
model.most_similar("kidney",topn=15)

結果

[('kidney)', 0.9012922048568726), ('renal', 0.8575583696365356), ('kidneys', 0.831729531288147), ('liver', 0.8133635520935059), ('kidneys,', 0.7893050909042358), ('whole-kidney', 0.7847826480865479), ('Kidney', 0.76973557472229), ('hepatorenal', 0.7679615616798401), ('pretransplant', 0.7505965232849121), ('kidney,', 0.7475892305374146), ('kidneys.', 0.7396260499954224), ('nephron', 0.7391433715820312), ('nephrotic', 0.7352806329727173), ('nephrogenic', 0.7335397005081177), ('(liver', 0.7216404676437378)]

あれれ?大文字、小文字の区別がされていない。
かつ「,」「.」「(」「)」などが消去されていない。
テキストファイルを準備する段階でそれらの下処理が必要らしい。

テキストファイルの下処理

Perlを使って行う。stem.plを作成。

while(<>){
    #大文字→小文字
    tr/A-Z/a-z/;
    #記号をスペースに置換
    s/[^\w\s]/ /ig;
    #数字を削除
    s/\b\d+\b//ig;
    print;
}
perl stem.pl Absts_utf8.txt > post_Absts.txt

「CKD(慢性腎臓病)」-「慢性」+「急性」=「AKI(急性腎障害)」

再度fastTextを実行し結果確認。

from gensim.models.wrappers.fasttext import FastText
model = FastText.load_fasttext_format("model")
model.most_similar(positive=["ckd","acute"],negative=["chronic"],topn=3)
[('aki', 0.7763621807098389), ('kidney', 0.7052872180938721), ('renal', 0.6896063089370728)]

感想

たしかに実行は「fast」である。
関係ないが、WindowsストアからインストールできるUbuntuは最高である。

Rから使用する場合(2018/2/19追記)

library(text2vec)

word_vec <- read.table("model.vec", header = F, sep = " ", skip = 1, row.names = 1)
word_vec <- word_vec[,-101]
word_vec <- as.matrix(word_vec)

ckd <- word_vec["ckd",, drop = F]
chronic <- word_vec["chronic",, drop = F]
acute <- word_vec["acute",, drop = F]

answer <- ckd - chronic + acute

cos_sim <- sim2(x = word_vec, y = answer, method = "cosine", norm = "l2")
head(sort(cos_sim[, 1], decreasing = TRUE)[-1], 3)

トピックモデルを使った文献スクリーニング (3)

touch-sp.hatenablog.com
上記をやや修正した

Perlを使って表記ゆれ対策

Rの「tm」パッケージ「stemDocument」はうまくいかないので頻出単語を調べたうえで自力で。
変換する単語数を増やした。

while(<>){

s/mortalities/mortality/ig;
s/therapies/therapy/ig;
s/injuries/injury/ig;
s/nephropathies/nephropathy/ig;
s/viruses/virus/ig;
s/kidneys/kidney/ig;
s/diseases/disease/ig;
s/antibodies/antibody/ig;
s/risks/risk/ig;
s/treatments/treatment/ig;
s/factors/factor/ig;

s/bacteria\b/bacterium/ig;
s/infections\b/infection/ig;

s/\bresults\b/result/ig;
s/\bmethods\b/method/ig;
s/\bmice\b/mouse/ig;
s/\brats\b/rat/ig;
s/\bfunctions\b/function/ig;
s/\bdysfunctions\b/dysfunction/ig;

s/studies/study/ig;
s/\b\d+study\b/study/ig;

s/patients/patient/ig;
s/\b\d+patient\b/patient/ig;

s/proteins/protein/ig;
s/\b\d+protein\b/protein/ig;

s/\women\b/woman/ig;
s/\b\d+woman\b/woman/ig;

s/groups/group/ig;
s/\b\d+group\b/group/ig;

s/\b\d*child(ren)?\b/child/ig;
s/\b\d*adult(s)?\b/adult/ig;

s/\b\d*m(a|e)n\b/man/ig;

s/\b\d*cell(s)?\b/cell/ig;

s/\b\d*case(s)?\b/case/ig;

s/\b\d*gene(s)?\b/gene/ig;

s/\b\d*cancer(s)?\b/cancer/ig;

s/\bincrease(s|d)\b/increase/ig;
s/\bdecrease(s|d)\b/decrease/ig;
s/\bameliorate(s|d)\b/ameliorate/ig;
s/\bindicate(s|d)\b/indicate/ig;

s/\bexpress(es|ed)\b/express/ig;
s/\bresearch(es|ed)\b/research/ig;

s/\bsuggest(s|ed)\b/suggest/ig;
s/\bpredict(s|ed)\b/predict/ig;

s/\bunder(goes|went)\b/undergo/ig;

s/expressions\b/expression/ig;
s/\bpredictors\b/predictor/ig;
s/\bpredictions\b/prediction/ig;
s/\bresearchers\b/researcher/ig;

s/\b\d+\b//ig;

print;
}
perl stem.pl Absts_0928.txt > post_Absts_0928.txt

下準備

ngramを設定する際にストップワードに設定した単語は使用されない。
そのためストップワードとリムーブタームを分けた。
リムーブタームはボキャブラリリストから削除する単語。
(「with」という単語は文章分類に役に立たないが、「patient wiht CKD」には意味がある)

library(tm)
new_stopwords <- stopwords("en")[c(1:29, 34:45, 109:111)]
remove_term <- c("of", "and", "or", "but", "in", "on", "with", "for", "by", "from", "to", "at")

Rのtext2vecパッケージを使う

it <- itoken(Absts, tolower, word_tokenizer)
voc <- create_vocabulary(it, stopwords = new_stopwords, ngram = c(1L, 4L))
voc <- prune_vocabulary(voc, doc_count_min = 15L)
voc <- voc[!(voc$term %in% remove_term),]

結果

f:id:touch-sp:20180212010123p:plain:w800
条件によってずいぶんと変わる印象。


今度はSCDVでも試してみようかな。

トピックモデルを使った文献スクリーニング (2)

データのダウンロード

ダウンロードはこちらから。

Linuxコマンドでmd5チェックと解凍

md5sum -c pubmed18n0928.xml.gz.md5
gunzip pubmed18n0928.xml.gz

C#を使ってアブストラクトを抽出

using System;
using System.Text;
using System.Xml.Linq;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
using System.Xml;

namespace pubmed
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("ファイル名を入力してください");
            string s;
            s = Console.ReadLine();

            Stopwatch watch = new Stopwatch();
            watch.Start();


            StreamWriter pmid = new StreamWriter("PMID.txt", false, Encoding.GetEncoding("shift_jis"));


            StreamWriter absts = new StreamWriter("Absts.txt", false, Encoding.GetEncoding("shift_jis"));


            foreach (XElement el in mystream(s))
            {
                if (el.Element("MedlineCitation").Element("Article").Element("Language").Value == "eng")
                {
                    string Absts = "";
                    foreach (XElement x2 in el.Descendants("AbstractText"))
                    {
                        Absts = Absts + x2.Value;
                    }

                    if (Absts != "")
                    {
                        //アブストラクトに改行文字がふくまれることがある
                        //改行を消すときはスペースで置換しないと単語が連なることがある
                        Absts = Absts.Replace("\r", " ").Replace("\n", " ");
                        pmid.WriteLine(el.Element("MedlineCitation").Element("PMID").Value);
                        absts.WriteLine(Absts);
                    }
                }
            }
            pmid.Close();
            absts.Close();

            watch.Stop();

            Console.WriteLine("経過時間の合計 = {0}", watch.Elapsed);
            Console.ReadLine();
        }
        static IEnumerable<XElement> mystream(string file_name)
        {
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.DtdProcessing = DtdProcessing.Parse;

            using (XmlReader reader = XmlReader.Create(file_name, settings))
            {
                while (reader.Read())
                {
                    if (reader.Name == "PubmedArticle")
                    {
                        yield return XElement.ReadFrom(reader) as XElement;
                    }
                }
            }
        }
    }
}

Perlを使って表記ゆれ対策

touch-sp.hatenablog.com

Rのtext2vecパッケージを使う

touch-sp.hatenablog.com

エンコードのことを何も考えていないがこれでエラーがでない。
すべてを「R」のみで行おうとしたが、実行速度の点で上記のような方法に行き着いた。

結果

いろいろ条件をかえてやってみた。
f:id:touch-sp:20180206170822p:plain:w800

Perlを使って表記ゆれ対策

WindowsストアからUbuntuをインストールしたらPerlが入っていた。
WindowsPerlを使いたければ直接インストールするよりも楽だと思う。


Rの「tm」パッケージ「stemDocument」はうまくいかないので自力で。

while(<>){

s/studies/study/ig;
s/mortalities/mortality/ig;
s/therapies/therapy/ig;
s/injuries/injury/ig;
s/nephropathies/nephropathy/ig;

s/kidneys/kidney/ig;
s/diseases/disease/ig;
s/patients/patient/ig;
s/risks/risk/ig;
s/treatments/treatment/ig;
s/groups/group/ig;
s/factors/factor/ig;

s/\b\d+\b//ig;

print;
}
perl stem.pl Absts_0920.txt > post_Absts_0920.txt

Ubuntuメモ

リムーバブルディスクをマウントする方法

sudo mkdir /mnt/d
sudo mount -t drvfs D: /mnt/d

アンマウントする方法

cd /
sudo umount /mnt/d