📏

3DCNNを用いた強度解析サロゲートモデル

2024/07/22に公開

サロゲートモデル(代替モデル)を製品開発でどう使うか?

  • 最近の製品開発の流れは、お客さんから製品のスペース、要求される機能などから設計部署が過去の知見を踏まえて、おおまかな製品形状を作成します。
  • ここから設計部署が(ここ大事です)簡易的なCAE解析を行い製品形状を作りこみます。
  • 設計部署が作りこんだ、形状を評価部署(私のようなCAE専任がいる部署)が詳細なCAE解析
    (精度は良いが時間がかかる)を行い○×判定します。
  • ただし簡易的なものでもそれなりに時間がかかったり、設定やモデル作成が細かすぎてCAE専任でないとできない解析もあります。
    (設計がやるなら、設定で30分~1時間、計算時間で3時間以内)
  • こういった時間がかかったり、設定が面倒くさい解析をサロゲートモデルに置き換えれたら製品開発のスピードUPになります。

精度はどれ位必要か?

  • 上でも書きましたが、サロゲートモデルは簡易的なCAE解析の代替になれれば良いので7割くらいの精度でよいと思います。
  • それ以上の精度が出るならこしたことはありませんが、オーバーフィットすることも危惧されるので慎重になる必要があります。
    (仕様がちょっと変わったら、全然合わないじゃん!!って設計に怒られます)

3DCNNを選んだ理由

  • ニューラルネットワークのメリットは、説明変数を考えなくて良いことです。
  • この製品形状のここの長さがとか、この面積がなんてことをやっていたらサロゲートモデルを作る前に製品開発が終わってしまいます。
  • また、モデルを使う側からして見ればCADデータからソリッドメッシュさえ作成してしまえば、(ほとんどの場合自動作成できる)使えるので手間がかかりません。

プログラムの説明

  • 使用したライブラリ
    ※CAE_File_Read_Write 前回記事で書いたkeywordファイル読み込ライブラリ
import sys
import numpy as np
from keras import models,layers
import CAE_File_Read_Write as fr#前回記事で書いたkeywordファイル読み込ライブラリ 
import random
import pandas as pd
import os
from sklearn.model_selection import train_test_split    
  • ①path行(ソリッドメッシュデータのパス)と
    label行(何らかの解析値:今回は強度解析の最大変位量)をcsv形式で作成して読み込む

  • ②パスからファイル(今回はkeyword)を読み込んでKerasのConv3Dに渡せるように
    XYZと形状の有り無し(0か1)の4次元テンソルに変換する。

  • こちらで紹介されていた方法をそのまま使わせてもらいました。

  • ボクセル化の際に形状がつぶれてしまうといけないのでサイズ(参考記事では32で固定)は
    引数cellsで可変できるようにしました。(cells×cells×cellsのボクセル空間になる)

  • ③読み込んだデータをトレーニング用とテスト用に分離する。

def parse_data_from_input(filename,cells):
    df=pd.read_csv(filename, index_col=0)
    images=[ MeshModel_to_3DCNNDATA(i,cells) for i in  df["path"]]   
    labels=df["label"]
    x=np.array(images)
    y=np.array(labels)
    train_x,test_x,train_y,test_y=train_test_split(x,y,test_size=0.2,random_state=0)
    return train_x,test_x,train_y,test_y

def MeshModel_to_3DCNNDATA(FileName, cells):
    TINY_NUMBER=sys.float_info.epsilon
    max_dist = 0.0
    DATA=fr.File_key().Read_File(FileName,fr.DATA_BASE())

    temp=[i.CRD for i in DATA.N.values()]
    np_pc=np.array(temp)
    for it in range(0, 3):
    # find min max & distance in current direction
        min = np.amin(np_pc[:, it])
        max = np.amax(np_pc[:, it])
        dist = max - min
    
        # find maximum distance
        if dist > max_dist:
            max_dist = dist
        
        # set middle to 0,0,0
        np_pc[:, it] = np_pc[:, it] - dist / 2 - min
        
        # covered cells
        covered_cells = cells-3
        
        # find voxel edge size
        vox_sz = dist / (covered_cells - 1)
        
        # if 0 divid
        if vox_sz == 0:
            vox_sz = TINY_NUMBER
        
        # render pc to size 30x30x30 from middle
        np_pc[:, it] = np_pc[:, it] / vox_sz
    
    for it in range(0, 3):
        np_pc[:, it] = np_pc[:, it] + (covered_cells - 1) / 2
    
    # round to integer array
    np_pc = np.rint(np_pc).astype(np.uint32)
    
    # fill voxel arrays
    vox = np.zeros([covered_cells+1, covered_cells+1, covered_cells+1])
    for (pc_x, pc_y, pc_z) in np_pc:
        if random.randint(0, 100) < 80:
            vox[pc_x, pc_y, pc_z] = 1.0
    
    np_vox = np.zeros([ covered_cells+3, covered_cells+3, covered_cells+3,1])
    np_vox[1:-1, 1:-1, 1:-1,0] = vox
    return np.array(np_vox)    
  • ④3DCNNで連続値を予想するモデルを作成する。

  • 今回は連続値(回帰)モデルなので最後の層はactivation='linear'にし、
    損失関数は、loss = 'mean_squared_error'しました。

  • 今回の目的は精度うんぬんよりとりあえずできるか、だったのでそれ以外の設定は適当です。

  • ⑤推定値とテストの値を比較して精度を確認する

  • ⑥作成したモデルを保存する

def make_model(train_3D_points, test_3D_points, train_labels, test_labels,cells):
    model = model_set(train_3D_points[0].shape)
    model.fit(train_3D_points, train_labels, epochs=20, batch_size=2*cells+1,
               verbose=1, validation_data=(test_3D_points,test_labels))    
    test_model(model)
    model.save(rf"{os.path.dirname(TRAINING_FILE)}/3DCNN.h5")
     
def model_set(in_shape):
    model =models.Sequential()
    model.add(layers.Conv3D(16,(3, 3, 3),activation='relu',input_shape=in_shape))
    model.add(layers.MaxPooling3D(pool_size=(2, 2, 2)))
    model.add(layers.Flatten())
    model.add(layers.Dense(64, activation='relu'))
    model.add(layers.Dense(1, activation='linear'))
    model.compile(loss = 'mean_squared_error', optimizer='rmsprop')
    return model

def test_model(model):
    test_Pred= model.predict(test_3D_points,batch_size=2*cells+1)
    error=[]
    for i in range(len(test_Pred)):
        error.append(np.abs(test_labels[i]-test_Pred[i]))
        print("実測",test_labels[i]," /予測",np.round(test_Pred[i]) ,
              " /精度",np.round(100-(error[i]/test_labels[i])*100,2),"%")
   

使い方

TRAINING_FILE = "csvファイルのパス"
cells=128 #ボクセルサイズ
train_3D_points,test_3D_points,train_labels,test_labels =parse_data_from_input(TRAINING_FILE,cells)
make_model(train_3D_points, test_3D_points, train_labels, test_labels, cells)
  • 保存したモデルファイルを使う時
model= models.load_model("保存したモデルファイル(.h5)のパス")

今回検証した形状と結果

  • 上記のような形状を基準に三角リブを減らした物を10個作成し強度の静解析を行いました。
  • 拘束条件は、底面の外周に上下拘束、一番外側の三角リブ上部の4点を完全拘束しました。
  • 加重は、底面全域に1Mpaの圧力を底面を押す方向に設定しました。
  • トレーニングデータとして8個、テスト用で2個使った結果は、90%以上の結果になりました。
  • 理由としては、形状や解析値のばらつきがほどんどないデータであったためと考えられます。
  • 実際の製品では、ばらつきはもっと大きいので今回より多くのサンプル数やモデル層の設定も
    必要だと感じました。
  • ただ、箸にも棒にもならなくなかったのでサンプル数30くらいあれば、
    実製品にも使えると感じました。

Discussion