技術資料

目次 

目的 

近年,経営環境は大きく変化しており,いわゆるVUCA な時代を迎えている.企業が持続的な発展を図るためには,自社の核となる独自の強みを生かし,他者との差別化を図ることが極めて重要である.そんな中,IP ランドスケープが注目を集めている.本研究では,今日に至るまでの莫大な特許文章群を対象とした知見発見および探索を目的とする.


使用するファイル全部 

扱うデータ用途ファイル名ファイルの場所
システムの内部処理flaskを用いたシステムの記述appli_2.pyapplication/practice
ドライバーのファイル自分の環境に合わせたChromeDriverの保存chromedriver.exeapplication/practice
staticファイルjavascriptや画像のファイルが入っているstaticapplication/practice
↑の中身3Dグラフを作成するときのjavascriptのファイルmain2.jsstatic
↑の中身javascriptで読み込む用のjsonファイルoutput.jsonstatic
↑の中身グラフのボタンを作成する用の画像xy2.png/xyz2.pngstatic
テキストデータ集めてきたテキストデータの一時保存text_data.pickleapplication/practice
ベクトル(数値)2次元に圧縮したベクトルvectors.pickleapplication/practice
ベクトル(数値)15次元に圧縮したベクトルvectors_15.pickleapplication/pracitce
シルエット係数それぞれのクラス数におけるシルエット係数の値shilhouette.pickleapplication/practice
クラスタリング結果クラスタリングの結果のデータdf_umap.pklapplication/practice
simpson係数simpson係数の値と単語の出現回数などjaccard_coef.pklapplication/practice
ユーザー辞書各クラスターのユーザー辞書の保存user_dic_{classXX}.csv[XX=クラスターの番号(例.class03)]application/practice
共起語ネットワーク2dの共起語ネットワークのhtmlファイルkyoki_100.htmlapplication/practice


動かし方 

1.practiceの中のappli_2.pyを動かす.
2.必要なモジュールをすべて入れる.(pip installなど)
2'.termextractはpipではインストールできないため別の入れ方をする.また,モジュールの中身を変更する必要もあり.

3.すべてのインストールが完了したらlocalhost:5000にアクセス. ⚠必ずlocalhost:5000にアクセス!

スクレピング処理 

ChromeDriverのインストール 

まず、ChromeDriverをインストールする.自身のGoogleChromeのバージョンを確認し,それに合ったバージョンをインストールする(https://chromedriver.chromium.org/downloads).

わからなかったらここを見て👇
👉https://zenn.dev/ryo427/articles/7ff77a86a2d86a


1.データ取得 

seleniumのインストール 

seleniumをインストールする.バージョン3でもよいが,プログラムの書き方が異なる.

<pythonのとき>
pip install selenium
<notebookのとき>
!python -m pip install selenium

必要なモジュールをインポートする. 

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service

driverのオプションを設定する. 

options = webdriver.ChromeOptions()
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')

chromedriverのパスを設定する. 

インストールしたchromedriver.exeの場所を指定する.

driver_path = "chromedriver-win64/chromedriver.exe"

driverを作成する. 

driver1 = webdriver.Chrome(service=ChromeService(driver_path), options=options)
driver1.implicitly_wait(10)

⚠seleniumのバージョンによってコードの書き方が異なる場合がある(今回はver=4.12.0 )

urlの指定方法 

urlでユーザーからのキーワードと取得する年数を指定する.

url_1 = (
    "https://patents.google.com/?q=("
    + str(keyword)
    + ")&before=priority:"
    + str(2000)
    + "1231&after=priority:"
    + str(2000)
    + "0101&sort=old"
)

取得方法 

def W1(url):
       driver1.get(url)
       try:
           results1 = driver1.find_element(
               By.XPATH, '//*[@id="count"]/div[1]/span[1]/span[3]'
           ).text.replace(",", "")
           if int(results1) <= 10:
               p_n_1 = 1
           else:
               p_n_1 = int(results1) // 10
       except Exception as e:
           print("error")
       for p1 in range(p_n_1 + 1):
           driver1.get(url + "&page=" + str(p1))
           link1 = driver1.find_elements(
               By.XPATH,
               '//*[@id="resultsContainer"]/section/search-result-item/article/state-modifier',
           )
           for i1 in range(len(link1)):
               try:
                   link_url1 = "https://patents.google.com/" + link1[i1].get_attribute(
                       "data-result"
                   )
                   patt1 = pat_text(link_url1)
                   patt1.get_soup()
                   # patt.get_title()
                   patt1.get_claims()
                   
                   d_save1 = []
                   d_save1.append(patt1.get_description())
                   d_save1.append(link1[i1].get_attribute("data-result"))
                   desc1.append(d_save1)
               except Exception as e:
                   print(e)

1.urlから中身を取得する.
2.find_elementで検索結果の数を取得する.
3.p_n_1にページ数を渡す.

4.各ページの中から特許番号を取得する.

5.find_elementで特許番号(例:patent/JP5965646B2/ja)の部分を取得する
6.取得した番号をもとにhtmlのurlを作成し,関数(pat_text)に渡す.
7.pat_textからの本文と特許番号をd_save1に渡す.
👇実際の取得結果

テキストフォーマット2.jpg



2.並列化 

threadsを用いて並列化を行う.

import threading
   thr1 = threading.Thread(target=W1, args=(url_1,))
   thr2 = threading.Thread(target=W2, args=(url_2,))
   thr3 = threading.Thread(target=W3, args=(url_3,))
   thr4 = threading.Thread(target=W4, args=(url_4,))
   thr5 = threading.Thread(target=W5, args=(url_5,))
   thr6 = threading.Thread(target=W6, args=(url_6,))
   ~~~~~省略~~~~~
           thr24まで
   ~~~~~~~~~~~~

threadを一年ごとに設定する.
それを6年ずつ実行する
要素が混在しないように一年ごととそれぞれのスレッドごとにdescを用意する.

   desc01 = []
   desc02 = []
   desc03 = []
   desc04 = []
   if int(year) == 24:
       #6年分
       desc1 = [] 
       desc2 = [] 
       desc3 = [] 
       desc4 = [] 
       desc5 = [] 
       desc6 = []
       
       #各スレッドのスタート
       thr1.start() 
       thr2.start() 
       thr3.start() 
       thr4.start() 
       thr5.start() 
       thr6.start()
       
       #各スレッドの処理が終了するまで待機
       thr1.join() 
       thr2.join() 
       thr3.join() 
       thr4.join() 
       thr5.join() 
       thr6.join()
       
       desc01 = desc1 + desc2 + desc3 + desc4 + desc5 + desc6
       
   if int(year) == 18 or int(year) == 24:
       ~~~~~省略~~~~~
           thr7からthr12まで
       ~~~~~~~~~~~~
       desc02 = desc1 + desc2 + desc3 + desc4 + desc5 + desc6
       
   if int(year) == 12 or int(year) == 18 or int(year) == 24:
       ~~~~~省略~~~~~
           thr13からthr18まで
       ~~~~~~~~~~~~
       desc03 = desc1 + desc2 + desc3 + desc4 + desc5 + desc6
   
   ~~~~~省略~~~~~
      thr19からthr24まで
   ~~~~~~~~~~~~
   desc04 = desc1 + desc2 + desc3 + desc4 + desc5 + desc6

最後に各スレッドのdescを合わせる

   desc = desc01 + desc02 + desc03 + desc04


3.保存の仕方と例外 

ほかのルーティングでテキストデータを参照したい場合がある.
csvに保存してもよいが,文字化けなどの可能性もあるため今回は pickleモジュールを用いてpickle形式のファイルで保存する.

最後に,取得できたデータの要素数によって例外処理を追加する.
要素数が0の場合に正しく動作しないことや,要素数が少なすぎることを考慮して,要素数が30未満の場合は,トップページ戻るようにしている.

desc_len = len(desc)
if desc_len < 30:
    return redirect(url_for('start'))

Sentence-BERT 

事前学習モデルは”sonoisa/sentence-bert-base-ja-mean-token”を用いる
SentenceBertJapaneseの中身はここを参照👇
👉https://huggingface.co/sonoisa/sentence-bert-base-ja-mean-tokens

UMAP 

UMAPでSentence-BERTから得られたベクトルを2次元と15次元に圧縮する.

UMAPのパラメータ 

実際の値

sentence_vectors_umap_15 = umap.UMAP(n_components=15, 
                                     random_state=42, 
                                     n_neighbors = 25, 
                                     min_dist = 0.1,
                                     metric = 'cosine').fit_transform(sentence_vectors)

上記は15次元の場合,2次元にするときはn_componentsの値を2にする.

ベクトル化されたデータもpickleを用いて保存しておく.

with open('vectors_15.pickle', mode='wb') as fo:
    pickle.dump(sentence_vectors_umap_15, fo)
with open('vectors.pickle', mode='wb') as fo:
    pickle.dump(sentence_vectors_umap_2, fo)


シルエット分析 

K-medoidsでクラスタリングを行うために最適なクラスター数を導出する. シルエット分析はクラスタリング結果における凝縮度と乖離度をもとに最適なクラスター数を導出する.
クラスター数が3から19までのシルエット係数を計算し係数が一番高くなったクラスター数を最適な数とする.

この時場合は一番シルエット係数が高い15を最適なクラスター数とする.

クラスタリング 

クラスタリングにはk-medoidsを用いる.
k-meansではデータの外れ値が大きい場合,クラスタリングの結果が大雑把になってしまうことが稀にあるため,外れ値につよいk-medoidsを用いる.

タイトルの提示 

各データの重心とのユークリッド距離を計算する.

centers = kmeans_model.cluster_centers_
df_umap_15["distance"] = np.nan

for j in range(class_n):
    class_name = str("cluster" + str(j))
    d = df_umap_15[df_umap_15["class"] == class_name]
       
    for i in range(d.shape[0]):
        v = d.iloc[i, :]
        v = v[:-2]

        distances = np.subtract(v, centers[j])
 
        distances_squared = np.square(distances)

        distance1 = np.sqrt(np.sum(distances_squared))

        df_umap_15.at[d.index[i], "distance"] = distance1
for a in tqdm.tqdm(range(class_n)):
    vec_dis = df_umap_15[df_umap_15["class"] == "cluster" + str(a)]
    vec_dis_sorted = vec_dis.sort_values('distance')
    title_all = []
    # #ランダム
    # if text.shape[0] >= 10:
    #     random_n = 10
    # else:
    #     random_n = text.shape[0]

    # for i in tqdm.tqdm(random.sample(range(text.shape[0]), k=random_n)):
    for i in tqdm.tqdm(range(vec_dis_sorted.head(10).shape[0])):
        # target_text = text.iloc[i][0]
        target_text = pd.DataFrame(df[0]).loc[vec_dis_sorted.head(10).index[i]][0]

        tagged_text = get_mecab_tagged(target_text)

        terms = term_ext(tagged_text)

        compound = remove_single_words(terms)
        title_all.extend([termextract.core.modify_agglutinative_lang(cmp_noun) for cmp_noun, value in compound.most_common(3)])
        
    set1 = sorted([k for k, v in collections.Counter(title_all).items() if v >= 1], key=title_all.index)
    title.append("/".join(set1[0:3]))

散布図グラフの描画 


分かち書き 

termextract 

専門用語や複合語などを抽出するためのモジュール

モジュールの入れ方 

以下のサイトからtermextractをダウンロードする.

👉http://gensen.dl.itc.u-tokyo.ac.jp/pytermextract/
ダウンロードしたら,ダウンロード先のファイル(termextract)のディレクトリで,コマンドプロンプトを起動する.
コマンドプロンプトで,以下の操作を行う.

pip install .


core.pyの変更 

既存のcore.pyを用いるとエラーが起こる場合があるため変更する.
まず自身のパソコンのtermextractがインストールされているファイルに移動

このファイルの中のcore.pyを変更する.(今回はcore2.pyとして別のファイルを作成している)
core2.pyにした時のモジュールの定義

import termextract.core2


変更箇所

from decimal import Decimal
~~~~~~~~~~~~~~~~~~~
84| importance = Decimal(importance) ** (1 / (2 * Decimal(average_rate) * count))
~~~~~~~~~~~~~~~~~~~

エラーが起こる理由はおそらく重要度を計算するときに,計算する式の値の桁数が大きすぎるため

Janomeの辞書登録 

termextractの出力結果をもとにJanomeの辞書の登録を行う.
csv形式で与えることでユーザー辞書を登録することができる.
termextactはjanomeを用いる元のmecabを用いるものがあるが,今回はmecabバージョンを使う.

分かち書き処理 

sentences = []
sentences_2 = []

for i in tqdm.tqdm(range(text.shape[0])):
    target_texts = text.iloc[i]
    t = Tokenizer('user_dic_' + str(class_set) +'.csv', udic_enc='cp932')
    texts = target_texts.str.split('。')
    wakati_list = []
    for s in texts[0]:
        words = []
        for token in t.tokenize(s):
            s_token = token.part_of_speech.split(',')
            # 一般名詞、自立動詞(「し」等の1文字の動詞は除く)、自立形容詞を抽出
            if (s_token[0] == '名詞' and s_token[1] == '一般') \
                    or (s_token[0] == '形容詞' and s_token[1] == '自立')\
                    or (s_token[0] == '名詞' and s_token[1] == '固有名詞'):
                words.append(token.surface)
        wakati_list.append(words)
    sentences.append(wakati_list)
    sentences_2.extend(wakati_list)

# combination_sentences = []
# for words in tqdm.tqdm(sentences_2):
combination_sentences = [list(itertools.combinations(words, 2)) for words in sentences_2]
combination_sentences = [[tuple(sorted(combi)) for combi in combinations] for combinations in combination_sentences]
tmp = []
for combinations in combination_sentences:
    tmp.extend(combinations)
combination_sentences = tmp

touroku_list = []

for i in tqdm.tqdm(range(len(combination_sentences))):
    if (combination_sentences[i][0] in df_frequency) or (combination_sentences[i][1] in df_frequency):
        touroku_list.append(combination_sentences[i])

共起語ネットワーク 

共起関係の導出 

Jaccard係数,Dice係数,Simpson係数の計算を行う.(実際に使っているのはSimpson係数)
それぞれの係数の値は{jaccard_coef}に格納されている.(変数名を変更するのが面倒くさかったため)

Three.js 


5.(実験後)検証 

使ったコード全部 

江崎のドライブに全部ある、はず
江崎のドライブに全部ある、はず名前が若干違うかも


トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS