- 投稿日:2021-08-29T22:13:47+09:00
グラフレイアウトの操作について(備忘録)
Pythonを使ったデータ可視化時のレイアウトについての備忘録です。 コード実行の際には下記ライブラリを事前にインポート import matplotlib.pyplot as plt import seaborn as sns import pandas as pd import numpy as np figureとaxes グラフ描画領域の概念として、figureとaxesがある。 figureは、イメージとしては大きな画用紙(描画領域全体) axesは、画用紙の中に描かれたグラフ描画領域 figureの作成 matplotlib.pyplot.figure関数で作成する。 # figureの作成 plt.figure(figsize=(5,10)) 引数 説明 figsize figureのサイズ(横幅, 高さ) dpi 解像度 facecolor 背景色 linewidth 外枠の太さ edgecolor 枠の色 figureの中にグラフを1つのみ描画する場合 axesは指定する必要なし。 # データの作成 x = np.array([1,2,3,4,5,6,7,8,9,10]) y1 = x #1次関数 y2 = np.exp(x) #指数関数 # 1つ目のfigure plt.figure(figsize=(3,3)) plt.plot(x,y1) # 2つ目のfigure plt.figure(figsize=(3,3)) plt.plot(x,y2) plt.show() ※実際は、上記のグラフが縦に並びます。 axexの作成(1つのfigureに複数のグラフを描画する場合) figureを用意して、その中にaxesを作成する方法。 まず、plt.figure()関数でキャンバスを用意する。次にplt.subplot()関数でaxesを作成。 plt.subplot()関数では、引数に行数,列数,描画位置を指定することで、キャンパスが行数×列数のグリッドに分割されたとした際の、指定した描画位置にaxesオブジェクトを作成することができる。 # figureを2行2列に分割されたとした場合の3の位置にaxesオブジェクトを作成 plt.subplot(2,2,3) とした場合、 の3の位置にaxesオブジェクトが作成される。 x = np.array([1,2,3,4,5,6,7,8,9,10]) y1 = x y2 = np.exp(x) y3 = x ** 2 y4 = x ** 3 # figureの作成 plt.figure(figsize=[10,5]) # axesの作成(2行2列に分割した場合の左上を指定) plt.subplot(2,2,1) plt.plot(x,y1) # axesの作成(2行2列に分割した場合の右上を指定) plt.subplot(2,2,2) plt.plot(x,y2) # axesの作成(2行2列に分割した場合の左下を指定) plt.subplot(2,2,3) plt.plot(x,y3) # axesの作成(2行2列に分割した場合の右下を指定) plt.subplot(2,2,4) plt.plot(x,y4) plt.show() figureオブジェクトとaxesオブジェクトの作成と操作 前述の操作の別の方法 figureオブジェクトの作成 ※これまでと同様のfigure関数です。 fig = plt.figure(figsize=(4,8)) axesオブジェクトをfigureオブジェクト内に作成(追加)する。 figureオブジェクト.add_subplot(行,列,配置番号) # 2行2列とした場合の、 ax1 = fig.add_subplot(2,2,1) # 左上 ax2 = fig.add_subplot(2,2,2) # 右上 ax3 = fig.add_subplot(2,2,3) # 左下 ax4 = fig.add_subplot(2,2,4) # 右下 グラフを描画する場合は、axesオブジェクトを使ってグラフを描画する。 ax1.plot(x, y1) ax2.plot(x, y2) ax3.plot(x, y3) ax4.plot(x, y4) まとめると・・・ x = np.array([1,2,3,4,5,6,7,8,9,10]) y1 = x y2 = np.exp(x) y3 = x ** 2 y4 = x ** 3 # figure作成 fig = plt.figure(figsize=(10,5)) # axes作成(追加) ax1 = fig.add_subplot(2,2,1) ax2 = fig.add_subplot(2,2,2) ax3 = fig.add_subplot(2,2,3) ax4 = fig.add_subplot(2,2,4) # グラフ描画 ax1.plot(x, y1) ax2.plot(x, y2) ax3.plot(x, y3) ax4.plot(x, y4) figureオブジェクトとaxesオブジェクトを一気に作成する 何行何列にグラフを配置するかを事前に指定して、figureとaxesを同時に作成する方法 fig, ax = plt.subplots(行数, 列数, figsize=(横幅, 高さ)) axはリストとなっている為、 axesオブジェクトが代入された変数[行インデックス, 列インデックス]として対象のグラフ領域を指定します。 x = np.array([1,2,3,4,5,6,7,8,9,10]) y1 = x y2 = np.exp(x) y3 = x ** 2 y4 = x ** 3 # figureとaxesの作成 fig, ax = plt.subplots(2,2,figsize=(10,5)) # グラフの作成 ax[0,0].plot(x, y1) ax[0,1].plot(x, y2) ax[1,0].plot(x, y3) ax[1,1].plot(x, y4) plt.show() おしまい
- 投稿日:2021-08-29T21:32:35+09:00
python-pptxの図形一覧
python-pptxの図形を調べてみた。 python-pptxはPowerPointをPythonでかけるライブラリです。 あまりにも画像の数が多かったり、毎回似たような表を作成しなければならないとき、自動化ツールとして活用できます。 そのため、画像挿入や表挿入、グラフ挿入に関する記事は調べればそれなりの数が出てきます。 しかし、図形の挿入となると記事はかなり少なくなるようです。 今回はpython-pptxで簡単な図形追加をまとめました。182種類の図形が追加できるようなのですが、そのうち約170個ほどを実際に出力してみています。 ちなみに、図形の細かい調整までは労力が大きすぎるので控えました。別の機会に調べてまとめたいと思います。 図形の追加の仕方 python-pptxの図形はshapes.add_shapeメソッドで追加できます。 引数はadd_shape(MSO_SHAPE.xxx, left, top, width, height)です。 このうちのxxxの部分に何を入れるかで挿入される図形が変わります。 例えば、直線を引く場合は次のようになります。直線のメソッドはLINE_INVERSEなので、これを上のxxxの部分に入れれば良いわけです。 test.py from pptx import Presentation from pptx.enum.shapes import MSO_SHAPE from pptx.util import Cm, Pt ppt = Presentation() slide = ppt.slides.add_slide(ppt.slide_layouts[6]) shapes = slide.shapes shapes.add_shape(MSO_SHAPE.LINE_INVERSE, Cm(2), Cm(2), Cm(2), Cm(2)) ppt.save("test.pptx") 挿入の仕方もわかったところで、一覧を見ましょう。 下のスライドは全てpython-pptxで出力しており、直接編集は一切おこなっていません。 ちなみに並べ方は任意です。 なぜ線の矢印がないのだろう。どなたか線の矢印の表示方法をご存知の方がいらっしゃれば教えてください。。。 以下一覧 図形の下にある名前部分が、MSO_SHAPEのメソッド名です。 List(3)のNO_SYMBOLはなぜかエラーが出ました。そのため出力からは外しています。 参考用コード こちらは出力の際に使用したコードです。 ご参考になれば幸いです。 pptx-utiltest.py from pptx import Presentation from pptx.enum.shapes import MSO_SHAPE from pptx.enum.text import PP_ALIGN, MSO_ANCHOR, MSO_AUTO_SIZE from pptx.util import Cm, Pt OUTPUT_PATH = "shapetest.pptx" ppt = Presentation() SLIDE_WIDTH = ppt.slide_width SLIDE_HEIGHT = ppt.slide_height plot_top = Cm(2) plotzone_height = SLIDE_HEIGHT-plot_top def table_x(x_posi,x_size): # 0~ sukima = (SLIDE_WIDTH/6 - x_size) / 2 left = SLIDE_WIDTH/6 * x_posi + sukima return left def table_y(y_posi): # 0~ sukima = Cm(0.2) top = plot_top+(plotzone_height/5)*y_posi+sukima return top def text_x(x_posi): return SLIDE_WIDTH/6*x_posi def text_y(y_posi): return plot_top+(plotzone_height/5)*(y_posi+1)-Cm(1) # Page 1 slide = ppt.slides.add_slide(ppt.slide_layouts[6]) shapes = slide.shapes # set line # horizontal line for i in range(6): shapes.add_shape(MSO_SHAPE.LINE_INVERSE, 0, plot_top+(plotzone_height/5)*i, SLIDE_WIDTH, 0) # virtical line for i in range(5): shapes.add_shape(MSO_SHAPE.LINE_INVERSE, SLIDE_WIDTH/6*(i+1), plot_top, 0, plotzone_height) # 直線 shapes.add_shape(MSO_SHAPE.LINE_INVERSE, table_x(0,Cm(2)), table_y(0), Cm(2), Cm(2)) # 矢印 shapes.add_shape(MSO_SHAPE.LEFT_RIGHT_ARROW, table_x(0,Cm(2)), table_y(1), Cm(2), Cm(1)) shapes.add_shape(MSO_SHAPE.UP_DOWN_ARROW, table_x(0,Cm(1)), table_y(2), Cm(1), Cm(2)) shapes.add_shape(MSO_SHAPE.QUAD_ARROW, table_x(0,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LEFT_ARROW, table_x(1, Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.RIGHT_ARROW, table_x(1, Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.UP_ARROW, table_x(1, Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.DOWN_ARROW, table_x(1, Cm(2)), table_y(3), Cm(2), Cm(2)) # BENT ARROW shapes.add_shape(MSO_SHAPE.BENT_ARROW, table_x(2, Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.BENT_UP_ARROW, table_x(2, Cm(2)), table_y(1), Cm(2), Cm(2)) # CIRCULAR_ARROW shapes.add_shape(MSO_SHAPE.CIRCULAR_ARROW, table_x(2, Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LEFT_CIRCULAR_ARROW, table_x(2, Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LEFT_RIGHT_CIRCULAR_ARROW, table_x(2, Cm(2)), table_y(4), Cm(2), Cm(2)) # curved arrow shapes.add_shape(MSO_SHAPE.CURVED_LEFT_ARROW, table_x(3, Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.CURVED_RIGHT_ARROW, table_x(3, Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.CURVED_UP_ARROW, table_x(3, Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.CURVED_DOWN_ARROW, table_x(3, Cm(2)), table_y(3), Cm(2), Cm(2)) # 丸(楕円) shapes.add_shape(MSO_SHAPE.OVAL, table_x(3, Cm(2)), table_y(4), Cm(2), Cm(2)) # 長方形 shapes.add_shape(MSO_SHAPE.RECTANGLE, table_x(4, Cm(2)), table_y(0), Cm(2), Cm(2)) # CUBE shapes.add_shape(MSO_SHAPE.CUBE, table_x(4, Cm(2)), table_y(1), Cm(2), Cm(2)) # 三角 shapes.add_shape(MSO_SHAPE.RIGHT_TRIANGLE, table_x(4, Cm(2)), table_y(2), Cm(2), Cm(2)) # 五角形 shapes.add_shape(MSO_SHAPE.PENTAGON, table_x(4, Cm(2)), table_y(3), Cm(2), Cm(2)) # 五角形 shapes.add_shape(MSO_SHAPE.HEXAGON, table_x(4, Cm(2)), table_y(4), Cm(2), Cm(2)) # 角が丸い四角形 shapes.add_shape(MSO_SHAPE.ROUND_1_RECTANGLE, table_x(5, Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.ROUND_2_DIAG_RECTANGLE, table_x(5, Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.ROUND_2_SAME_RECTANGLE, table_x(5, Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, table_x(5, Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGULAR_CALLOUT, table_x(5, Cm(2)), table_y(4), Cm(2), Cm(2)) mso_list = [ ["LINE_INVERSE","LEFT_ARROW","BENT_ARROW","CURVED_LEFT_ARROW","RECTANGLE","ROUND_1_RECTANGLE"], ["LEFT_RIGHT_ARROW","RIGHT_ARROW","BENT_UP_ARROW","CURVED_RIGHT_ARROW","CUBE","ROUND_2_DIAG_RECTANGLE"], ["UP_DOWN_ARROW","UP_ARROW","CIRCULAR_ARROW","CURVED_UP_ARROW","RIGHT_TRIANGLE","ROUND_2_SAME_RECTANGLE"], ["QUAD_ARROW","DOWN_ARROW","LEFT_CIRCULAR_ARROW","CURVED_DOWN_ARROW","PENTAGON","ROUNDED_RECTANGLE"], ["","","LEFT_RIGHT_CIRCULAR_ARROW","OVAL","HEXAGON","ROUNDED_RECTANGULAR\v_CALLOUT"], ] title_width = Cm(25) title_left = (SLIDE_WIDTH - title_width) / 2 textbox = shapes.add_textbox(title_left, 0, title_width, plot_top) textbox.text = "MSO_SHAPE List MAIN" textbox.text_frame.paragraphs[0].font.size = Pt(30) textbox.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER textbox.text_frame.virtical_anchor = MSO_ANCHOR.MIDDLE for row in range(5): for col in range(6): textbox = shapes.add_textbox(text_x(col), text_y(row), SLIDE_WIDTH/6, Cm(1)) textbox.text = mso_list[row][col] textbox.text_frame.paragraphs[0].font.size = Pt(9) textbox.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER textbox.text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE # 図形内のテキストに合わせて、必要に応じてフォントサイズを縮小 # Page 2 slide = ppt.slides.add_slide(ppt.slide_layouts[6]) shapes = slide.shapes # set line # horizontal line for i in range(6): shapes.add_shape(MSO_SHAPE.LINE_INVERSE, 0, plot_top+(plotzone_height/5)*i, SLIDE_WIDTH, 0) # virtical line for i in range(5): shapes.add_shape(MSO_SHAPE.LINE_INVERSE, SLIDE_WIDTH/6*(i+1), plot_top, 0, plotzone_height) shapes.add_shape(MSO_SHAPE.ARC, table_x(0,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.BALLOON, table_x(0,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.BEVEL, table_x(0,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.BLOCK_ARC, table_x(0,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.CAN, table_x(0,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.CHART_PLUS, table_x(1,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.CHART_STAR, table_x(1,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.CHART_X, table_x(1,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.CHEVRON, table_x(1,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.CHORD, table_x(1,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.CLOUD, table_x(2,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.CLOUD_CALLOUT, table_x(2,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.CORNER, table_x(2,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.CORNER_TABS, table_x(2,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.CROSS, table_x(2,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.CURVED_DOWN_RIBBON, table_x(3,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.CURVED_UP_RIBBON, table_x(3,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.DECAGON, table_x(3,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.DIAGONAL_STRIPE, table_x(3,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.DIAMOND, table_x(3,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.DODECAGON, table_x(4,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.DONUT, table_x(4,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.DOUBLE_BRACE, table_x(4,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.DOUBLE_BRACKET, table_x(4,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.DOUBLE_WAVE, table_x(4,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.DOWN_RIBBON, table_x(5,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.EXPLOSION1, table_x(5,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.EXPLOSION2, table_x(5,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FOLDED_CORNER, table_x(5,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FRAME, table_x(5,Cm(2)), table_y(4), Cm(2), Cm(2)) mso_list = [ ["ARC" ,"CHART_PLUS" ,"CLOUD" ,"CURVED_DOWN_RIBBON" ,"DODECAGON" ,"DOWN_RIBBON" ], ["BALLOON" ,"CHART_STAR" ,"CLOUD_CALLOUT","CURVED_UP_RIBBON" ,"DONUT" ,"EXPLOSION1" ], ["BEVEL" ,"CHART_X" ,"CORNER" ,"DECAGON" ,"DOUBLE_BRACE" ,"EXPLOSION2" ], ["BLOCK_ARC","CHERVRON" ,"CORNER_TABS" ,"DIAGONAL_STRIPE" ,"DOUBLE_BRACKET" ,"FOLDED_CORNER"], ["CAN" ,"CHORD" ,"CROSS" ,"DIAMOND" ,"DOUBLE_WAVE" ,"FRAME" ], ] title_width = Cm(25) title_left = (SLIDE_WIDTH - title_width) / 2 textbox = shapes.add_textbox(title_left, 0, title_width, plot_top) textbox.text = "MSO_SHAPE List (1)" textbox.text_frame.paragraphs[0].font.size = Pt(30) textbox.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER textbox.text_frame.virtical_anchor = MSO_ANCHOR.MIDDLE for row in range(5): for col in range(6): textbox = shapes.add_textbox(text_x(col), text_y(row), SLIDE_WIDTH/6, Cm(1)) textbox.text = mso_list[row][col] textbox.text_frame.paragraphs[0].font.size = Pt(9) textbox.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER textbox.text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE # 図形内のテキストに合わせて、必要に応じてフォントサイズを縮小 # Page 3 slide = ppt.slides.add_slide(ppt.slide_layouts[6]) shapes = slide.shapes # set line # horizontal line for i in range(6): shapes.add_shape(MSO_SHAPE.LINE_INVERSE, 0, plot_top+(plotzone_height/5)*i, SLIDE_WIDTH, 0) # virtical line for i in range(5): shapes.add_shape(MSO_SHAPE.LINE_INVERSE, SLIDE_WIDTH/6*(i+1), plot_top, 0, plotzone_height) shapes.add_shape(MSO_SHAPE.FLOWCHART_ALTERNATE_PROCESS ,table_x(0,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_CARD ,table_x(0,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_COLLATE ,table_x(0,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_CONNECTOR ,table_x(0,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_DATA ,table_x(0,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_DECISION ,table_x(1,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_DELAY ,table_x(1,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_DIRECT_ACCESS_STORAGE ,table_x(1,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_DISPLAY ,table_x(1,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_DOCUMENT ,table_x(1,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_EXTRACT ,table_x(2,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_INTERNAL_STORAGE ,table_x(2,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_MAGNETIC_DISK ,table_x(2,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_MANUAL_INPUT ,table_x(2,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_MANUAL_OPERATION ,table_x(2,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_MERGE ,table_x(3,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_MULTIDOCUMENT ,table_x(3,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_OFFLINE_STORAGE ,table_x(3,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_OFFPAGE_CONNECTOR ,table_x(3,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_OR ,table_x(3,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_PREDEFINED_PROCESS ,table_x(4,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_PREPARATION ,table_x(4,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_PROCESS ,table_x(4,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_PUNCHED_TAPE ,table_x(4,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_SEQUENTIAL_ACCESS_STORAGE ,table_x(4,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_SORT ,table_x(5,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_STORED_DATA ,table_x(5,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_SUMMING_JUNCTION ,table_x(5,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FLOWCHART_TERMINATOR ,table_x(5,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.FOLDED_CORNER ,table_x(5,Cm(2)), table_y(4), Cm(2), Cm(2)) mso_list = [ ["FLOWCHART_\vALTERNATE_PROCESS","FLOWCHART_DECISION" ,"FLOWCHART_EXTRACT" ,"FLOWCHART_MERGE" ,"FLOWCHART\v_PREDEFINED_PROCESS" ,"FLOWCHART_SORT" ], ["FLOWCHART_CARD" ,"FLOWCHART_DELAY" ,"FLOWCHART_INTERNAL_STORAGE" ,"FLOWCHART_MULTIDOCUMENT" ,"FLOWCHART_PREPERATION" ,"FLOWCHART_STORED_DATA" ], ["FLOWCHART_COLLATE" ,"FLOWCHART_DIRECT_\vACCESS_STORAGE" ,"FLOWCHART_MAGNETIC_DISK" ,"FLOWCHART_OFFLINE_STRAGE" ,"FLOWCHART_PROCESS" ,"FLOWCHART\v_SUMMING_JUNCTION" ], ["FLOWCHART_CONNECTOR" ,"FLOWCHART_DISPLAY" ,"FLOWCHART_MANUAL_INPUT" ,"FLOWCHART_\vOFFPAGE_CONNECTOR","FLOWCHART_PUNCHED_TAPE" ,"FLOWCHART_TERMINATOR" ], ["FLOWCHART_DATA" ,"FLOWCHART_DOCUMENT" ,"FLOWCHART\v_MANUAL_OPERATION" ,"FLOWCHART_OR" ,"FLOWCHART_SEQUENTIAL\v_ACCESS_STORAGE" ,"FOLDED_CORNER" ], ] title_width = Cm(25) title_left = (SLIDE_WIDTH - title_width) / 2 textbox = shapes.add_textbox(title_left, 0, title_width, plot_top) textbox.text = "MSO_SHAPE List (2) FLOWCHART" textbox.text_frame.paragraphs[0].font.size = Pt(30) textbox.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER textbox.text_frame.virtical_anchor = MSO_ANCHOR.MIDDLE for row in range(5): for col in range(6): textbox = shapes.add_textbox(text_x(col), text_y(row), SLIDE_WIDTH/6, Cm(1)) textbox.text = mso_list[row][col] textbox.text_frame.paragraphs[0].font.size = Pt(9) textbox.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER textbox.text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE # 図形内のテキストに合わせて、必要に応じてフォントサイズを縮小 # Page 4 slide = ppt.slides.add_slide(ppt.slide_layouts[6]) shapes = slide.shapes # set line # horizontal line for i in range(6): shapes.add_shape(MSO_SHAPE.LINE_INVERSE, 0, plot_top+(plotzone_height/5)*i, SLIDE_WIDTH, 0) # virtical line for i in range(5): shapes.add_shape(MSO_SHAPE.LINE_INVERSE, SLIDE_WIDTH/6*(i+1), plot_top, 0, plotzone_height) shapes.add_shape(MSO_SHAPE.FUNNEL ,table_x(0,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.GEAR_6 ,table_x(0,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.GEAR_9 ,table_x(0,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.HALF_FRAME ,table_x(0,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.HEART ,table_x(0,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.HEPTAGON ,table_x(1,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.HORIZONTAL_SCROLL ,table_x(1,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.ISOSCELES_TRIANGLE ,table_x(1,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LEFT_BRACE ,table_x(1,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LEFT_BRACKET ,table_x(1,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LEFT_RIGHT_RIBBON ,table_x(2,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LIGHTNING_BOLT ,table_x(2,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.MATH_DIVIDE ,table_x(2,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.MATH_EQUAL ,table_x(2,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.MATH_MINUS ,table_x(2,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.MATH_MULTIPLY ,table_x(3,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.MATH_NOT_EQUAL ,table_x(3,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.MATH_PLUS ,table_x(3,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.MOON ,table_x(3,Cm(2)), table_y(3), Cm(2), Cm(2)) #shapes.add_shape(MSO_SHAPE.NO_SYMBOL ,table_x(3,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.NON_ISOSCELES_TRAPEZOID ,table_x(4,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.NOTCHED_RIGHT_ARROW ,table_x(4,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.OCTAGON ,table_x(4,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.PARALLELOGRAM ,table_x(4,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.PIE ,table_x(4,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.PIE_WEDGE ,table_x(5,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.PLAQUE ,table_x(5,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.PLAQUE_TABS ,table_x(5,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.REGULAR_PENTAGON ,table_x(5,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.SMILEY_FACE ,table_x(5,Cm(2)), table_y(4), Cm(2), Cm(2)) mso_list = [ ["FUNNEL" ,"HEPTAGON" ,"LEFT_RIGHT_RIBBON","MATH_MULTIPLY" ,"NON_ISOSCELES_TRAPEZOID" ,"PIE_WEDGE" ], ["GEAR_6" ,"HORIZONTAL_SCROLL" ,"LIGHTNING_BOLT" ,"MATH_NOT_EQUAL" ,"NOTCHED_RIGHT_ARROW" ,"PLAQUE" ], ["GEAR_9" ,"ISOSCELES_TRIANGLE" ,"MATH_DIVIDE" ,"MATH_PLUS" ,"OCTAGON" ,"PLAQUE_TABS" ], ["HALF_FRAME" ,"LEFT_BRACE" ,"MATH_EQUAL" ,"MOON" ,"PARALLELOGRAM" ,"REGULAR_PENTAGON" ], ["HEART" ,"LEFT_BRACKET" ,"MATH_MINUS" ,"NO_SYMBOL" ,"PIE" ,"SMILEY_FACE" ], ] title_width = Cm(25) title_left = (SLIDE_WIDTH - title_width) / 2 textbox = shapes.add_textbox(title_left, 0, title_width, plot_top) textbox.text = "MSO_SHAPE List (3)" textbox.text_frame.paragraphs[0].font.size = Pt(30) textbox.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER textbox.text_frame.virtical_anchor = MSO_ANCHOR.MIDDLE for row in range(5): for col in range(6): textbox = shapes.add_textbox(text_x(col), text_y(row), SLIDE_WIDTH/6, Cm(1)) textbox.text = mso_list[row][col] textbox.text_frame.paragraphs[0].font.size = Pt(9) textbox.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER textbox.text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE # 図形内のテキストに合わせて、必要に応じてフォントサイズを縮小 # Page 5 slide = ppt.slides.add_slide(ppt.slide_layouts[6]) shapes = slide.shapes # set line # horizontal line for i in range(6): shapes.add_shape(MSO_SHAPE.LINE_INVERSE, 0, plot_top+(plotzone_height/5)*i, SLIDE_WIDTH, 0) # virtical line for i in range(5): shapes.add_shape(MSO_SHAPE.LINE_INVERSE, SLIDE_WIDTH/6*(i+1), plot_top, 0, plotzone_height) shapes.add_shape(MSO_SHAPE.RIGHT_BRACE ,table_x(0,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.RIGHT_BRACKET ,table_x(0,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.RIGHT_TRIANGLE ,table_x(0,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.SNIP_1_RECTANGLE ,table_x(0,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.SNIP_2_DIAG_RECTANGLE ,table_x(0,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.SNIP_2_SAME_RECTANGLE ,table_x(1,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.SNIP_ROUND_RECTANGLE ,table_x(1,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.SQUARE_TABS ,table_x(1,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.STAR_10_POINT ,table_x(1,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.STAR_12_POINT ,table_x(1,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.STAR_16_POINT ,table_x(2,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.STAR_24_POINT ,table_x(2,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.STAR_32_POINT ,table_x(2,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.STAR_4_POINT ,table_x(2,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.STAR_5_POINT ,table_x(2,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.STAR_6_POINT ,table_x(3,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.STAR_7_POINT ,table_x(3,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.STAR_8_POINT ,table_x(3,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.STRIPED_RIGHT_ARROW ,table_x(3,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.SUN ,table_x(3,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.SWOOSH_ARROW ,table_x(4,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.TEAR ,table_x(4,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.TRAPEZOID ,table_x(4,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.U_TURN_ARROW ,table_x(4,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.UP_RIBBON ,table_x(4,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.VERTICAL_SCROLL ,table_x(5,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.WAVE ,table_x(5,Cm(2)), table_y(1), Cm(2), Cm(2)) mso_list = [ ["RIGHT_BRACE" ,"SNIP_2_SAME_RECTANGLE","STAR_16_POINT","STAR_6_POINT" ,"SWOOSH_ARROW" ,"VERTICAL_SCROLL"], ["RIGHT_BRACKET" ,"SNIP_ROUND_RECTANGLE" ,"STAR_24_POINT","STAR_7_POINT" ,"TEAR" ,"WAVE"], ["RIGHT_TRIANGLE" ,"SQUARE_TABS" ,"STAR_32_POINT","STAR_8_POINT" ,"TRAPEZOID" ,""], ["SNIP_1_RECTANGLE" ,"STAR_10_POINT" ,"STAR_4_POINT" ,"STRIPED_RIGHT_ARROW" ,"U_TURN_ARROW" ,""], ["SNIP_2_DIAG_RECTANGLE","STAR_12_POINT" ,"STAR_5_POINT" ,"SUN" ,"UP_RIBBON" ,""], ] title_width = Cm(25) title_left = (SLIDE_WIDTH - title_width) / 2 textbox = shapes.add_textbox(title_left, 0, title_width, plot_top) textbox.text = "MSO_SHAPE List (4)" textbox.text_frame.paragraphs[0].font.size = Pt(30) textbox.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER textbox.text_frame.virtical_anchor = MSO_ANCHOR.MIDDLE for row in range(5): for col in range(6): textbox = shapes.add_textbox(text_x(col), text_y(row), SLIDE_WIDTH/6, Cm(1)) textbox.text = mso_list[row][col] textbox.text_frame.paragraphs[0].font.size = Pt(9) textbox.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER textbox.text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE # 図形内のテキストに合わせて、必要に応じてフォントサイズを縮小 # Page 5 slide = ppt.slides.add_slide(ppt.slide_layouts[6]) shapes = slide.shapes # set line # horizontal line for i in range(6): shapes.add_shape(MSO_SHAPE.LINE_INVERSE, 0, plot_top+(plotzone_height/5)*i, SLIDE_WIDTH, 0) # virtical line for i in range(5): shapes.add_shape(MSO_SHAPE.LINE_INVERSE, SLIDE_WIDTH/6*(i+1), plot_top, 0, plotzone_height) shapes.add_shape(MSO_SHAPE.DOWN_ARROW_CALLOUT ,table_x(0,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LEFT_ARROW_CALLOUT ,table_x(0,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.UP_ARROW_CALLOUT ,table_x(0,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.RIGHT_ARROW_CALLOUT ,table_x(0,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LEFT_RIGHT_ARROW_CALLOUT ,table_x(0,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.UP_DOWN_ARROW_CALLOUT ,table_x(1,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LINE_CALLOUT_1 ,table_x(1,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LINE_CALLOUT_1_ACCENT_BAR ,table_x(1,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LINE_CALLOUT_1_BORDER_AND_ACCENT_BAR ,table_x(1,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LINE_CALLOUT_1_NO_BORDER ,table_x(1,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LINE_CALLOUT_2 ,table_x(2,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LINE_CALLOUT_2_ACCENT_BAR ,table_x(2,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LINE_CALLOUT_2_BORDER_AND_ACCENT_BAR ,table_x(2,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LINE_CALLOUT_2_NO_BORDER ,table_x(2,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LINE_CALLOUT_3 ,table_x(2,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LINE_CALLOUT_3_ACCENT_BAR ,table_x(3,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LINE_CALLOUT_3_BORDER_AND_ACCENT_BAR ,table_x(3,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LINE_CALLOUT_3_NO_BORDER ,table_x(3,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LINE_CALLOUT_4 ,table_x(3,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LINE_CALLOUT_4_ACCENT_BAR ,table_x(3,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LINE_CALLOUT_4_BORDER_AND_ACCENT_BAR ,table_x(4,Cm(2)), table_y(0), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.LINE_CALLOUT_4_NO_BORDER ,table_x(4,Cm(2)), table_y(1), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.OVAL_CALLOUT ,table_x(4,Cm(2)), table_y(2), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.QUAD_ARROW_CALLOUT ,table_x(4,Cm(2)), table_y(3), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.RECTANGULAR_CALLOUT ,table_x(4,Cm(2)), table_y(4), Cm(2), Cm(2)) shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGULAR_CALLOUT ,table_x(5,Cm(2)), table_y(0), Cm(2), Cm(2)) mso_list = [ ["DOWN_ARROW_CALLOUT" ,"UP_DOWN_ARROW_CALLOUT" ,"LINE_CALLOUT_2" ,"LINE_CALLOUT_3_ACCENT_BAR" ,"LINE_CALLOUT_4\v_BORDER_AND_ACCENT_BAR","RPINDED_RECTANGULAR\v_CALLOUT"], ["LEFT_ARROW_CALLOUT" ,"LINE_CALLOUT_1" ,"LINE_CALLOUT_2_ACCENT_BAR" ,"LINE_CALLOUT_3\v_BORDER_AND_ACCENT_BAR","LINE_CALLOUT_4_NO_BORDER",""], ["UP_ARROW_CALLOUT" ,"LINE_CALLOUT_1_ACCENT_BAR" ,"LINE_CALLOUT_2\v_BORDERED_AND_ACCENT_BAR","LINE_CALLOUT_3_NO_BORDER","OVAL_CALLOUT",""], ["RIGHT_ARROW_CALLOUT" ,"LINE_CALLOUT_1\v_BORDERED_AND_ACCENT_BAR" ,"LINE_CALLOUT_2_NO_BORDER" ,"LINE_CALLOUT_4","QUAD_ARROW_CALLOUT" ,""], ["LEFT_RIGHT_ARROW_CALLOUT" ,"LINE_CALLOUT_1_NO_BORDER" ,"LINE_CALLOUT_3" ,"LINE_CALLOUT_4_ACCENT_BAR","RECTANGULAR_CALLOUT" ,""], ] title_width = Cm(25) title_left = (SLIDE_WIDTH - title_width) / 2 textbox = shapes.add_textbox(title_left, 0, title_width, plot_top) textbox.text = "MSO_SHAPE List (5) callout" textbox.text_frame.paragraphs[0].font.size = Pt(30) textbox.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER textbox.text_frame.virtical_anchor = MSO_ANCHOR.MIDDLE for row in range(5): for col in range(6): textbox = shapes.add_textbox(text_x(col), text_y(row), SLIDE_WIDTH/6, Cm(1)) textbox.text = mso_list[row][col] textbox.text_frame.paragraphs[0].font.size = Pt(9) textbox.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER textbox.text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE ppt.save(OUTPUT_PATH) まとめ このように出力してふと思ったのですが、パワーポイントにおける図形って、毎回毎回違うものが求められるような気がします。 図形に関しては、自動化を目指すよりはショートカットを覚えた方が効率は良さそうだと感じました。 実用性がありそうなのは、直線を引くときか、何個も同じような図形を綺麗に配置するときくらいだと思います。
- 投稿日:2021-08-29T21:29:43+09:00
CSVを読み込んで,”,”で分割するコード
読み込んだcsv1が次のようなものだった: 基準日,基準価額(円),純資産総額(億円),分配金税引前(円) 0 2002年4月2日,81084,32.4,- 1 2002年4月3日,82157,32.9,- 2 2002年4月4日,82456,33.0,- 3 2002年4月5日,81347,32.5,- 4 2002年4月8日,81880,32.8,- ... ... 4763 2021年8月23日,89354,18.9,- 4764 2021年8月24日,90156,19.1,- 4765 2021年8月25日,90248,19.1,- 4766 2021年8月26日,89937,19.1,- 4767 2021年8月27日,89544,19.0,- 1つのcolumnに日付,基準価額等のデータが入っている.全て,","で分割したい. df = pd.read_csv("txp_c30.csv",header=1) print(df[:3]) df=df.set_axis(["NEXT_FUNDS_TOPIX_Core30"],axis=1) print(df[:3]) df_spr = df['NEXT_FUNDS_TOPIX_Core30'].str.split(',',expand=True) print(df_spr[:3]) df=df_spr.set_axis(['Date','Base price','Net asset',"pre-tax dividend"],axis=1) print(df[:3]) .set_axisはaxis=0でrow,=1でcolumnのindexを変更できる. 結果 基準日,基準価額(円),純資産総額(億円),分配金税引前(円) 0 2002年4月2日,81084,32.4,- 1 2002年4月3日,82157,32.9,- 2 2002年4月4日,82456,33.0,- NEXT_FUNDS_TOPIX_Core30 0 2002年4月2日,81084,32.4,- 1 2002年4月3日,82157,32.9,- 2 2002年4月4日,82456,33.0,- 0 1 2 3 0 2002年4月2日 81084 32.4 - 1 2002年4月3日 82157 32.9 - 2 2002年4月4日 82456 33.0 - Date Base price Net asset pre-tax dividend 0 2002年4月2日 81084 32.4 - 1 2002年4月3日 82157 32.9 - 2 2002年4月4日 82456 33.0 - https://nextfunds.jp/lineup/1311/ NEXT FUNDS TOPIX Core 30連動型上場投信 ↩
- 投稿日:2021-08-29T21:21:00+09:00
異常検知手法 Isolation Forestの解説、スクラッチでの実装
Isolation Forestとは 異常検知に用いられる手法の一つです。 名前からお察しの通り、Isolation ForestはRandom Forestと同様に決定木に基づいて構築されます。 決定木を各データが孤立するまで分割を繰り返し、データが孤立するまでの距離(深さ)から異常値を推定しよう、というのが基本的なアイディアです(Isolate=孤立)。全データを使って決定木を一つだけ作成すると過学習してしまうので、データをサンプリングした上で、大量の決定木を作成し、作成した決定木の各データが孤立するまでの距離の平均を使用して異常値スコアを算出します。 なお、分割する際の閾値は変数の最大/最小値の範囲からランダムに選択されますが(普通の決定木は情報利得により閾値を決定)、この手順により異常値は早く孤立することが直感的に理解できる例があったのでそのまま記載します。 例えば、IQはだいたい100を中心に分布するよう調整されていますが、たまたまIQ3億のデータがあると、そのデータの区間は(80くらい, 3億)になります。この区間で分割する値をランダムに決める(一様分布とする)と、3億のデータ点がisolateされる確率はめちゃくちゃ高いですよね。 参考:https://www.mojirca.com/2019/12/isolation-forest.html 異常値スコアの算出方法 異常値スコアを算出する際は、孤立するまでの深さの平均をそのまま使用するのではなく、二分探索する際の平均の深さc(n)で正規化します。これによりスコアは0~1に収まる用になり、比較がしやすくなります。 スコアの解釈については下記の通りになります。 スコアが1に近いと異常値である可能性が高い スコアが0.5よりはるかに小さい場合、通常値である可能性が高い 全てのデータのスコアが≒0.5のとき、サンプル全体に異常はない可能性が高い 実装 ここからはsklearnを使用した実装と、スクラッチでの実装を行ってきます。始めに使用するデータを生成します。 import pandas as pd import numpy as np import seaborn as sns sns.set() np.random.seed(123) # 平均と分散 mean1 = np.array([2, 2]) mean2 = np.array([-2, -2]) cov = np.array([[1, 0], [0, 1]]) # 正常データの生成(2つの正規分布から生成) norm1 = np.random.multivariate_normal(mean1, cov, size=100) norm2 = np.random.multivariate_normal(mean2, cov, size=100) # 異常データの生成(一様分布から生成) lower, upper = -10, 10 anom = (upper - lower)*np.random.rand(10, 2) + lower df = np.vstack([norm1, norm2, anom]) df = pd.DataFrame(df, columns=["feat1", "feat2"]) # 可視化 sns.scatterplot(x="feat1", y="feat2", data=df) sklearnを使用した実装 sklearnを使用すれば、数行で実装が可能です。-1が異常値(青)、1が通常値(オレンジ)となります。 # sklearnでの実装 from sklearn.ensemble import IsolationForest sk_df = df.copy() clf = IsolationForest(n_estimators=100, random_state=123) clf.fit(sk_df) sk_df["predict"] = clf.predict(sk_df) # 可視化 sns.scatterplot(x="feat1", y="feat2", data=sk_df, hue='predict', palette='bright') スクラッチでの実装 続いて、理解を深めるためにスクラッチでの実装を行います。 こちらのgitに公開されているコードを一部修正して、日本語のコメントを差し込んだ形となります。 https://github.com/tianqwang/isolation-forest-from-scratch/blob/master/iforest.py まずは決定木を作るclassから。 ランダムに変数を決定→その変数の最大/最小値からランダムに閾値を決定→分割 の流れを分岐条件を保持しつつ、再起処理で繰り返します。今回の実装では、分割処理はデータが孤立する or 一定の深さまで到達するまで繰り返し行います。興味があるのは浅いところで孤立するデータなので、ある程度の深さまで到達すれば打ち切ってしまってOKという考え方です。 # 葉の情報と分岐条件を保存する箱 class LeafNode: def __init__(self, size, data): self.size = size self.data = data # 分岐条件を保存する箱 class DecisionNode: def __init__(self, left, right, splitAtt, splitVal): self.left = left self.right = right self.splitAtt = splitAtt self.splitVal = splitVal class IsolationTree: def __init__(self, height, height_limit): self.height = height self.height_limit = height_limit def fit(self, X: np.ndarray, improved=False): if isinstance(X, pd.DataFrame): X = X.values # 再起処理の終了要件(データが孤立する or 指定の深さに達する) if X.shape[0] <= 1 or self.height >= self.height_limit: # gitではX.shape[0] <= 2だが、多分1が正しい # 葉の情報の保存 self.root = LeafNode(size=X.shape[0], data=X) return self.root # 変数を1つランダムに抽出 num_features = X.shape[1] splitAtt = np.random.randint(0, num_features) # 変数のmin/maxから閾値をランダムに決定し、2分割 splitVal = np.random.uniform(min(X[:, splitAtt]), max(X[:, splitAtt])) X_left = X[X[:, splitAtt] < splitVal] X_right = X[X[:, splitAtt] >= splitVal] # 再起処理により分割を実施 left = IsolationTree(self.height + 1, self.height_limit) right = IsolationTree(self.height + 1, self.height_limit) left.fit(X_left) right.fit(X_right) # 分岐条件の保存 self.root = DecisionNode(left.root, right.root, splitAtt, splitVal) return self.root 続いてIsolationTreeを繰り返し作成し、path_length(孤立するまでの深さ)と異常値スコアを算出するクラスです。 各ツリーを作成する前に、指定した件数でデータのサンプリングを行います。 def c(size): # 二分探索する際の平均の深さc(n)の算出 if size > 2: return 2 * (np.log(size-1)+0.5772156649) - 2*(size-1)/size if size == 2: return 1 return 0 class IsolationTreeEnsemble: def __init__(self, sample_size, n_trees=10): self.sample_size = sample_size self.n_trees = n_trees def fit(self, X: np.ndarray, improved=False): if isinstance(X, pd.DataFrame): X = X.values # データの件数 n_rows = X.shape[0] # 深さの上限の指定 height_limit = np.ceil(np.log2(self.sample_size)) # 指定の数だけIsolationTreeを作成 self.trees = [] for i in range(self.n_trees): # 指定した件数分、ランダムにデータを抽出 data_index = np.random.randint(0, n_rows, self.sample_size) X_sub = X[data_index] # IsolationTreeを作成 tree = IsolationTree(0, height_limit) tree.fit(X_sub) self.trees.append(tree) return self def path_length(self, X: np.ndarray) -> np.ndarray: paths = [] # 各データを作成したツリーに当てはめ for row in X: path = [] for tree in self.trees: node = tree.root # leafまで到達するまでの距離を測る length = 0 while isinstance(node, DecisionNode): if row[node.splitAtt] < node.splitVal: node = node.left else: node = node.right length += 1 leaf_size = node.size # path lengthの算出。孤立する前に既定の深さに達した場合は+ c(leaf_size)で調整 # ※ leaf_size=1のとき、c(leaf_size) = 0 pathLength = length + c(leaf_size) path.append(pathLength) paths.append(path) # 深さの平均値を算出 paths = np.array(paths) return np.mean(paths, axis=1) def anomaly_score(self, X: pd.DataFrame) -> np.ndarray: if isinstance(X, pd.DataFrame): X = X.values # 各データの深さの平均を算出し、その後異常値スコアを算出 avg_length = self.path_length(X) scores = np.array([np.power(2, -l/c(self.sample_size)) for l in avg_length]) return scores 挙動確認 作成したクラスの挙動を確認します。sklearnに合わせて、スコアが閾値を上回る場合は-1(異常値)、下回る場合は1(通常値)でラベルをつけます。今回は閾値を0.5に設定しましたが、概ねsklearnと同じ結果が得られることが確認できます。 # 作成したクラスでの実装 scratch_df = df.copy() # 閾値 thr = 0.5 scratch_tree = IsolationTreeEnsemble(sample_size=300) scratch_tree.fit(scratch_df) scratch_df["score"] = scratch_tree.anomaly_score(scratch_df) # スコアが閾値を上回る場合は-1(異常値)、下回る場合は1(通常値)でラベル付け scratch_df["predict"] = scratch_df.score.apply(lambda x: 1 if x < thr else -1) # 可視化 sns.scatterplot(x="feat1", y="feat2", data=scratch_df, hue='predict', palette='bright') 参考 論文 https://cs.nju.edu.cn/zhouzh/zhouzh.files/publication/icdm08b.pdf 異常検出アルゴリズム Isolation Forest ラベルなし異常検出アルゴリズムIsolationForestについて解説する 異常検知入門と手法まとめ https://github.com/tianqwang/isolation-forest-from-scratch/blob/master/iforest.py その他 別の異常検知の手法であるLocal Outlier Factor (LOF) についても理論解説、スクラッチでの実装を行っています。よろしければこちらの記事もご参照下さい。 Local Outlier Factor (LOF) の算出方法、スクラッチでの実装
- 投稿日:2021-08-29T21:11:11+09:00
Python仮想環境
TL;DR Pythonでは、Dockerなどを用いることなく簡単に仮想環境を構築することができます。 仮想環境のメリット 仮想環境構築が簡単 PC上にライブラリをインストールしなくていいので無駄にハードディスク内を汚さなくて済む。 仕様ライブラリを一括インストールできるので複数人での開発で便利 導入しない理由がない導入しましょう! 仮想環境 仮想環境作成 プロジェクト作成時に行うこと。 $ python -m venv [フォルダ名] $ python -m venv .venv .venvというフォルダができたかと思います。 ここにライブラリをインストールしていくことになります。 プロジェクトで一度作れば次回からはこのコマンドは必要なし。 仮想環境に入る Windows $ source .venv/Scripts/Activate Mac $ source .venv/bin/activate シェルに(.venv)と表示されていれば仮想環境内に入っている状態です。 zshのテーマによっては出てこないテーマもあるので注意。。。 alias作っておくと便利。 VSCodeのインタープリター設定 venvがついてるものを選択 これで仮想環境に入れました。 ライブラリインストール $ pip install <ライブラリ> pipの一括インストールオプション インストールしたライブラリを書き込み $ pip freeze > requirements.txt requirements.txtのライブラリを一括インストール $ pip install -r requirements.txt 仮想環境に入ってる状態でインストールしないとローカルに一括インストールされてしまう。 一括インストールするときは必ず仮想環境の中に入っていることを確認する。
- 投稿日:2021-08-29T21:03:56+09:00
ワードウルフのお題(単語ペア)を word2vec で自動生成する
はじめに 「ワードウルフ」という人狼系のゲームがあります。 ワードウルフとは、みんなで“あるお題”について話し合う中、「みんなとは異なるお題」を与えられた少数派の人(ワードウルフ)を探し出すゲームです。村人に紛れた人狼を見つけ出す「人狼ゲーム」に似ているので、ワード人狼と呼ばれることもあります。 出典:【全てわかる】『ワードウルフ』少数派探しゲームを徹底紹介 | ぼくとボドゲ スマホアプリなどで手軽に遊べて盛り上がるゲームなんですが、長く楽しむためには、良いお題(村人に与えるお題と人狼に与えるお題のペア=近いけど異なる単語ペア)がたくさん必要です。というわけで、ワードウルフのお題を大量に自動生成してみます。 やったこと 近いけど異なる単語ペアを自動生成できればよいのですが、やり方は色々ありそうです。今回は、単語をベクトル化(分散表現化)1 して単語間の類似度を定量的に算出できるようにするアプローチでやってみました。「単語のベクトル化(分散表現化)」手法としては、word2vec2 を使いました。word2vec では、同じような意味や使われ方をする単語は同じような文脈の中に登場するという考え方に基づいて、単語をベクトル化します。 というわけで、方針は以下のとおりです。 wikipedia の日本語記事を word2vec で学習して単語の分散表現を獲得 分散表現間の距離(単語の類似度)に基づいて単語ペアを自動生成 以降では、Python での実装方法を説明していきます。 wikipedia の日本語記事を word2vec で学習して単語の分散表現を獲得 単語の分散表現を学習するためのデータセットとしては、wikipedia の日本語記事を使いました。TensorFlow Datasets の wiki40b が前処理済みでとても便利です3。 wikipedia の日本語記事(wiki40b/ja)を読み込み wiki40b/ja には、wikipedia の日本語記事 80万ページほどが収録されています。今回は、train(収録全体のうち 90%)、validation(収録全体のうち 5%)、test(収録全体のうち 5%)の全てを使います。 # wiki40b/ja を読み込む import tensorflow_datasets as tfds dataset = tfds.load('wiki40b/ja', split='train+validation+test') 読み込んだ記事を単語単位に分かち書き 形態素解析器の MeCab(辞書は、比較的新しい固有表現にも対応している mecab-ipadic-NEologdを選択)を使って分かち書きするための関数を、以下のように定義しておきます。 # テキストを分かち書きする関数を定義 import MeCab mecab = MeCab.Tagger('-d <辞書が格納された場所のパス>') def wakati(text): # 形態素解析 mecab.parse('') node = mecab.parseToNode(text) arr_all = [] arr_nav = [] arr_n = [] while node: pos0 = node.feature.split(',')[0] # 品詞 pos1 = node.feature.split(',')[1] # 品詞細分類1 pos2 = node.feature.split(',')[2] # 品詞細分類2 pos3 = node.feature.split(',')[3] # 品詞細分類3 word = node.feature.split(',')[6] # 原形 if word == '*': word = node.surface # 表層形 if len(word) > 0: # 全部出力 arr_all.append(word) # 名詞・形容詞・動詞のみ出力 if ( ( pos0 == '名詞' and ( pos1 == 'サ変接続' or pos1 == 'ナイ形容詞語幹' or pos1 == '形容動詞語幹' or pos1 == '一般' or pos1 == '固有名詞' ) ) or (pos0 == '形容詞' and pos1 == '自立') or (pos0 == '動詞' and pos1 == '自立') ): arr_nav.append(word) # 名詞のみ出力 if ( pos0 == '名詞' and ( pos1 == '一般' or ( pos1 == '固有名詞' and ( pos2 == '一般' or pos2 == '地域' or pos2 == '組織' or ( pos2 == '人名' and pos3 == '一般' ) ) ) ) ): arr_n.append(word) node = node.next # スペース区切りの文字列にして出力 return ( ' '.join(arr_all) if len(arr_all) > 0 else '', ' '.join(arr_nav) if len(arr_nav) > 0 else '', ' '.join(arr_n) if len(arr_n) > 0 else '' ) この関数は、「全品詞」「名詞・形容詞・動詞のみ」「名詞のみ」の3通りの分かち書き結果を出力します。たとえば、この関数で『ワードウルフのお題を自動で作ってみます』を分かち書きすると、以下のようになります。 ('ワードウルフ の お題 を 自動 で 作る て みる ます', 'ワードウルフ お題 自動 作る', 'ワードウルフ お題 自動') word2vec で学習しやすいよう、単語は原型に変換して出力しています。また、この関数の3つの出力のうち、学習では2つ目の「名詞・形容詞・動詞のみ」を、単語ペアを作るときには3つ目の「名詞のみ」を使います。 学習用のデータセットと単語ペア作成用のデータセットを準備 後で使い回せるよう、記事を分かち書きした結果を、1ページ1行のテキストファイルに保存しておきます。 # データセットの準備 file_all = 'data/wiki40b_ja_all.txt' file_nav = 'data/wiki40b_ja_nav.txt' file_n = 'data/wiki40b_ja_n.txt' print('start converting wiki40b/ja') with ( open(file_all, 'w') as fw_all, open(file_nav, 'w') as fw_nav, open(file_n , 'w') as fw_n, ): count = 0 # wiki40b のデータをイテレート(ページ単位) for item in dataset.as_numpy_iterator(): arr_all = [] arr_nav = [] arr_n = [] # ページをパース for text in item['text'].decode('utf-8').split('\n'): if ( len(text) > 0 and text != '_START_ARTICLE_' and text != '_START_SECTION_' and text != '_START_PARAGRAPH_' ): text = text.replace('_NEWLINE_', ' ') words_all, words_nav, words_n = wakati(text) arr_all.append(words_all) arr_nav.append(words_nav) arr_n.append(words_n) # 出力 fw_all.write(' '.join(arr_all) + '\n') fw_nav.write(' '.join(arr_nav) + '\n') fw_n.write(' '.join(arr_n) + '\n') # 進捗表示 count += 1 if count % 10000 == 0: print('*', end='') if count % 100000 == 0: print(' {:,} paragraphs finished'.format(count)) print('\nfinished!') word2vec で学習して単語の分散表現を獲得 自然言語処理でよく使われる Python ライブラリ gensim を使って、word2vec していきます4。 # gensim で word2vec の学習 from datetime import datetime as dt from gensim.models import word2vec from gensim.models.callbacks import CallbackAny2Vec # エポック毎にロス(学習データでの損失)を表示するコールバッククラスを定義 class MyLossCalculator(CallbackAny2Vec): def __init__(self, file_model, file_log, dt_start, every): self.file_model = file_model self.file_log = file_log self.dt_start = dt_start self.every = every self.epoch = 1 # エポック開始時の処理 def on_epoch_begin(self, model): pass # エポック終了時の処理 def on_epoch_end(self, model): # ロスを取得 loss = model.get_latest_training_loss() print('Epock #{} end : loss = {:.2f}'.format(self.epoch, loss)) # 学習済みモデルを保存 if self.epoch % self.every == 0: model.save(self.file_model.format(self.epoch)) # ロスをログに記録 with open(self.file_log, mode='a') as f: f.write('{},{:.2f},{:.1f}\n'.format( self.epoch, loss, (dt.now() - self.dt_start).total_seconds() )) # 次のエポックの準備 self.epoch += 1 model.running_training_loss = 0.0 # 入出力データのパス設定 file_dataset = 'data/wiki40b_ja_nav.txt' file_model = 'data/model-size200_win5_min10_ns20.epoch{:04d}.model' file_log = 'data/model-size200_win5_min10_ns20.log' # 時間計測準備 dt_start = dt.now() print('start training') # ログファイル準備 with open(file_log, mode='w') as f: f.write('epoch,loss,seconds\n') # コールバック準備(10エポックごとに学習済みモデルを出力) mycallback = MyLossCalculator(file_model, file_log, dt_start, 10) # 学習実行 model = word2vec.Word2Vec( corpus_file=file_dataset, vector_size=200, # ベクトルの次元数 window=5, # 窓サイズ(出現判定のための単語間距離の最大値) min_count=10, # 最小出現頻度(これより少ない単語は無視) sg=1, # skip-gram モデルを使用 hs=0, # 全結合層に negative sampling を使用 negative=20, # negative sampling する要素数 ns_exponent=0.75, # negative sampling のための頻度分布平滑化係数 workers=3, # 並列計算のワーカースレッド数 epochs=1000, # 総学習エポック数 compute_loss=True, callbacks=[mycallback] ) print('finished! processing time = {:,.2f} seconds'.format( (dt.now() - dt_start).total_seconds() )) wikipedia の全記事を扱うため、学習にはかなりの時間がかかります。Xeon が載ったハイスペックな環境で実行しても、1000エポック回すのには1週間程度かかりました。下のグラフは100エポックあたりまでの学習ロスの変化ですが、これ以降は殆ど学習が進まなかったので、今回は100エポック時点の学習済みモデルを使うことにしました。 分散表現間の距離(単語の類似度)に基づいて単語ペアを自動生成 前述したとおり、今回は、wikipedia の記事に登場する名詞(のうち、一般名詞、および、固有名詞の一部)の中から単語ペアを作ります。 単語ペア作成用の語彙辞書を作成 準備しておいた単語ペア作成用のデータセットから、gensim の機能を使って語彙辞書(記事に出現する単語の集合)を作ります。 # 単語ペアを作るための語彙辞書を生成 from gensim.corpora import Dictionary # 語彙辞書 dct = Dictionary() # 単語ペア作成用の wikipedia データセット(名詞のみ)を読み込み count = 0 with open('data/wiki40b_ja_n.txt') as f: for line in f: # データセットから wikipedia 記事を取得して単語辞書に登録 dct.add_documents([line.split()], prune_at=4000000) # 進捗表示 count += 1 if count % 10000 == 0: print('*', end='') if count % 100000 == 0: print(' {:,} lines finished'.format(count)) print('\nfinished!') # 語彙を枝刈り print('num original tokens = {}'.format(len(dct.token2id.keys()))) dct.filter_extremes( no_below=10, # 最小出現頻度(これより少ない単語はドロップ) no_above=0.1, # 最大出現ページ割合(これより大きな単語はドロップ) keep_n=1000000 ) print('num filtered tokens = {}'.format(len(dct.token2id.keys()))) # 語彙をリスト化 tokens = list(dct.token2id.keys()) 実行結果はこんな感じ。 ********** 100,000 lines finished ********** 200,000 lines finished ********** 300,000 lines finished ********** 400,000 lines finished ********** 500,000 lines finished ********** 600,000 lines finished ********** 700,000 lines finished ********** 800,000 lines finished ** finished! num original tokens = 2443401 num filtered tokens = 331836 wikipedia 記事に登場する名詞(のうち、一般名詞、および、固有名詞の一部)総数約 244 万語のうち、約 33 万語が抽出されました。この語彙辞書から単語ペアを作っていきます。 語彙辞書から類似する(分散表現間の距離が近い)単語のペアを自動選定 作成した語彙辞書から単語をランダムに選定し、選定した単語に類似した単語を word2vec の学習済みモデルで抽出することで、単語ペアを機械的に作ることができます。 一方で、語彙辞書には形態素解析で名詞(のうち、一般名詞、および、固有名詞の一部)と判定された単語のみが登録されていますが、この中には、「2021年」や「12時」などの時間表現や、「120円」「10kg」「41人」などの数値表現など、ワードウルフのお題としてふさわしくない名詞が多数含まれています。 そこでまず、これらの固有表現を自動でチェックするための関数を用意しておきます。固有表現抽出には GiNZA5を使います。 # テキストの固有表現をチェックする関数を定義 import spacy nlp = spacy.load('ja_ginza') # 時間表現や数値表現に該当する固有表現ラベルの集合を設定 # http://liat-aip.sakura.ne.jp/ene/ene8/definition_jp/html/enedetail.html drop_entity = set([ 'Time', 'Date', 'Period_Time', 'Period_Day', 'Period_Week', 'Period_Month', 'Period_Year', 'Numex_Other', 'Money', 'Point', 'Percent', 'Multiplication', 'Frequency', 'Age', 'School_Age', 'Ordinal_Number', 'Rank', 'Latitude_Longitude', 'Measurement', 'Measurement_Other', 'Physical_Extent', 'Space', 'Volume', 'Weight', 'Speed', 'Intensity', 'Temperature', 'Calorie', 'Seismic_Intensity', 'Seismic_Magnitude', 'N_Person', 'N_Organization', 'N_Location', 'N_Location_Other', 'N_Country', 'N_Facility', 'N_Product', 'N_Event', 'N_Natural_Object', 'N_Natural_Object_Other', 'N_Animal', 'N_Flora' ]) # text が時間表現や数値表現なら True、それ以外は False def is_time_or_numex(text): # テキストを形態素解析しつつ固有表現を抽出 doc = nlp(text) # 時間表現や数値表現が含まれるかどうか判定 for ent in doc.ents: if ent.label_ in drop_entity: return True return False また、選んだ単語ペアがあまりにも似すぎているとワードウルフのお題としてふさわしくありません。せめて表記くらいは似すぎないようにしたいので、お互いに同一の文字をなるべく含まないようなチェックをするための関数を用意しておきます。 今回は、文字列間の『表記上の』類似度を定量的に測る指標として、編集距離(レーベンシュタイン距離)を採用しました。編集距離の算出には python-Levenshtein パッケージ、カタカナとかなの表記統一には jaconv パッケージをそれぞれ使いました。 # テキスト間の表記上の類似度を判定する関数を定義 import Levenshtein import jaconv # 似ているなら True、似ていないなら False def is_similar(str1, str2): # カタカナはひらがなに統一 str1h = jaconv.kata2hira(str1) str2h = jaconv.kata2hira(str2) # 編集距離を算出 dist = Levenshtein.distance(str1h, str2h) # スコア化(長い方の文字列長で編集距離を正規化) score = dist / max(len(str1h), len(str2h)) # スコアが0.5未満か、お互いにお互いの文字列が含まれている場合「似ている」 if score < 0.5 or str1h in str2h or str2h in str1h: return True else: return False というわけで、ようやく、語彙辞書から類似(分散表現間の距離が近い)単語のペアを自動選定する関数が作れます。こんな感じにしてみました。 # 類似する単語ペアを自動生成する関数を定義 import random from gensim.models import word2vec # word2vec の学習済みモデルを読み込み model = word2vec.Word2Vec.load('data/model-size200_win5_min10_ns20.epoch0100.model') # 単語ペアを自動生成する def get_pair(model, tokens, max_try=100): # 最大100回試行 cnt = 0 while cnt < 100: # 語彙辞書から単語(token1)をランダムに選定 token1 = None while True: token1 = random.choice(tokens) if not is_time_or_numex(token1): # 時間表現や数値表現でないか break # token1 に類似した単語(上位100個)を word2vec で取得 results = model.wv.most_similar(positive=token1, topn=100) for result in results: token2 = result[0] if ( token2 in tokens and # 語彙辞書に含まれているか not is_time_or_numex(token2) and # 時間表現や数値表現でないか not is_similar(token1, token2) # token1 と表記が似ていないか ): # 条件に合致するペアが見つかったら出力 return (token1, token2) # 試行回数をインクリメント cnt += 1 # 条件に合致するペアが見つからなかったら None を出力 return None 自動生成された単語ペアの例 get_pair() の出力例をいくつか載せておきます。 ('ゆうもあ大賞', 'アカデミー名誉賞') ('手元資金', '投資額') ('薄田兼相', '後藤基次') ('ビードル', 'マクリントック') ('魔獣', '幻獣') ('経済産業事務次官', '国土交通事務次官') ('豊川市内', '岡崎市内') ('東京都社会人サッカーリーグ', 'J3リーグ') ('アナルコサンディカリスム', '無政府主義') ('ピーター・トッシュ', 'ボブ・マーリー') これ、、、確かに「近いけど異なる単語ペア」ではあるんですが、ワードウルフのお題としてはあまりにもマニアック過ぎますね。 おわりに というわけで、自然言語処理技術を活用して「近いけど異なる単語ペア」を大量に自動生成できるようになりました。が、ワードウルフのお題とするには、もう少しの(いや、かなりの)工夫が必要そうです。 「分散表現(単語埋め込み) - 岩波データサイエンス」 ↩ 「Word2vec - Wikipedia」 ↩ 「Wikipediaの前処理はもうやめて「Wiki-40B」を使う - Ahogrammer」「wiki-40b の使い方|npaka|note」 ↩ 「models.word2vec – Word2vec embeddings — gensim」「Python で「老人と海」を word2vec する - #m0t0k1ch1st0ry」「【Python】Word2Vecの使い方 - Qiita」 ↩ つい数日前(2021年8月26日)に、Transformer モデルを取り入れて大幅に性能が上がったとされるGiNZA v5 がリリースされていますが、今回は旧バージョンの GiNZA を利用しています。 ↩
- 投稿日:2021-08-29T20:54:35+09:00
[Python]HeartRails ExpresとホットペッパーAPIを用いた飲食店検索API作成方法 メモ
指定した駅近辺の飲食店情報を取得するAPIの作成方法をメモする。 以下のAPIを利用する。 HeartRails Expres API : 指定した駅名からの緯度・経度取得に利用する。 ホットペッパーAPI : 店舗情報取得に利用する。 FastAPIを使用してAPI化する。 事前準備 ホットペッパーAPIキー取得 こちらのページからユーザー登録を行い、APIキーを取得する。 構成 app --- main.py |_ conf/setting.conf コード conf/setting.conf 事前準備で取得したAPIキーを記述する。 [CRED] API_KEY=YOUR_API_KEY main.py リクエストボディに、都道府県名(pref)と駅名(station)を指定し、近辺の飲食店情報を取得する。 流れ 指定された駅の緯度・経度をHeartRails Express/駅情報取得APIを利用して取得する。 1で取得した緯度・経度からホットペッパーAPIを使用して、飲食店情報(店名、URL、住所、営業時間)を取得する。 from fastapi import FastAPI from pydantic import BaseModel import pandas as pd import configparser import requests import urllib import json app = FastAPI() # 設定ファイルから接続情報読み込み conf = configparser.ConfigParser() conf.read('conf/setting.conf') API_KEY = conf['CRED']['API_KEY'] # HeartRails Expres/ 駅情報取得 API # http://express.heartrails.com/api.html # 都道府県、駅を指定し、緯度・経度を取得 def get_station_position(pref, station): api = 'http://express.heartrails.com/api/json?method=getStations&name={station_name}&prefecture={pref_name}' # URLエンコードした値をクエリパラメータ指定する url = api.format(station_name=urllib.parse.quote( station), pref_name=urllib.parse.quote(pref)) res = requests.get(url) station_info = json.loads(res.text)['response']['station'] station_lat = station_info[0]['y'] station_lng = station_info[0]['x'] return station_lat, station_lng # Hotpepper グルメサーチAPI # https://webservice.recruit.co.jp/doc/hotpepper/reference.html # 緯度・経度を指定し、店舗情報を取得 def get_shops(lat, lng): api_base = "http://webservice.recruit.co.jp/hotpepper/gourmet/v1/?key={key}&lat={lat}&lng={lng}&range=2&order=1&format=json" api_url = api_base.format(key=API_KEY, lat=lat, lng=lng) response = requests.get(api_url) result_list = json.loads(response.text)["results"]["shop"] shops = [] for shop in result_list: shops.append({"name": shop['name'], "url": shop['urls']['pc'], "address": shop['address'], "open": shop['open']}) return shops # リクエストボディモデル定義 class SearchQuery(BaseModel): station: str pref: str # API @app.post("/search_shop") def search_shop(query: SearchQuery): # リクエストボディに指定された都道府県、駅名を取得 pref = query.pref station = query.station station_lat, station_lng = get_station_position(pref, station) return get_shops(station_lat, station_lng) 動作確認 API起動 uvicorn main:app --reload INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) INFO: Started reloader process [1248] using statreload INFO: Started server process [14768] INFO: Waiting for application startup. INFO: Application startup complete. リクエスト POST /search_shop HTTP/1.1 Host: localhost:8000 Content-Type: application/json Content-Length: 43 { "station":"新宿", "pref":"東京都" } レスポンス [ { "name": "ワイアードカフェ WIRED CAFE ルミネ新宿店", "url": "https://www.hotpepper.jp/strJ000788700/?vos=nhppalsa000016", "address": "東京都新宿区西新宿1-1-5 新宿ルミネ2 6F", "open": "月~日、祝日、祝前日: 11:00~22:00" }, { "name": "ラ メゾン アンソレイユターブル ルミネ新宿店", "url": "https://www.hotpepper.jp/strJ000641101/?vos=nhppalsa000016", "address": "東京都新宿区新宿3-38-2 新宿ルミネ2 5F", "open": "月~金、祝前日: 11:00~21:30 (料理L.O. 21:00)土、日、祝日: 10:30~21:30 (料理L.O. 21:00)" }, { "name": "洋麺屋 五右衛門 新宿ミロード店", "url": "https://www.hotpepper.jp/strJ000124697/?vos=nhppalsa000016", "address": "東京都新宿区西新宿1-1-3 小田急新宿ミロード7F", "open": "月~日、祝日、祝前日: 11:00~23:00" }, { "name": "とんかつ和幸 新宿ミロード店", "url": "https://www.hotpepper.jp/strJ000124698/?vos=nhppalsa000016", "address": "東京都新宿区西新宿1-1-3 小田急新宿ミロード8F", "open": "月~日、祝日、祝前日: 11:00~22:30" }, { "name": "卵と私 新宿ミロード店", "url": "https://www.hotpepper.jp/strJ000229709/?vos=nhppalsa000016", "address": "東京都新宿区西新宿1-1-3 小田急新宿ミロード7F", "open": "月~日、祝日、祝前日: 11:00~22:00" }, { "name": "COTO‐COTO 茶寮 新宿ミロード店", "url": "https://www.hotpepper.jp/strJ001125156/?vos=nhppalsa000016", "address": "東京都新宿区西新宿1-1-3 小田急新宿ミロード8F", "open": "月~日、祝日、祝前日: 11:00~23:00 (料理L.O. 22:00 ドリンクL.O. 22:30)" }, { "name": "太陽のトマト麺 Withチーズ 新宿ミロード店", "url": "https://www.hotpepper.jp/strJ000745453/?vos=nhppalsa000016", "address": "東京都新宿区西新宿1-1-3 小田急新宿ミロード7F", "open": "月~日、祝日、祝前日: 11:00~23:00 (料理L.O. 22:30 ドリンクL.O. 22:30)" }, { "name": "ぱすたかん 新宿ミロード店", "url": "https://www.hotpepper.jp/strJ000012392/?vos=nhppalsa000016", "address": "東京都新宿区西新宿1-1-3 小田急新宿ミロード8F", "open": "月~日、祝日、祝前日: 11:00~20:00 (料理L.O. 19:30 ドリンクL.O. 19:30)" }, { "name": "THE MEAT&LABO 新宿ミロード店", "url": "https://www.hotpepper.jp/strJ001201122/?vos=nhppalsa000016", "address": "東京都新宿区西新宿1-1-3 新宿ミロード7F", "open": "月~日、祝日、祝前日: 11:00~21:00 (料理L.O. 20:15)" }, { "name": "Mr.FARMER 新宿ミロード店", "url": "https://www.hotpepper.jp/strJ001145708/?vos=nhppalsa000016", "address": "東京都新宿区西新宿1-1-3 小田急新宿ミロード モザイク通り", "open": "月~日、祝日、祝前日: 11:00~20:00 (料理L.O. 19:00)" } ] 参考情報 ホットペッパー API(リファレンス) HeartRails Expres/ 駅情報取得 API
- 投稿日:2021-08-29T19:15:19+09:00
GANに魅了された人の備忘録#02Autoencorderの簡単な説明から実装まで
はじめに 前回の記事では生成モデルと識別モデルについて説明し、分布間の距離を図るようなKLダイバージェンスについても触れました。本記事では、次元削減や異常検知に利用できるAutoencoder(オートエンコーダー)について説明していきたいと思います。さらに、Pythonで実装してみて理解を深めていきたいと思います。 ↓↓↓前回の記事↓↓↓ ↑↑↑↑↑↑↑↑↑↑↑ Autoencoder(自己符号化器) Autoencoderは、入力する画像を圧縮しそれに近い画像を復元する手法です。ネットワークは入力層、中間層、隠れ層の3層になっています。入力層と出力層は元画像と同じ次元数で、中間層はその次元数よりも小さく設定します。そうすることで、少ない次元数で元の画像の特徴を抽出しているとも言えます。入力層から中間層へ向けて画像を圧縮することをエンコーダと言い、中間層から出力層へ向けて画像を復元することをデコーダと言います。Autoencoderの学習では、画像をピクセル単位で比較します。Autoencoderに限らず、画像を扱う際にはピクセル単位で学習を行うことが多いです。 数学チックな表現 上で紹介したエンコーダやデコーダについて、少し数学チックに表現していきたいと思います。トレーニングデータ$\underline{x}_1,\underline{x}_2,\dots,\underline{x}_n$が得られたとします。ただし、$\underline{x}_i\in\mathbb{R}^p,i=1,2,\dots,n$です。MNISTの手書き数字データだと1枚の画像は28$\times$28=784ピクセルの画像なので$p=784$となり、$n$はデータの個数を表しています。ここで、エンコーダ$h_{\underline{\theta}}$とデコーダ$g_{\underline{\theta}'}$を \begin{align} h_{\underline{\theta}}&:\mathbb{R}^p\to\mathbb{R}^r;\underline{x}\mapsto \sigma\left(W\underline{x}+\underline{b}\right),\\ g_{\underline{\theta}'}&:\mathbb{R}^r\to\mathbb{R}^p;\underline{z}\mapsto \sigma'\left(W'\underline{z}+\underline{b}'\right)\\ \end{align} のように定義します。ただし、$r<p$であり、中間層の次元数は入力層の次元よりも小さく、圧縮しているということを表現しています。また、$W$は$(r\times p)$の重み行列、$\underline{b}$は$r$次元バイアスベクトルで、同様にして、$W'$は$(p\times r)$の重み行列、$\underline{b}'$は$p$次元バイアスベクトルを表しています。エンコーダとデコーダそれぞれの重み行列とバイアスベクトルの大きさや次元数には注意が必要です。ここで、 $\underline{\theta} = \begin{pmatrix}W^T&\underline{b}\end{pmatrix}^T,\underline{\theta}' = \begin{pmatrix}W'^T&\underline{b}'\end{pmatrix}^T$です。推定したいパラメータを$\underline{\theta},\underline{\theta}'$としてまとめています。 さらに、エンコーダとデコーダにある$\sigma:\mathbb{R}^r\to\mathbb{R}^r$や$\sigma':\mathbb{R}^p\to\mathbb{R}^p$は活性化関数です。$\sigma$や$\sigma'$によって学習の精度は変わります。エンコードの活性化関数$\sigma$ではReLUやSigmoid、デコーダの活性化関数$\sigma'$ではSigmoidやIdentityが使われるそうです。そのためデータを正規化して$[0,1]$の値を取るようにデータ加工する必要があります。ここでは、ベクトルを活性化関数で計算するというのはベクトルの各成分を活性化関数で計算していることを意味しています。 少し複雑になりましたが、エンコーダとデコーダを用いて入力層から出力層までの流れを簡単に書くと \begin{align} \text{入力層} &: \underline{x}\\ \text{中間層} &: h_{\underline{\theta}}\left(\underline{x}\right)=:\underline{z}\\ \text{出力層} &: g_{\underline{\theta}'}\left(\underline{z}\right)=g_{\underline{\theta}'}\left(h_{\underline{\theta}}\left(\underline{x}\right)\right)=:\underline{y} \end{align} のようになります。要するに中間層と出力層では前の層に対してエンコーダやデコーダを噛ませているだけですね。ちなみに、"=:"の意味は左辺を右辺で定義するという意味です。 Autoencoderの損失関数 Autoencoderの学習では、パラメータ$\underline{\theta},\underline{\theta}'$をトレーニングデータを用いて再構成誤差関数$L$(Reconstruction error function)を最小化するように構成します。これは目的関数 J_{AE}=\frac{1}{n}\sum_{i=1}^nL\left(\underline{x}_i,\underline{y}_i\right) を最小化することに対応しています。$L$としてクロスエントロピーを使います。クロスエントロピーは \begin{align} \text{クロスエントロピー} &: L\left(\underline{x},\underline{y}\right)=\sum_{k=1}^{p}\left\{-x_k\log(y_k) - (1-x_k)\log(1-y_k) \right\} \end{align} と表されます。 前回の記事との対応を考えると、モデル分布の最適化で最後には \mathbb{E}_{p_d}[\log p_{\underline{\theta}}(\underline{x})] \approx \frac{1}{n}\sum_{i=1}^{n}\log p_{\underline{\theta}}(\underline{x}_i) を最小化すれば良いということでした。ただし、$p_{\underline{\theta}}(\underline{x})$はモデル分布、$p_d(\underline{x})$はデータ分布を表しています。$J_{AE}$の式とよく似ていると思いませんか?そう、モデル分布として二項分布を仮定すれば、上の話と一致します。 具体的にどうやって最適化するのかということには触れません。実際にPythonで見ていきましょう。 AutoencoderのPython実装 それでは、AutoencoderをPythonで実装していきますが、特に「異常検知」に着目して実装を行います。すなわち、AutoEncoderを用いて異常検知を行います。紹介するコードはColaboratoryで実行できることを確認しています。ローカルで実行しようと思いましたが8Gメモリではダメでした。 本記事で使用したデータはFashion-MNISTデータです。このデータは、28$\times$28ピクセルの10種類の服や靴、バッグなどのファッションの白黒の画像データです。トレーニング用には60000個、テストデータ用には10000個のデータがあります。こちらからcsvファイルを入手しました。ラベルは10種類ありましたが、これをさらに大きく分けると、服、ズボン、靴、カバンになります。ここは私の主観で分けていますのでご注意ください。これについては、「データの前処理」という項目で紹介します。この大別から、靴を正常データ、服を異常データとしてAutoencoderで異常検知を行います。 前置きが長くなりましたが、データの読み込みから学習、結果の可視化まで行っていきたいと思います。 準備 データの読み込みや学習の前に準備を行います。 ライブラリのインポート 今回使ったライブラリをインポートします。 ライブラリのインポート #よく使うライブラリ import numpy as np import pandas as pd # torchライブラリ import torch import torchvision from torch.utils.data import DataLoader # 学習に関するライブラリ import torch.nn as nn import torch.optim as optimizers from tqdm.notebook import tqdm # 図の描画に関するライブラリ import seaborn as sns import matplotlib import matplotlib.pyplot as plt %matplotlib inline 乱数の固定 再現性を持たせるために乱数を固定します。 乱数の固定 np.random.seed(777) torch.manual_seed(777) 演算を行うデバイス 演算を行うデバイスの設定のための準備を行います。ColaboratoryでGPUを使うには、こちらの「Google Colab環境でGPUを使うには」という項目を参考にしてください。 演算を行うデバイス device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') print(device) を実行してみて、 実行結果 cuda が出力されれば、GPUを使える状態になっています。 データの準備・前処理・可視化 データを読み込み、前処理もしていきましょう。前処理といってもほとんど何もしていません。ラベルごとに分けたり、Tensor型にしているだけです。Tensor型とは?と思った人はこちらが参考になるかもしれません。 データの読み込み csvファイルのあるフォルダまでのパスをXXXXXに入れてください。ここは人によって違いが出てきます。私は、Colaboratoryでマウントしてそのパスを入れました。マウントすることによって、自分のGoogle Driveにあるデータを読み込むことができます。こちらの記事が参考になるかもしれません。 データの読み込み train_data = pd.read_csv("XXXXX/fashion_mnist_train.csv") test_data = pd.read_csv("XXXXX/fashion_mnist_test.csv") データの前処理 Fashion-MNISTデータには10種類のラベルがあります。それぞれのラベルについては次のようになっています。 0:"T-Shirt" 1:"Trouser" 2:"Pullover" 3:"Dress" 4:"Coat" 5:"Sandal" 6:"Shirt" 7:"Sneaker" 8:"Bag" 9:"Ankle Boot" 以上のように、ラベル0,2,3,4,6は服、1はズボン、5,7,9は靴、8はカバンというように分けられます。先ほども記しましたが、靴を正常データ、服を異常データとして異常検知をAutoencoderで行います。それでは、ラベルごとにトレーニングデータを ラベルごとにデータを分割 train_data0 = train_data[train_data["ID"] == 0].iloc[:,1:] train_data1 = train_data[train_data["ID"] == 1].iloc[:,1:] train_data2 = train_data[train_data["ID"] == 2].iloc[:,1:] train_data3 = train_data[train_data["ID"] == 3].iloc[:,1:] train_data4 = train_data[train_data["ID"] == 4].iloc[:,1:] train_data5 = train_data[train_data["ID"] == 5].iloc[:,1:] train_data6 = train_data[train_data["ID"] == 6].iloc[:,1:] train_data7 = train_data[train_data["ID"] == 7].iloc[:,1:] train_data8 = train_data[train_data["ID"] == 8].iloc[:,1:] train_data9 = train_data[train_data["ID"] == 9].iloc[:,1:] のように実行して分割します。0列目の列名は"ID"でありラベルの情報が入っているため、.iloc[:,1:]として、各画像の各ピクセルの値だけを取り出しています。続いて、靴系をshoes、服系をclothesとしてデータをconcatでまとめています。これよりシンプルなやり方があれば教えていただきたいです。 靴系はshoes、服系はclothes shoes = pd.concat([train_data5, train_data7], axis = 0) shoes = pd.concat([shoes, train_data9], axis = 0) clothes = pd.concat([train_data0, train_data2], axis = 0) clothes = pd.concat([clothes, train_data3], axis = 0) clothes = pd.concat([clothes, train_data4], axis = 0) clothes = pd.concat([clothes, train_data6], axis = 0) データの可視化 どんなデータなのかを可視化しましょう。特に靴と服のデータを見てみます。靴データからランダムに25個選んでそれを描画します。白黒画像は0に近いと黒、255に近いと白を表します。すなわち、値が大きいところが白色となり、画像が浮き上がるため直感とは逆になるかと思います。ここでは、255から各ピクセルの値を引くことで白黒反転します。これについては、コードの2行目に記載があります。 靴の描画 pixel_size = 28 learning_data = (255 - shoes).copy() x_img = learning_data.to_numpy().reshape([learning_data.shape[0], pixel_size, pixel_size]) fig = plt.figure(figsize = (8,6)) fig.subplots_adjust(wspace = 0.1, hspace = 0.1) for i in range(0,25): idx = random.randint(0, len(learning_data)) axes = fig.add_subplot(5, 5, i%25+1) axes.imshow(x_img[idx,:], cmap='gray') axes.axis("off") 実行して得られる図形は次のようになります。色々な靴がありますね。 続いて、服の描画ですが、コードの2行目のshoesをclothesにすれば良いです。これを実行すると次のようになります。 Tensor型への変換 Tensor型でデータを変換し、GPUを使用して演算等を可能にします。さらに、本記事では学習をミニバッチ学習で行うため、データをDataloaderに渡します。最初に255.0で割っているのは、0から1の値をとるように正規化しています。これは、「学習」の項目でも触れますが、デコーダの活性化関数にSigmoid関数を用います。Sigmoid関数は0から1の出力であるため、入力値と出力値の範囲を同じにするために正規化を行っています。同じにする理由は入力と同じになるように学習をするというAutoencoderの特徴のためです。また、ミニバッチのバッチサイズは64として行ってみます。バッチサイズは、メモリとの関係で2のべき乗とすると良いそうです。 Tensor型からDataloaderへ x_train = learning_data.astype(np.float32) / 255.0 x_train = x_train.to_numpy().reshape((len(x_train), np.prod(x_train.shape[1:]))) train_tor = torch.tensor(x_train).float() batch_size = 64 train_loader = DataLoader(train_tor, batch_size = batch_size, shuffle = True) 学習 データの準備、前処理をしたので、次に学習を行っていきましょう。まずは、Autoencoderのネットワークの定義をします。 ネットワークの定義 Autoencoderのネットワークの定義 class Autoencoder(nn.Module): def __init__(self, device = "cpu"): super().__init__() self.device = device self.l1 = nn.Linear(784, 200) self.l2 = nn.Linear(200,784) def forward(self, x): #エンコーダ h1 = self.l1(x) #エンコーダの活性化関数はRelu関数 h2 = torch.relu(h1) #デコーダ h3 = self.l2(h2) #デコーダの活性化関数はシグモイド関数 y = torch.sigmoid(h3) return y forward関数の中に色々と関数があります。これと、上で記載している「数学チックな表現」という項目との対応を見ていきましょう。まず、$p=784$で$r=200$ですね。そして、h1は$\mathbb{R}^{784}$から$\mathbb{R}^{100}$への線形変換$W\underline{x}+\underline{b}$を表しています。そして、h2のRelu関数は$\sigma$に対応します。同じように、h3は$\mathbb{R}^{100}$から$\mathbb{R}^{784}$への線形変換$W'\underline{z}+\underline{b}'$を表しており、yのSigmoid関数は$\sigma'$に対応しています。 少しややこしいかもしれませんがコードに現れる関数がどの関数に対応しているかの把握は重要だと思っていますので、このように対応の説明しました。 モデル、損失関数、最適化関数の設定 学習のモデル、損失関数、最適化関数の設定を行います。モデルは先ほどの「ネットワークの定義」の項目で行ったものです。続いて、損失関数はクロスエントロピーを用います。他にも様々な損失関数がありますので、こちらのLoss Functionsという項目をご参照ください。そして、最適化の計算にはAdamを使っています。 設定 #モデルの設定 model = Autoencoder(device = device).to(device) #損失関数の設定 criterion = nn.BCELoss() #最適化関数の設定 optimizer = optimizers.Adam(model.parameters()) 学習の実行 いよいよ学習の実行です。エポック数を30として行います。また、tqdm()関数を使って、学習の進み具合を可視化しています。最後には損失関数の値をプロットしています。 学習の実行 epochs = 30 #エポックのループ loss_val = [] for epoch in tqdm(range(epochs)): train_loss = 0. #バッチサイズのループ for x in train_loader: x = x.to(device) # 訓練モードへの切替 model.train() # 順伝播計算 preds = model(x) # 入力画像xと復元画像predsの誤差計算 loss = criterion(preds, x) # 勾配の初期化 optimizer.zero_grad() # 誤差の勾配計算 loss.backward() # パラメータの更新 optimizer.step() # 訓練誤差の更新 train_loss += loss.item() train_loss /=len(train_loader) loss_val.append(train_loss) plt.plot(range(1,epochs+1), loss_val) plt.show() エポックごとに損失関数の値が減少していることがわかります。良い感じです。 学習結果 試しに一つだけ画像を復元してみましょう。 画像の復元 #data_loaderからデータの取り出し x = next(iter(train_loader)) x = x.to(device) # 評価モードへの切り替え model.eval() #復元画像 idx = 0 x_rec = model(x) #入力画像、復元画像の表示 for i, image in enumerate([x[idx], x_rec[idx]]): image = image.view(28, 28).detach().cpu().numpy() plt.subplot(1, 2, i + 1) plt.imshow(image, cmap = "gray") plt.axis("off") plt.show() 左側は元の画像、右側が復元した画像になります。ぼやけてはいますがちゃんと靴の画像を復元できていそうですね。 異常検知 服のデータを入力とします。これをTensor型に変換します。試しに一つ復元してみましょう。 異常(anomaly)データ anomaly_data = clothes.copy() x_test = (255.0-anomaly_data).astype(np.float32) / 255.0 x_test = x_test.to_numpy().reshape((len(x_test), np.prod(x_test.shape[1:]))) test_tor = torch.tensor(x_test).float() test_loader = DataLoader(test_tor, batch_size=batch_size, shuffle=False) #data_loaderからデータの取り出し x = next(iter(test_loader)) x = x.to(device) # 評価モードへの切り替え model.eval() idx = 0 x_rec = model(x) #入力画像、復元画像の表示 for i, image in enumerate([x[idx], x_rec[idx]]): image = image.view(28, 28).detach().cpu().numpy() plt.subplot(1, 2, i + 1) plt.imshow(image, cmap = "binary_r") plt.axis("off") plt.show() 靴を学習しているので服はこのようにぼやけてしまっていますね。なんとなくブーツにも見えなくはないですが、服ではないことは確かです。 正常データと異常データの誤差に着目 次に正常データと異常データの誤差に着目してみます。ここでいう誤差とは、元の画像と復元した画像の各ピクセル毎の二乗誤差の和のことです。これを各画像ごとに計算します。本記事ではバッチサイズの分だけ箱ひげ図を用いて見てみることにします。 正常データと異常データのそれぞれの誤差 # 正常データ(shoes)の誤差 x = next(iter(train_loader)) x = x.to(device) x_rec = model(x) error_of_normal = [] for i in range(batch_size): error_of_normal.append(sum(((x[i]-x_rec[i])**2).tolist())) # 異常データ(clothes)の誤差 x = next(iter(test_loader)) x = x.to(device) x_rec = model(x) error_of_abnormal = [] for i in range(batch_size): error_of_anomaly.append(sum(((x[i]-x_rec[i])**2).tolist())) # それぞれの箱ひげ図を描画 fig, ax = plt.subplots() bp = ax.boxplot((error_of_normal,error_of_anomaly)) ax.set_xticklabels(['shoes', 'clothes']) plt.show() 左側が正常データ、右側が異常データの誤差の箱ひげ図です。全体的には異常データの誤差が大きくなっていることがわかります。一方で、正常データに誤差が大きいもの、異常データに誤差が小さいものがあり、完璧に異常検知ができるとは言えないでしょう。層の数を変えたり、活性化関数の選択やエポック数など調整することでまだ学習精度が向上できるかもしれません。とにもかくにも、Autoencoderを実装できなんとなく異常検知に使えそうで、本記事ではあまり触れてはいないですが特徴抽出にも使えそうだなと思いました。 まとめ Autoencoderの簡単な説明から実装までを行ってきました。異常データの服を復元したとき、なんとなく靴っぽくなっている点が面白かったです。Autoencoderの派生系は他にもたくさんあるそうなので今後の課題です。ここまで読んでくださりありがとうございました。 参考文献 毛利拓也, 大郷友海, 嶋田宏樹, 大政孝充, むぎたろう, 寅蔵, もちまる(2021). GANディープラーニング実装ハンドブック. 秀和システム
- 投稿日:2021-08-29T18:29:26+09:00
【Python】モダンなツール群でゼロからパッケージ開発
概要 自分の知識が昔読んだ初版の『入門Python3』から更新されていないことに危機感を覚え、一念発起でモダンなツール群を調べた際の備忘録。以下のツール群1を駆使してゼロからパッケージを開発し、PyPiへの公開までやってみる。コードはGitHubに置いておくので、誰かの参考になれば。 Poetry pytest mypy Black Flake8 題材 手の込んだパッケージを作るのが目的ではないので、今回はシンプルにNPS (Net Promoter Score)を計算するパッケージを作ってみる。利用イメージは以下。 import npspy npspy.categorize(0) # "detractor" npspy.calculate([0, 7, 9]) # 0 それでは実装に移ろう。 実装 下準備 コードを書き始める前に、まずはいい感じのディレクトリを作りたい。これはPoetryに任せる。以下のコマンドでインストールしよう2。 curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python - その後パッケージ名を指定して以下のコマンドを実行。 poetry new npspy このようなディレクトリができているはず。 npspy ├── npspy │ └── __init__.py ├── pyproject.toml ├── README.rst └── tests ├── __init__.py └── test_npspy.py pyproject.tomlにはすでに依存パッケージ(pytest)が指定されているので、ディレクトリに移動して以下のコマンドも実行しておく。 poetry install ※この時点のコミット コーディング 続いて必要な機能を実装する。この記事の本題ではないのでさらっとコードを見せるだけ。 npspy/core.py class InvalidAnswerError(Exception): """Class for invalid answer related errors.""" pass def categorize(answer): """Categorize an answwer for the NPS question. Args: answer: An answer for the NPS question. Returns: Answerer's category. Raises: InvalidAnswerError: If the answer is not between 0 and 10. """ if 0 <= answer <= 6: return "detractor" elif 7 <= answer <= 8: return "passive" elif 9 <= answer <= 10: return "promoter" else: raise InvalidAnswerError(f"Invalid answer: {answer}") def calculate(answers): """Calculates nps. Args: answers: Answers for the NPS question. Returns: Calculated nps. """ count = {} for answer in answers: category = categorize(answer) count[category] = count.get(category, 0) + 1 nps = ( (count.get("promoter", 0) - count.get("detractor", 0)) / sum(count.values()) * 100 ) return nps npspy/__init__.py from npspy.core import InvalidAnswerError, categorize, calculate __version__ = '0.1.0' ※この時点のコミット テスト 単純なコードではあるが、テストは書いておきたい。ここではpytestの枠組みに乗っかろう。すでにあるtests/test_npspy.pyをこんな感じで書き換える。 tests/test_npspy.py import pytest from npspy import InvalidAnswerError, categorize, calculate def test_categorize_negative(): with pytest.raises(InvalidAnswerError): categorize(-1) def test_categorize_0(): assert categorize(0) == "detractor" def test_categorize_more_than_10(): with pytest.raises(InvalidAnswerError): categorize(11) def test_calculate_0(): assert calculate([0, 9]) == 0 def test_calculate_100(): assert calculate([9, 10]) == 100 関数名はtestから始めること(詳細はこちら)。テストを書き終えたら以下のコマンドを実行。poetry runが必要なのは、poetryが準備したvirtualenvで実行するため。 poetry run pytest ※この時点のコミット 型テスト とりあえず意図通りに動くことはテストできたが、今後のために型テストも導入したい。まずはmypyのインストール。--devを指定するのは、開発環境でのみ必要であることを明示するため。 poetry add mypy --dev 先ほどのコードに型アノテーションを足すと、こんな感じ。 npspy/core.py from typing import Dict, Iterable, Literal Categories = Literal["detractor", "passive", "promoter"] class InvalidAnswerError(Exception): """Class for invalid answer related errors.""" pass def categorize(answer: int) -> Categories: """Categorize an answwer for the NPS question. 省略 """ if 0 <= answer <= 6: return "detractor" elif 7 <= answer <= 8: return "passive" elif 9 <= answer <= 10: return "promoter" else: raise InvalidAnswerError(f"Invalid answer: {answer}") def calculate(answers: Iterable[int]) -> float: """Calculates nps. 省略 """ count: Dict[Categories, int] = {} for answer in answers: category = categorize(answer) count[category] = count.get(category, 0) + 1 nps = ( (count.get("promoter", 0) - count.get("detractor", 0)) / sum(count.values()) * 100 ) return nps npspy以下のファイルを対象に型テストを実行。--strictフラグは付けておくのがおすすめ(詳細はこちら)。 poetry run mypy npspy/**.py --strict ※この時点のコミット フォーマッタ コーディングスタイルを統一するためにフォーマッタもほしい。Blackを導入しよう。 poetry add black --dev 以下のように実行する。 poetry run black . 不足していた空行などがいい感じに補われた。 ※この時点のコミット linter コードの品質を担保するためにlinterも導入したい。ここではFlake8を導入する。 poetry add flake8 --dev Blackと併用するために、設定ファイルを作っておく(詳細はこちら)。 .flake8 [flake8] max-line-length = 88 extend-ignore = E203 実行方法は以下の通り。だいぶ注意深くコーディングしていたつもりだが怒られた。 poetry run flake8 せっかくなのでnpspy/__init__.pyを修正。 npspy/__init__.py from npspy.core import InvalidAnswerError, Categories, categorize, calculate __all__ = [ "InvalidAnswerError", "Categories", "categorize", "calculate", ] __version__ = "0.1.0" ※この時点のコミット 公開 公開もPoetryを利用すれば難しくない。先に進む前に以下は済ませておく。 README.mdの記載&pyproject.tomlへの追記(これ) LICENSEの記載(GitHubの機能を使えば簡単) PyPiアカウントの作成(本番用とテスト用それぞれ) 上記の準備が済んだら、テスト用のPyPiを使えるようにpoetry.tomlというファイルを作成しておく(本番用は特別な準備不要)。 poetry.toml [repositories] [repositories.testpypi] url = "https://test.pypi.org/legacy/" では、いよいよ公開。コマンドは以下(UsernameとPasswordの入力が必要)。 # テスト用 poetry build poetry publish -r testpypi # 本番用 poetry build poetry publish 無事に公開できたようだ。 ※この時点のコミット 最後に どうにか公開まで漕ぎつけられた。この記事が、いろいろなツールを試すチュートリアルとして誰かの役にたてば幸い。 モダンといいつつ、長く使われているツールも混ざっているかもしれない。少なくとも、いずれも初版の『入門Python3』では紹介されていなかった。 ↩ powershell以外はこれでいけそうだが、うまくいかない場合はドキュメントの該当ページを見ること。ちなみに、紹介したinstall-poetry.pyが最新で、get-poetry.pyから置き換えられるとのこと。 ↩
- 投稿日:2021-08-29T18:18:12+09:00
Pythonを通してプログラミングの基礎を学ぶ
コンピュータとは そもそもコンピュータとは何なのでしょうか?コンピュータは電子回路を利用して計算を行う機械です。コンピュータ内の様々な情報処理は、大元に行きつけば電圧のon offの組み合わせや電流の流れで物理的に実行されています。イメージを持ってもらうためにショートカットキーの例で説明します。 ショートカットキーとは、キーボードのある二つのキーを同時に押したとき、特定の処理が実行される機能です。例えば「Ctrl」 と 「S」を同時に押すと「保存」行われます。Macの場合「Command」+「S」です。この命令は以下のような電気回路で表すことができます。 この回路は、2つの入力の両方の電流がonのときのみ出力側に電流を通過させるような回路です。それぞれの入力端子が直列に出力方向につながれているような構造になっていて、ANDゲートと呼ばれます。 「Ctrl」を押している信号と「S」を押している信号、両方の電気信号が回路に入って通過したときのみ出力側に電気が流れ、「保存」する命令が実行されます。上記のような電流のon offで表現されるのですね。数字に置き換えれば、1,0 と対応付けることができます。この0,1の一つのペアはコンピュータの情報量の基本単位として1bitと表現されます。8bit = 1byteです。日常生活においてもスマホの容量は128GB(ギガバイト)のように見かけるかと思います。数学的には2進法で計算されていきます。このような原理を基にして、コンピュータは命令を実行しているのだと理解しておきましょう。 他にもORゲートやNANDゲートなどの回路が存在します。より詳しく知りたい方は、「論理回路」のようなワードでググってみてください。 上記の原理を基にして、CPUと呼ばれる中央処理装置やメモリと呼ばれる記憶装置、マウスなどの入力装置、ディスプレイなどの出力装置を組み合わせたものが所謂今日のコンピュータです。 プログラミングとは 「プログラミング」はコンピュータを動かすための指令を書くことを言います。指令のことを「プログラム」と言います。コンピュータはそれらの指令を最終的には電気信号に落とし込んで処理を行います。しかし、電気信号の命令を直接私たちが書き下すことは困難です。そこで、人間が理解しやすく、言葉のように命令を記述できる様々なプログラミング言語が開発されてきました。(C言語, HTML, CSS, Ruby, Java script, Python etc...)私たちはそれらのプログラミング言語を利用することで、簡単にコンピュータに命令を与えることができるというわけです。 それぞれの言語には設計思想があり、定められた規則、文法に厳格に従わなければいけません。また、コンピュータは最終的には物理的な電気信号によって処理が行われるため、一つでもミスがあれば思うように動いてくれません。これを「エラー」とか「バグ」と言ったりします。一般的にプログラミングを用いたアプリ開発においては、たくさんのエラーを経験することになります。 次に、Pythonの基本文法を通して、プログラミングの概要について触れていきます。 Pythonの基本文法 Pythonは人気言語であり、AIをはじめとした多くの分野でよく用いられています。Pythonの文法を簡単に触れていきましょう。これを通してプログラミングの基本的な考え方の理解に役立てて頂ければと思います。 ※以下のコードは実行する必要はありません。読み物として読んでいって軽く頭に入れておいてもらえれば大丈夫です。 計算処理 簡単な計算処理は、算数と同じように書くことができます。 足し算 1+1 => 2 掛け算 2*3 => 6 余り 15%4 => 3 変数 変数とは、データにアクセスするためにつける名前のことです。 変数名 = データ のように記述します。ここで用いる「=」は数学の等号の意味は無く、代入の意味で用いられます。 変数 x = 3 y = 4 x * y => 12 これはどのようなときに役立つのでしょうか? 例えば、Webアプリを利用する際、ログインするときにメールアドレスを入力しますよね。このメールアドレスそのものは「データ」ですが、これをemailという変数に代入することで、後から参照したり利用するときにとても便利になります。 email = ユーザーが入力したメールアドレス password = ユーザーが入力したパスワード # 上記で定義された変数、email, passwordを用いて以下のプログラムを実行することができます。 1 : emailがデータベースの中に存在するか調べる 2 : もし、存在したらpasswordが合っているか照合する 3 : 合っていればユーザーの閲覧を許可する データ型 コンピュータ内で扱われる情報は、最終的には電気信号に落とし込まれます。電気信号は2進法として計算されるため、数字のように考えることができます。普段私たちが入力する文字も実はすべての文字に番号が割り振られています。文字化けという現象に遭遇したことはありますか?日本語の番号対応付けにはいくつかの規格化されたものが存在します。(UTF-8, Shift-JISなど) これらをごちゃ混ぜにしてしまうと、異なる番号に対応した文字が呼び出されてしまうため文字化けが起きるのですね。ここで、「文字には番号が振られている」と言いました。この番号は、数値計算されてしまっては全く意味を持たないものです。そのため、コンピュータ内で情報を扱う際は、数値と文字を区別して扱わないと、筋が通らなくなりバグの温床になってしまいます。 そこで、データ型という概念が出てきます。意味のある適切な演算を実行するために、情報をいくつかグルーピングしておくことで、違法な演算を未然に防ぐことができるのです。 データ型はいくつか存在するのですが、ここではおおまかに数字と文字列を例にして以下を実行してみます。 文字列はダブルクオーテーション「""」で囲うことで表現できます。 文字列同士の足し算は、連結されます。 文字列の計算 x = "おはよう" y = "ございます" x + y => "おはようございます" "文字列としての数値"を足し算してみましょう。 文字列の計算 x = "12" y = "3" x + y => "123" このように足し算の記号を用いた場合、数値計算されて15になるのではなく、文字列としての連結が行われます。 では、数値と文字列同士の足し算ではどうなるのでしょうか? 数値と文字列の計算 x = 12 y = "3" x + y => TypeError: unsupported operand type(s) for +: 'int' and 'str' エラーが出力されました。Integer=整数, String=文字列を意味します。TypeError(種類のエラー)とあるので、データ型が一致しないために足し算の演算は行えないといったことがエラー文から読み取れるかと思います。 このようにコンピュータは、データの種類を区別して扱う必要があるのだと覚えておきましょう。 比較演算子 比較演算子は、データ同士を比較する際に用いる演算です。例えば、値同士が一致しているかどうかを調べたり、値の大小関係を調べたりするときに用います。Webアプリでは、検索機能や、投稿の並べ替えなどに利用されます。 ここでも注意が必要で、比較する値同士のデータ型は揃えなければ意味を持たなくなるためエラーが出力されてしまいます。 例として同値かどうかを調べる「==」演算子を取り上げます。 ※変数を代入するときは「=」、同値かどうかを調べる比較演算子は「==」です。数学的な意味とは異なりプログラミング特有の概念なので区別して理解しましょう。 同値 x = "佐藤" y = "佐藤" x == y => True 同値 x = "佐藤" y = "田中" x == y => False 同値である場合はTrue、異なる場合はFalseが出力されました。この比較演算子は以下の条件分岐を表現する際に大いに役立ちます。 条件分岐 条件分岐とは、呼び名の通り「もしこうだった場合はこうする、そうでなかった場合はこうする」のように条件によって分岐されることを表現する際に用います。これも物理的には電気回路によって実現され、電流が流れるor流れないで考えることができます。 これはif文を用いて記述されます。以下の具体例を見てみてください。 条件分岐 age = 23 if age >= 20: "adult" else: "child" => "adult" 英語の意味のまま素直に読解することができます。もし年齢が20歳以上であれば大人、そうでなければ子供、という条件を表現しています。 age変数は23という数値が代入されていて、age変数をif文の中で用いています。結果として"adult"が出力されました。 このような条件分岐はプログラミングの世界では非常によく使われるものです。変数の章でも出てきたように、Webアプリにログインする際、メールが存在し、パスワードが一致した場合はログインを許可する、といったような動作を書くことができます。 繰り返し処理 コンピュータは情報をメモリという場所に物理的に保存しています。それぞれの情報には住所に当たる番地が割り振られていて、特定の情報にアクセスするときはその番地を参照しています。 また、複数の同じ性質の情報はひとまとまりの集合体として扱うことができ、この集合体のことを配列と言います。ここでは例として、ある生徒の国語、数学、英語3科目のテストの点数が順にまとまったものを考えます。 配列は大かっこ[]を用いて以下のように記述されます。 配列の定義 scores = [80, 85, 94] 配列の中にある複数の情報も、物理的にはメモリ上に番地が割り当てられています。もし、配列の各要素を順に取り出して何らかの処理をしたい場合、各要素ごとに毎回処理を記述していては同じコードを何度も書くことになってしまいます。そこでプログラムの醍醐味、繰り返し処理の登場です。 例として、それぞれの点数が90点以上であればA, それ以外であればBをつけるということを実装していきます。繰り返し処理は主にfor文を用いて記述されます。また、if文を用いて条件分岐処理を記述します。 繰り返し処理 scores = [80, 85, 94] for score in scores: if score >= 90: "A" else: "B" => B B A 上記のfor文も素直に読解することができます。scores配列に入っている各要素が順にscore変数に代入されて、各要素に対しif文の条件処理が行われています。例えば1回目はscore = 80となり、if文で用いられます。scores配列は3つの要素を持っているので、2回目、3回目も同様にscoreに85, 94が順に代入されて実行されています。 上記のように、配列を用いてその要素を順に取り出して繰り返し処理を行うといった実装は、機械学習においても頻繁に登場してきます。例えば、学習用の画像セットが配列の中に入っていて、それを一枚ずつ取り出して検証するといった具合です。 この考え方は今後もよく扱うのでぜひ押さえておきましょう。 関数 関数は、数学的にはy=f(x)のような表記で馴染み深い?かと思います。中学数学で数学的な関数の定義を学びました。覚えていますか?それは「xが決まったらyも一意に定まる」というものでした。y=2x という関数では、x=5だったときxが2倍されてy=10となります。プログラミングの世界でも同様に「ある入力を受けたら、特定の処理を行い、ある結果を出力する」といったものを関数と呼びます。 変数の章でデータを扱う際、名前を付けると後で利用するときに便利だと書かれていましたね。関数もこれと同じ考え方で、「処理」に名前を付けます。そうすることで、特定の処理を後で簡単に再利用することができるようになります。また、処理ごとにグルーピングすることで、プログラミングの全体像を構造化でき、全体像が理解しやすくなります。 例として、3つの変数を受けたら、その平均値を返す関数を定義します。関数の記述にはdefを用います。 関数の定義 def average(a,b,c): sum = a+b+c average_value = sum / 3 return average_value 上記のようにして定義できました。average(a,b,c)の()の中は引数と言い、入力を表します。変数を用いて計算を行い、return average_valueによって平均値が出力されます。 上記は、ただ定義しただけなので、次に具体的な数字を入れてこれを実行してみます。 関数の実行 average(3,5,7) => 5.0 このようにして実行することができました。平均値を求める作業を繰り返し行いたいとき、毎回平均値の数値計算を書き下していては、読みにくく分かりにくいコードになってしまいます。そのため、関数化してしまえれば1行で処理してくれるのでとても便利です。 機械学習では、ある処理を行う一連の流れがいたるところで用いられることがあります。例えば、正解と予測の乖離を計算する損失関数を定義して、この関数をモデル中で利用したりしています。 最後にもう一つPythonの文法を扱う際に留意しておかなければいけないことがあります。if文, for文, 関数に共通していることは、各ブロックのカタマリはインデントで認識されるというものです。 インデントで各ブロックを認識 for score in scores: if score >= 90: "A" else: "B" 入れ子構造が深くなってくると、インデントで視覚的に読解できるのでとても便利なしくみですね。しかし、同じブロックのインデントを揃えないとIndentationError: unexpected indentのようなエラーが出力されてしまいます。頭の片隅に入れておきましょう。 まとめ コンピュータとは何か?から始まってPythonを用いてプログラミングについて解説してみました。皆さんのお役に立てると幸いです。
- 投稿日:2021-08-29T18:09:13+09:00
Python必須級VSCode拡張機能まとめ
TL;DR Python必須級VSCode拡張機能紹介 python これがないと始まらない始められない Pylance 型チェックしてくれる機能。 静的型付け言語みたいになる。 WindowsならCTRL+,、MacならCTRL+,で設定画面表示。 python.analysis.typeCheckingModeで検索し、「basic」を選択 文字型に指定した変数に数字を代入しようとすると怒られるようになる。 Python Extended 入力した文字から予測して補完してくれる拡張機能。 開発効率爆上がり!
- 投稿日:2021-08-29T17:59:38+09:00
Tkinterでオブジェクトを複数、変数に依存して作る(覚え)
環境 ・macOS High Sierra 10.13.6 ・Python 3.8.8 ・tkinter 8.6.10 ・エディタ Atom ・AtomはデフォルトでPythonの実行をサポートしていないので、必要なパッケージをダウンロードする必要がある はじめに tkinterでインターフェイスを作るときに、似たようなオブジェクトを複数作りたいときがある。 とりあえず良さそうな方法が見つかったのでちょっと覚書きする。 GUIの基本 まずはHeloo world!に相当するコードを書く。以下の通り。 main.py from tkinter import * from tkinter import ttk root = Tk() root.title('My App') #ウィジェットの作成 label_1 = ttk.Label(root,text='Hello World!') #レイアウト label_1.pack() # ウィンドウの表示開始 root.mainloop() 実行すると次のようなウィンドウが表示される さて、表示したいラベルが複数ある場合、たとえば main.py from tkinter import * from tkinter import ttk root = Tk() root.title('My App') #ウィジェットの作成 label_1 = ttk.Label(root,text='Hello World!') label_2 = ttk.Label(root,text='Hello World!') label_3 = ttk.Label(root,text='Hello World!') label_4 = ttk.Label(root,text='Hello World!') label_5 = ttk.Label(root,text='Hello World!') #レイアウト label_1.pack() label_2.pack() label_3.pack() label_4.pack() label_5.pack() # ウィンドウの表示開始 root.mainloop() 表示は以下のようになる。 以上のようにするのが手っ取り早いのは直感的にわかるが、このコードでは、表示したい行の数が変わったときにコードの大部分を修正する必要があるし、表示させたいオブジェクトが大量にあるときには役に立たないので別の方法を考える。 オブジェクトの作成数を変数依存化する 例として、ビートルズのメンバーのリストを与え、それをラベルで表示するGUIを考える main.py from tkinter import * from tkinter import ttk root = Tk() root.title('The Beatles') member = ['John Lennon','Paul McCartney','George Harrison','Ringo Starr'] #ウィジェットの作成 labels = [ttk.Label(root,text=x) for x in member] #レイアウト [labels[i].pack() for i in range(len(labels))] # ウィンドウの表示開始 root.mainloop() メンバーのリストはmemberで与えている まず、リスト内包表記で、memberの中身を一つずつ取り出したものをに一度格納し、それをtext属性の値としてttk.Labelオブジェクトを作る。 labelsは、ttk.Labelオブジェクトがメンバーの数と同じ長さを持ったオブジェクトである。 labels = [ttk.Label(root,text=x) for x in member] つぎに、同じくリスト内包表記を使って、表示すべきlabelsの長さと同じ連番を持つrangeオブジェクトから順次値を取り出し、それに対応するlabels[i]を順次表示させる。 [labels[i].pack() for i in range(len(labels))] 何をやっているのかリスト内包表記にいなれていないとわかりにくいかもしれないが、for文で考えると、その中でやっていることはラベルを表示することだけで、値がオブジェクトが何かに代入されているわけではない。 for i in range(len(labels)): labels[i].pack() 表示は以下のようになる。 これで、冗長なコードを書く必要性はなくなった。
- 投稿日:2021-08-29T17:43:50+09:00
Pythonで時間測るためのコンテクストマネージャ書いた
Pythonでの消費時間を測りたい 僕はpythonで高速に計算したい時に時間を測りまくります。 以前はtime.time関数でいちいちやっていましたが、散らかるんですよね。 面倒くさくなりましたので、今回はコンテクストマネージャで書いてみました。 いや、自分で実装するなよ、だって? ん?他の人の書いたのを使えと?おおげさな! このくらいのなら自分で書くほうが(僕は)はやい。 コード class TimeIt: def __init__(self, name: str = 'TimeIt') -> None: self.name = name def __enter__(self) -> 'TimeIt': self.time = self.start_time = time() print(f'Measurement named {self.name} started') return self def report(self, label: str) -> None: current_time = time() print(self.name, label, current_time - self.time) self.time = current_time def __exit__(self, exc_type, exc_value, traceback) -> None: print(f'{self.name} total time comsumption:', time() - self.start_time) 使い方 単にwith噛ませるだけ。 with TimeIt('hoge') as timeit: omoi_shori_1() timeit.report('途中経過') omoi_shori_2() Measurement named hoge started hoge 途中経過 0.0435 hoge total time consumption: 0.345
- 投稿日:2021-08-29T17:27:19+09:00
KaggleタイタニックでNameだけで予測精度80%超えた話(BERTすごい)
BERTは自然言語処理を勉強する上で外せなくなりつつあるモデルです。 理解を深めるために実際に実装してみました。 BERTを試すにあたりKaggleチュートリアルのタイタニックを名前(Name)だけで予測を行います。 タイトルにもある通り予測にNameしか使っていないにもかかわらず正解率が80%を超えたので驚きました。 他のスコアとの比較ですが、女性=生存とした場合の正解率は76.555%(Kaggleのサンプルファイル)、過去の記事(Kaggleのタイタニックに挑戦してみた(その2))ではかなりがんばって77.99%でした。 (私の実力がまだないだけかもしれませんが…) また、BERTを使わないNameのみからの予測(前回)でのスコアは72.248%です。 ・Kaggle関係の記事 Kaggleのタイタニックに挑戦してみた(その1) Kaggleのタイタニックに挑戦してみた(その2) Kaggleで書いたコードの備忘録その1~データ分析で使った手法一通り~ Kaggleで書いたコードの備忘録その2~自然言語処理まとめ~ 実装コード Kaggle notebook 0.Nameだけの予測について Nameだけで予測できるのかについて簡単に話しておきます。 まず生存者の傾向ですが、以下の乗客が生き残る傾向にあります。 ・女性や子供 ・位が高い人(1stクラスの客室や貴族など) 名前にこれらの情報が含まれていれば十分に予測できることが想定されます。 以下は名前をワードクラウドで表示した結果です。 mr(男性)、miss(未婚女性)、master(支配人)など予測に必要な情報は含まれていそうです。 1.データ読み込み google colab のドライブにマウントして読み込むコード例を記載しておきます。 from google.colab import drive, files drive.mount('/content/drive') # drive をマウント # 保存ディレクトリ BASE_DIR = "/content/drive/MyDrive/kaggle/Titanic" DATA_PATH = os.path.join(BASE_DIR, "data") # 対象データの保存ディレクトリ MODEL_PATH = os.path.join(BASE_DIR, "model") # モデルを保存するディレクトリ # ディレクトリがなければ作成 os.makedirs(MODEL_PATH, exist_ok=True) タイタニックのデータは"DATA_PATH"配下にある想定です。 # import import numpy as np import pandas as pd from matplotlib import pyplot as plt import time from tqdm import tqdm #from tqdm.notebook import tqdm # notebookの場合 from pprint import pprint # データ読み込み df_train = pd.read_csv(os.path.join(DATA_PATH, "train.csv")) df_test = pd.read_csv(os.path.join(DATA_PATH, "test.csv")) target_column = "Survived" # データをマージ df_test[target_column] = np.nan df = pd.concat([df_train, df_test], ignore_index=True, sort=False) print(df_train.shape) print(df_test.shape) print(df.shape) print(df.columns) (891, 12) (418, 12) (1309, 12) Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp', 'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'], dtype='object') 2.前処理 BERTは前処理も含めて実装されているため基本は前処理の必要はありません。 ただ今回は記号だけ気になったので、余分な情報として事前に除外しておきます。 def str_normalize(ds): # アルファベットと数字のみにする ds = ds.str.replace("[^a-zA-Z0-9]+", " ", regex=True) return ds df["Name_normalize"] = str_normalize(df["Name"]) 3.BERT BERTはディープラーニングを利用した自然言語処理モデルです。 以下の動画が参考になりました。 参考:【深層学習】BERT - 実務家必修。実務で超応用されまくっている自然言語処理モデル【ディープラーニングの世界vol.32】#110 #VRアカデミア #DeepLearning BERT自体はクラス分類以外のタスクにも使えますが、今回はクラス分類に限った実装を行います。 使用するライブラリは transformers/tensorflow2.0 を使います。 3-1.import関係 !pip install -q transformers !pip install -q silence_tensorflow !pip install -q janome # tensorflow のログが多いので silence_tensorflow を入れています from silence_tensorflow import silence_tensorflow silence_tensorflow() # tensorflow import tensorflow as tf import tensorflow.keras.layers as kl # transformers import transformers # transformerのログをエラー以上のみに from transformers import logging logging.set_verbosity_error() 3-2.学習済みモデルの選択 公開されている学習済みモデルは以下から検索できます。 ・[transformers Docs] Pretrained models ・huggingface casedが小文字大文字を区別し、uncasedが小文字のみとなります。 今回はドキュメントの一番上にあるの小文字のみモデルを採用しています。 pretrained_model_name = "bert-base-uncased" 以下でモデルを実際にダウンロードして使えるかどうか確認できます。 tokenizer = transformers.AutoTokenizer.from_pretrained(pretrained_model_name) bert_model = transformers.TFAutoModel.from_pretrained(pretrained_model_name) #bert_model = transformers.TFAutoModel.from_pretrained(pretrained_model_name, from_pt=True) # ※(1) print(bert_model.config) BertConfig { "_name_or_path": "bert-base-uncased", "architectures": [ "BertForMaskedLM" ], "attention_probs_dropout_prob": 0.1, "gradient_checkpointing": false, "hidden_act": "gelu", "hidden_dropout_prob": 0.1, "hidden_size": 768, "initializer_range": 0.02, "intermediate_size": 3072, "layer_norm_eps": 1e-12, "max_position_embeddings": 512, "model_type": "bert", "num_attention_heads": 12, "num_hidden_layers": 12, "pad_token_id": 0, "position_embedding_type": "absolute", "transformers_version": "4.6.1", "type_vocab_size": 2, "use_cache": true, "vocab_size": 30522 } ※(1) 何も指定しないとtensorflowのモデルを読み込みます。(ファイルで言うと tf_model.h5) from_py=Trueを有効にするとpytouchのモデルを読み込みます。(ファイルで言うと pytorch_model.bin) どちらのファイルがあるかは学習済みモデルの Files and versions から確認できます。 tokenizer の動作について tokenizer が前処理を担当しています。 どういった処理をしているか見てみます。 tokenizerに与える引数は以下に説明があります。 Everything you always wanted to know about padding and truncation # 適当に名前の情報を使ってみてみる sample_name = df["Name"][0] print(sample_name) # Tokenizeした結果 token_words = tokenizer.tokenize(sample_name) print(token_words) # BERTに入力する形式に変換 encode_token = tokenizer(sample_name, padding="max_length", max_length=12, truncation=True) pprint(encode_token) # BERTへの入力形式をデコードした結果 print(tokenizer.decode(encode_token["input_ids"])) # オリジナル文字列 Braund, Mr. Owen Harris # Tokenizeした結果 ['braun', '##d', ',', 'mr', '.', 'owen', 'harris'] # BERTの入力形式 {'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0], 'input_ids': [101, 21909, 2094, 1010, 2720, 1012, 7291, 5671, 102, 0, 0, 0], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]} # BERTへの入力形式をデコードした結果 [CLS] braund, mr. owen harris [SEP] [PAD] [PAD] [PAD] 3-3.入力単語数の確認 BERTに入力する単語数はハイパーパラメータで、最大512単語です。 (事前学習のサイズが512っぽいです) 参考:(自然言語処理モデル(BERT)を利用した日本語の文章分類 〜GoogleColab & Pytorchによるファインチューニング〜 Nameの最大単語数が何かを求めてハイパーパラメータを設定します。 # 最大単語数の確認 max_len = [] # 1文づつ処理 for sent in df["Name_normalize"]: # Tokenizeで分割 token_words = tokenizer.tokenize(sent) # 文章数を取得してリストへ格納 max_len.append(len(token_words)) # 最大の値を確認 print('最大単語数: ', max(max_len)) print('上記の最大単語数にSpecial token([CLS], [SEP])の+2をした値が最大単語数') # 単語数を設定 sequence_max_length = max(max_len) + 2 if sequence_max_length > 512: sequence_max_length = 512 最大単語数: 20 上記の最大単語数にSpecial token([CLS], [SEP])の+2をした値が最大単語数 3-4. BERTモデルの作成 BERTモデルを作成します。 モデルですがタスクによりいくつか用意されているようです。 例えばクラス分類ですとTFBertForSequenceClassificationです。 ただ、いろいろ応用できそうなTFBertModelを元に作成します。 def build_model(learning_rate, is_print=False): # BERTモデルをロード bert_model = transformers.TFAutoModel.from_pretrained(pretrained_model_name) #bert_model = transformers.TFAutoModel.from_pretrained(pretrained_model_name, from_pt=True) # pytouchの場合 # tfへの入力テンソルを作成 # 入力はsequence_max_lengthサイズを3つ(['input_ids', 'token_type_ids', 'attention_mask']) inputs = [ kl.Input(shape=(sequence_max_length,), dtype=tf.int32, name=name) for name in tokenizer.model_input_names ] if is_print: pprint(inputs) # BERTモデルの出力を得る # 出力は TFBaseModelOutputWithPooling (https://huggingface.co/transformers/main_classes/output.html#tfbasemodeloutput) # x[0](last_hidden_state) : 最後のレイヤーの出力 # x[1](pooler_output) : 分類トークンの状態 x = bert_model(inputs) # BERT出力の0番目がクラス分類で使う出力 x1 = x[0][:, 0, :] # 分類用の出力層を用意 # 出力層の構成はTFBertForSequenceClassificationを参考 x1 = kl.Dropout(0.1)(x1) x1 = kl.Dense(1, activation='sigmoid', kernel_initializer=transformers.modeling_tf_utils.get_initializer(0.02))(x1) model_train = tf.keras.Model(inputs=inputs, outputs=x1) # オリジナルの出力値を特徴量としたいので予測専用のモデルも別途作っておく model_pred = tf.keras.Model(inputs=inputs, outputs=[x1, x[0][:, 0, :]]) # optimizerは AdamW を使用 optimizer = transformers.AdamWeightDecay(learning_rate=learning_rate) model_train.compile(optimizer, loss="binary_crossentropy", metrics=["acc"]) #model_train.compile(optimizer, loss="categorical_crossentropy", metrics=["acc"]) # softmaxの場合 if is_print: print(model_train.summary()) return model_train, model_pred # 試しに実行 build_model(0.1, is_print=True) [<KerasTensor: shape=(None, 22) dtype=int32 (created by layer 'input_ids')>, <KerasTensor: shape=(None, 22) dtype=int32 (created by layer 'token_type_ids')>, <KerasTensor: shape=(None, 22) dtype=int32 (created by layer 'attention_mask')>] Model: "model" __________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ================================================================================================== input_ids (InputLayer) [(None, 22)] 0 __________________________________________________________________________________________________ token_type_ids (InputLayer) [(None, 22)] 0 __________________________________________________________________________________________________ attention_mask (InputLayer) [(None, 22)] 0 __________________________________________________________________________________________________ tf_bert_model_4 (TFBertModel) TFBaseModelOutputWit 109482240 input_ids[0][0] token_type_ids[0][0] attention_mask[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_1 (Sli (None, 768) 0 tf_bert_model_4[0][0] __________________________________________________________________________________________________ dropout_186 (Dropout) (None, 768) 0 tf.__operators__.getitem_1[0][0] __________________________________________________________________________________________________ dense_1 (Dense) (None, 1) 769 dropout_186[0][0] ================================================================================================== Total params: 109,483,009 Trainable params: 109,483,009 Non-trainable params: 0 __________________________________________________________________________________________________ 3-5. BERTの学習(ファインチューニング) TPU BERTはモデルがかなり大きく学習に時間がかかります。 Google Colaboratory では TPU が使えるので使うためのコードを書いておきます。 import tensorflow as tf import os runtime_type = "" try: if "COLAB_TPU_ADDR" in os.environ: resolver = tf.distribute.cluster_resolver.TPUClusterResolver('grpc://' + os.environ['COLAB_TPU_ADDR']) else: resolver = tf.distribute.cluster_resolver.TPUClusterResolver() #--- TPU print('Running on TPU ', resolver.cluster_spec().as_dict()['worker']) runtime_type = "TPU" # This is the TPU initialization code that has to be at the beginning. tf.config.experimental_connect_to_cluster(resolver) tf.tpu.experimental.initialize_tpu_system(resolver) tpu_strategy = tf.distribute.TPUStrategy(resolver) tf.keras.backend.clear_session() print("All devices: ", tf.config.list_logical_devices('TPU')) except ValueError: if tf.test.gpu_device_name() != "": #--- GPU runtime_type = "GPU" else: runtime_type = "CPU" print("runtime_type: ", runtime_type) さらにTPUではモデルを作成する時に上記で作成した tpu_strategy を使う必要があります。 if runtime_type == "TPU": # TPU はモデル作成に tpu_strategy.scope で囲む with tpu_strategy.scope(): model_train, model_pred = build_model(learning_rate) else: model_train, model_pred = build_model(learning_rate) 学習コード ファインチューニングですが、以下の記事を参考に学習パラメータを設定しています。 参考:BERTのfine-tuning不安定性はどのように解決できるか? ポイントは、 ・epochsは20 ・学習率は、最初の10%epochは0.00002まで増加、以降は0に減少 です。 また、学習に時間がかかるので一度学習したモデルはファイルに保存するコードにしています。 import sklearn.metrics def train_bert( df_train, # 学習用のデータ text_column, # 対象のカラム名 target_column, # 目的変数のカラム名 df_valid=None, # 検証用データ df_pred_list=[], # 予測用データ model_file_prefix="", # 保存時のファイル名識別子 epochs=20, batch_size=8, ): #-------------------- # 学習率 #-------------------- lr0 = 0.000005 learning_rate = [ 0.00001, 0.00002, ] if epochs-len(learning_rate) > 0: lr_list = np.linspace(0.00002, 0, epochs-len(learning_rate)) learning_rate.extend(lr_list) def lr_scheduler(epoch): return learning_rate[epoch] lr_callback = tf.keras.callbacks.LearningRateScheduler(lr_scheduler) #-------------------- # file #-------------------- model_path = "{}_{}.h5".format( model_file_prefix, pretrained_model_name, ) #-------------------- # モデル #-------------------- if runtime_type == "TPU": with tpu_strategy.scope(): model_train, model_pred = build_model(lr0) else: model_train, model_pred = build_model(lr0) #----------------------------- # モデル入出力用のデータ作成関数 #----------------------------- def _build_x_from_df(df): # Series -> list x = df[text_column].tolist() # tokenize x = tokenizer(x, padding="max_length", max_length=sequence_max_length, truncation=True, return_tensors="tf") # BatchEncoding -> dict return dict(x) def _build_y_from_df(df): return df[target_column] #return tf.keras.utils.to_categorical(df[target_column], num_classes=2) # softmax用 #------------------- # valid用のdatasetを作成 #------------------- if df_valid is not None: valid_x = _build_x_from_df(df_valid) valid_y = _build_y_from_df(df_valid) valid_dataset = ( tf.data.Dataset.from_tensor_slices((valid_x, valid_y)) .batch(batch_size) .cache() ) else: valid_dataset = None #------------------- # 学習 #------------------- if os.path.isfile(model_path): # 学習済みモデルをload print(model_path) model_train.load_weights(model_path) else: train_x = _build_x_from_df(df_train) train_y = _build_y_from_df(df_train) train_dataset = ( tf.data.Dataset.from_tensor_slices((train_x, train_y)) .shuffle(len(train_x), seed=1234) .batch(batch_size) .prefetch(tf.data.experimental.AUTOTUNE) # GPUが計算している間にBatchデータをCPU側で用意しておく機能 ) model_train.fit(train_dataset, epochs=epochs, validation_data=valid_dataset, callbacks=[lr_callback]) model_train.save_weights(model_path) #------------------- # 評価 #------------------- if df_valid is not None: print("valid") pred_y = model_train.predict(valid_dataset, verbose=1) # 正解率 pred_y_label = np.where(pred_y < 0.5, 0, 1) metric = sklearn.metrics.accuracy_score(valid_y, pred_y_label) print("acc", metric) else: metric = 0 #------------------- # 予測 #------------------- print("pred") pred_y_list = [] emb_list = [] for df_pred in df_pred_list: pred_x = _build_x_from_df(df_pred) pred_dataset = ( tf.data.Dataset.from_tensor_slices((pred_x,)) .batch(batch_size) .cache() ) # 予測 pred_output = model_pred.predict(pred_dataset, verbose=1) # pred pred_y = pred_output[0].reshape((-1,)) # (-1,1) -> (-1) #pred_y = pred_y[0][:,1] # softmax用 pred_y_list.append(pred_y) # emb emb_list.append(pred_output[1]) return metric, pred_y_list, emb_list #--- 実行例 train_bert( df_train=df[df["Survived"].notnull()][:10], # 学習データ text_column="Name_normalize", target_column="Survived", df_valid=df[df["Survived"].notnull()][:10], # 検証データ(仮で学習データと同じ) df_pred_list=[df[df["Survived"].isnull()][:10]], # 予測データ epochs=2, # 試しなので少な目 ) print(metric) print(pred_y_list[0].shape) print(emb_list[0].shape) Epoch 1/2 2/2 [==============================] - 31s 6s/step - loss: 0.6952 - acc: 0.4333 - val_loss: 0.6520 - val_acc: 0.8000 Epoch 2/2 2/2 [==============================] - 4s 2s/step - loss: 0.6455 - acc: 0.7167 - val_loss: 0.6547 - val_acc: 0.5000 valid 2/2 [==============================] - 4s 160ms/step acc 0.5 pred 2/2 [==============================] - 4s 164ms/step 0.5 (10,) (10, 768) 4.BERTモデルから予測結果と特徴量を取得する クロスバリデーションで全予測結果と特徴量を取得します。 イメージは以下です。(前記事と同じ図です) import sklearn.model_selection def train_cv(df, text_column, target_column, n_splits): df_train = df[df[target_column].notnull()] df_test = df[df[target_column].isnull()] df_train_idx = df_train.index # 結果用 df_pred = pd.DataFrame(df.index, columns=["index"]).set_index("index") df_emb = pd.DataFrame(df.index, columns=["index"]).set_index("index") df_emb_pred = None metric_list = [] #---------------- # cross validation #---------------- kf = sklearn.model_selection.StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=1234) for i, (train_idx, test_idx) in enumerate(kf.split(df_train, df_train[target_column])): df_train_sub = df_train.iloc[train_idx] df_test_sub = df_train.iloc[test_idx] df_pred_list = [df_test_sub] df_pred_list.append(df_test) model_file_prefix = "cv_{}".format(i) # train metric, pred_y_list, emb_list = train_bert( df_train=df_train_sub, text_column=text_column, target_column=target_column, df_valid=df_test_sub, df_pred_list=df_pred_list, model_file_prefix=model_file_prefix, ) metric_list.append(metric) # 予測結果を保存 result_name = "result_{}".format(i) df_pred.loc[df_train_idx[test_idx], result_name] = pred_y_list[0] df_pred.loc[df_test.index, result_name] = pred_y_list[1] #--------- a = pd.DataFrame(emb_list[0], index=df_train_idx[test_idx]) df_emb = df_emb.combine_first(a) if df_emb_pred is None: df_emb_pred = pd.DataFrame(emb_list[1], index=df_test.index) else: df_emb_pred += emb_list[1] pred_y = df_pred.mean(axis=1) df_emb_pred /= n_splits df_emb = df_emb.combine_first(df_emb_pred) return np.mean(metric_list), pred_y.values, df_emb #--- 結果と特徴量を取得 metric, pred_y, df_emb = train_cv(df, "Name_normalize", "Survived", n_splits=3) print(metric) print(pred_y.shape) print(df_emb.shape) 実行結果 クリックで展開 Epoch 1/20 75/75 [==============================] - 72s 168ms/step - loss: 0.6232 - acc: 0.6738 - val_loss: 0.4911 - val_acc: 0.7845 Epoch 2/20 75/75 [==============================] - 5s 66ms/step - loss: 0.5015 - acc: 0.7881 - val_loss: 0.4903 - val_acc: 0.8249 Epoch 3/20 75/75 [==============================] - 5s 67ms/step - loss: 0.4287 - acc: 0.8344 - val_loss: 0.5410 - val_acc: 0.8081 Epoch 4/20 75/75 [==============================] - 5s 66ms/step - loss: 0.3543 - acc: 0.8761 - val_loss: 0.5543 - val_acc: 0.7980 Epoch 5/20 75/75 [==============================] - 5s 67ms/step - loss: 0.2457 - acc: 0.9094 - val_loss: 0.6346 - val_acc: 0.7946 Epoch 6/20 75/75 [==============================] - 5s 67ms/step - loss: 0.1725 - acc: 0.9361 - val_loss: 0.7133 - val_acc: 0.7845 Epoch 7/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0832 - acc: 0.9732 - val_loss: 0.9226 - val_acc: 0.7778 Epoch 8/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0563 - acc: 0.9798 - val_loss: 0.9967 - val_acc: 0.7980 Epoch 9/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0291 - acc: 0.9932 - val_loss: 0.9987 - val_acc: 0.7946 Epoch 10/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0238 - acc: 0.9928 - val_loss: 0.9397 - val_acc: 0.7912 Epoch 11/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0462 - acc: 0.9900 - val_loss: 0.9016 - val_acc: 0.7845 Epoch 12/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0156 - acc: 0.9957 - val_loss: 0.9964 - val_acc: 0.7744 Epoch 13/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0190 - acc: 0.9949 - val_loss: 1.0513 - val_acc: 0.7744 Epoch 14/20 75/75 [==============================] - 6s 76ms/step - loss: 0.0115 - acc: 0.9975 - val_loss: 1.0042 - val_acc: 0.8013 Epoch 15/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0063 - acc: 1.0000 - val_loss: 1.0379 - val_acc: 0.8047 Epoch 16/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0117 - acc: 0.9966 - val_loss: 1.0797 - val_acc: 0.7980 Epoch 17/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0066 - acc: 1.0000 - val_loss: 1.0950 - val_acc: 0.7912 Epoch 18/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0040 - acc: 1.0000 - val_loss: 1.1076 - val_acc: 0.7946 Epoch 19/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0044 - acc: 1.0000 - val_loss: 1.1138 - val_acc: 0.7946 Epoch 20/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0045 - acc: 1.0000 - val_loss: 1.1138 - val_acc: 0.7946 valid 38/38 [==============================] - 8s 105ms/step acc 0.7946127946127947 pred 38/38 [==============================] - 8s 107ms/step 53/53 [==============================] - 2s 13ms/step Epoch 1/20 75/75 [==============================] - 72s 163ms/step - loss: 0.6437 - acc: 0.6153 - val_loss: 0.4935 - val_acc: 0.7879 Epoch 2/20 75/75 [==============================] - 5s 66ms/step - loss: 0.5036 - acc: 0.7832 - val_loss: 0.4796 - val_acc: 0.8013 Epoch 3/20 75/75 [==============================] - 5s 73ms/step - loss: 0.4667 - acc: 0.8191 - val_loss: 0.5475 - val_acc: 0.7778 Epoch 4/20 75/75 [==============================] - 5s 67ms/step - loss: 0.4285 - acc: 0.8395 - val_loss: 0.5287 - val_acc: 0.8047 Epoch 5/20 75/75 [==============================] - 5s 66ms/step - loss: 0.3448 - acc: 0.8785 - val_loss: 0.5742 - val_acc: 0.8114 Epoch 6/20 75/75 [==============================] - 5s 66ms/step - loss: 0.2957 - acc: 0.8997 - val_loss: 0.6470 - val_acc: 0.8114 Epoch 7/20 75/75 [==============================] - 5s 68ms/step - loss: 0.2138 - acc: 0.9410 - val_loss: 0.6874 - val_acc: 0.8182 Epoch 8/20 75/75 [==============================] - 5s 67ms/step - loss: 0.2018 - acc: 0.9291 - val_loss: 0.6803 - val_acc: 0.7980 Epoch 9/20 75/75 [==============================] - 5s 68ms/step - loss: 0.1191 - acc: 0.9639 - val_loss: 0.7696 - val_acc: 0.8047 Epoch 10/20 75/75 [==============================] - 5s 68ms/step - loss: 0.0709 - acc: 0.9831 - val_loss: 0.7891 - val_acc: 0.8081 Epoch 11/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0470 - acc: 0.9802 - val_loss: 0.8624 - val_acc: 0.8148 Epoch 12/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0310 - acc: 0.9939 - val_loss: 0.9067 - val_acc: 0.8148 Epoch 13/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0127 - acc: 0.9943 - val_loss: 0.9453 - val_acc: 0.8215 Epoch 14/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0236 - acc: 0.9903 - val_loss: 0.9734 - val_acc: 0.8047 Epoch 15/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0089 - acc: 0.9999 - val_loss: 1.0278 - val_acc: 0.7879 Epoch 16/20 75/75 [==============================] - 6s 78ms/step - loss: 0.0133 - acc: 0.9946 - val_loss: 1.0168 - val_acc: 0.7912 Epoch 17/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0057 - acc: 0.9993 - val_loss: 1.0603 - val_acc: 0.8114 Epoch 18/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0045 - acc: 1.0000 - val_loss: 1.0189 - val_acc: 0.8283 Epoch 19/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0063 - acc: 1.0000 - val_loss: 1.0131 - val_acc: 0.8215 Epoch 20/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0035 - acc: 1.0000 - val_loss: 1.0131 - val_acc: 0.8215 valid 38/38 [==============================] - 8s 107ms/step acc 0.8215488215488216 pred 38/38 [==============================] - 8s 105ms/step 53/53 [==============================] - 1s 14ms/step Epoch 1/20 75/75 [==============================] - 72s 169ms/step - loss: 0.6213 - acc: 0.6627 - val_loss: 0.5171 - val_acc: 0.7845 Epoch 2/20 75/75 [==============================] - 5s 66ms/step - loss: 0.4798 - acc: 0.7928 - val_loss: 0.5462 - val_acc: 0.7643 Epoch 3/20 75/75 [==============================] - 5s 66ms/step - loss: 0.4163 - acc: 0.8262 - val_loss: 0.5584 - val_acc: 0.7845 Epoch 4/20 75/75 [==============================] - 5s 69ms/step - loss: 0.3644 - acc: 0.8592 - val_loss: 0.5975 - val_acc: 0.7677 Epoch 5/20 75/75 [==============================] - 5s 69ms/step - loss: 0.2189 - acc: 0.9272 - val_loss: 0.6186 - val_acc: 0.8013 Epoch 6/20 75/75 [==============================] - 5s 70ms/step - loss: 0.1510 - acc: 0.9555 - val_loss: 0.6811 - val_acc: 0.7609 Epoch 7/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0933 - acc: 0.9778 - val_loss: 0.8140 - val_acc: 0.7475 Epoch 8/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0695 - acc: 0.9865 - val_loss: 0.7720 - val_acc: 0.7912 Epoch 9/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0419 - acc: 0.9886 - val_loss: 0.9185 - val_acc: 0.7677 Epoch 10/20 75/75 [==============================] - 6s 78ms/step - loss: 0.0256 - acc: 0.9931 - val_loss: 0.8664 - val_acc: 0.7744 Epoch 11/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0185 - acc: 0.9974 - val_loss: 0.8704 - val_acc: 0.7744 Epoch 12/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0174 - acc: 0.9965 - val_loss: 0.9173 - val_acc: 0.8047 Epoch 13/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0065 - acc: 1.0000 - val_loss: 1.0711 - val_acc: 0.7744 Epoch 14/20 75/75 [==============================] - 5s 68ms/step - loss: 0.0122 - acc: 0.9954 - val_loss: 1.0262 - val_acc: 0.7879 Epoch 15/20 75/75 [==============================] - 5s 68ms/step - loss: 0.0048 - acc: 1.0000 - val_loss: 1.0226 - val_acc: 0.8013 Epoch 16/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0031 - acc: 1.0000 - val_loss: 1.0460 - val_acc: 0.7946 Epoch 17/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0032 - acc: 1.0000 - val_loss: 1.0550 - val_acc: 0.8047 Epoch 18/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0026 - acc: 1.0000 - val_loss: 1.0651 - val_acc: 0.8047 Epoch 19/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0022 - acc: 1.0000 - val_loss: 1.0694 - val_acc: 0.8013 Epoch 20/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0023 - acc: 1.0000 - val_loss: 1.0694 - val_acc: 0.8013 valid 38/38 [==============================] - 9s 113ms/step acc 0.8013468013468014 pred 38/38 [==============================] - 8s 108ms/step 53/53 [==============================] - 1s 13ms/step 0.8058361391694726 (1309,) (1309, 768) 4-1.予測結果を出力 df["BERT"] = pred_y df["BERT_label"] = np.where(pred_y < 0.5, 0, 1) # 学習データの正解率 _df = df[df["Survived"].notnull()] print(sklearn.metrics.accuracy_score(_df["Survived"], _df["BERT_label"])) # 予測結果をcsvで出力 _df = df[df["Survived"].isnull()] df_submit = pd.DataFrame() df_submit["PassengerId"] = _df["PassengerId"] df_submit["Survived"] = _df["BERT_label"] df_submit.to_csv('submit1.csv', header=True, index=False) # 検証結果 0.8024691358024691 提出スコア 正解率81.1%です。 4-2.特徴量を元にAutoMLで予測 import pycaret.classification as pcc # pycaret にデータを渡す df_emb["Survived"] = df["Survived"] r = pcc.setup(df_emb[df["Survived"].notnull()], target="Survived", silent=True, session_id=1234) # モデルを比較 best_models = pcc.compare_models(n_select=3) # --- 比較した結果(Acc) --- # knn : 0.8090 # catboost: 0.8057 # lightgbm: 0.8009 # モデルのパラメータをチューニング models = [] models.append(pcc.tune_model(best_models[0])) # acc: 0.8074 models.append(pcc.tune_model(best_models[1])) # acc: 0.8057 models.append(pcc.tune_model(best_models[2])) # acc: 0.7977 # モデルをブレンドしてチューニング blend_model = pcc.blend_models(models) tuned_model = pcc.tune_model(blend_model) # acc: 0.8089 # チューニング後のパラメータを表示 pcc.plot_model(tuned_model, plot='parameter') # モデルを検証 pcc.predict_model(tuned_model) # acc: 0.8284 # 予測 x_pred = df_emb[df_emb["Survived"].isnull()] df_pred = pcc.predict_model(tuned_model, data=x_pred) # 予測結果をcsvで出力 df_submit = pd.DataFrame() df_submit["PassengerId"] = df[df["Survived"].isnull()]["PassengerId"] df_submit["Survived"] = df_pred["Label"].apply(lambda x:int(float(x))) df_submit.to_csv('submit2.csv', header=True, index=False) 提出スコア 正解率80.382%です。 AutoMLで予測させたら少し下がっちゃいましたね。 その他参考文献 はじめての自然言語処理 第3回 BERT を用いた自然言語処理における転移学習 Transformers
- 投稿日:2021-08-29T17:27:19+09:00
KaggleタイタニックでNameだけで予測精度80%超えた話(BERT/TF2.0)
BERTは自然言語処理を勉強する上で外せなくなりつつあるモデルです。 理解を深めるために実際に実装してみました。 BERTを試すにあたりKaggleチュートリアルのタイタニックを名前(Name)だけで予測を行います。 タイトルにもある通り予測にNameしか使っていないにもかかわらず正解率が80%を超えたので驚きました。 他のスコアとの比較ですが、女性=生存とした場合の正解率は76.555%(Kaggleのサンプルファイル)、過去の記事(Kaggleのタイタニックに挑戦してみた(その2))ではかなりがんばって77.99%でした。 (私の実力がまだないだけかもしれませんが…) また、BERTを使わないNameのみからの予測(前回)でのスコアは72.248%です。 ・Kaggle関係の記事 Kaggleのタイタニックに挑戦してみた(その1) Kaggleのタイタニックに挑戦してみた(その2) Kaggleで書いたコードの備忘録その1~データ分析で使った手法一通り~ Kaggleで書いたコードの備忘録その2~自然言語処理まとめ~ 実装コード Kaggle notebook 0.Nameだけの予測について Nameだけで予測できるのかについて簡単に話しておきます。 まず生存者の傾向ですが、以下の乗客が生き残る傾向にあります。 ・女性や子供 ・位が高い人(1stクラスの客室や貴族など) 名前にこれらの情報が含まれていれば十分に予測できることが想定されます。 以下は名前をワードクラウドで表示した結果です。 mr(男性)、miss(未婚女性)、master(支配人)など予測に必要な情報は含まれていそうです。 1.データ読み込み google colab のドライブにマウントして読み込むコード例を記載しておきます。 from google.colab import drive, files drive.mount('/content/drive') # drive をマウント # 保存ディレクトリ BASE_DIR = "/content/drive/MyDrive/kaggle/Titanic" DATA_PATH = os.path.join(BASE_DIR, "data") # 対象データの保存ディレクトリ MODEL_PATH = os.path.join(BASE_DIR, "model") # モデルを保存するディレクトリ # ディレクトリがなければ作成 os.makedirs(MODEL_PATH, exist_ok=True) タイタニックのデータは"DATA_PATH"配下にある想定です。 # import import numpy as np import pandas as pd from matplotlib import pyplot as plt import time from tqdm import tqdm #from tqdm.notebook import tqdm # notebookの場合 from pprint import pprint # データ読み込み df_train = pd.read_csv(os.path.join(DATA_PATH, "train.csv")) df_test = pd.read_csv(os.path.join(DATA_PATH, "test.csv")) target_column = "Survived" # データをマージ df_test[target_column] = np.nan df = pd.concat([df_train, df_test], ignore_index=True, sort=False) print(df_train.shape) print(df_test.shape) print(df.shape) print(df.columns) (891, 12) (418, 12) (1309, 12) Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp', 'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'], dtype='object') 2.前処理 BERTは前処理も含めて実装されているため基本は前処理の必要はありません。 ただ今回は記号だけ気になったので、余分な情報として事前に除外しておきます。 def str_normalize(ds): # アルファベットと数字のみにする ds = ds.str.replace("[^a-zA-Z0-9]+", " ", regex=True) return ds df["Name_normalize"] = str_normalize(df["Name"]) 3.BERT BERTはディープラーニングを利用した自然言語処理モデルです。 以下の動画が参考になりました。 参考:【深層学習】BERT - 実務家必修。実務で超応用されまくっている自然言語処理モデル【ディープラーニングの世界vol.32】#110 #VRアカデミア #DeepLearning BERT自体はクラス分類以外のタスクにも使えますが、今回はクラス分類に限った実装を行います。 使用するライブラリは transformers/tensorflow2.0 を使います。 3-1.import関係 !pip install -q transformers !pip install -q silence_tensorflow !pip install -q janome # tensorflow のログが多いので silence_tensorflow を入れています from silence_tensorflow import silence_tensorflow silence_tensorflow() # tensorflow import tensorflow as tf import tensorflow.keras.layers as kl # transformers import transformers # transformerのログをエラー以上のみに from transformers import logging logging.set_verbosity_error() 3-2.学習済みモデルの選択 公開されている学習済みモデルは以下から検索できます。 ・[transformers Docs] Pretrained models ・huggingface casedが小文字大文字を区別し、uncasedが小文字のみとなります。 今回はドキュメントの一番上にあるの小文字のみモデルを採用しています。 pretrained_model_name = "bert-base-uncased" 以下でモデルを実際にダウンロードして使えるかどうか確認できます。 tokenizer = transformers.AutoTokenizer.from_pretrained(pretrained_model_name) bert_model = transformers.TFAutoModel.from_pretrained(pretrained_model_name) #bert_model = transformers.TFAutoModel.from_pretrained(pretrained_model_name, from_pt=True) # ※(1) print(bert_model.config) BertConfig { "_name_or_path": "bert-base-uncased", "architectures": [ "BertForMaskedLM" ], "attention_probs_dropout_prob": 0.1, "gradient_checkpointing": false, "hidden_act": "gelu", "hidden_dropout_prob": 0.1, "hidden_size": 768, "initializer_range": 0.02, "intermediate_size": 3072, "layer_norm_eps": 1e-12, "max_position_embeddings": 512, "model_type": "bert", "num_attention_heads": 12, "num_hidden_layers": 12, "pad_token_id": 0, "position_embedding_type": "absolute", "transformers_version": "4.6.1", "type_vocab_size": 2, "use_cache": true, "vocab_size": 30522 } ※(1) 何も指定しないとtensorflowのモデルを読み込みます。(ファイルで言うと tf_model.h5) from_py=Trueを有効にするとpytouchのモデルを読み込みます。(ファイルで言うと pytorch_model.bin) どちらのファイルがあるかは学習済みモデルの Files and versions から確認できます。 tokenizer の動作について tokenizer が前処理を担当しています。 どういった処理をしているか見てみます。 tokenizerに与える引数は以下に説明があります。 Everything you always wanted to know about padding and truncation # 適当に名前の情報を使ってみてみる sample_name = df["Name"][0] print(sample_name) # Tokenizeした結果 token_words = tokenizer.tokenize(sample_name) print(token_words) # BERTに入力する形式に変換 encode_token = tokenizer(sample_name, padding="max_length", max_length=12, truncation=True) pprint(encode_token) # BERTへの入力形式をデコードした結果 print(tokenizer.decode(encode_token["input_ids"])) # オリジナル文字列 Braund, Mr. Owen Harris # Tokenizeした結果 ['braun', '##d', ',', 'mr', '.', 'owen', 'harris'] # BERTの入力形式 {'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0], 'input_ids': [101, 21909, 2094, 1010, 2720, 1012, 7291, 5671, 102, 0, 0, 0], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]} # BERTへの入力形式をデコードした結果 [CLS] braund, mr. owen harris [SEP] [PAD] [PAD] [PAD] 3-3.入力単語数の確認 BERTに入力する単語数はハイパーパラメータで、最大512単語です。 (事前学習のサイズが512っぽいです) 参考:(自然言語処理モデル(BERT)を利用した日本語の文章分類 〜GoogleColab & Pytorchによるファインチューニング〜 Nameの最大単語数が何かを求めてハイパーパラメータを設定します。 # 最大単語数の確認 max_len = [] # 1文づつ処理 for sent in df["Name_normalize"]: # Tokenizeで分割 token_words = tokenizer.tokenize(sent) # 文章数を取得してリストへ格納 max_len.append(len(token_words)) # 最大の値を確認 print('最大単語数: ', max(max_len)) print('上記の最大単語数にSpecial token([CLS], [SEP])の+2をした値が最大単語数') # 単語数を設定 sequence_max_length = max(max_len) + 2 if sequence_max_length > 512: sequence_max_length = 512 最大単語数: 20 上記の最大単語数にSpecial token([CLS], [SEP])の+2をした値が最大単語数 3-4. BERTモデルの作成 BERTモデルを作成します。 モデルですがタスクによりいくつか用意されているようです。 例えばクラス分類ですとTFBertForSequenceClassificationです。 ただ、いろいろ応用できそうなTFBertModelを元に作成します。 def build_model(learning_rate, is_print=False): # BERTモデルをロード bert_model = transformers.TFAutoModel.from_pretrained(pretrained_model_name) #bert_model = transformers.TFAutoModel.from_pretrained(pretrained_model_name, from_pt=True) # pytouchの場合 # tfへの入力テンソルを作成 # 入力はsequence_max_lengthサイズを3つ(['input_ids', 'token_type_ids', 'attention_mask']) inputs = [ kl.Input(shape=(sequence_max_length,), dtype=tf.int32, name=name) for name in tokenizer.model_input_names ] if is_print: pprint(inputs) # BERTモデルの出力を得る # 出力は TFBaseModelOutputWithPooling (https://huggingface.co/transformers/main_classes/output.html#tfbasemodeloutput) # x[0](last_hidden_state) : 最後のレイヤーの出力 # x[1](pooler_output) : 分類トークンの状態 x = bert_model(inputs) # BERT出力の0番目がクラス分類で使う出力 x1 = x[0][:, 0, :] # 分類用の出力層を用意 # 出力層の構成はTFBertForSequenceClassificationを参考 x1 = kl.Dropout(0.1)(x1) x1 = kl.Dense(1, activation='sigmoid', kernel_initializer=transformers.modeling_tf_utils.get_initializer(0.02))(x1) model_train = tf.keras.Model(inputs=inputs, outputs=x1) # オリジナルの出力値を特徴量としたいので予測専用のモデルも別途作っておく model_pred = tf.keras.Model(inputs=inputs, outputs=[x1, x[0][:, 0, :]]) # optimizerは AdamW を使用 optimizer = transformers.AdamWeightDecay(learning_rate=learning_rate) model_train.compile(optimizer, loss="binary_crossentropy", metrics=["acc"]) #model_train.compile(optimizer, loss="categorical_crossentropy", metrics=["acc"]) # softmaxの場合 if is_print: print(model_train.summary()) return model_train, model_pred # 試しに実行 build_model(0.1, is_print=True) [<KerasTensor: shape=(None, 22) dtype=int32 (created by layer 'input_ids')>, <KerasTensor: shape=(None, 22) dtype=int32 (created by layer 'token_type_ids')>, <KerasTensor: shape=(None, 22) dtype=int32 (created by layer 'attention_mask')>] Model: "model" __________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ================================================================================================== input_ids (InputLayer) [(None, 22)] 0 __________________________________________________________________________________________________ token_type_ids (InputLayer) [(None, 22)] 0 __________________________________________________________________________________________________ attention_mask (InputLayer) [(None, 22)] 0 __________________________________________________________________________________________________ tf_bert_model_4 (TFBertModel) TFBaseModelOutputWit 109482240 input_ids[0][0] token_type_ids[0][0] attention_mask[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_1 (Sli (None, 768) 0 tf_bert_model_4[0][0] __________________________________________________________________________________________________ dropout_186 (Dropout) (None, 768) 0 tf.__operators__.getitem_1[0][0] __________________________________________________________________________________________________ dense_1 (Dense) (None, 1) 769 dropout_186[0][0] ================================================================================================== Total params: 109,483,009 Trainable params: 109,483,009 Non-trainable params: 0 __________________________________________________________________________________________________ 3-5. BERTの学習(ファインチューニング) TPU BERTはモデルがかなり大きく学習に時間がかかります。 Google Colaboratory では TPU が使えるので使うためのコードを書いておきます。 import tensorflow as tf import os runtime_type = "" try: if "COLAB_TPU_ADDR" in os.environ: resolver = tf.distribute.cluster_resolver.TPUClusterResolver('grpc://' + os.environ['COLAB_TPU_ADDR']) else: resolver = tf.distribute.cluster_resolver.TPUClusterResolver() #--- TPU print('Running on TPU ', resolver.cluster_spec().as_dict()['worker']) runtime_type = "TPU" # This is the TPU initialization code that has to be at the beginning. tf.config.experimental_connect_to_cluster(resolver) tf.tpu.experimental.initialize_tpu_system(resolver) tpu_strategy = tf.distribute.TPUStrategy(resolver) tf.keras.backend.clear_session() print("All devices: ", tf.config.list_logical_devices('TPU')) except ValueError: if tf.test.gpu_device_name() != "": #--- GPU runtime_type = "GPU" else: runtime_type = "CPU" print("runtime_type: ", runtime_type) さらにTPUではモデルを作成する時に上記で作成した tpu_strategy を使う必要があります。 if runtime_type == "TPU": # TPU はモデル作成に tpu_strategy.scope で囲む with tpu_strategy.scope(): model_train, model_pred = build_model(learning_rate) else: model_train, model_pred = build_model(learning_rate) 学習コード ファインチューニングですが、以下の記事を参考に学習パラメータを設定しています。 参考:BERTのfine-tuning不安定性はどのように解決できるか? ポイントは、 ・epochsは20 ・学習率は、最初の10%epochは0.00002まで増加、以降は0に減少 です。 また、学習に時間がかかるので一度学習したモデルはファイルに保存するコードにしています。 import sklearn.metrics def train_bert( df_train, # 学習用のデータ text_column, # 対象のカラム名 target_column, # 目的変数のカラム名 df_valid=None, # 検証用データ df_pred_list=[], # 予測用データ model_file_prefix="", # 保存時のファイル名識別子 epochs=20, batch_size=8, ): #-------------------- # 学習率 #-------------------- lr0 = 0.000005 learning_rate = [ 0.00001, 0.00002, ] if epochs-len(learning_rate) > 0: lr_list = np.linspace(0.00002, 0, epochs-len(learning_rate)) learning_rate.extend(lr_list) def lr_scheduler(epoch): return learning_rate[epoch] lr_callback = tf.keras.callbacks.LearningRateScheduler(lr_scheduler) #-------------------- # file #-------------------- model_path = "{}_{}.h5".format( model_file_prefix, pretrained_model_name, ) #-------------------- # モデル #-------------------- if runtime_type == "TPU": with tpu_strategy.scope(): model_train, model_pred = build_model(lr0) else: model_train, model_pred = build_model(lr0) #----------------------------- # モデル入出力用のデータ作成関数 #----------------------------- def _build_x_from_df(df): # Series -> list x = df[text_column].tolist() # tokenize x = tokenizer(x, padding="max_length", max_length=sequence_max_length, truncation=True, return_tensors="tf") # BatchEncoding -> dict return dict(x) def _build_y_from_df(df): return df[target_column] #return tf.keras.utils.to_categorical(df[target_column], num_classes=2) # softmax用 #------------------- # valid用のdatasetを作成 #------------------- if df_valid is not None: valid_x = _build_x_from_df(df_valid) valid_y = _build_y_from_df(df_valid) valid_dataset = ( tf.data.Dataset.from_tensor_slices((valid_x, valid_y)) .batch(batch_size) .cache() ) else: valid_dataset = None #------------------- # 学習 #------------------- if os.path.isfile(model_path): # 学習済みモデルをload print(model_path) model_train.load_weights(model_path) else: train_x = _build_x_from_df(df_train) train_y = _build_y_from_df(df_train) train_dataset = ( tf.data.Dataset.from_tensor_slices((train_x, train_y)) .shuffle(len(train_x), seed=1234) .batch(batch_size) .prefetch(tf.data.experimental.AUTOTUNE) # GPUが計算している間にBatchデータをCPU側で用意しておく機能 ) model_train.fit(train_dataset, epochs=epochs, validation_data=valid_dataset, callbacks=[lr_callback]) model_train.save_weights(model_path) #------------------- # 評価 #------------------- if df_valid is not None: print("valid") pred_y = model_train.predict(valid_dataset, verbose=1) # 正解率 pred_y_label = np.where(pred_y < 0.5, 0, 1) metric = sklearn.metrics.accuracy_score(valid_y, pred_y_label) print("acc", metric) else: metric = 0 #------------------- # 予測 #------------------- print("pred") pred_y_list = [] emb_list = [] for df_pred in df_pred_list: pred_x = _build_x_from_df(df_pred) pred_dataset = ( tf.data.Dataset.from_tensor_slices((pred_x,)) .batch(batch_size) .cache() ) # 予測 pred_output = model_pred.predict(pred_dataset, verbose=1) # pred pred_y = pred_output[0].reshape((-1,)) # (-1,1) -> (-1) #pred_y = pred_y[0][:,1] # softmax用 pred_y_list.append(pred_y) # emb emb_list.append(pred_output[1]) return metric, pred_y_list, emb_list #--- 実行例 train_bert( df_train=df[df["Survived"].notnull()][:10], # 学習データ text_column="Name_normalize", target_column="Survived", df_valid=df[df["Survived"].notnull()][:10], # 検証データ(仮で学習データと同じ) df_pred_list=[df[df["Survived"].isnull()][:10]], # 予測データ epochs=2, # 試しなので少な目 ) print(metric) print(pred_y_list[0].shape) print(emb_list[0].shape) Epoch 1/2 2/2 [==============================] - 31s 6s/step - loss: 0.6952 - acc: 0.4333 - val_loss: 0.6520 - val_acc: 0.8000 Epoch 2/2 2/2 [==============================] - 4s 2s/step - loss: 0.6455 - acc: 0.7167 - val_loss: 0.6547 - val_acc: 0.5000 valid 2/2 [==============================] - 4s 160ms/step acc 0.5 pred 2/2 [==============================] - 4s 164ms/step 0.5 (10,) (10, 768) 4.BERTモデルから予測結果と特徴量を取得する クロスバリデーションで全予測結果と特徴量を取得します。 イメージは以下です。(前記事と同じ図です) import sklearn.model_selection def train_cv(df, text_column, target_column, n_splits): df_train = df[df[target_column].notnull()] df_test = df[df[target_column].isnull()] df_train_idx = df_train.index # 結果用 df_pred = pd.DataFrame(df.index, columns=["index"]).set_index("index") df_emb = pd.DataFrame(df.index, columns=["index"]).set_index("index") df_emb_pred = None metric_list = [] #---------------- # cross validation #---------------- kf = sklearn.model_selection.StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=1234) for i, (train_idx, test_idx) in enumerate(kf.split(df_train, df_train[target_column])): df_train_sub = df_train.iloc[train_idx] df_test_sub = df_train.iloc[test_idx] df_pred_list = [df_test_sub] df_pred_list.append(df_test) model_file_prefix = "cv_{}".format(i) # train metric, pred_y_list, emb_list = train_bert( df_train=df_train_sub, text_column=text_column, target_column=target_column, df_valid=df_test_sub, df_pred_list=df_pred_list, model_file_prefix=model_file_prefix, ) metric_list.append(metric) # 予測結果を保存 result_name = "result_{}".format(i) df_pred.loc[df_train_idx[test_idx], result_name] = pred_y_list[0] df_pred.loc[df_test.index, result_name] = pred_y_list[1] #--------- a = pd.DataFrame(emb_list[0], index=df_train_idx[test_idx]) df_emb = df_emb.combine_first(a) if df_emb_pred is None: df_emb_pred = pd.DataFrame(emb_list[1], index=df_test.index) else: df_emb_pred += emb_list[1] pred_y = df_pred.mean(axis=1) df_emb_pred /= n_splits df_emb = df_emb.combine_first(df_emb_pred) return np.mean(metric_list), pred_y.values, df_emb #--- 結果と特徴量を取得 metric, pred_y, df_emb = train_cv(df, "Name_normalize", "Survived", n_splits=3) print(metric) print(pred_y.shape) print(df_emb.shape) 実行結果 クリックで展開 Epoch 1/20 75/75 [==============================] - 72s 168ms/step - loss: 0.6232 - acc: 0.6738 - val_loss: 0.4911 - val_acc: 0.7845 Epoch 2/20 75/75 [==============================] - 5s 66ms/step - loss: 0.5015 - acc: 0.7881 - val_loss: 0.4903 - val_acc: 0.8249 Epoch 3/20 75/75 [==============================] - 5s 67ms/step - loss: 0.4287 - acc: 0.8344 - val_loss: 0.5410 - val_acc: 0.8081 Epoch 4/20 75/75 [==============================] - 5s 66ms/step - loss: 0.3543 - acc: 0.8761 - val_loss: 0.5543 - val_acc: 0.7980 Epoch 5/20 75/75 [==============================] - 5s 67ms/step - loss: 0.2457 - acc: 0.9094 - val_loss: 0.6346 - val_acc: 0.7946 Epoch 6/20 75/75 [==============================] - 5s 67ms/step - loss: 0.1725 - acc: 0.9361 - val_loss: 0.7133 - val_acc: 0.7845 Epoch 7/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0832 - acc: 0.9732 - val_loss: 0.9226 - val_acc: 0.7778 Epoch 8/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0563 - acc: 0.9798 - val_loss: 0.9967 - val_acc: 0.7980 Epoch 9/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0291 - acc: 0.9932 - val_loss: 0.9987 - val_acc: 0.7946 Epoch 10/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0238 - acc: 0.9928 - val_loss: 0.9397 - val_acc: 0.7912 Epoch 11/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0462 - acc: 0.9900 - val_loss: 0.9016 - val_acc: 0.7845 Epoch 12/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0156 - acc: 0.9957 - val_loss: 0.9964 - val_acc: 0.7744 Epoch 13/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0190 - acc: 0.9949 - val_loss: 1.0513 - val_acc: 0.7744 Epoch 14/20 75/75 [==============================] - 6s 76ms/step - loss: 0.0115 - acc: 0.9975 - val_loss: 1.0042 - val_acc: 0.8013 Epoch 15/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0063 - acc: 1.0000 - val_loss: 1.0379 - val_acc: 0.8047 Epoch 16/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0117 - acc: 0.9966 - val_loss: 1.0797 - val_acc: 0.7980 Epoch 17/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0066 - acc: 1.0000 - val_loss: 1.0950 - val_acc: 0.7912 Epoch 18/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0040 - acc: 1.0000 - val_loss: 1.1076 - val_acc: 0.7946 Epoch 19/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0044 - acc: 1.0000 - val_loss: 1.1138 - val_acc: 0.7946 Epoch 20/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0045 - acc: 1.0000 - val_loss: 1.1138 - val_acc: 0.7946 valid 38/38 [==============================] - 8s 105ms/step acc 0.7946127946127947 pred 38/38 [==============================] - 8s 107ms/step 53/53 [==============================] - 2s 13ms/step Epoch 1/20 75/75 [==============================] - 72s 163ms/step - loss: 0.6437 - acc: 0.6153 - val_loss: 0.4935 - val_acc: 0.7879 Epoch 2/20 75/75 [==============================] - 5s 66ms/step - loss: 0.5036 - acc: 0.7832 - val_loss: 0.4796 - val_acc: 0.8013 Epoch 3/20 75/75 [==============================] - 5s 73ms/step - loss: 0.4667 - acc: 0.8191 - val_loss: 0.5475 - val_acc: 0.7778 Epoch 4/20 75/75 [==============================] - 5s 67ms/step - loss: 0.4285 - acc: 0.8395 - val_loss: 0.5287 - val_acc: 0.8047 Epoch 5/20 75/75 [==============================] - 5s 66ms/step - loss: 0.3448 - acc: 0.8785 - val_loss: 0.5742 - val_acc: 0.8114 Epoch 6/20 75/75 [==============================] - 5s 66ms/step - loss: 0.2957 - acc: 0.8997 - val_loss: 0.6470 - val_acc: 0.8114 Epoch 7/20 75/75 [==============================] - 5s 68ms/step - loss: 0.2138 - acc: 0.9410 - val_loss: 0.6874 - val_acc: 0.8182 Epoch 8/20 75/75 [==============================] - 5s 67ms/step - loss: 0.2018 - acc: 0.9291 - val_loss: 0.6803 - val_acc: 0.7980 Epoch 9/20 75/75 [==============================] - 5s 68ms/step - loss: 0.1191 - acc: 0.9639 - val_loss: 0.7696 - val_acc: 0.8047 Epoch 10/20 75/75 [==============================] - 5s 68ms/step - loss: 0.0709 - acc: 0.9831 - val_loss: 0.7891 - val_acc: 0.8081 Epoch 11/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0470 - acc: 0.9802 - val_loss: 0.8624 - val_acc: 0.8148 Epoch 12/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0310 - acc: 0.9939 - val_loss: 0.9067 - val_acc: 0.8148 Epoch 13/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0127 - acc: 0.9943 - val_loss: 0.9453 - val_acc: 0.8215 Epoch 14/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0236 - acc: 0.9903 - val_loss: 0.9734 - val_acc: 0.8047 Epoch 15/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0089 - acc: 0.9999 - val_loss: 1.0278 - val_acc: 0.7879 Epoch 16/20 75/75 [==============================] - 6s 78ms/step - loss: 0.0133 - acc: 0.9946 - val_loss: 1.0168 - val_acc: 0.7912 Epoch 17/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0057 - acc: 0.9993 - val_loss: 1.0603 - val_acc: 0.8114 Epoch 18/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0045 - acc: 1.0000 - val_loss: 1.0189 - val_acc: 0.8283 Epoch 19/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0063 - acc: 1.0000 - val_loss: 1.0131 - val_acc: 0.8215 Epoch 20/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0035 - acc: 1.0000 - val_loss: 1.0131 - val_acc: 0.8215 valid 38/38 [==============================] - 8s 107ms/step acc 0.8215488215488216 pred 38/38 [==============================] - 8s 105ms/step 53/53 [==============================] - 1s 14ms/step Epoch 1/20 75/75 [==============================] - 72s 169ms/step - loss: 0.6213 - acc: 0.6627 - val_loss: 0.5171 - val_acc: 0.7845 Epoch 2/20 75/75 [==============================] - 5s 66ms/step - loss: 0.4798 - acc: 0.7928 - val_loss: 0.5462 - val_acc: 0.7643 Epoch 3/20 75/75 [==============================] - 5s 66ms/step - loss: 0.4163 - acc: 0.8262 - val_loss: 0.5584 - val_acc: 0.7845 Epoch 4/20 75/75 [==============================] - 5s 69ms/step - loss: 0.3644 - acc: 0.8592 - val_loss: 0.5975 - val_acc: 0.7677 Epoch 5/20 75/75 [==============================] - 5s 69ms/step - loss: 0.2189 - acc: 0.9272 - val_loss: 0.6186 - val_acc: 0.8013 Epoch 6/20 75/75 [==============================] - 5s 70ms/step - loss: 0.1510 - acc: 0.9555 - val_loss: 0.6811 - val_acc: 0.7609 Epoch 7/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0933 - acc: 0.9778 - val_loss: 0.8140 - val_acc: 0.7475 Epoch 8/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0695 - acc: 0.9865 - val_loss: 0.7720 - val_acc: 0.7912 Epoch 9/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0419 - acc: 0.9886 - val_loss: 0.9185 - val_acc: 0.7677 Epoch 10/20 75/75 [==============================] - 6s 78ms/step - loss: 0.0256 - acc: 0.9931 - val_loss: 0.8664 - val_acc: 0.7744 Epoch 11/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0185 - acc: 0.9974 - val_loss: 0.8704 - val_acc: 0.7744 Epoch 12/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0174 - acc: 0.9965 - val_loss: 0.9173 - val_acc: 0.8047 Epoch 13/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0065 - acc: 1.0000 - val_loss: 1.0711 - val_acc: 0.7744 Epoch 14/20 75/75 [==============================] - 5s 68ms/step - loss: 0.0122 - acc: 0.9954 - val_loss: 1.0262 - val_acc: 0.7879 Epoch 15/20 75/75 [==============================] - 5s 68ms/step - loss: 0.0048 - acc: 1.0000 - val_loss: 1.0226 - val_acc: 0.8013 Epoch 16/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0031 - acc: 1.0000 - val_loss: 1.0460 - val_acc: 0.7946 Epoch 17/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0032 - acc: 1.0000 - val_loss: 1.0550 - val_acc: 0.8047 Epoch 18/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0026 - acc: 1.0000 - val_loss: 1.0651 - val_acc: 0.8047 Epoch 19/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0022 - acc: 1.0000 - val_loss: 1.0694 - val_acc: 0.8013 Epoch 20/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0023 - acc: 1.0000 - val_loss: 1.0694 - val_acc: 0.8013 valid 38/38 [==============================] - 9s 113ms/step acc 0.8013468013468014 pred 38/38 [==============================] - 8s 108ms/step 53/53 [==============================] - 1s 13ms/step 0.8058361391694726 (1309,) (1309, 768) 4-1.予測結果を出力 df["BERT"] = pred_y df["BERT_label"] = np.where(pred_y < 0.5, 0, 1) # 学習データの正解率 _df = df[df["Survived"].notnull()] print(sklearn.metrics.accuracy_score(_df["Survived"], _df["BERT_label"])) # 予測結果をcsvで出力 _df = df[df["Survived"].isnull()] df_submit = pd.DataFrame() df_submit["PassengerId"] = _df["PassengerId"] df_submit["Survived"] = _df["BERT_label"] df_submit.to_csv('submit1.csv', header=True, index=False) # 検証結果 0.8024691358024691 提出スコア 正解率81.1%です。 4-2.特徴量を元にAutoMLで予測 import pycaret.classification as pcc # pycaret にデータを渡す df_emb["Survived"] = df["Survived"] r = pcc.setup(df_emb[df["Survived"].notnull()], target="Survived", silent=True, session_id=1234) # モデルを比較 best_models = pcc.compare_models(n_select=3) # --- 比較した結果(Acc) --- # knn : 0.8090 # catboost: 0.8057 # lightgbm: 0.8009 # モデルのパラメータをチューニング models = [] models.append(pcc.tune_model(best_models[0])) # acc: 0.8074 models.append(pcc.tune_model(best_models[1])) # acc: 0.8057 models.append(pcc.tune_model(best_models[2])) # acc: 0.7977 # モデルをブレンドしてチューニング blend_model = pcc.blend_models(models) tuned_model = pcc.tune_model(blend_model) # acc: 0.8089 # チューニング後のパラメータを表示 pcc.plot_model(tuned_model, plot='parameter') # モデルを検証 pcc.predict_model(tuned_model) # acc: 0.8284 # 予測 x_pred = df_emb[df_emb["Survived"].isnull()] df_pred = pcc.predict_model(tuned_model, data=x_pred) # 予測結果をcsvで出力 df_submit = pd.DataFrame() df_submit["PassengerId"] = df[df["Survived"].isnull()]["PassengerId"] df_submit["Survived"] = df_pred["Label"].apply(lambda x:int(float(x))) df_submit.to_csv('submit2.csv', header=True, index=False) 提出スコア 正解率80.382%です。 AutoMLで予測させたら少し下がっちゃいましたね。 その他参考文献 はじめての自然言語処理 第3回 BERT を用いた自然言語処理における転移学習 Transformers
- 投稿日:2021-08-29T17:27:19+09:00
KaggleタイタニックのNameだけで予測精度80%超えた話(BERT/TF2.0)
BERTは自然言語処理を勉強する上で外せなくなりつつあるモデルです。 理解を深めるために実際に実装してみました。 BERTを試すにあたりKaggleチュートリアルのタイタニックを名前(Name)だけで予測を行います。 タイトルにもある通り予測にNameしか使っていないにもかかわらず正解率が80%を超えたので驚きました。 他のスコアとの比較ですが、女性=生存とした場合の正解率は76.555%(Kaggleのサンプルファイル)、過去の記事(Kaggleのタイタニックに挑戦してみた(その2))ではかなりがんばって77.99%でした。 (私の実力がまだないだけかもしれませんが…) また、BERTを使わないNameのみからの予測(前回)でのスコアは72.248%です。 ・Kaggle関係の記事 Kaggleのタイタニックに挑戦してみた(その1) Kaggleのタイタニックに挑戦してみた(その2) Kaggleで書いたコードの備忘録その1~データ分析で使った手法一通り~ Kaggleで書いたコードの備忘録その2~自然言語処理まとめ~ 実装コード Kaggle notebook 0.Nameだけの予測について Nameだけで予測できるのかについて簡単に話しておきます。 まず生存者の傾向ですが、以下の乗客が生き残る傾向にあります。 ・女性や子供 ・位が高い人(1stクラスの客室や貴族など) 名前にこれらの情報が含まれていれば十分に予測できることが想定されます。 以下は名前をワードクラウドで表示した結果です。 mr(男性)、miss(未婚女性)、master(支配人)など予測に必要な情報は含まれていそうです。 1.データ読み込み google colab のドライブにマウントして読み込むコード例を記載しておきます。 from google.colab import drive, files drive.mount('/content/drive') # drive をマウント # 保存ディレクトリ BASE_DIR = "/content/drive/MyDrive/kaggle/Titanic" DATA_PATH = os.path.join(BASE_DIR, "data") # 対象データの保存ディレクトリ MODEL_PATH = os.path.join(BASE_DIR, "model") # モデルを保存するディレクトリ # ディレクトリがなければ作成 os.makedirs(MODEL_PATH, exist_ok=True) タイタニックのデータは"DATA_PATH"配下にある想定です。 # import import numpy as np import pandas as pd from matplotlib import pyplot as plt import time from tqdm import tqdm #from tqdm.notebook import tqdm # notebookの場合 from pprint import pprint # データ読み込み df_train = pd.read_csv(os.path.join(DATA_PATH, "train.csv")) df_test = pd.read_csv(os.path.join(DATA_PATH, "test.csv")) target_column = "Survived" # データをマージ df_test[target_column] = np.nan df = pd.concat([df_train, df_test], ignore_index=True, sort=False) print(df_train.shape) print(df_test.shape) print(df.shape) print(df.columns) (891, 12) (418, 12) (1309, 12) Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp', 'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'], dtype='object') 2.前処理 BERTは前処理も含めて実装されているため基本は前処理の必要はありません。 ただ今回は記号だけ気になったので、余分な情報として事前に除外しておきます。 def str_normalize(ds): # アルファベットと数字のみにする ds = ds.str.replace("[^a-zA-Z0-9]+", " ", regex=True) return ds df["Name_normalize"] = str_normalize(df["Name"]) 3.BERT BERTはディープラーニングを利用した自然言語処理モデルです。 以下の動画が参考になりました。 参考:【深層学習】BERT - 実務家必修。実務で超応用されまくっている自然言語処理モデル【ディープラーニングの世界vol.32】#110 #VRアカデミア #DeepLearning BERT自体はクラス分類以外のタスクにも使えますが、今回はクラス分類に限った実装を行います。 使用するライブラリは transformers/tensorflow2.0 を使います。 3-1.import関係 !pip install -q transformers !pip install -q silence_tensorflow !pip install -q janome # tensorflow のログが多いので silence_tensorflow を入れています from silence_tensorflow import silence_tensorflow silence_tensorflow() # tensorflow import tensorflow as tf import tensorflow.keras.layers as kl # transformers import transformers # transformerのログをエラー以上のみに from transformers import logging logging.set_verbosity_error() 3-2.学習済みモデルの選択 公開されている学習済みモデルは以下から検索できます。 ・[transformers Docs] Pretrained models ・huggingface casedが小文字大文字を区別し、uncasedが小文字のみとなります。 今回はドキュメントの一番上にあるの小文字のみモデルを採用しています。 pretrained_model_name = "bert-base-uncased" 以下でモデルを実際にダウンロードして使えるかどうか確認できます。 tokenizer = transformers.AutoTokenizer.from_pretrained(pretrained_model_name) bert_model = transformers.TFAutoModel.from_pretrained(pretrained_model_name) #bert_model = transformers.TFAutoModel.from_pretrained(pretrained_model_name, from_pt=True) # ※(1) print(bert_model.config) BertConfig { "_name_or_path": "bert-base-uncased", "architectures": [ "BertForMaskedLM" ], "attention_probs_dropout_prob": 0.1, "gradient_checkpointing": false, "hidden_act": "gelu", "hidden_dropout_prob": 0.1, "hidden_size": 768, "initializer_range": 0.02, "intermediate_size": 3072, "layer_norm_eps": 1e-12, "max_position_embeddings": 512, "model_type": "bert", "num_attention_heads": 12, "num_hidden_layers": 12, "pad_token_id": 0, "position_embedding_type": "absolute", "transformers_version": "4.6.1", "type_vocab_size": 2, "use_cache": true, "vocab_size": 30522 } ※(1) 何も指定しないとtensorflowのモデルを読み込みます。(ファイルで言うと tf_model.h5) from_py=Trueを有効にするとpytouchのモデルを読み込みます。(ファイルで言うと pytorch_model.bin) どちらのファイルがあるかは学習済みモデルの Files and versions から確認できます。 tokenizer の動作について tokenizer が前処理を担当しています。 どういった処理をしているか見てみます。 tokenizerに与える引数は以下に説明があります。 Everything you always wanted to know about padding and truncation # 適当に名前の情報を使ってみてみる sample_name = df["Name"][0] print(sample_name) # Tokenizeした結果 token_words = tokenizer.tokenize(sample_name) print(token_words) # BERTに入力する形式に変換 encode_token = tokenizer(sample_name, padding="max_length", max_length=12, truncation=True) pprint(encode_token) # BERTへの入力形式をデコードした結果 print(tokenizer.decode(encode_token["input_ids"])) # オリジナル文字列 Braund, Mr. Owen Harris # Tokenizeした結果 ['braun', '##d', ',', 'mr', '.', 'owen', 'harris'] # BERTの入力形式 {'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0], 'input_ids': [101, 21909, 2094, 1010, 2720, 1012, 7291, 5671, 102, 0, 0, 0], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]} # BERTへの入力形式をデコードした結果 [CLS] braund, mr. owen harris [SEP] [PAD] [PAD] [PAD] 3-3.入力単語数の確認 BERTに入力する単語数はハイパーパラメータで、最大512単語です。 (事前学習のサイズが512っぽいです) 参考:(自然言語処理モデル(BERT)を利用した日本語の文章分類 〜GoogleColab & Pytorchによるファインチューニング〜 Nameの最大単語数が何かを求めてハイパーパラメータを設定します。 # 最大単語数の確認 max_len = [] # 1文づつ処理 for sent in df["Name_normalize"]: # Tokenizeで分割 token_words = tokenizer.tokenize(sent) # 文章数を取得してリストへ格納 max_len.append(len(token_words)) # 最大の値を確認 print('最大単語数: ', max(max_len)) print('上記の最大単語数にSpecial token([CLS], [SEP])の+2をした値が最大単語数') # 単語数を設定 sequence_max_length = max(max_len) + 2 if sequence_max_length > 512: sequence_max_length = 512 最大単語数: 20 上記の最大単語数にSpecial token([CLS], [SEP])の+2をした値が最大単語数 3-4. BERTモデルの作成 BERTモデルを作成します。 モデルですがタスクによりいくつか用意されているようです。 例えばクラス分類ですとTFBertForSequenceClassificationです。 ただ、いろいろ応用できそうなTFBertModelを元に作成します。 def build_model(learning_rate, is_print=False): # BERTモデルをロード bert_model = transformers.TFAutoModel.from_pretrained(pretrained_model_name) #bert_model = transformers.TFAutoModel.from_pretrained(pretrained_model_name, from_pt=True) # pytouchの場合 # tfへの入力テンソルを作成 # 入力はsequence_max_lengthサイズを3つ(['input_ids', 'token_type_ids', 'attention_mask']) inputs = [ kl.Input(shape=(sequence_max_length,), dtype=tf.int32, name=name) for name in tokenizer.model_input_names ] if is_print: pprint(inputs) # BERTモデルの出力を得る # 出力は TFBaseModelOutputWithPooling (https://huggingface.co/transformers/main_classes/output.html#tfbasemodeloutput) # x[0](last_hidden_state) : 最後のレイヤーの出力 # x[1](pooler_output) : 分類トークンの状態 x = bert_model(inputs) # BERT出力の0番目がクラス分類で使う出力 x1 = x[0][:, 0, :] # 分類用の出力層を用意 # 出力層の構成はTFBertForSequenceClassificationを参考 x1 = kl.Dropout(0.1)(x1) x1 = kl.Dense(1, activation='sigmoid', kernel_initializer=transformers.modeling_tf_utils.get_initializer(0.02))(x1) model_train = tf.keras.Model(inputs=inputs, outputs=x1) # オリジナルの出力値を特徴量としたいので予測専用のモデルも別途作っておく model_pred = tf.keras.Model(inputs=inputs, outputs=[x1, x[0][:, 0, :]]) # optimizerは AdamW を使用 optimizer = transformers.AdamWeightDecay(learning_rate=learning_rate) model_train.compile(optimizer, loss="binary_crossentropy", metrics=["acc"]) #model_train.compile(optimizer, loss="categorical_crossentropy", metrics=["acc"]) # softmaxの場合 if is_print: print(model_train.summary()) return model_train, model_pred # 試しに実行 build_model(0.1, is_print=True) [<KerasTensor: shape=(None, 22) dtype=int32 (created by layer 'input_ids')>, <KerasTensor: shape=(None, 22) dtype=int32 (created by layer 'token_type_ids')>, <KerasTensor: shape=(None, 22) dtype=int32 (created by layer 'attention_mask')>] Model: "model" __________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ================================================================================================== input_ids (InputLayer) [(None, 22)] 0 __________________________________________________________________________________________________ token_type_ids (InputLayer) [(None, 22)] 0 __________________________________________________________________________________________________ attention_mask (InputLayer) [(None, 22)] 0 __________________________________________________________________________________________________ tf_bert_model_4 (TFBertModel) TFBaseModelOutputWit 109482240 input_ids[0][0] token_type_ids[0][0] attention_mask[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_1 (Sli (None, 768) 0 tf_bert_model_4[0][0] __________________________________________________________________________________________________ dropout_186 (Dropout) (None, 768) 0 tf.__operators__.getitem_1[0][0] __________________________________________________________________________________________________ dense_1 (Dense) (None, 1) 769 dropout_186[0][0] ================================================================================================== Total params: 109,483,009 Trainable params: 109,483,009 Non-trainable params: 0 __________________________________________________________________________________________________ 3-5. BERTの学習(ファインチューニング) TPU BERTはモデルがかなり大きく学習に時間がかかります。 Google Colaboratory では TPU が使えるので使うためのコードを書いておきます。 import tensorflow as tf import os runtime_type = "" try: if "COLAB_TPU_ADDR" in os.environ: resolver = tf.distribute.cluster_resolver.TPUClusterResolver('grpc://' + os.environ['COLAB_TPU_ADDR']) else: resolver = tf.distribute.cluster_resolver.TPUClusterResolver() #--- TPU print('Running on TPU ', resolver.cluster_spec().as_dict()['worker']) runtime_type = "TPU" # This is the TPU initialization code that has to be at the beginning. tf.config.experimental_connect_to_cluster(resolver) tf.tpu.experimental.initialize_tpu_system(resolver) tpu_strategy = tf.distribute.TPUStrategy(resolver) tf.keras.backend.clear_session() print("All devices: ", tf.config.list_logical_devices('TPU')) except ValueError: if tf.test.gpu_device_name() != "": #--- GPU runtime_type = "GPU" else: runtime_type = "CPU" print("runtime_type: ", runtime_type) さらにTPUではモデルを作成する時に上記で作成した tpu_strategy を使う必要があります。 if runtime_type == "TPU": # TPU はモデル作成に tpu_strategy.scope で囲む with tpu_strategy.scope(): model_train, model_pred = build_model(learning_rate) else: model_train, model_pred = build_model(learning_rate) 学習コード ファインチューニングですが、以下の記事を参考に学習パラメータを設定しています。 参考:BERTのfine-tuning不安定性はどのように解決できるか? ポイントは、 ・epochsは20 ・学習率は、最初の10%epochは0.00002まで増加、以降は0に減少 です。 また、学習に時間がかかるので一度学習したモデルはファイルに保存するコードにしています。 import sklearn.metrics def train_bert( df_train, # 学習用のデータ text_column, # 対象のカラム名 target_column, # 目的変数のカラム名 df_valid=None, # 検証用データ df_pred_list=[], # 予測用データ model_file_prefix="", # 保存時のファイル名識別子 epochs=20, batch_size=8, ): #-------------------- # 学習率 #-------------------- lr0 = 0.000005 learning_rate = [ 0.00001, 0.00002, ] if epochs-len(learning_rate) > 0: lr_list = np.linspace(0.00002, 0, epochs-len(learning_rate)) learning_rate.extend(lr_list) def lr_scheduler(epoch): return learning_rate[epoch] lr_callback = tf.keras.callbacks.LearningRateScheduler(lr_scheduler) #-------------------- # file #-------------------- model_path = "{}_{}.h5".format( model_file_prefix, pretrained_model_name, ) #-------------------- # モデル #-------------------- if runtime_type == "TPU": with tpu_strategy.scope(): model_train, model_pred = build_model(lr0) else: model_train, model_pred = build_model(lr0) #----------------------------- # モデル入出力用のデータ作成関数 #----------------------------- def _build_x_from_df(df): # Series -> list x = df[text_column].tolist() # tokenize x = tokenizer(x, padding="max_length", max_length=sequence_max_length, truncation=True, return_tensors="tf") # BatchEncoding -> dict return dict(x) def _build_y_from_df(df): return df[target_column] #return tf.keras.utils.to_categorical(df[target_column], num_classes=2) # softmax用 #------------------- # valid用のdatasetを作成 #------------------- if df_valid is not None: valid_x = _build_x_from_df(df_valid) valid_y = _build_y_from_df(df_valid) valid_dataset = ( tf.data.Dataset.from_tensor_slices((valid_x, valid_y)) .batch(batch_size) .cache() ) else: valid_dataset = None #------------------- # 学習 #------------------- if os.path.isfile(model_path): # 学習済みモデルをload print(model_path) model_train.load_weights(model_path) else: train_x = _build_x_from_df(df_train) train_y = _build_y_from_df(df_train) train_dataset = ( tf.data.Dataset.from_tensor_slices((train_x, train_y)) .shuffle(len(train_x), seed=1234) .batch(batch_size) .prefetch(tf.data.experimental.AUTOTUNE) # GPUが計算している間にBatchデータをCPU側で用意しておく機能 ) model_train.fit(train_dataset, epochs=epochs, validation_data=valid_dataset, callbacks=[lr_callback]) model_train.save_weights(model_path) #------------------- # 評価 #------------------- if df_valid is not None: print("valid") pred_y = model_train.predict(valid_dataset, verbose=1) # 正解率 pred_y_label = np.where(pred_y < 0.5, 0, 1) metric = sklearn.metrics.accuracy_score(valid_y, pred_y_label) print("acc", metric) else: metric = 0 #------------------- # 予測 #------------------- print("pred") pred_y_list = [] emb_list = [] for df_pred in df_pred_list: pred_x = _build_x_from_df(df_pred) pred_dataset = ( tf.data.Dataset.from_tensor_slices((pred_x,)) .batch(batch_size) .cache() ) # 予測 pred_output = model_pred.predict(pred_dataset, verbose=1) # pred pred_y = pred_output[0].reshape((-1,)) # (-1,1) -> (-1) #pred_y = pred_y[0][:,1] # softmax用 pred_y_list.append(pred_y) # emb emb_list.append(pred_output[1]) return metric, pred_y_list, emb_list #--- 実行例 train_bert( df_train=df[df["Survived"].notnull()][:10], # 学習データ text_column="Name_normalize", target_column="Survived", df_valid=df[df["Survived"].notnull()][:10], # 検証データ(仮で学習データと同じ) df_pred_list=[df[df["Survived"].isnull()][:10]], # 予測データ epochs=2, # 試しなので少な目 ) print(metric) print(pred_y_list[0].shape) print(emb_list[0].shape) Epoch 1/2 2/2 [==============================] - 31s 6s/step - loss: 0.6952 - acc: 0.4333 - val_loss: 0.6520 - val_acc: 0.8000 Epoch 2/2 2/2 [==============================] - 4s 2s/step - loss: 0.6455 - acc: 0.7167 - val_loss: 0.6547 - val_acc: 0.5000 valid 2/2 [==============================] - 4s 160ms/step acc 0.5 pred 2/2 [==============================] - 4s 164ms/step 0.5 (10,) (10, 768) 4.BERTモデルから予測結果と特徴量を取得する クロスバリデーションで全予測結果と特徴量を取得します。 イメージは以下です。(前記事と同じ図です) import sklearn.model_selection def train_cv(df, text_column, target_column, n_splits): df_train = df[df[target_column].notnull()] df_test = df[df[target_column].isnull()] df_train_idx = df_train.index # 結果用 df_pred = pd.DataFrame(df.index, columns=["index"]).set_index("index") df_emb = pd.DataFrame(df.index, columns=["index"]).set_index("index") df_emb_pred = None metric_list = [] #---------------- # cross validation #---------------- kf = sklearn.model_selection.StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=1234) for i, (train_idx, test_idx) in enumerate(kf.split(df_train, df_train[target_column])): df_train_sub = df_train.iloc[train_idx] df_test_sub = df_train.iloc[test_idx] df_pred_list = [df_test_sub] df_pred_list.append(df_test) model_file_prefix = "cv_{}".format(i) # train metric, pred_y_list, emb_list = train_bert( df_train=df_train_sub, text_column=text_column, target_column=target_column, df_valid=df_test_sub, df_pred_list=df_pred_list, model_file_prefix=model_file_prefix, ) metric_list.append(metric) # 予測結果を保存 result_name = "result_{}".format(i) df_pred.loc[df_train_idx[test_idx], result_name] = pred_y_list[0] df_pred.loc[df_test.index, result_name] = pred_y_list[1] #--------- a = pd.DataFrame(emb_list[0], index=df_train_idx[test_idx]) df_emb = df_emb.combine_first(a) if df_emb_pred is None: df_emb_pred = pd.DataFrame(emb_list[1], index=df_test.index) else: df_emb_pred += emb_list[1] pred_y = df_pred.mean(axis=1) df_emb_pred /= n_splits df_emb = df_emb.combine_first(df_emb_pred) return np.mean(metric_list), pred_y.values, df_emb #--- 結果と特徴量を取得 metric, pred_y, df_emb = train_cv(df, "Name_normalize", "Survived", n_splits=3) print(metric) print(pred_y.shape) print(df_emb.shape) 実行結果 クリックで展開 Epoch 1/20 75/75 [==============================] - 72s 168ms/step - loss: 0.6232 - acc: 0.6738 - val_loss: 0.4911 - val_acc: 0.7845 Epoch 2/20 75/75 [==============================] - 5s 66ms/step - loss: 0.5015 - acc: 0.7881 - val_loss: 0.4903 - val_acc: 0.8249 Epoch 3/20 75/75 [==============================] - 5s 67ms/step - loss: 0.4287 - acc: 0.8344 - val_loss: 0.5410 - val_acc: 0.8081 Epoch 4/20 75/75 [==============================] - 5s 66ms/step - loss: 0.3543 - acc: 0.8761 - val_loss: 0.5543 - val_acc: 0.7980 Epoch 5/20 75/75 [==============================] - 5s 67ms/step - loss: 0.2457 - acc: 0.9094 - val_loss: 0.6346 - val_acc: 0.7946 Epoch 6/20 75/75 [==============================] - 5s 67ms/step - loss: 0.1725 - acc: 0.9361 - val_loss: 0.7133 - val_acc: 0.7845 Epoch 7/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0832 - acc: 0.9732 - val_loss: 0.9226 - val_acc: 0.7778 Epoch 8/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0563 - acc: 0.9798 - val_loss: 0.9967 - val_acc: 0.7980 Epoch 9/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0291 - acc: 0.9932 - val_loss: 0.9987 - val_acc: 0.7946 Epoch 10/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0238 - acc: 0.9928 - val_loss: 0.9397 - val_acc: 0.7912 Epoch 11/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0462 - acc: 0.9900 - val_loss: 0.9016 - val_acc: 0.7845 Epoch 12/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0156 - acc: 0.9957 - val_loss: 0.9964 - val_acc: 0.7744 Epoch 13/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0190 - acc: 0.9949 - val_loss: 1.0513 - val_acc: 0.7744 Epoch 14/20 75/75 [==============================] - 6s 76ms/step - loss: 0.0115 - acc: 0.9975 - val_loss: 1.0042 - val_acc: 0.8013 Epoch 15/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0063 - acc: 1.0000 - val_loss: 1.0379 - val_acc: 0.8047 Epoch 16/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0117 - acc: 0.9966 - val_loss: 1.0797 - val_acc: 0.7980 Epoch 17/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0066 - acc: 1.0000 - val_loss: 1.0950 - val_acc: 0.7912 Epoch 18/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0040 - acc: 1.0000 - val_loss: 1.1076 - val_acc: 0.7946 Epoch 19/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0044 - acc: 1.0000 - val_loss: 1.1138 - val_acc: 0.7946 Epoch 20/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0045 - acc: 1.0000 - val_loss: 1.1138 - val_acc: 0.7946 valid 38/38 [==============================] - 8s 105ms/step acc 0.7946127946127947 pred 38/38 [==============================] - 8s 107ms/step 53/53 [==============================] - 2s 13ms/step Epoch 1/20 75/75 [==============================] - 72s 163ms/step - loss: 0.6437 - acc: 0.6153 - val_loss: 0.4935 - val_acc: 0.7879 Epoch 2/20 75/75 [==============================] - 5s 66ms/step - loss: 0.5036 - acc: 0.7832 - val_loss: 0.4796 - val_acc: 0.8013 Epoch 3/20 75/75 [==============================] - 5s 73ms/step - loss: 0.4667 - acc: 0.8191 - val_loss: 0.5475 - val_acc: 0.7778 Epoch 4/20 75/75 [==============================] - 5s 67ms/step - loss: 0.4285 - acc: 0.8395 - val_loss: 0.5287 - val_acc: 0.8047 Epoch 5/20 75/75 [==============================] - 5s 66ms/step - loss: 0.3448 - acc: 0.8785 - val_loss: 0.5742 - val_acc: 0.8114 Epoch 6/20 75/75 [==============================] - 5s 66ms/step - loss: 0.2957 - acc: 0.8997 - val_loss: 0.6470 - val_acc: 0.8114 Epoch 7/20 75/75 [==============================] - 5s 68ms/step - loss: 0.2138 - acc: 0.9410 - val_loss: 0.6874 - val_acc: 0.8182 Epoch 8/20 75/75 [==============================] - 5s 67ms/step - loss: 0.2018 - acc: 0.9291 - val_loss: 0.6803 - val_acc: 0.7980 Epoch 9/20 75/75 [==============================] - 5s 68ms/step - loss: 0.1191 - acc: 0.9639 - val_loss: 0.7696 - val_acc: 0.8047 Epoch 10/20 75/75 [==============================] - 5s 68ms/step - loss: 0.0709 - acc: 0.9831 - val_loss: 0.7891 - val_acc: 0.8081 Epoch 11/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0470 - acc: 0.9802 - val_loss: 0.8624 - val_acc: 0.8148 Epoch 12/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0310 - acc: 0.9939 - val_loss: 0.9067 - val_acc: 0.8148 Epoch 13/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0127 - acc: 0.9943 - val_loss: 0.9453 - val_acc: 0.8215 Epoch 14/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0236 - acc: 0.9903 - val_loss: 0.9734 - val_acc: 0.8047 Epoch 15/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0089 - acc: 0.9999 - val_loss: 1.0278 - val_acc: 0.7879 Epoch 16/20 75/75 [==============================] - 6s 78ms/step - loss: 0.0133 - acc: 0.9946 - val_loss: 1.0168 - val_acc: 0.7912 Epoch 17/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0057 - acc: 0.9993 - val_loss: 1.0603 - val_acc: 0.8114 Epoch 18/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0045 - acc: 1.0000 - val_loss: 1.0189 - val_acc: 0.8283 Epoch 19/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0063 - acc: 1.0000 - val_loss: 1.0131 - val_acc: 0.8215 Epoch 20/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0035 - acc: 1.0000 - val_loss: 1.0131 - val_acc: 0.8215 valid 38/38 [==============================] - 8s 107ms/step acc 0.8215488215488216 pred 38/38 [==============================] - 8s 105ms/step 53/53 [==============================] - 1s 14ms/step Epoch 1/20 75/75 [==============================] - 72s 169ms/step - loss: 0.6213 - acc: 0.6627 - val_loss: 0.5171 - val_acc: 0.7845 Epoch 2/20 75/75 [==============================] - 5s 66ms/step - loss: 0.4798 - acc: 0.7928 - val_loss: 0.5462 - val_acc: 0.7643 Epoch 3/20 75/75 [==============================] - 5s 66ms/step - loss: 0.4163 - acc: 0.8262 - val_loss: 0.5584 - val_acc: 0.7845 Epoch 4/20 75/75 [==============================] - 5s 69ms/step - loss: 0.3644 - acc: 0.8592 - val_loss: 0.5975 - val_acc: 0.7677 Epoch 5/20 75/75 [==============================] - 5s 69ms/step - loss: 0.2189 - acc: 0.9272 - val_loss: 0.6186 - val_acc: 0.8013 Epoch 6/20 75/75 [==============================] - 5s 70ms/step - loss: 0.1510 - acc: 0.9555 - val_loss: 0.6811 - val_acc: 0.7609 Epoch 7/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0933 - acc: 0.9778 - val_loss: 0.8140 - val_acc: 0.7475 Epoch 8/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0695 - acc: 0.9865 - val_loss: 0.7720 - val_acc: 0.7912 Epoch 9/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0419 - acc: 0.9886 - val_loss: 0.9185 - val_acc: 0.7677 Epoch 10/20 75/75 [==============================] - 6s 78ms/step - loss: 0.0256 - acc: 0.9931 - val_loss: 0.8664 - val_acc: 0.7744 Epoch 11/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0185 - acc: 0.9974 - val_loss: 0.8704 - val_acc: 0.7744 Epoch 12/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0174 - acc: 0.9965 - val_loss: 0.9173 - val_acc: 0.8047 Epoch 13/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0065 - acc: 1.0000 - val_loss: 1.0711 - val_acc: 0.7744 Epoch 14/20 75/75 [==============================] - 5s 68ms/step - loss: 0.0122 - acc: 0.9954 - val_loss: 1.0262 - val_acc: 0.7879 Epoch 15/20 75/75 [==============================] - 5s 68ms/step - loss: 0.0048 - acc: 1.0000 - val_loss: 1.0226 - val_acc: 0.8013 Epoch 16/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0031 - acc: 1.0000 - val_loss: 1.0460 - val_acc: 0.7946 Epoch 17/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0032 - acc: 1.0000 - val_loss: 1.0550 - val_acc: 0.8047 Epoch 18/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0026 - acc: 1.0000 - val_loss: 1.0651 - val_acc: 0.8047 Epoch 19/20 75/75 [==============================] - 5s 66ms/step - loss: 0.0022 - acc: 1.0000 - val_loss: 1.0694 - val_acc: 0.8013 Epoch 20/20 75/75 [==============================] - 5s 67ms/step - loss: 0.0023 - acc: 1.0000 - val_loss: 1.0694 - val_acc: 0.8013 valid 38/38 [==============================] - 9s 113ms/step acc 0.8013468013468014 pred 38/38 [==============================] - 8s 108ms/step 53/53 [==============================] - 1s 13ms/step 0.8058361391694726 (1309,) (1309, 768) 4-1.予測結果を出力 df["BERT"] = pred_y df["BERT_label"] = np.where(pred_y < 0.5, 0, 1) # 学習データの正解率 _df = df[df["Survived"].notnull()] print(sklearn.metrics.accuracy_score(_df["Survived"], _df["BERT_label"])) # 予測結果をcsvで出力 _df = df[df["Survived"].isnull()] df_submit = pd.DataFrame() df_submit["PassengerId"] = _df["PassengerId"] df_submit["Survived"] = _df["BERT_label"] df_submit.to_csv('submit1.csv', header=True, index=False) # 検証結果 0.8024691358024691 提出スコア 正解率81.1%です。 4-2.特徴量を元にAutoMLで予測 import pycaret.classification as pcc # pycaret にデータを渡す df_emb["Survived"] = df["Survived"] r = pcc.setup(df_emb[df["Survived"].notnull()], target="Survived", silent=True, session_id=1234) # モデルを比較 best_models = pcc.compare_models(n_select=3) # --- 比較した結果(Acc) --- # knn : 0.8090 # catboost: 0.8057 # lightgbm: 0.8009 # モデルのパラメータをチューニング models = [] models.append(pcc.tune_model(best_models[0])) # acc: 0.8074 models.append(pcc.tune_model(best_models[1])) # acc: 0.8057 models.append(pcc.tune_model(best_models[2])) # acc: 0.7977 # モデルをブレンドしてチューニング blend_model = pcc.blend_models(models) tuned_model = pcc.tune_model(blend_model) # acc: 0.8089 # チューニング後のパラメータを表示 pcc.plot_model(tuned_model, plot='parameter') # モデルを検証 pcc.predict_model(tuned_model) # acc: 0.8284 # 予測 x_pred = df_emb[df_emb["Survived"].isnull()] df_pred = pcc.predict_model(tuned_model, data=x_pred) # 予測結果をcsvで出力 df_submit = pd.DataFrame() df_submit["PassengerId"] = df[df["Survived"].isnull()]["PassengerId"] df_submit["Survived"] = df_pred["Label"].apply(lambda x:int(float(x))) df_submit.to_csv('submit2.csv', header=True, index=False) 提出スコア 正解率80.382%です。 AutoMLで予測させたら少し下がっちゃいましたね。 その他参考文献 はじめての自然言語処理 第3回 BERT を用いた自然言語処理における転移学習 Transformers
- 投稿日:2021-08-29T17:22:02+09:00
venvで立ち上げた仮想環境の中で指定したバージョンのPythonを起動させる方法
直面した事象 ローカルのTerminalでpyenv localで設定したPythonのバージョンが、python -m venvで立ち上げた仮想環境の中で引き継がれない。 やりたいこと venv仮想環境野中で、Python 3.7.9系を使いたい。(Python 3.9.6ではなく) Pyenv localで3.7.9に変えたのに、そのディレクトリでvenvコマンドを叩いて仮想環境を作ると、仮想環境野中では3.7.9が立ち上がらない。 成功した解決策 (参考) 解決方法 https://github.com/pyenv/pyenv#homebrew-on-mac-os-x を読み直してみたら、「pyenv initを使うように!」とあったので、早速実行。 $ pyenv init # Load pyenv automatically by appending # the following to ~/.bash_profile: eval "$(pyenv init -)" 指示通り、~/.bash_profileにeval "$(pyenv init -)"を追記して、source ~/.bash_profileを実行したところ… $ python -V Python 3.5.6 解決! ドキュメントはきちんと読めということですね(反省) 書いてあるとおり、bash_profileに1行追記したところ解決しましたsunny: Terminal electron@diynoMacBook-Pro ~ % ls ~/.bash_profile /Users/electron/.bash_profile electron@diynoMacBook-Pro ~ % cat ~/.bash_profile eval OPAM_SWITCH_PREFIX='/Users/electron/.opam/default'; export OPAM_SWITCH_PREFIX; CAML_LD_LIBRARY_PATH='/Users/electron/.opam/default/lib/stublibs:Updated by package ocaml'; export CAML_LD_LIBRARY_PATH; OCAML_TOPLEVEL_PATH='/Users/electron/.opam/default/lib/toplevel'; export OCAML_TOPLEVEL_PATH; PATH='/Users/electron/.opam/default/bin:/Users/electron/.pyenv/shims:/Users/electron/.pyenv/bin:/Users/electron/.nodebrew/current/bin:/Users/electron/.nodebrew/current/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/Users/electron/.cargo/bin'; export PATH; export PATH=~/bin:$PATH electron@diynoMacBook-Pro ~ % Vimで編集 Terminal electron@diynoMacBook-Pro ~ % vi ~/.bash_profile 最終行に、eval "$(pyenv init -)"を追記した。 Terminal electron@diynoMacBook-Pro ~ % cat ~/.bash_profile eval OPAM_SWITCH_PREFIX='/Users/electron/.opam/default'; export OPAM_SWITCH_PREFIX; CAML_LD_LIBRARY_PATH='/Users/electron/.opam/default/lib/stublibs:Updated by package ocaml'; export CAML_LD_LIBRARY_PATH; OCAML_TOPLEVEL_PATH='/Users/electron/.opam/default/lib/toplevel'; export OCAML_TOPLEVEL_PATH; PATH='/Users/electron/.opam/default/bin:/Users/electron/.pyenv/shims:/Users/electron/.pyenv/bin:/Users/electron/.nodebrew/current/bin:/Users/electron/.nodebrew/current/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/Users/electron/.cargo/bin'; export PATH; export PATH=~/bin:$PATH eval "$(pyenv init -)" electron@diynoMacBook-Pro ~ % sourceで、bash_profileを再読み込み。 Terminal electron@diynoMacBook-Pro ~ % source ~/.bash_profile Pythonが3.7.9系に切り替わった Terminal electron@diynoMacBook-Pro ~ % python -V Python 3.7.9 electron@diynoMacBook-Pro ~ % python3 -V Python 3.7.9 electron@diynoMacBook-Pro ~ % vemv仮想環境の中でも、Pythonが3.7.9系に切り替わっている Terminal electron@diynoMacBook-Pro ~ % python3 -m venv venv-test electron@diynoMacBook-Pro ~ % source venv-test/bin/activate (venv-test) electron@diynoMacBook-Pro ~ % python -V Python 3.7.9 (venv-test) electron@diynoMacBook-Pro ~ % python3 -V Python 3.7.9 (venv-test) electron@diynoMacBook-Pro ~ % 失敗した方法 この固定されたPythonバージョンを変更するにはどうすればよいかというと、結論としては、仮想環境内のPythonバージョンを切り替えるためには、venvの--オプションを利用して仮想環境を一度クリアする必要があります。 実際にやってみます。ローカル環境でpython -m venv [venvディレクトリ] --を実行し、作成済みの仮想環境をクリアします。 Terminal electron@diynoMacBook-Pro ~ % python3 -m venv test electron@diynoMacBook-Pro ~ % source test/bin/activate (test) electron@diynoMacBook-Pro ~ % python --version Python 3.9.6 (test) electron@diynoMacBook-Pro ~ % pip --version pip 21.1.3 from /Users/electron/test/lib/python3.9/site-packages/pip (python 3.9) (test) electron@diynoMacBook-Pro ~ % Terminal (test) electron@diynoMacBook-Pro ~ % deactivate electron@diynoMacBook-Pro ~ % python -m venv test -- /System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python: No module named venv electron@diynoMacBook-Pro ~ % electron@diynoMacBook-Pro ~ % pyenv versions system 2.7.16 3.6.0 3.6.1 3.6.3 3.6.3/envs/gpt2_ja 3.6.6 3.7.0 3.7.4 * 3.9.0 (set by /Users/electron/.python-version) gpt2_ja electron@diynoMacBook-Pro ~ % pyenv local 3.7.9 pyenv: version `3.7.9' not installed electron@diynoMacBook-Pro ~ % pyenv install 3.7.9 python-build: use openssl@1.1 from homebrew python-build: use readline from homebrew Downloading Python-3.7.9.tar.xz... -> https://www.python.org/ftp/python/3.7.9/Python-3.7.9.tar.xz Installing Python-3.7.9... python-build: use readline from homebrew python-build: use zlib from xcode sdk Installed Python-3.7.9 to /Users/electron/.pyenv/versions/3.7.9 electron@diynoMacBook-Pro ~ % electron@diynoMacBook-Pro ~ % pyenv versions electron@diynoMacBook-Pro ~ % pyenv local 3.7.9 electron@diynoMacBook-Pro ~ % pyenv versions system 2.7.16 3.6.0 3.6.1 3.6.3 3.6.3/envs/gpt2_ja 3.6.6 3.7.0 3.7.4 * 3.7.9 (set by /Users/electron/.python-version) 3.9.0 gpt2_ja electron@diynoMacBook-Pro ~ % Terminal electron@diynoMacBook-Pro ~ % python3 -m venv test electron@diynoMacBook-Pro ~ % source test/bin/activate (test) electron@diynoMacBook-Pro ~ % python --version Python 3.9.6 (test) electron@diynoMacBook-Pro ~ % deactivate Terminal electron@diynoMacBook-Pro ~ % pyenv global 3.7.9 electron@diynoMacBook-Pro ~ % python3 -m venv test electron@diynoMacBook-Pro ~ % source test/bin/activate (test) electron@diynoMacBook-Pro ~ % python --version Python 3.9.6 (test) electron@diynoMacBook-Pro ~ % python -V Python 3.9.6 (test) electron@diynoMacBook-Pro ~ % (test) electron@diynoMacBook-Pro ~ % pyenv versions system 2.7.16 3.6.0 3.6.1 3.6.3 3.6.3/envs/gpt2_ja 3.6.6 3.7.0 3.7.4 * 3.7.9 (set by /Users/electron/.python-version) 3.9.0 gpt2_ja (test) electron@diynoMacBook-Pro ~ % (test) electron@diynoMacBook-Pro ~ % python3 --version Python 3.9.6 (test) electron@diynoMacBook-Pro ~ % python --version Python 2.7.16 (test) electron@diynoMacBook-Pro ~ % (test) electron@diynoMacBook-Pro ~ % pyenv local 3.7.9 (test) electron@diynoMacBook-Pro ~ % python -V Python 2.7.16 (test) electron@diynoMacBook-Pro ~ % pyenv shell 3.7.9 (test) electron@diynoMacBook-Pro ~ % python -V Python 2.7.16 (test) electron@diynoMacBook-Pro ~ % deactivate
- 投稿日:2021-08-29T16:53:47+09:00
ダイヤモンド継承
ふと、これってダイヤモンド継承の出番か? という場面が来たので検証してみました。 ダイヤモンド継承とは? 下記図のように親クラスから2つ以上の子クラスに継承し 孫クラスに分岐させた子クラスを継承して1つのクラスにすることです。 このメリットはクラスのメソッドを分岐させることによって、 メンテナンス性を向上させることだと思います。 実際にこれを書いているときにダイヤモンド継承をやりたいと考えた理由は ドメイン駆動設計の構築中に1クラスのメソッドが多くなりすぎて、 分岐させられないか?と考えたのがキッカケです。 Pythonで実践 まずはダイヤモンドの一番上のこの箇所から作成します。 親クラス:共通で使用したいコンストラクタとメソッド class AnimalCommon(): def __init__(self, name): self.name = name def show_name(self): print(self.name) 続いて両脇のAnimalCommonからコンストラクタとメソッドを継承させて、 別の機能を持たせます。 子クラス:分岐して機能作成 # 分岐させた1つ目のクラス class AnimalAction(AnimalCommon): def __init__(self, name): super().__init__(name) def cry(self, cry): print(f'{self.name}は{cry}鳴く') # 分岐させた2つ目のクラス class AnimalStatus(AnimalCommon): def __init__(self, name): super().__init__(name) def disease(self): print(f'{self.name}は病気になった') 最後に分けて機能を作成したものを一つにまとめます。 孫クラス:実際に使用するクラス class Animal(AnimalAction, AnimalStatus): def __init__(self, name): super().__init__(name) このクラスをインスタンス化するとちゃんと どのクラスで定義したメソッドも使えることが確認できます。 crow = Animal('カラス') crow.show_name() # カラス crow.cry('カーカー') # カラスはカーカー鳴く crow.disease() # カラスは病気になった 1つのクラスが大きすぎるとメンテナンスが大変なので、 分けて機能管理をしたいときに使用します。 今回は2つに分岐させて別機能を作成しましたが、 3つ以上でも同様に動かすことは可能です。
- 投稿日:2021-08-29T16:37:00+09:00
ABC211 A~D問題解説 python 灰色~茶色コーダー向け
ABC211(AtCoder Beginner Contest 211) A~D問題の解説記事です。 灰色~茶色コーダーの方向けに解説しています。 A - Blood Pressure 入力を受け取り、問題文のとおりに計算して出力すればOKです。 ※入力例1の場合、出力例1が「110」なのに自分で書いたコードが「110.0」を出力して不安になった人がいるかもしれませんが、「110」と「110.0」は同じものとみなされるので問題ありません。 入力の受け取り、出力がわからない方は以下の記事を参考にしてください。 【提出】 # 入力を受け取る A,B=map(int, input().split()) # Cを計算する C=(A-B)/3+B # Cを出力する print(C) B - Cycle Hit 問題文の通り、H,2B,3B,HR がそれぞれ1つずつあるか確認しましょう。 入力を受け取り、どれにあたるかを変数で管理します。(あったら変数=1にします) 受け取り終わったら全部が1になっているか確認します。 ※「2B」があるか確認する変数名は2Bとしたいところですが変数名の最初を数字にすることはできないため、しかたなくB2にしています。B3も同様です。 【提出】 # それぞれ存在するか確認する変数(存在しない=0,存在する=1) H=0 B2=0 B3=0 HR=0 # 一行ずつ受け取り for i in range(4): # 入力を受け取る S=input() # 入力が一致したものを1へ変更 if S=="H":H=1 if S=="2B":B2=1 if S=="3B":B3=1 if S=="HR":HR=1 # 全部1ならば if H==1 and B2==1 and B3==1 and HR==1: # Yesを出力 print("Yes") # そうでないならば else: # Noを出力 print("No") C - chokudai DPを使って解きます。 DPとは「ある状態までの最適解がわかっていれば→その次の状態の最適解も出せる」という手続きを何度も行って最終的な答えを出す方法です。 具体的な手順は以下です。 (1)表を作る (2)すぐにわかるところを埋める (3)表の小さい方から答えにたどり着くまで埋める (4)答えを出力する (1)表を作る 今回は二次元の表を作ります。 具体的には「Sのi文字目までを使って、chokudaiのj文字目までを作る方法の数」とします。 具体例を見ましょう。 入力例1を使って説明します。 S=chchokudai 表は以下のようになります。 i=0,j=0については0文字目という意味ですので何もありません。ここでは「×」(バツ)としておきます。 (chokudaiのcは1文字目とします) (2)すぐにわかるところを埋める j=0の列はどういう意味でしょうか。 表は「Sのi文字目までを使って、chokudaiのj文字目までを作る方法の数」ですからj=0ということは 「Sのi文字目までを使って、chokudaiの0文字目までを作る方法の数」 となります。 chokudaiの0文字目は何もありませんから、なにも使わないという方法が1個ある、つまり全て1とします。 次にi=0の行を考えます。 表は「Sのi文字目までを使って、chokudaiのj文字目までを作る方法の数」ですからi=0ということは 「Sの0文字目までを使って、chokudaiのj文字目までを作る方法の数」 となります。 Sを1文字も使えないならば作りようがないので0です。 ゆえにi=0の場合は0です。(ただしi=0,j=0の場合は1です) 表を埋めると以下のようになります。 (3)表の小さい方から答えにたどり着くまで埋める 「Sのi文字目までを使って、chokudaiのj文字目までを作る方法の数」を求めたいです。 ここで表における今埋めようとしている場所の上、左部分は全て埋まっているとします。 ・Sのi文字目≠chokudaiのj文字目の場合 Sのi文字目は使えないので 「Sのi-1文字目までを使って、chokudaiのj文字目までを作る方法の数」=「Sのi-1文字目までを使って、chokudaiのj文字目までを作る方法の数」 となります。 ・Sのi文字目=chokudaiのj文字目の場合 Sのi文字目を使わない場合=「Sのi-1文字目までを使って、chokudaiのj文字目までを作る方法の数」 Sのi文字目を使う場合=「Sのi-1文字目までを使って、chokudaiのj-1文字目までを作る方法の数」 となります。 以上を踏まえ、dp[i][j]=「Sのi文字目までを使って、chokudaiのj文字目までを作る方法の数」とした時、以下の式が成り立ちます。 ・j=0 dp[i][j]=1 ・i=0,1≤j≤8 dp[i][j]=0 ・i=0,1≤j≤8 Sのi文字目≠chokudaiのj文字目の場合 dp[i][j]=dp[i-1][j] Sのi文字目=chokudaiのj文字目の場合 dp[i][j]=dp[i-1][j]+dp[i-1][j-1] 大体の人が何を言っているかわからないと思うので具体例で説明します。 今途中まで表が埋まっているとします。 i=4,j=2、すなわち 「Sの4文字目までを使って、chokudaiの2文字目まで(=ch)を作る方法の数」 を考えます。 ・Sの4文字目を使わない場合 Sの3文字目まででchokudaiの2文字目まで(=ch)を作った方法はそのまま使えます。 つまりi=3,j=2の値(dp[3][2])→1通りは作れます。 (Sの4文字目を使って、ではなくSの4文字目までを使って、というところに注意してください。3文字目までだけを使っても構わないということです) ・Sの4文字目を使う場合 Sの4文字目はhなので、chokudaiの1文字目まで(=c)を作ったものにhをくっつければchokudaiの2文字目まで(=ch)を作ることができます。 つまり「Sの3文字目までを使って、chokudaiの1文字目(=c)までを作る方法の数」でcを作ったものにhをくっつければ良いわけです。 ゆえにi=3,j=1の値(dp[3][1])→2通りをプラスします。 まとめると Sの4文字目=chokudaiの2文字目であるから dp[4][2]=dp[3][2]+dp[3][1]=3 (dp[i][j]=dp[i-1][j]+dp[i-1][j-1]) となります。 もうひとつ例を見ましょう i=5,j=2、すなわち 「Sの5文字目までを使って、chokudaiの2文字目まで(=ch)を作る方法の数」 を考えます。 ・Sの5文字目を使わない場合 Sの4文字目まででchokudaiの2文字目まで(=ch)を作った方法はそのまま使えます。 つまりi=4,j=2の値(dp[4][2])→3通りは作れます。 (Sの5文字目を使って、ではなくSの5文字目までを使って、というところに注意してください。4文字目までだけを使っても構わないということです) ・Sの5文字目を使う場合(使えない) Sの5文字目はoなので、chokudaiの2文字目と違います。ゆえにchokudaiの1文字目まで(=c)を作ったものにoをくっつけて・・・というやり方は使えません。 5文字目は使えないので0通りです。 まとめると Sの4文字目≠chokudaiの2文字目であるから dp[5][2]=dp[4][2]=3 (dp[i][j]=dp[i-1][j]) 表をすべて埋めると以下のようになります。 (4)答えを出力する 答えは「Sの10文字目までを使って、chokudaiの8文字目まで(=chokudai)を作る方法の数」 ですから、dp[10(Sの文字数)][8]=3となります。 【提出】 # 入力の受け取り S=input() # 文字数=N N=len(S) # Sの0文字目を?にする S="?"+S # T=?chokudai(?は0文字目) T="?chokudai" # 余りの定義 mod=10**9+7 # dp表を作る dp=[[0]*9 for i in range(N+1)] # Sのi文字目までを使う for i in range(N+1): # chokudaiのj文字目までを作る for j in range(9): # j=0(chokudaiの0文字目までを作る→1通り) if j==0: # 1通り dp[i][j]=1 # j=1~8 else: # i=0(Sの0文字目までを使う→使える文字なし) if i==0: # 作りようがないから0 dp[i][j]=0 # i=1~N else: # もしSのi文字目とchokudaiのj文字目が違うなら if S[i]!=T[j]: # Sのi-1文字目までを使ってchokudaiのj文字目を作る場合と同じ数 dp[i][j]=dp[i-1][j] # もしSのi文字目とchokudaiのj文字目が同じなら else: # dp[i-1][j]:Sのi-1文字目までを使ってchokudaiのj文字目を作る場合(Sのi文字目を使わない) # dp[i-1][j-1]:Sのi-1文字目までを使ってchokudaiのj-1文字目を作る場合(Sのi文字目を使う) dp[i][j]=dp[i-1][j]+dp[i-1][j-1] # 余りを取る dp[i][j]%=mod # SのN文字目までを使ってchokudaiの8文字目までを作る方法の数 print(dp[N][8]) D - Number of Shortest paths BFS(幅優先探索)を使います。 BFSの細かい実装方法については動画で詳しく説明しているのでそちらも御覧ください。 各頂点についてBFSで回りながら ・訪問済みか? ・最短距離はいくらか? ・そこにいたるまでの経路数はいくらか? を管理、更新していきます。 具体的には以下の手順です。 スタート地点1について ○訪問済みにします ○距離を0とします ○経路数を1とします キューに1を入れます。 (1)キューの左端を取り出します(今いる場所) (2)今いる場所から行ける場所を確認します。 ・行ける場所が未訪問ならば(初めて来たならば) ○行ける場所の経路数=今いる場所の経路数 と更新 ○行ける場所の距離=今いる場所までの距離+1 ○行ける場所を訪問済みにする ○キューに行ける場所を追加 ・行ける場所が訪問済みならば ・行ける場所に書いている距離=今いる場所の距離+1ならば(最短経路ならば) ○行ける場所の経路数に今いる場所の経路数を足す (1)(2)をキューが空になるまで繰り返します。 ・行ける場所が訪問済みならば・・・の部分が分かりづらいと思いますので例で説明します。 (入力例1) 今1,2,3は訪問済みで、次に2→4への探索を行おうとしているとします。 c:経路数 d:距離 赤チェック:訪問済み 4は未訪問なので 行ける場所の経路数=今いる場所の経路数 と更新(今いる場所2の経路数=1に更新) 行ける場所の距離=今いる場所までの距離+1(今いる場所2の距離+1=2に更新) 行ける場所を訪問済みにする(赤チェック) キューに行ける場所を追加(4追加) として、以下のようになります。 さらに3→4への探索を行う場合、今度は4は訪問済みです。(赤チェックがついています) ・行ける場所が訪問済みならば ・行ける場所に書いている距離=今いる場所の距離+1ならば(最短経路ならば) (今いる場所3の距離+1=2→4に書いている距離と同じ) 行ける場所の経路数に今いる場所の経路数を足す(3に書いている経路数1+4に書いている経路数1=2) よって以下のように更新されます。 簡単にいうと、一度行ったことがある場所に再度たどり着いたら、 距離からそのルートは最短経路か確認→最短経路なら経路の数をプラス ということを繰り返しています。 実装における deque や while 0<len(que) や for to in connect[now]: の部分がよくわからないという人は上記動画で確認してください。 【提出】 # 入力の受け取り N,M=map(int, input().split()) # 道路の情報を格納する変数 connect=[[] for i in range(N+1)] # つながっている道路の情報を受け取って格納 for i in range(M): A,B=map(int, input().split()) connect[A].append(B) connect[B].append(A) # あまりの定義 mod=10**9+7 # 訪問済みか記録するリスト Falseなら未訪問、Trueなら訪問済み visited=[False]*(N+1) # 1を訪問済みにする visited[1]=True # 1からの距離を記録するリスト dist=[0]*(N+1) # そこに至るまでの経路数を記録するリスト count=[0]*(N+1) # 1に行く方法は1通り count[1]=1 # キューを用意 from collections import deque que=deque() # キューに1を入れる que.append(1) # キューが空になるまで while 0<len(que): # now=今いる場所 をキューから取り出す now=que.popleft() # nowから行ける場所を順にtoへ格納 for to in connect[now]: # もしtoが未訪問ならば(初めてきたならば) if visited[to]==False: # toに到達する経路の数=nowに到達する経路の数 count[to]=count[now] # あまりを取る count[to]%=mod # 距離を今いる場所までの距離+1とする dist[to]=dist[now]+1 # 訪問済みにする visited[to]=True # キューに入れる que.append(to) # toが訪問済みならば else: # toまでの距離=今いる場所の距離+1(最短経路ならば) if dist[to]==dist[now]+1: # toに到達する経路の数にnowに到達する経路の数をプラス count[to]+=count[now] # あまりを取る count[to]%=mod # Nが未訪問であれば if visited[N]==False: # たどり着けないため0を出力 print(0) # そうでないならば(Nが訪問済みならば) else: # count[N]を出力 print(count[N]) 【広告】 「AtCoder 凡人が『緑』になるための精選50問詳細解説」 AtCoderで緑になるための典型50問をひくほど丁寧に解説した本(kindle)、pdf(booth)を販売しています。 お値段100円。 (Kindle Unlimited対象です) kindle:https://www.amazon.co.jp/gp/product/B09C3TPQYV/ booth(pdf):https://sano192.booth.pm/items/3179185 サンプルは以下から。 https://qiita.com/sano192/items/eb2c9cbee6ec4dc79aaf
- 投稿日:2021-08-29T16:14:57+09:00
alexaスキルをvisual studio codeでデバッグする(DynamoDBを使う場合)
前回に引き続き、今度はDynamo DBを使ったスキルをローカルデバッグする環境を作っていきます。 前回と同じく、alexa-hostedスキルの場合です。 DynamoDBを使うためのコードは公式ドキュメントの通りで本番環境だと問題なく動くのですが、vscodeに持ってくると全く動いてくれないので、順番に設定していきます。 環境 環境を再掲しておきます。前回と同じです。 OS mac OS 10.15.7(Catalina) visual studio code 1.59.1 python 3.9.4 (homebrewからインストール) ASK Toolkit Extension 2.8.0 1. pythonパッケージのインストール 公式ドキュメントのようにrequirements.txtを修正して、パッケージをインストールします。 lambda/requirements.txt boto3==1.9.216 ask-sdk-core==1.11.0 ask-sdk-dynamodb-persistence-adapter==1.15.0 $ pip install -r requirements.txt 2. 環境変数の設定 本番環境ではDynamoDBにアクセスするために環境変数を使っていますので、本番環境の環境変数を取得するために、以下のようにlambda_function.pyを変更します。 lambda_function.py import os ... print("environments:", os.environ) ddb_region = os.environ.get('DYNAMODB_PERSISTENCE_REGION') ddb_table_name = os.environ.get('DYNAMODB_PERSISTENCE_TABLE_NAME') ... deployして、本番環境でsimulatorテストしてみてください。 ところで、本番環境でprint文を実行した結果はどこに行くのでしょうか。 答えはCloudWatchログです。web上のalexa developer consoleの「コードエディタ」タグにCloudWatchへのリンクがありますので、そこから開くことができます。 ただ私の環境ではデフォルトではus-east-1(バージニア北部)リージョンのページが開くのですが、そのままだと「ロググループが存在しません」というエラーが出てしまい、ログをみることができなくて悩んでいました。 私の環境ではリージョンをus-west-2(オレゴン)にするとログを見ることができました。(リージョンはCloudWatchページの右上あたりで選択することができます) さて私の環境では環境変数は - DYNAMODB_PERSISTENCE_REGION=us-east-1 - DYNAMODB_PERSISTENCE_TABLE_NAME=1ca5e6da-87d5-47e7-a25d-e46599c41fb になっています。 ちなみにテーブル名はスキルIDに一致しています。 launch.jsonを変更して、環境変数を設定します。後述の理由で、テーブル名の方だけ設定します。 .vscode/launch.json ... "configurations": [ { "name": "Debug Alexa Skill (Python)", "type": "python", "request": "launch", "program": "${command:ask.debugAdapterPath}", "pythonPath": "${command:python.interpreterPath}", "args": [ "--accessToken", "${command:ask.accessToken}", "--skillId", "${command:ask.skillIdFromWorkspace}", "--skillHandler", "lambda_handler", "--skillFilePath", "${workspaceFolder}/lambda/lambda_function.py", "--region", "FE" ], "console": "internalConsole", "cwd": "${workspaceFolder}/lambda", "justMyCode": false, "env": { "DYNAMODB_PERSISTENCE_TABLE_NAME" : "1ca5e6da-87d5-47e7-a25d-e46599c41fbf" } }, ... 3. .awsフォルダの設定 $HOMEディレクトリに、.awsというディレクトリを作ります。すでにあるかもしれません。 そこにcredentialsファイルとconfigファイルを作成します。 $HOME/.aws/credentials [default] aws_access_key_id=XXXXXX aws_secret_access_key=XXXXXXXXX $HOME/.aws/config [default] region=us-east-1 後述しますがDynamoDBローカルを使うので、credentialsの値は何でも構いません。すでに値が入っていればそのままでいいと思います。 configのregionは環境変数DYNAMODB_PERSISTENCE_REGIONに合わせておいた方がいいかと思います。 この設定をしていないと、DynamoDbAdapterのimportの時点でNoRegionErrorがraiseされてしまいます。 4. DynamoDBローカル 本来なら、本番環境と同じDynamoDBにアクセスできる方がテスト環境としてはいいと思うのですが、残念ながらうまくいきませんでした。 というのも、CloudWatchログも同じなので上記画像を見るとわかるかと思いますが、alexaスキルがアクセスするDynamoDBはVoiceHubSSORole?にフェデレーションログインしてアクセスするもので、個人アカウントとは別のアカウントになります。 この特殊アカウントではIAM(Identity and Access Management)を設定する権限もありませんし、credentialが取得できないのでローカルからアクセスする方法がわかりませんでした。 こういったデバッグをしたい場合は、alexa-hostedではなくてawsのlambdaを使ってねという事なのでしょうか… そういう理由でローカルにDynamoDBサーバーを立てて、デバッグではこちらを使う事にします。 やり方はawsドキュメントの通りなのですが、今回はdockerを使ってみようと思います。 dockerはdocker公式サイトからインストールしています。 適当なディレクトリを作り、docker-compose.ymlというファイルを作ります。 docker-compose.yml version: '3.8' services: dynamodb-local: command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ./data" image: "amazon/dynamodb-local:latest" container_name: dynamodb-local ports: - "8000:8000" volumes: - "./docker/dynamodb:/home/dynamodblocal/data" working_dir: /home/dynamodblocal 起動します $ docker-compose up Creating network "dynamodb-local_default" with the default driver Pulling dynamodb-local (amazon/dynamodb-local:latest)... latest: Pulling from amazon/dynamodb-local 2cbe74538cb5: Pull complete 137077f50205: Pull complete 58932e640a40: Pull complete Digest: sha256:bdd26570dc0e0ae49e1ea9d49ff662a6a1afe9121dd25793dc40d02802e7e806 Status: Downloaded newer image for amazon/dynamodb-local:latest Creating dynamodb-local ... done Attaching to dynamodb-local dynamodb-local | Initializing DynamoDB Local with the following configuration: dynamodb-local | Port: 8000 dynamodb-local | InMemory: false dynamodb-local | DbPath: ./data dynamodb-local | SharedDb: true dynamodb-local | shouldDelayTransientStatuses: false dynamodb-local | CorsParams: * dynamodb-local | 5. テーブルの作成 DynamoDBローカルサーバーにテーブルを作成します。 下記pythonプログラムを実行してください。(table_nameは変更してください) import boto3 dynamodb = boto3.resource('dynamodb', endpoint_url="http://localhost:8000") table_name = "1ca5e6da-87d5-47e7-a25d-e46599c41fbf" dynamodb.create_table( TableName=table_name, KeySchema=[ { 'AttributeName': 'id', 'KeyType': 'HASH' # Partition key } ], AttributeDefinitions=[ { 'AttributeName': 'id', 'AttributeType': 'S' }, ], ProvisionedThroughput={ 'ReadCapacityUnits': 10, 'WriteCapacityUnits': 10 } ) テーブルを作ると、先ほどdockerを立ち上げたディレクトリーの下に、docker/dynamodb/shared-local-instance.dbというファイルが作成されます。 6. lambda_function.pyの変更 最後に、vscodeでデバッグ中はDynamoDBローカルを使うようにコードを変更します。 デバッグ中かどうかを判定するために、環境変数DYNAMODB_PERSISTENCE_REGIONがあるかどうかで判断する事にします。 そのため、先ほどlaunch.jsonにこの環境変数を設定しませんでした。 lambda_function.py ddb_region = os.environ.get('DYNAMODB_PERSISTENCE_REGION') ddb_table_name = os.environ.get('DYNAMODB_PERSISTENCE_TABLE_NAME') from ask_sdk_dynamodb.adapter import DynamoDbAdapter if ddb_region : ddb_resource = boto3.resource('dynamodb', region_name=ddb_region) else : ddb_resource = boto3.resource('dynamodb', endpoint_url="http://localhost:8000") dynamodb_adapter = DynamoDbAdapter(table_name=ddb_table_name, create_table=False, dynamodb_resource=ddb_resource) 他の部分は公式ドキュメント通りで動いてくれました。 まとめ 以上で、DynamoDBを使ってもvscodeでデバッグできるようになりました。 直接サーバーのDynamoDBを使う方法がないかと色々調べるのに苦労しましたが、できないとあきらめれば何とかなりました。 DynamoDBで調べていても、alexa-hostedの場合なのかawsの場合なのかわかりずらくて、なかなかいい情報に行き当たらなかったのでまとめてみました。 どなたかの助けになれば幸いです。
- 投稿日:2021-08-29T16:12:17+09:00
ネットワークのクラスタリング - GraphRicciCurvature - 備忘録
概要 コミュニティのクラスタリングがいずれ仕事にも使えそうなので、お試ししてみた備忘録を残す。 いろいろ調べててGraphRicciCurvatureを見つけた。ユークリッド空間ではなく位相空間で引っ張ったり伸ばしたりするやつらしい。なんか面白そう。 でもGraphRicciCurvatureで検索してもヒットしなかったので、Qitta初か? それほど新しいアルゴリズムではないようだが、日本語情報はいつにも増して少ない。 実施期間: 2021年8月 環境:Ubuntu20.04 LTS パケージ:GraphRicciCurvature, networkxなど 1. 準備 パケージをインストールする。前回のpostが動作するパケージはインストール済みとする。 オフィシャルの通りにインストールするだけだが、依存パケージのコンパイルの必要があるので諸々必要。 # 入っていなければ。 sudo apt install g++ sudo apt install cmake sudo apt install clang # 各パケージのオフィシャルの通り pip3 install networkit pip install POT pip3 install GraphRicciCurvature 今回使用するパケージをすべてimportする。 今回もお試しのデータセットはkarateを使用する。 基本的にはオフィシャルチュートリアルに沿ってかいつまんで記載する。 チュートリアルはColabでも動作するように書かれているが、何故かcommunityが見つからないとエラーが出る。Colab上でGithubのソースからpython-louvainをインストールすれば良いような気もするがGPUは必要ないので試していない。 import networkx as nx import numpy as np import math # matplotlib setting %matplotlib inline import matplotlib.pyplot as plt # to print logs in jupyter notebook import logging logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.ERROR) # load GraphRicciCuravture package from GraphRicciCurvature.OllivierRicci import OllivierRicci from GraphRicciCurvature.FormanRicci import FormanRicci # load python-louvain for modularity computation import community as community_louvain # for ARI computation from sklearn import preprocessing, metrics import GraphRicciCurvature print(GraphRicciCurvature.__version__) 0.5.3 helper関数は長いので最後に記載する。 2. GraphRicciCurvatureでクラスタリング トポロジーの知識が必要なようだが、そんなものは専攻したことがないので理屈は理解できない。 パケージの解説によると、 「edgeのRicci曲率がグラフの構造に重要な役割を果たしていることが観察されています。正の曲率を持つedgeはクラスタ内のedgeを表し、負の曲率を持つedgeはクラスタ内の架け橋となります。また、負の曲率を持つedgeはグラフの連結性に大きく関係しており、連結されたグラフから負の曲率を持つedge取り除くと、グラフはすぐに切断されてしまいます。 Ricci flowは、グラフのエッジのRicci曲率を均一化するプロセスである。Ricci flowは、与えられたグラフに対して、各edgeに"Ricci flow metric"をedge weightとして与え、このedge weightより小さいければグラフのRicci曲率がどこでもほぼ等しくなるようにするものです。この"Ricci flow metric"がコミュニティを検出できます。 Ricci曲率とRicci flow metricの両方とも、グラフ分類のためのグラフフィンガープリントとして機能します。グラフが異なれば、edgeのRicci曲率分布もRicci flow metricも異なります。」 あわせてココのレクチャによると、metric tensor: gは球体のどの部分を伸長・収縮させるかを示す。その曲率をRicci curveture: Rと呼び、一般式はココに定義あり。 従い、gがわかればRもわかる。 これをRicci flowに適用する。Ricci flowではgをiterativeに変化させ球体を球に近づける。 このとき球体の凹んだ部分(R<0)は膨らみ、凸った部分(R>0)はへこむ。つまり凹んだ部分では膨らますためgは増加し、凸った部分ではgは減少する。 R = -dg/dt 従いほっておくとR>0なら縮退し消滅する。 縮退の過程で球体の一部が限りなくゼロに近づく(ネック)が放置するとゼロに縮退するのでこのネック部分をマニュアルで削除し、その部分に球体をかぶせる(Surgery Theory)。この後の解説がどうしても納得できなかった。 まぁ、このパケージではこのRがiterationを通じて一定になるようにしているのでグラフは消滅しないし、更新されるedge weightを閾値としてクラスタ間を切断(surgery)することもできる。 アプローチが異なる2つの手法が用意されている。 Ollivier-Ricci curvature: 最適輸送理論に基づく曲率。各edgeの曲率をミクロに見ることができますが、計算量が多くなります。 Forman-Ricci curvature: CWコンプレックスに基づく曲率です。各エッジの曲率をマクロ的に見ることができ、計算速度も非常に軽い。 使い方は同じなので前者のコードを残す。 全APIはココを参照 2.1 Ollivier-Ricci curvature G = nx.karate_club_graph() draw_graph(G_rf) orc = OllivierRicci(G, alpha=0.5, verbose="INFO") orc.compute_ricci_curvature() G_orc = orc.G.copy() # save an intermediate result show_results(G_orc) Karate Club Graph, first 5 edges: Ricci curvature of edge (0,1) is 0.111111 Ricci curvature of edge (0,2) is -0.143750 Ricci curvature of edge (0,3) is 0.041667 Ricci curvature of edge (0,4) is -0.114583 Ricci curvature of edge (0,5) is -0.281250 通常、Ricci curvature(以降R)は[-1, 1]の間に入る。 weightは重要なパラメータだが、元のに設定されていなければdefaultで1となる。 2.2 Ricci flow Rを一定に、かつ曲率をなめらかにしながらedge weightを更新する。Rが大きくマイナス/プラスになるとそのedgeを伸ばす/縮めることとなる。 経験的には20~50回iterationすると、Rはなめらかになるらしい。 とりあえず一度iterationを回すとどうなるか見てみる。 # Start a Ricci flow with Lin-Yau's probability distribution setting with 4 process. orf = OllivierRicci(G, alpha=0.5, base=1, exp_power=0, proc=4, verbose="INFO") # Do Ricci flow for 2 iterations orf.compute_ricci_flow(iterations=2) orf.set_verbose("ERROR") # mute logs orf.compute_ricci_flow(iterations=50) # 上の2回(iterations=2)に続き、行なうことになる。 G_rf = orf.G.copy() show_results(G_rf) Karate Club Graph, first 5 edges: Ricci curvature of edge (0,1) is -0.002740 Ricci curvature of edge (0,2) is -0.002740 Ricci curvature of edge (0,3) is -0.002740 Ricci curvature of edge (0,4) is -0.002740 Ricci curvature of edge (0,5) is -0.002740 Rが一定値に収束し、全edge weightが調整されていることがわかる。 draw_graph(G_rf) surgery実行していないので全edgeはそのまま。色は元の"club"属性を示す(以降同じ)。 2.3 Ricci flow for Community detection クラスタリングにおいては、weight閾値を見つけそれよりも大きなweightを持つedgeを取り除く。 例えば,weight=1.5と1.0でカットした場合を見てみる。 draw_graph(my_surgery(G_rf, cut=1.5)) 左上のnode=[4,5,6,10,15]は前回のLouvainのデンドロが最初に見つけた小グループと同じである。全く違うアルゴリズムだが見つけるものは見つけてくれる。 draw_graph(my_surgery(G_rf, cut=1.0)) cut=1.5や1.0はaccuracyを見ながら最適値に調整する。次項で説明する。 2.4 Optimal transportation and Communities 別のインスタンス(orf2)を作成して、それでやってみる。 draw_graph(my_surgery(G_rf, cut=1.0)) orf2 = OllivierRicci(G, alpha=0.5, base=math.e, exp_power=1, verbose="ERROR") orf2.compute_ricci_flow(iterations=50) G_rf2 = orf2.G.copy() show_results(G_rf2) Karate Club Graph, first 5 edges: Ricci curvature of edge (0,1) is -0.019349 Ricci curvature of edge (0,2) is -0.001516 Ricci curvature of edge (0,3) is -0.010322 Ricci curvature of edge (0,4) is -0.001809 Ricci curvature of edge (0,5) is -0.002310 ヒストグラムが前項と異なるのは引数がbase=math.e, exp_power=1に変わっているから。これとalphaを調整する必要がありそう。チュートリアルによると、もしくはbase=math.e, exp_power=2が理論的に最適(熱拡散に近い)になるとのこと。なお、exp_power=0にすると元論文となる。 alphaは、確率分布の元nodeと隣接nodeの分割割合になる。[0, 1.0] このときのaccuracyを確認する。 check_accuracy(G_rf2, clustering_label="club",plot_cut=True) チャート中のGood cutoffは多分weight=0から増加させながらaccが最初に落ちたときを拾っているぽい。 全体を見るとaccが最高なのは3.0 ~ 3.88の間なので3.88でcutしてみる。 draw_graph(my_surgery(G_rf2, cut=3.88)) うまく分離できている。node=9とnode=8を取り違えているのは前回のLouvainと同じだが、大きく異なるのはRによりedgeを任意に取り除くことができる点で、こっちのほうがよい。 ただ、3.0~3.88を求めるロジックやalphaの調整ロジックは考えないといけない。 一意に決まらないようであれば使うのは難しいか? ちなみにcutoffを2.0にすると例の小クラスタが分離される。 draw_graph(my_surgery(G_rf2, cut=2.0)) 答えはないのでクラスタリングを何に使うかによって調整しなければならない。 2.5 Helper関数 helper関数をオフィシャルから転記しておく。 def check_accuracy(G_origin, weight="weight", clustering_label="value", plot_cut=True): """To check the clustering quality while cut the edges with weight using different threshold Parameters ---------- G_origin : NetworkX graph A graph with ``weight`` as Ricci flow metric to cut. weight: float The edge weight used as Ricci flow metric. (Default value = "weight") clustering_label : str Node attribute name for ground truth. plot_cut: bool To plot the good guessed cut or not. """ G = G_origin.copy() modularity, ari = [], [] maxw = max(nx.get_edge_attributes(G, weight).values()) cutoff_range = np.arange(maxw, 1, -0.025) print(maxw) for cutoff in cutoff_range: edge_trim_list = [] for n1, n2 in G.edges(): if G[n1][n2][weight] > cutoff: edge_trim_list.append((n1, n2)) G.remove_edges_from(edge_trim_list) # Get connected component after cut as clustering clustering = {c: idx for idx, comp in enumerate(nx.connected_components(G)) for c in comp} # Compute modularity and ari modularity.append(community_louvain.modularity(clustering, G, weight)) ari.append(ARI(G, clustering, clustering_label=clustering_label)) plt.xlim(maxw, 0) plt.xlabel("Edge weight cutoff") plt.plot(cutoff_range, modularity, alpha=0.8) plt.plot(cutoff_range, ari, alpha=0.8) if plot_cut: good_cut = -1 mod_last = modularity[-1] drop_threshold = 0.01 # at least drop this much to considered as a drop for good_cut # check drop from 1 -> maxw for i in range(len(modularity) - 1, 0, -1): mod_now = modularity[i] if mod_last > mod_now > 1e-4 and abs(mod_last - mod_now) / mod_last > drop_threshold: if good_cut != -1: print("Other cut:%f, diff:%f, mod_now:%f, mod_last:%f, ari:%f" % ( cutoff_range[i + 1], mod_last - mod_now, mod_now, mod_last, ari[i + 1])) else: good_cut = cutoff_range[i + 1] print("*Good Cut:%f, diff:%f, mod_now:%f, mod_last:%f, ari:%f" % ( good_cut, mod_last - mod_now, mod_now, mod_last, ari[i + 1])) mod_last = mod_now plt.axvline(x=good_cut, color="red") plt.legend(['Modularity', 'Adjust Rand Index', 'Good cut']) else: plt.legend(['Modularity', 'Adjust Rand Index']) def show_results(G, curvature="ricciCurvature"): # Print the first five results print("Karate Club Graph, first 5 edges: ") for n1,n2 in list(G.edges())[:5]: print("Ricci curvature of edge (%s,%s) is %f" % (n1 ,n2, G[n1][n2][curvature])) # Plot the histogram of Ricci curvatures plt.subplot(2, 1, 1) ricci_curvtures = nx.get_edge_attributes(G, curvature).values() plt.hist(ricci_curvtures,bins=20) plt.xlabel('Ricci curvature') plt.title("Histogram of Ricci Curvatures (Karate Club)") # Plot the histogram of edge weights plt.subplot(2, 1, 2) weights = nx.get_edge_attributes(G, "weight").values() plt.hist(weights,bins=20) plt.xlabel('Edge weight') plt.title("Histogram of Edge weights (Karate Club)") plt.tight_layout() def ARI(G, clustering, clustering_label="club"): """ Computer the Adjust Rand Index (clustering accuracy) of "clustering" with "clustering_label" as ground truth. Parameters ---------- G : NetworkX graph A given NetworkX graph with node attribute "clustering_label" as ground truth. clustering : dict or list or list of set Predicted community clustering. clustering_label : str Node attribute name for ground truth. Returns ------- ari : float Adjust Rand Index for predicted community. """ complex_list = nx.get_node_attributes(G, clustering_label) le = preprocessing.LabelEncoder() y_true = le.fit_transform(list(complex_list.values())) if isinstance(clustering, dict): # python-louvain partition format y_pred = np.array([clustering[v] for v in complex_list.keys()]) elif isinstance(clustering[0], set): # networkx partition format predict_dict = {c: idx for idx, comp in enumerate(clustering) for c in comp} y_pred = np.array([predict_dict[v] for v in complex_list.keys()]) elif isinstance(clustering, list): # sklearn partition format y_pred = clustering else: return -1 return metrics.adjusted_rand_score(y_true, y_pred) def my_surgery(G_origin: nx.Graph(), weight="weight", cut=0): """A simple surgery function that remove the edges with weight above a threshold Parameters ---------- G_origin : NetworkX graph A graph with ``weight`` as Ricci flow metric to cut. weight: str The edge weight used as Ricci flow metric. (Default value = "weight") cut: float Manually assigned cutoff point. Returns ------- G : NetworkX graph A graph after surgery. """ G = G_origin.copy() w = nx.get_edge_attributes(G, weight) assert cut >= 0, "Cut value should be greater than 0." if not cut: cut = (max(w.values()) - 1.0) * 0.6 + 1.0 # Guess a cut point as default to_cut = [] for n1, n2 in G.edges(): if G[n1][n2][weight] > cut: to_cut.append((n1, n2)) print("*************** Surgery time ****************") print("* Cut %d edges." % len(to_cut)) G.remove_edges_from(to_cut) print("* Number of nodes now: %d" % G.number_of_nodes()) print("* Number of edges now: %d" % G.number_of_edges()) cc = list(nx.connected_components(G)) print("* Modularity now: %f " % nx.algorithms.community.quality.modularity(G, cc)) print("* ARI now: %f " % ARI(G, cc)) print("*********************************************") return G def draw_graph(G, clustering_label="club"): """ A helper function to draw a nx graph with community. """ complex_list = nx.get_node_attributes(G, clustering_label) le = preprocessing.LabelEncoder() node_color = le.fit_transform(list(complex_list.values())) nx.draw_spring(G, nodelist=G.nodes(), node_color=node_color, cmap=plt.cm.rainbow, alpha=0.8, with_labels=True) @article{ni2019community, title={Community detection on networks with ricci flow}, author={Ni, Chien-Chun and Lin, Yu-Yao and Luo, Feng and Gao, Jie}, journal={Scientific reports}, volume={9}, number={1}, pages={1--12}, year={2019}, publisher={Nature Publishing Group} } 以上
- 投稿日:2021-08-29T16:06:49+09:00
OWON VDS3104 をpythonで制御 (その3)
前回に引き続き、OWON の USBオシロスコープ VDS3104(LAN無しモデル) をpythonで制御してみます。 環境 OS : Windows 10 対象機器 : VDS3104 アプリ : OWON VDS 2.0.19.4 言語 : python 3.7 データと波形イメージを自動採取 実用的な例として、pythonで信号源を操作しながらデータと波形イメージを複数回採取してみます。このようなプログラムにしておけば、オシロのモード設定やオフセット設定などの地味に面倒くさい作業から解放されますし、後日同じような波形を採取する時にプログラムを実行するだけで完全に同じ条件を再現できます。 今回の例では、USB-シリアル変換ICのRTS出力をpySerialで操作しながら、立ち下り波形と立ち上り波形を順次採取してみました。(無負荷だと面白くないので)RTS出力にRCフィルター(1kΩ, 0.1uF)を接続し、オシロのCH1にRTS出力を、CH2にフィルター後の信号を接続しています。。 ポイントとしては、 *RUNSTop? クエリで現在の状態を確認してから、必要に応じて *RUNSTop コマンドを送る。 *RUNSTop コマンドでRUN状態にするとトリガーモードがAUTOになってしまうので、必要ならその後に :TRIGger:MODE SINGle または :TRIGger:MODE NORMal コマンドを送る。 RUN状態で *LDM? , *RDM? , または :SAVE:CSV コマンドを送ると STOP 状態になるので、次の採取をするには上記の *RUNSTop コマンドと、必要なら :TRIGger:MODE SINGle または :TRIGger:MODE NORMal コマンドを送る。 最初に *RST コマンドで初期化しておくと良いが、 :MEASure と :SAVE:IMAGe:INVert の設定は残るようなので、必要なら :MEASure:DELete ALL と :SAVE:IMAGe:INVert OFF コマンドを送っておく。 vds03.py from socket import AF_INET, socket, SOCK_STREAM, setdefaulttimeout from time import sleep import serial class LanSource: def __init__(self, address): #param address: (ip, port) -> (str, int) super(LanSource, self).__init__() setdefaulttimeout(5) client = socket(AF_INET, SOCK_STREAM) client.connect(address) client.settimeout(5) self.address = address self.client = client def close(self): self.client.close() def PlayCmds(ls, cmdlist): for cmd, slp, dst in cmdlist: print(cmd) ls.client.send(cmd) sleep(slp) dat = b'' while True: rsv = ls.client.recv(2048) dat += rsv if len(rsv) < 2048: break if dst: with open(dst,"wb") as f: f.write(dat) else: print(dat) return dat def RUNSingle(ls): tg = b'STOP\n\r' res = PlayCmds(ls, [[b'*RUNStop?', 0, '']]) if (res == tg): PlayCmds(ls, [[b'*RUNStop', 0, '']]) PlayCmds(ls, [[b':TRIGger:MODE SINGle', 1, '']]) Commands_Init = [ [b'*IDN?', 0, ''] ,[b'*RST', 1, ''] ,[b':MEASure:DELete ALL', 0, ''] ,[b':SAVE:IMAGe:INVert OFF', 0, ''] ,[b':CHANnel1:DISPlay ON', 0, ''] ,[b':CHANnel1:COUPling DC', 0, ''] ,[b':CHANnel1:SCALe 1', 0, ''] ,[b':CHANnel1:OFFSet -75', 0, ''] ,[b':CHANnel2:DISPlay ON', 0, ''] ,[b':CHANnel2:COUPling DC', 0, ''] ,[b':CHANnel2:SCALe 1', 0, ''] ,[b':CHANnel2:OFFSet -75', 0, ''] ,[b':CHANnel3:DISPlay OFF', 0, ''] ,[b':CHANnel4:DISPlay OFF', 0, ''] ,[b':ACQuire:MDEPth 10K', 0, ''] ,[b':TIMebase:SCALe 100us', 0, ''] ,[b':TIMebase:HOFFset 400', 0, ''] ,[b':TRIGger:TYPE SINGle', 0, ''] ,[b':TRIGger:SINGle:EDGE:SOURce CH1', 0, ''] ,[b':TRIGger:SINGle:EDGE:SLOPe FALL', 0, ''] ,[b':TRIGger:SINGle:EDGE:LEVel 0', 0, ''] ] Commands1 = [ [b'*LDM? C:\\tmp\\DAT', 2, ''] ,[b':SAVE:IMAGe C:\\TMP\\FALL.png', 1, ''] ,[b':TRIGger:SINGle:EDGE:SLOPe RISE', 0, ''] ] Commands2 = [ [b'*LDM? C:\\tmp\\DAT', 2, ''] ,[b':SAVE:IMAGe C:\\TMP\\RISE.png', 1, ''] ] if __name__ == '__main__': ls = LanSource(('localhost', 5188)) PlayCmds(ls, Commands_Init) ser = serial.Serial() ser.baudrate = 9600 ser.port = 'COM5' ser.setRTS(0) ser.open() RUNSingle(ls) ser.setRTS(1) PlayCmds(ls, Commands1) RUNSingle(ls) ser.setRTS(0) PlayCmds(ls, Commands2) ser.close() ls.close() 動作例(動画)
- 投稿日:2021-08-29T16:03:26+09:00
【GitHub】Actions で pytest を実行する
actions で python の実行環境を用意するには、actions/setup-pythonを使用する。あとは、pytest を含む必要なモジュールをインストールすれば、pytest が実行できる。 github/workflows/pytest.yaml name: Pytest on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.8' architecture: x64 - name: Install dependencies run: | python -m pip install --upgrade pip if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Test with pytest run: | python -m pytest 参考 Python のビルドとテスト https://docs.github.com/ja/actions/guides/building-and-testing-python actions/setup-python https://github.com/actions/setup-python
- 投稿日:2021-08-29T15:08:16+09:00
Robbins 定数の極限値
今日 Twitter で Robbins 定数というのを知ったのだが、高次元立方体で次元を無限大極限をとったらどのような振る舞いをするのか気になったので調べた。 問題設定 一辺の長さ 1 で次元 $d$ の立方体を考え、その中にランダムに2点をとった場合、その2点間の長さの平均はいくらか?特に $d \rightarrow \infty$ 極限をとるとどんな値に収束するか? $d$ 次元ベクトル $X_n$ の成分 $X_{nj} \ (j = 1, ... d)$ が独立な連続一様分布 $\mathrm{Uniform}(0, 1)$ する。また、別のベクトル $X_m\ (n \neq m)$ とは i.i.d とする。この下、 $\mathrm{E}_{X_m, X_n}[|X_m - X_n|]$ の次元 $d$ のリーディングオーダーを見る。 また、記号の定義として、$\mu:=\mathrm{E}_X[X]$ 、$\Sigma:=\mathrm{E}_X[(X-\mu)(X-\mu)^T]$ で、それぞれ $X$ の平均と共分散行列である。 計算してみる 非常に高い次元で計算するので、いわゆる高次元統計学の手法が使える。詳しくは青嶋高次元統計学などを参照してください。以下にはお気持ち計算を書いておきます。(球形条件と一致性条件が必要だが、今回のシチュエーションなら問題ないはず) $|X-\mu| \sim \sqrt{\mathrm{tr}(\Sigma)}$ 証明:Chebyshev の不等式より、任意の $\epsilon > 0$ に対して \begin{align} P_X\left(\left|\left|X-\mu\right|^2 - \mathrm{tr}(\Sigma)\right| \geq \epsilon\mathrm{tr}(\Sigma)\right)&\leq \frac{\mathrm{Var}_{X}\left(\left|X-\mu\right|^2\right)}{\left(\epsilon \mathrm{tr}\left(\Sigma\right)\right)^2}\\ &\sim \frac{O(d)}{O(d^2)} \rightarrow 0 \end{align} $|X_m-X_n| \sim \sqrt{2\mathrm{tr}(\Sigma)}$ 証明:計算する。 \begin{align} |X_m-X_n|^2 = |X_m-\mu|^2 + |X_n-\mu|^2 - 2 (X_m - \mu) \cdot (X_n - \mu) \sim |X_m - \mu|^2 + |X_n - \mu| \sim 2\mathrm{tr}(\Sigma) \end{align} $|X-\mu|^2$ のオーダー $d$ に対して $(X_m - \mu) \cdot (X_n - \mu)$ は $\sqrt{d}$ オーダーとなるので落とせる。 今回のシチュエーションの下、$|X_m-X_n|\sim \sqrt{\frac{d}{6}}$ 証明:計算する。 一様分布だと $\Sigma_{ii} = \frac{1}{12}$ なので、 \begin{align} |X_m-X_n| \sim \sqrt{2\mathrm{tr}(\Sigma)} = \sqrt{2d \Sigma_{ii}} = \sqrt{\frac{d}{6}} \end{align} python でシミュレーション 以下のコードでシミュレーションした。Scipy の連続一様分布を使用し、シードは 0 とした。 次元は 10, 100, 1000, 10000, 100000, 1000000 で、各次元で1000個のサンプルを作成し、2点間の長さを $\sqrt{d}$ で割った値の統計量を計算した。前の計算の結果から、理論的には $\sqrt{6}^{-1}$ に収束することが期待される。 import scipy import scipy.stats import numpy as np import pandas as pd np.random.seed = 0 def create_sample(dimension: int): lefts = scipy.stats.uniform.rvs(loc=0, scale=1, size=dimension) rights = scipy.stats.uniform.rvs(loc=0, scale=1, size=dimension) return np.linalg.norm([left - right for left, right in zip(lefts, rights)]) def create_samples(dimension: int, size: int): return np.array([create_sample(dimension) for _ in range(size)]) def create_regularized_samples(dimension: int, size: int): return create_samples(dimension, size) / np.sqrt(dimension) def create_result_stats(dimension: int, size: int): samples_ = create_regularized_samples(dimension, size) df_ = pd.DataFrame() df_[f'dimension:{dimension}'] = samples_ return df_.describe() dims = [10, 100, 1000, 10000, 100000, 1000000] size = 1000 dfs = [create_result_stats(dim, size) for dim in dims] df = pd.concat(dfs, axis=1) display(df) df.to_csv('./result.csv', index=False) 得られた結果は以下の表の通りで、次元が上がるにつれ、サンプルの分布の幅が狭まり、中央値が $\sqrt{6}^{-1}\sim0.408248$ に近づいていることがわかる。 dimension:10 dimension:100 dimension:1000 dimension:10000 dimension:100000 dimension:1000000 count 1000 1000 1000 1000 1000 1000 mean 0.398629 0.406624 0.408061 0.408101 0.408245 0.408245 std 0.0772576 0.0244614 0.00759514 0.00250201 0.000772142 0.000245222 min 0.136903 0.33142 0.382426 0.400345 0.405884 0.407418 25% 0.346834 0.38949 0.402894 0.406306 0.407733 0.408087 50% 0.39714 0.406455 0.4082 0.408049 0.408214 0.408245 75% 0.451607 0.423186 0.413324 0.409826 0.408759 0.408405 max 0.611364 0.484639 0.430834 0.417192 0.410589 0.409004 さいごに これ、球形条件と一致性条件が満たされるなら基本的にどんな図形でも $\sqrt{2d \Sigma_{ii}}$ になりそうですね。。。
- 投稿日:2021-08-29T14:23:33+09:00
pyenv installで"C compiler cannot create executables"で失敗するときの対応方法
概要 pyenv install 3.9.4で以下のエラーが出た。(エラーの前後は省略) configure: error: C compiler cannot create executables 3.9.4を別のバージョンに変えても同じエラーで失敗する。 結論としては、xcode-selectをインストールし直したら成功した。 環境 macOS Big Sur pyenv 2.0.5 対応方法 $ sudo rm -rf /Library/Developer/CommandLineTools $ xcode-select --install 自分はこの方法でうまくいきましたが、実行するときは自己責任でお願いします。
- 投稿日:2021-08-29T13:58:01+09:00
PySimpleGUIでシンプルなフォトフレームを作る
PySimpleGUIで画像ビュアー機能を実装するTipsの備忘録(シンプルなサンプルコード付)。 画像コンポーネントをウィンドウ全体に広げて配置する方法や、クリック位置の取得方法を日本語で見つけたい人向け。 できるもの とてもシンプルなフォトフレーム的画像ビュアー。 カレントフォルダのimagesフォルダから画像を取得し、タイトルバーのないウィンドウに表示して3秒ごとに画像を切り替える。 画面の左半分をクリックすると前の画像を表示、右半分をクリックすると次の画像を表示する。 画面上部(ウィンドウ高さの1/10まで)をクリックすると終了ダイアログを表示する。 ESCキーで即時終了する。 ラズパイとかタッチスクリーンをフォトフレームにする時の参考になるかも。 シンプルなサンプルコード viewer.py import PySimpleGUI as sg from PIL import Image import io from glob import glob image_dir = "images" # 画像フォルダのパス interval = 1000 * 3 # 画像切り替えインターバル(3秒) exts = ["jpg", "png", "bmp"] # 表示する画像の拡張子 sg.theme("Black") # カラーテーマ(背景色を黒にするため) # 画面を最大化する場合 from screeninfo import get_monitors monitor = get_monitors()[0] window_size = (monitor.width, monitor.height) # 画面を最大化しない場合(下の行の#を削除すること) #window_size = (800, 600) # リストの画像を表示し、indexにループ補正した値を返す def show_image(window, images, index): image_size = (window_size[0] - 20, window_size[1] - 20) # 画像が若干見切れる対策 # indexがマイナスならリストの上限値、indexがリスト上限より上なら0を設定する index = len(images) - 1 if index < 0 else 0 if index >= len(images) else index path = images[index] # 画像を縮小表示 image = Image.open(path) image.thumbnail(image_size) bio = io.BytesIO() image.save(bio, format="PNG") window["-IMAGE-"].update(data=bio.getvalue()) return index def main(): # 画像リスト取得 images = [x for ext in exts for x in glob(f"{image_dir}/*.{ext}")] # 内包表記2回で二次元配列を一次元に平坦化 # ウィンドウ初期化 layout = [ [sg.Image(key="-IMAGE-", enable_events=True, expand_x=True, expand_y=True)], ] window = sg.Window("Image Viewer", layout, size=window_size, return_keyboard_events=True, finalize=True, no_titlebar=True) # メインループ index = show_image(window, images, 0) # ループ前に先頭の画像表示 while True: event, values = window.read(timeout=interval) if event == "Exit" or event == sg.WIN_CLOSED or event.startswith("Escape"): break elif event == "__TIMEOUT__": index = show_image(window, images, index + 1) elif event == "-IMAGE-": # 画像コンポーネントクリックイベント widget = window["-IMAGE-"].Widget y = widget.winfo_pointery() - widget.winfo_rooty() if y < (window_size[1] // 10): if sg.popup_ok_cancel("終了しますか?") == "OK": break x = widget.winfo_pointerx() - widget.winfo_rootx() if x < (window_size[0] // 2): index = show_image(window, images, index - 1) else: index = show_image(window, images, index + 1) window.close() if __name__ == "__main__": main() 事前準備 Python 3.7以上に下記のパッケージをpipインストールすること PySimpleGUI GUIパッケージ。これがないと記事が始まらない Pillow 画像の縮小表示に必要 screeninfo 画面解像度を楽に取得できる 最大化しない場合やサイズ決め打ちで指定する場合はサンプルコードの# 画面を最大化する場合以下4行を削除して良い 備忘録 モニタの解像度を取得する screeninfoのgetmonitorsからwidthとheightを取得できる。 from screeninfo import get_monitors monitor = get_monitors()[0] window_size = (monitor.width, monitor.height) ウィンドウをフォトフレーム向けに設定する Windowのreturn_keyboard_eventsでキーボードイベントを有効にし、初期表示で画像を表示するためにfinalizeを設定し、no_titlebarでタイトルバーを消す。 return_keyboard_events=TrueにするとESCキーを打った時にEscape:27の文字がevent変数に格納される。 finalize=Trueでないとwindow.read()する前にshow_imageする処理がエラーになる。 window = sg.Window("Image Viewer", layout, size=window_size, return_keyboard_events=True, finalize=True, no_titlebar=True) # メインループ index = show_image(window, images, 0) # ループ前に先頭の画像表示 while True: event, values = window.read(timeout=interval) if event == "Exit" or event == sg.WIN_CLOSED or event.startswith("Escape"): 画像を縮小表示する 原寸で表示するだけならPySimpleGUI.Imageにファイルパスを送るだけでいいけど、アスペクト比を保ったまま縮小表示するならPillowのImage.thumbnailを使うのが便利。 記事執筆時点ではImageはGIFとPNGしか表示できないから下記のコードでJPEGやBMPをPNG化する処理も兼ねている。 このコードではアニメーションGIFを考慮していない。 image = Image.open(path) image.thumbnail(image_size) bio = io.BytesIO() image.save(bio, format="PNG") window["-IMAGE-"].update(data=bio.getvalue()) PySimpleGUIのコンポーネントを広げて表示する Imageコンポーネントは左上に画像サイズで表示され、クリックイベントも発生しない。 expand_xとexpand_yを指定することでコンポーネントのサイズをウィンドウ内で広げて配置することができる。 なお、Imageコンストラクタの引数は数か月前に追加されたので、古いバージョンだとエラーになる。 sg.Image(key="-IMAGE-", enable_events=True, expand_x=True, expand_y=True) Imageコンポーネントのクリック座標を取得する 今回書きたかったことはこれ。 クリック座標を取得する方法をpysimplegui click positionとかpysimplegui get mouse locationとかでWeb検索しても全然見つからなかった。 でもWidgetで裏側のTkinterにアクセスできる。 クリックしたときにwinfo pointerxなどでマウスの位置を取得すればクリック座標が分かる。 widget = window["-IMAGE-"].Widget x = widget.winfo_pointerx() - widget.winfo_rootx() y = widget.winfo_pointery() - widget.winfo_rooty() 今回のオチ 記事を書く調べものして気づく。 あれ?公式サンプルのPySimpleGUI/DemoPrograms/Demo_Img_Viewer.pyで大体似たようなことやってる…。 マウスホイールスクロールで切り替えたい人はそちらを参考にどうぞ。 参考資料 PYTHONを使って、ディスプレイの解像度・サイズを取得しよう PySimpleGUI/DemoPrograms/Demo_Img_Viewer.py Answer: Is there an pysimplegui function to get the the loction of the input cursor? Answer: Mouse Position Python Tkinter PySimpleGUI.Window PySimpleGUI.Image PySimpleGUI.Widget Image.thumbnail winfo pointerx
- 投稿日:2021-08-29T13:58:01+09:00
PySimpleGUIでシンプルなフォトフレームアプリを作る
PySimpleGUIで画像ビュアー機能を実装するTipsの備忘録(シンプルなサンプルコード付)。 画像コンポーネントをウィンドウ全体に広げて配置する方法や、クリック位置の取得方法を日本語で見つけたい人向け。 できるもの とてもシンプルなフォトフレーム的画像ビュアー。 カレントフォルダのimagesフォルダから画像を取得し、タイトルバーのないウィンドウに表示して3秒ごとに画像を切り替える。 画面の左半分をクリックすると前の画像を表示、右半分をクリックすると次の画像を表示する。 画面上部(ウィンドウ高さの1/10まで)をクリックすると終了ダイアログを表示する。 ESCキーで即時終了する。 ラズパイとかタッチスクリーンをフォトフレームにする時の参考になるかも。 シンプルなサンプルコード viewer.py import PySimpleGUI as sg from PIL import Image import io from glob import glob image_dir = "images" # 画像フォルダのパス interval = 1000 * 3 # 画像切り替えインターバル(3秒) exts = ["jpg", "png", "bmp"] # 表示する画像の拡張子 sg.theme("Black") # カラーテーマ(背景色を黒にするため) # 画面を最大化する場合 from screeninfo import get_monitors monitor = get_monitors()[0] window_size = (monitor.width, monitor.height) # 画面を最大化しない場合(下の行の#を削除すること) #window_size = (800, 600) # リストの画像を表示し、indexにループ補正した値を返す def show_image(window, images, index): image_size = (window_size[0] - 20, window_size[1] - 20) # 画像が若干見切れる対策 # indexがマイナスならリストの上限値、indexがリスト上限より上なら0を設定する index = len(images) - 1 if index < 0 else 0 if index >= len(images) else index path = images[index] # 画像を縮小表示 image = Image.open(path) image.thumbnail(image_size) bio = io.BytesIO() image.save(bio, format="PNG") window["-IMAGE-"].update(data=bio.getvalue()) return index def main(): # 画像リスト取得 images = [x for ext in exts for x in glob(f"{image_dir}/*.{ext}")] # 内包表記2回で二次元配列を一次元に平坦化 # ウィンドウ初期化 layout = [ [sg.Image(key="-IMAGE-", enable_events=True, expand_x=True, expand_y=True)], ] window = sg.Window("Image Viewer", layout, size=window_size, return_keyboard_events=True, finalize=True, no_titlebar=True) # メインループ index = show_image(window, images, 0) # ループ前に先頭の画像表示 while True: event, values = window.read(timeout=interval) if event == "Exit" or event == sg.WIN_CLOSED or event.startswith("Escape"): break elif event == "__TIMEOUT__": index = show_image(window, images, index + 1) elif event == "-IMAGE-": # 画像コンポーネントクリックイベント widget = window["-IMAGE-"].Widget y = widget.winfo_pointery() - widget.winfo_rooty() if y < (window_size[1] // 10): if sg.popup_ok_cancel("終了しますか?") == "OK": break x = widget.winfo_pointerx() - widget.winfo_rootx() if x < (window_size[0] // 2): index = show_image(window, images, index - 1) else: index = show_image(window, images, index + 1) window.close() if __name__ == "__main__": main() 事前準備 Python 3.7以上に下記のパッケージをpipインストールすること PySimpleGUI GUIパッケージ。これがないと記事が始まらない Pillow 画像の縮小表示に必要 screeninfo 画面解像度を楽に取得できる 最大化しない場合やサイズ決め打ちで指定する場合はサンプルコードの# 画面を最大化する場合以下4行を削除して良い 備忘録 モニタの解像度を取得する screeninfoのgetmonitorsからwidthとheightを取得できる。 from screeninfo import get_monitors monitor = get_monitors()[0] window_size = (monitor.width, monitor.height) ウィンドウをフォトフレーム向けに設定する Windowのreturn_keyboard_eventsでキーボードイベントを有効にし、初期表示で画像を表示するためにfinalizeを設定し、no_titlebarでタイトルバーを消す。 return_keyboard_events=TrueにするとESCキーを打った時にEscape:27の文字がevent変数に格納される。 finalize=Trueでないとwindow.read()する前にshow_imageする処理がエラーになる。 window = sg.Window("Image Viewer", layout, size=window_size, return_keyboard_events=True, finalize=True, no_titlebar=True) # メインループ index = show_image(window, images, 0) # ループ前に先頭の画像表示 while True: event, values = window.read(timeout=interval) if event == "Exit" or event == sg.WIN_CLOSED or event.startswith("Escape"): 画像を縮小表示する 原寸で表示するだけならPySimpleGUI.Imageにファイルパスを送るだけでいいけど、アスペクト比を保ったまま縮小表示するならPillowのImage.thumbnailを使うのが便利。 記事執筆時点ではImageはGIFとPNGしか表示できないから下記のコードでJPEGやBMPをPNG化する処理も兼ねている。 このコードではアニメーションGIFを考慮していない。 image = Image.open(path) image.thumbnail(image_size) bio = io.BytesIO() image.save(bio, format="PNG") window["-IMAGE-"].update(data=bio.getvalue()) PySimpleGUIのコンポーネントを広げて表示する Imageコンポーネントは左上に画像サイズで表示され、クリックイベントも発生しない。 expand_xとexpand_yを指定することでコンポーネントのサイズをウィンドウ内で広げて配置することができる。 なお、Imageコンストラクタの引数は数か月前に追加されたので、古いバージョンだとエラーになる。 sg.Image(key="-IMAGE-", enable_events=True, expand_x=True, expand_y=True) Imageコンポーネントのクリック座標を取得する 今回書きたかったことはこれ。 クリック座標を取得する方法をpysimplegui click positionとかpysimplegui get mouse locationとかでWeb検索しても全然見つからなかった。 でもWidgetで裏側のTkinterにアクセスできる。 クリックしたときにwinfo pointerxなどでマウスの位置を取得すればクリック座標が分かる。 widget = window["-IMAGE-"].Widget x = widget.winfo_pointerx() - widget.winfo_rootx() y = widget.winfo_pointery() - widget.winfo_rooty() 今回のオチ 記事を書く調べものして気づく。 あれ?公式サンプルのPySimpleGUI/DemoPrograms/Demo_Img_Viewer.pyで大体似たようなことやってる…。 マウスホイールスクロールで切り替えたい人はそちらを参考にどうぞ。 参考資料 PYTHONを使って、ディスプレイの解像度・サイズを取得しよう PySimpleGUI/DemoPrograms/Demo_Img_Viewer.py Answer: Is there an pysimplegui function to get the the loction of the input cursor? Answer: Mouse Position Python Tkinter PySimpleGUI.Window PySimpleGUI.Image PySimpleGUI.Widget Image.thumbnail winfo pointerx
- 投稿日:2021-08-29T13:49:43+09:00
SQL未経験者にオススメのユーデミー動画講座6選!データ分析したい人向け
タイトルの通り、SQL初心者におすすめのユーデミー講座のまとめです。 ウェブアプリ開発を始めたい人やPython等でデータ分析を始めたい人におすすめです。 はじめてのSQL ・データ分析入門 -データベースのデータをビジネスパーソンが現場で活用するためのSQL初心者向コース おすすめ度:★★★★ ・はじめてのSQL ・データ分析入門 -データベースのデータをビジネスパーソンが現場で活用するためのSQL初心者向コース この講座で学べる内容 ・SQLを使用してデータベースからデータを取得する ・データベースのデータを更新できる ・SQLを使用してデータ分析を実行する ・MySQLをmacOSまたはWindowsにインストールできる ・データベース構造の変更ができる ・MySQLを操作できる ・MySQL公式ツール MySQL Workbenchが使用できる 3時間で学ぶ SQL ・データベース 超入門【丁寧な解説+演習問題で SQL データ抽出の基本が身につく】標準 SQL おすすめ度:★★★★ ・3時間で学ぶ SQL ・データベース 超入門【丁寧な解説+演習問題で SQL データ抽出の基本が身につく】標準 SQL この講座で学べる内容 ・SQLの基礎的な文法を、未経験者が本当に理解すべき項目だけ厳選して、少しづつ丁寧に学んでいきます。 ・SQLを用いた簡単なデータ分析を行う経験が得られます。 ・SQL・データベースとは?という内容から説明をはじめるので、知識ゼロからでも取り組むことができます。 ・講座終了後には、SQL・データベースの基礎的な概念を理解し、もっとSQLについて学びたい!というマインドになっていることを目指します。 BigQuery で学ぶ非エンジニアのための SQL データ分析入門 おすすめ度:★★★★★ ・BigQuery で学ぶ非エンジニアのための SQL データ分析入門 この講座で学べる内容 ・BigQueryを学習環境とした、データ分析のための標準SQL記述方法で、以下の句やテクニックを含みます。 ・基本的な句(select, from, where, order by, group by, having, limit) ・集計関数(sum, max, min, avg, count) ・関数(round,ceiling, floor, abs,mod,cast, concat, left, length, replace,trim,split,regex_xxx, date,date_add, date_sub,date_trunc,current_date) ・分析関数(rank, row_number, first_value, last_value, lead, lag) ・JOIN、サブクエリ、テーブル同士の集合演算 最新版: 基本情報技術者試験+応用情報技術者試験+Python+SQL 初心者からプロのエンジニアになる講座 おすすめ度:★★★★★ ・最新版: 基本情報技術者試験+応用情報技術者試験+Python+SQL 初心者からプロのエンジニアになる講座 この講座で学べる内容 ・企業の経営戦略、マーケティング、財務、技術戦略、システム戦略に関する知見 ・システムの開発技術、プロジェクトマネジメント、サービスマネジメント、システム監査に関する知見 ・ITのテクノロジーに関する知識、アルゴリズム、コンピュータアーキテクチャ、データベース、ネットワーク、セキュリティ関する知見 ・AI, IoT, ビッグデータなどの最新の分野の知識 ・Pythonについて詳細な文法を用いたプログラミング(ボーナスコンテンツ) ・SQLについて基礎的な内容(ボーナスコンテンツ) ・システムの基礎的な内容、システムの性能などシステム開発で必須となる要素(ボーナスコンテンツ) 経験ゼロからSQLServerをインストールしてある程度SQLが書けるようになる方法 おすすめ度:★★★★ ・経験ゼロからSQLServerをインストールしてある程度SQLが書けるようになる方法 この講座で学べる内容 ・SQLServerがインストールできる ・基本的なSQLが書けるようになる ・データベース設計や正規化が理解できる ・設計したテーブルの作成ができる ・内部結合と外部結合が理解できる 関連記事 ・Java初心者に超オススメ出来るUdemy動画講座7選まとめ。最速マスター特化 ・Typescript入門におすすめのユーデミー講座5選。JS未経験者も可 ・【Androidアプリ開発】Kotlin初心者向けの超おすすめUdemy講座6選をまとめた
- 投稿日:2021-08-29T13:43:41+09:00
Pythonで3群比較 Levene→Bartlett→Welch_ANOVA→Tukey_HSDを一気に。
今日やること 今日は敢えて、Levene→Bartlett→Welch_Anova→Tukey_HSDを一気に実行してみます。 日中韓のTOEICのスコアに3群(データ数不一致)で、統計的に有意差があるか検定していきます。 Python環境 Windows10 > Anaconda > JupyterLab/Notebook 使うPythonライブラリー pip install sci_analysis pip install pingouin pip install statsmodels pip install pandas 使うオブジェクト from scipy.stats import bartlett, levene #バートレットとルビーン from pingouin import welch_anova, pairwise_tukey #ウェルチANOVAとTukey from statsmodels.stats.multicomp import pairwise_tukeyhsd #Tukey ★同じモジュールの異なるオブジェクトはカンマで並列すると切り替えが便利。 Windowsでのファイルの読み込み メモ:デスクトップからcsvを読み込む際はpandas?で読み込む際は、ファイルパス名に r'を付ける。 pandas.read_csv(r'C:\Users\ユーザー名\Desktop\Book1.csv') ★ExcelからCSVに変換するときは CSV UTF-8(コンマ区切り)(*.csv)を選択して、名前を付けて保存。 Long-format DataFrame 読み込むcsvは、Long-format DataFrameにする必要があります。 下の表をcsvに貼り付けるとOK。 中国人4人平均スコア812.5、日本人5人平均スコア637.0、韓国人3人平均スコア866.7 Name Score Nationality Gao 850 Chinese Liu 700 Chinese Wu 800 Chinese Xu 900 Chinese Kato 800 Japanese Sato 680 Japanese Suzuki 555 Japanese Tanaka 450 Japanese Yoshida 700 Japanese Kim 950 Korean Lim 880 Korean Park 770 Korean Levene(ルビーン)の検定 PythonでLevene検定、csv読み込み from scipy.stats import bartlett, levene import pingouin as pg import pandas as pd # Long-format dataframe df = pd.read_csv(r'C:\Users\ユーザー名\Desktop\Book1.csv') pg.homoscedasticity(data=df, dv='Score', group='Nationality',method='levene',alpha=.05) PythonでLevene検定、セルの配列から from scipy.stats import bartlett, levene import pingouin as pg df = [ [850,700,800,900], [800,680,555,450,700], [950,880,770]] pg.homoscedasticity(df, dv='Score', group='Nationality',method='levene',alpha=.05) 出力結果 p<0.05なら、等分散ではない。pval = 0.686743なので等分散。 Bartlett(バートレット)の検定 PythonでBartlett検定、csv読み込み from scipy.stats import bartlett, levene import pingouin as pg import pandas as pd # Long-format dataframe df = pd.read_csv(r'C:\Users\ユーザー名\Desktop\Book1.csv') pg.homoscedasticity(data=df, dv='Score', group='Nationality',method='bartlett',alpha=.05) PythonでBartlett検定、セルの配列から from scipy.stats import bartlett, levene import pingouin as pg df = [ [850,700,800,900], [800,680,555,450,700], [950,880,770]] pg.homoscedasticity(df, dv='Score', group='Nationality',method='bartlett',alpha=.05) 出力結果 p<0.05なら、等分散ではない。pval = 0.686743(Leveneと同じ値)で等分散。 PingouinでWelch_Anova(ウェルチのt検定の拡張) PythonでWelch_Anova、csv読み込み import pingouin as pg from pingouin import welch_anova, pairwise_tukey import pandas as pd # Long-format dataframe df = pd.read_csv(r'C:\Users\ユーザー名\Desktop\Book1.csv') pg.welch_anova(data=df, dv='Score', between='Nationality') 出力結果 p<0.05なら、母平均に差がない。p-unc = 0.08818なので母平均に差がある。 PingouinでTukey_HSD 検定(Tukey-Kramer法) PythonでTukey_HSD、csv読み込み import pingouin as pg from pingouin import welch_anova, pairwise_tukey import pandas as pd # Long-format dataframe df = pd.read_csv(r'C:\Users\ユーザー名\Desktop\Book1.csv') # 'p-tukey': Tukey-HSD corrected p-values pg.pairwise_tukey(data=df, dv='Score', between='Nationality') 出力結果 p-tukey:Tukey-HSD corrected p-values Chinese-Japanese:p-tukey = 0.100318 なので、有意差なし Chinese-Korean:p-tukey = 0.793393 なので、有意差なし Japanese-Korean:p-tukey = 0.048149 なので、有意差あり 備考:サンプルサイズが異なる場合、PingouinのPairwise_TukeyではTukey-Kramer(テューキ・クレーマー法)が自動的に使用されるそうです。 詳しくは☟ StatmodelsでTukey_HSD 検定 PythonでTukey_HSD、csv読み込み from statsmodels.stats.multicomp import pairwise_tukeyhsd import pandas as pd df = pd.read_csv(r'C:\Users\ユーザー名\Desktop\Book1.csv') pairwise_tukeyhsd(df["Score"], df["Nationality"], alpha = 0.05).summary() 出力結果 Chinese-Japanese:p-tukey = 0.1003 なので、有意差なし Chinese-Korean:p-tukey = 0.7934 なので、有意差なし Japanese-Korean:p-tukey = 0.0481 なので、有意差あり ということで、StatsmodelsとPingouinで、同じp値が得られました。信頼区間がある分、StatsmodelsのTukey_HSDを利用した方が手っ取り早そうです。 今回のデータにおいて、日本人と韓国人のTOEICのスコアで有意差がありましたが、日本人と中国人、中国人と韓国人の間では有意な差はありませんでした。 参考サイト ルビーンの検定 https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.levene.html バートレットの検定 https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.bartlett.html Pingouin Welch_Annova https://pingouin-stats.org/generated/pingouin.homoscedasticity.html Pingouin Tukey HSD https://pingouin-stats.org/generated/pingouin.pairwise_tukey.html#pingouin.pairwise_tukey Statsmodels Tukey HSD https://www.statsmodels.org/stable/generated/statsmodels.stats.multicomp.pairwise_tukeyhsd.html 先週の記事投降後、以下サイトの貴重な情報をいただきました。ありがとうございました。 一元配置分散分析は,各群の分散が等しいことを前提にしている。分散の均一性をまえもって検定しておく方がよい。 等分散でないときの平均値の差の検定によれば,「等分散を仮定しない検定法(Welch の方法の拡張)」を採用するのが良さそうである。 Ref: http://aoki2.si.gunma-u.ac.jp/lecture/Average/oneway-ANOVA.html
- 投稿日:2021-08-29T13:43:41+09:00
Pythonで3群比較 Levene→Bartlett→Welch_ANOVA→Tukey_HSDを一気にRun All Cells。
今日やること 今日は敢えて、Levene→Bartlett→Welch_Anova→Tukey_HSDを一気に実行(Run All Cells)します。 日中韓のTOEICのスコアに3群(データ数不一致)で、統計的に有意差があるか検定していきます。 Python環境 Windows10 > Anaconda > JupyterLab/Notebook 使うPythonライブラリー pip install sci_analysis pip install pingouin pip install statsmodels pip install pandas 使うオブジェクト from scipy.stats import bartlett, levene #バートレットとルビーン from pingouin import welch_anova, pairwise_tukey #ウェルチANOVAとTukey from statsmodels.stats.multicomp import pairwise_tukeyhsd #Tukey ★同じモジュールの異なるオブジェクトはカンマで並列すると切り替えが便利。 Windowsでのファイルの読み込み メモ:デスクトップからcsvを読み込む際はpandas?で読み込む際は、ファイルパス名に r'を付ける。 pandas.read_csv(r'C:\Users\ユーザー名\Desktop\Book1.csv') ★ExcelからCSVに変換するときは CSV UTF-8(コンマ区切り)(*.csv)を選択して、名前を付けて保存。 Long-format DataFrame 読み込むcsvは、Long-format DataFrameにする必要があります。 下の表をcsvに貼り付けるとOK。 次の3群のどの組み合わせに差があるかを最終的に多重比較するのが目的です。 中国人4人平均スコア812.5 日本人5人平均スコア637.0 韓国人3人平均スコア866.7 (架空のデータです) Name Score Nationality Gao 850 Chinese Liu 700 Chinese Wu 800 Chinese Xu 900 Chinese Kato 800 Japanese Sato 680 Japanese Suzuki 555 Japanese Tanaka 450 Japanese Yoshida 700 Japanese Kim 950 Korean Lim 880 Korean Park 770 Korean Pandas?でcsvデータの確認 Pandasでcsvデータの確認 import pandas as pd df = pd.read_csv(r'C:\Users\Y\Desktop\Book1.csv') df 出力結果 下準備ができたので、検定開始~♪ Levene(ルビーン)の検定 PythonでLevene検定(csv読み込み) from scipy.stats import bartlett, levene import pingouin as pg import pandas as pd # Long-format dataframe df = pd.read_csv(r'C:\Users\ユーザー名\Desktop\Book1.csv') pg.homoscedasticity(data=df, dv='Score', group='Nationality',method='levene',alpha=.05) PythonでLevene検定(セルの配列から) from scipy.stats import bartlett, levene import pingouin as pg df = [ [850,700,800,900], [800,680,555,450,700], [950,880,770]] pg.homoscedasticity(df, dv='Score', group='Nationality',method='levene',alpha=.05) 出力結果 p<0.05なら、等分散ではない。pval = 0.686743なので等分散。 Bartlett(バートレット)の検定 PythonでBartlett検定(csv読み込み) from scipy.stats import bartlett, levene import pingouin as pg import pandas as pd # Long-format dataframe df = pd.read_csv(r'C:\Users\ユーザー名\Desktop\Book1.csv') pg.homoscedasticity(data=df, dv='Score', group='Nationality',method='bartlett',alpha=.05) PythonでBartlett検定(セルの配列から) from scipy.stats import bartlett, levene import pingouin as pg df = [ [850,700,800,900], [800,680,555,450,700], [950,880,770]] pg.homoscedasticity(df, dv='Score', group='Nationality',method='bartlett',alpha=.05) 出力結果 p<0.05なら、等分散ではない。pval = 0.686743(Leveneと同じ値)で等分散。 PingouinでWelch_Anova(ウェルチのt検定の拡張) PythonでWelch_Anova(csv読み込み) import pingouin as pg from pingouin import welch_anova, pairwise_tukey import pandas as pd # Long-format dataframe df = pd.read_csv(r'C:\Users\ユーザー名\Desktop\Book1.csv') pg.welch_anova(data=df, dv='Score', between='Nationality') 出力結果 p<0.05なら、母平均に差がない。p-unc = 0.08818なので母平均に差がある。 PingouinでTukey_HSD 検定(Tukey-Kramer法) PythonでTukey_HSD(csv読み込み) import pingouin as pg from pingouin import welch_anova, pairwise_tukey import pandas as pd # Long-format dataframe df = pd.read_csv(r'C:\Users\ユーザー名\Desktop\Book1.csv') # 'p-tukey': Tukey-HSD corrected p-values pg.pairwise_tukey(data=df, dv='Score', between='Nationality') 出力結果 p-tukey:Tukey-HSD corrected p-values Chinese-Japanese:p-tukey = 0.100318 なので、有意差なし Chinese-Korean:p-tukey = 0.793393 なので、有意差なし Japanese-Korean:p-tukey = 0.048149 なので、有意差あり 備考:サンプルサイズが異なる場合、PingouinのPairwise_TukeyではTukey-Kramer(テューキ・クレーマー法)が自動的に使用されるそうです。 詳しくは☟ StatsmodelsでTukey_HSD 検定 PythonでTukey_HSD(csv読み込み) from statsmodels.stats.multicomp import pairwise_tukeyhsd import pandas as pd df = pd.read_csv(r'C:\Users\ユーザー名\Desktop\Book1.csv') pairwise_tukeyhsd(df["Score"], df["Nationality"], alpha = 0.05).summary() 出力結果 Chinese-Japanese:p-tukey = 0.1003 なので、有意差なし Chinese-Korean:p-tukey = 0.7934 なので、有意差なし Japanese-Korean:p-tukey = 0.0481 なので、有意差あり ということで、StatsmodelsとPingouinで、同じp値が得られました。信頼区間がある分、StatsmodelsのTukey_HSDを利用した方が手っ取り早そうです。 今回のデータにおいて、日本人と韓国人のTOEICのスコアで有意差がありましたが、日本人と中国人、中国人と韓国人の間では有意な差はありませんでした(架空のデータです)。 参考サイト Leven(ルビーン) https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.levene.html Bartlett(バートレット) https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.bartlett.html Pingouin Welch_Annova https://pingouin-stats.org/generated/pingouin.homoscedasticity.html Pingouin Tukey HSD https://pingouin-stats.org/generated/pingouin.pairwise_tukey.html#pingouin.pairwise_tukey Statsmodels Tukey HSD https://www.statsmodels.org/stable/generated/statsmodels.stats.multicomp.pairwise_tukeyhsd.html 先週の記事投降後、以下サイトの貴重な情報をいただきました。ありがとうございました。 一元配置分散分析は,各群の分散が等しいことを前提にしている。分散の均一性をまえもって検定しておく方がよい。 等分散でないときの平均値の差の検定によれば,「等分散を仮定しない検定法(Welch の方法の拡張)」を採用するのが良さそうである。 Ref: http://aoki2.si.gunma-u.ac.jp/lecture/Average/oneway-ANOVA.html