TF-IDFとTextRankアルゴリズム抽出キーワードソース分析
28846 ワード
TF-IDFとTextRankアルゴリズム抽出キーワードソース分析
jieba分詞のキーワード抽出機能は,jieba/analyseディレクトリの下で実現される.
ここで、 __ init__.pyは主にjieba分詞をカプセル化するキーワード抽出インタフェースに用いられる. tfidf.pyはTF-IDFアルゴリズムに基づいてキーワードを抽出することを実現した. textrank.pyはTextRankアルゴリズムに基づくキーワード抽出を実現した.
1.TF-IDFアルゴリズム
TF-IDFアルゴリズムに基づいてキーワードを抽出する主調関数はTFIDFである.extract_tags関数は主にjieba/analyse/tfidfである.pyで実装します.
ここで、TFIDFは、TF−IDFアルゴリズムのキーワード抽出のために定義されたクラスである.クラスは初期化時に、デフォルトで分詞関数tokenizer=jiebaをロードします.dt、品詞表記関数postokenizer=jieba.posseg.dt、ストップワードstop_words = self.STOP_WORDS.copy()、idf辞書idf_loader=IDFLoader(idf_path or DEFAULT_IDF)などを取得し、idf辞書およびidf中値(ある語がidf辞書に表示されない場合、idf中値をこの語のidf値とする)を取得する.
そして,TF−IDFアルゴリズムによるキーワード抽出を開始する.
まず,品詞制限セットが入力されたか否かに基づいて,品詞注釈インタフェースを呼び出すか分詞インタフェースを呼び出すかを決定する.例えば,品詞制限集合は,["ns","n","vn","v","nr"]であり,品詞が地名,名詞,動名詞,動詞,人名といった品詞のみからキーワードを抽出できることを示す.語性制限集合が伝達された場合、まず語性表示インタフェースを呼び出し、入力文に対して語性表示を行い、分詞と対応する語性を得る.分詞結果を順次遍歴し、その語の語性が語性制限集合にない場合はスキップする.単語の長さが2未満、または単語が無効な場合はスキップします.最後に条件を満たす語を語周波数辞書に追加し、出現回数を1とする.その後、語周波数辞書を遍歴し、idf辞書に基づいて各語のidf値を得、語周波数辞書の回数の総和で割って、各語のtf*idf値を得る.重みフラグビットが設定されている場合、tf-idf値に基づいて語周波数辞書の語を降順に並べ替え、topK個の語をキーワードとして出力する. 語性制限集合が入力されていない場合、まず分詞インタフェースを呼び出し、入力文を分詞し、分詞を得る.分詞の結果を順次遍歴し、語の長さが2未満、または語が停用語であればスキップする.最後に条件を満たす語を語周波数辞書に追加し、出現回数を1とする.その後、語周波数辞書を遍歴し、idf辞書に基づいて各語のidf値を得、語周波数辞書の回数の総和で割って、各語のtf*idf値を得る.重みフラグビットが設定されている場合、tf-idf値に基づいて語周波数辞書の語を降順に並べ替え、topK個の語をキーワードとして出力する.
ソース分析:
2.TextRankアルゴリズム
TextRankアルゴリズムに基づいてキーワードを抽出する主調関数はTextRankである.textrank関数は、主にjieba/analyse/textrankである.pyで実装します.
ここで,TextRankはTextRankアルゴリズムのキーワード抽出のために定義されたクラスである.クラスは初期化時に、デフォルトで分詞関数と品詞寸法関数tokenizer=postokenizer=jiebaをロードします.posseg.dt、ストップワードテーブルstop_words = self.STOP_WORDS.copy()、語フィルタ集合pos_filt=frozenset(‘ns’,‘n’,‘vn’,‘v’),ウィンドウspan=5,((‘ns’,‘n’,‘vn’,‘v’))は語性が地名,名詞,動名詞,動詞であることを表す.
まず無方向有権図を定義し、文を分詞する.分詞の結果を順次たどり、ある語iがフィルタ条件(語性は語性フィルタ集合にあり、語の長さは2以上であり、語は停用語ではない)を満たした後、その語の後のウィンドウ範囲内の語j(これらの語もフィルタ条件を満たす必要がある)をkeyとし、出現回数をvalueとし、共起辞書に追加します.
次に、共起辞書を順に巡回し、辞書内の各要素、key=(語i、語j)、value=語iおよび語jが出現する回数を、語i、語jをエッジの開始点および終了点とし、共起の回数をエッジの重みとして、前に定義した無方向有権図に追加する.
その後、この無方向有権図に対して反復演算textrankアルゴリズムを行い、最終的にいくつかの反復を経た後、アルゴリズムは収束し、各語は1つの指標値に対応する.
重みフラグビットが設定されている場合、無方向有権図の語を指標値に基づいて降順に並べ替え、最後にtopK個の語をキーワードとして出力する.
ここで,無重みマップの定義と実装はUndirectWeightedGraphクラスで実現される.UndirectWeightedGraphクラスによる初期化関数_init__,無方向有権図とは辞書であり,辞書のkeyは後続に追加する語であり,辞書のvalueは,(開始点,終了点,エッジの重み)からなる三元群からなるリストであり,この語を開始点とするすべてのエッジを表すことが分かった.
無方向図にエッジを追加する操作はaddEdge関数で行われます.無方向図であるため、startを開始点とし、endを終了点とし、startを終了点とし、endを開始点とする必要があります.この2つのエッジの重みは同じです.
textrankアルゴリズム反復の実行はrank関数で行われる.
まず、各ノードに同じ重みを付与し、そのノードのすべての出度の和を算出する.
その後、安定した結果が得られるように何回か反復する.
反復ごとに、各ノードを順次巡回します.ノードnについては、まず、無方向有権図からノードnの全ての入度ノード(無方向有権図については、入度ノードは出度ノードと同じであり、いずれもノードnに接続されたノードである)を得る、この入度ノードの全ての出度の回数を先に算出したが、一方、ノードnに対する重み値の寄与は、ノードnとの共起回数/このノードのすべての出度の回数に、それ自体の重み値を乗じ、各入度ノードで得られた重み値を加算し、一定の減衰係数を乗じることで、ノードnの重み値を得ることができる.
反復が完了すると、重み値が正規化され、各ノードと対応する重み値が返されます.
jieba分詞のキーワード抽出機能は,jieba/analyseディレクトリの下で実現される.
ここで、
1.TF-IDFアルゴリズム
TF-IDFアルゴリズムに基づいてキーワードを抽出する主調関数はTFIDFである.extract_tags関数は主にjieba/analyse/tfidfである.pyで実装します.
ここで、TFIDFは、TF−IDFアルゴリズムのキーワード抽出のために定義されたクラスである.クラスは初期化時に、デフォルトで分詞関数tokenizer=jiebaをロードします.dt、品詞表記関数postokenizer=jieba.posseg.dt、ストップワードstop_words = self.STOP_WORDS.copy()、idf辞書idf_loader=IDFLoader(idf_path or DEFAULT_IDF)などを取得し、idf辞書およびidf中値(ある語がidf辞書に表示されない場合、idf中値をこの語のidf値とする)を取得する.
def __init__(self, idf_path=None):
#
self.tokenizer = jieba.dt
self.postokenizer = jieba.posseg.dt
self.stop_words = self.STOP_WORDS.copy()
self.idf_loader = IDFLoader(idf_path or DEFAULT_IDF)
self.idf_freq, self.median_idf = self.idf_loader.get_idf()
そして,TF−IDFアルゴリズムによるキーワード抽出を開始する.
まず,品詞制限セットが入力されたか否かに基づいて,品詞注釈インタフェースを呼び出すか分詞インタフェースを呼び出すかを決定する.例えば,品詞制限集合は,["ns","n","vn","v","nr"]であり,品詞が地名,名詞,動名詞,動詞,人名といった品詞のみからキーワードを抽出できることを示す.
ソース分析:
def extract_tags(self, sentence, topK=20, withWeight=False, allowPOS=(), withFlag=False):
#
if allowPOS:
allowPOS = frozenset(allowPOS)
#
words = self.postokenizer.cut(sentence)
#
else:
#
words = self.tokenizer.cut(sentence)
freq = {}
for w in words:
if allowPOS:
if w.flag not in allowPOS:
continue
elif not withFlag:
w = w.word
wc = w.word if allowPOS and withFlag else w
# 2,
if len(wc.strip()) < 2 or wc.lower() in self.stop_words:
continue
# , 1
freq[w] = freq.get(w, 0.0) + 1.0
#
total = sum(freq.values())
for k in freq:
kw = k.word if allowPOS and withFlag else k
# tf-idf
freq[k] *= self.idf_freq.get(kw, self.median_idf) / total
# tf-idf
if withWeight:
tags = sorted(freq.items(), key=itemgetter(1), reverse=True)
else:
tags = sorted(freq, key=freq.__getitem__, reverse=True)
# topK
if topK:
return tags[:topK]
else:
return tags
2.TextRankアルゴリズム
TextRankアルゴリズムに基づいてキーワードを抽出する主調関数はTextRankである.textrank関数は、主にjieba/analyse/textrankである.pyで実装します.
ここで,TextRankはTextRankアルゴリズムのキーワード抽出のために定義されたクラスである.クラスは初期化時に、デフォルトで分詞関数と品詞寸法関数tokenizer=postokenizer=jiebaをロードします.posseg.dt、ストップワードテーブルstop_words = self.STOP_WORDS.copy()、語フィルタ集合pos_filt=frozenset(‘ns’,‘n’,‘vn’,‘v’),ウィンドウspan=5,((‘ns’,‘n’,‘vn’,‘v’))は語性が地名,名詞,動名詞,動詞であることを表す.
まず無方向有権図を定義し、文を分詞する.分詞の結果を順次たどり、ある語iがフィルタ条件(語性は語性フィルタ集合にあり、語の長さは2以上であり、語は停用語ではない)を満たした後、その語の後のウィンドウ範囲内の語j(これらの語もフィルタ条件を満たす必要がある)をkeyとし、出現回数をvalueとし、共起辞書に追加します.
次に、共起辞書を順に巡回し、辞書内の各要素、key=(語i、語j)、value=語iおよび語jが出現する回数を、語i、語jをエッジの開始点および終了点とし、共起の回数をエッジの重みとして、前に定義した無方向有権図に追加する.
その後、この無方向有権図に対して反復演算textrankアルゴリズムを行い、最終的にいくつかの反復を経た後、アルゴリズムは収束し、各語は1つの指標値に対応する.
重みフラグビットが設定されている場合、無方向有権図の語を指標値に基づいて降順に並べ替え、最後にtopK個の語をキーワードとして出力する.
def textrank(self, sentence, topK=20, withWeight=False, allowPOS=('ns', 'n', 'vn', 'v'), withFlag=False):
self.pos_filt = frozenset(allowPOS)
#
g = UndirectWeightedGraph()
#
cm = defaultdict(int)
#
words = tuple(self.tokenizer.cut(sentence))
#
for i, wp in enumerate(words):
# i
if self.pairfilter(wp):
# i
for j in xrange(i + 1, i + self.span):
# j
if j >= len(words):
break
# j ,
if not self.pairfilter(words[j]):
continue
# i j key, value,
if allowPOS and withFlag:
cm[(wp, words[j])] += 1
else:
cm[(wp.word, words[j].word)] += 1
# , i, j ,
for terms, w in cm.items():
g.addEdge(terms[0], terms[1], w)
# textrank
nodes_rank = g.rank()
#
if withWeight:
tags = sorted(nodes_rank.items(), key=itemgetter(1), reverse=True)
else:
tags = sorted(nodes_rank, key=nodes_rank.__getitem__, reverse=True)
# topK
if topK:
return tags[:topK]
else:
return tags
ここで,無重みマップの定義と実装はUndirectWeightedGraphクラスで実現される.UndirectWeightedGraphクラスによる初期化関数_init__,無方向有権図とは辞書であり,辞書のkeyは後続に追加する語であり,辞書のvalueは,(開始点,終了点,エッジの重み)からなる三元群からなるリストであり,この語を開始点とするすべてのエッジを表すことが分かった.
無方向図にエッジを追加する操作はaddEdge関数で行われます.無方向図であるため、startを開始点とし、endを終了点とし、startを終了点とし、endを開始点とする必要があります.この2つのエッジの重みは同じです.
def addEdge(self, start, end, weight):
# use a tuple (start, end, weight) instead of a Edge object
self.graph[start].append((start, end, weight))
self.graph[end].append((end, start, weight))
textrankアルゴリズム反復の実行はrank関数で行われる.
まず、各ノードに同じ重みを付与し、そのノードのすべての出度の和を算出する.
その後、安定した結果が得られるように何回か反復する.
反復ごとに、各ノードを順次巡回します.ノードnについては、まず、無方向有権図からノードnの全ての入度ノード(無方向有権図については、入度ノードは出度ノードと同じであり、いずれもノードnに接続されたノードである)を得る、この入度ノードの全ての出度の回数を先に算出したが、一方、ノードnに対する重み値の寄与は、ノードnとの共起回数/このノードのすべての出度の回数に、それ自体の重み値を乗じ、各入度ノードで得られた重み値を加算し、一定の減衰係数を乗じることで、ノードnの重み値を得ることができる.
反復が完了すると、重み値が正規化され、各ノードと対応する重み値が返されます.
def rank(self):
ws = defaultdict(float)
outSum = defaultdict(float)
wsdef = 1.0 / (len(self.graph) or 1.0)
#
#
for n, out in self.graph.items():
ws[n] = wsdef
outSum[n] = sum((e[2] for e in out), 0.0)
# this line for build stable iteration
sorted_keys = sorted(self.graph.keys())
#
for x in xrange(10): # 10 iters
#
for n in sorted_keys:
s = 0
#
for e in self.graph[n]:
#
# = n /
s += e[2] / outSum[e[1]] * ws[e[1]]
# n
ws[n] = (1 - self.d) + self.d * s
(min_rank, max_rank) = (sys.float_info[0], sys.float_info[3])
#
for w in itervalues(ws):
if w < min_rank:
min_rank = w
if w > max_rank:
max_rank = w
#
for n, w in ws.items():
# to unify the weights, don't *100.
ws[n] = (w - min_rank / 10.0) / (max_rank - min_rank / 10.0)
return ws