{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Colaboratoryのサンプルを加工してテキストファイルの分類を行ってみます。"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"# Colaboratoryのサンプルを加工してテキストファイルの分類を行ってみます。"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"id": "ItXfxkxvosLH"
},
"source": [
"# CalculiXとOpenFoamのファイル確認"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"id": "Eg62Pmz3o83v"
},
"source": [
"# CalculiX,OpenFoam,Elmerのファイル群を用いて、クラス分類器をトレーニングします。"
]
},
{
"cell_type": "code",
"execution_count": 126,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:19.098938Z",
"iopub.status.busy": "2022-08-09T02:01:19.098448Z",
"iopub.status.idle": "2022-08-09T02:01:21.642238Z",
"shell.execute_reply": "2022-08-09T02:01:21.641569Z"
},
"id": "8RZOuS9LWQvv"
},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"import os\n",
"import re\n",
"import shutil\n",
"import string\n",
"import tensorflow as tf\n",
"\n",
"from tensorflow.keras import layers\n",
"from tensorflow.keras import losses\n"
]
},
{
"cell_type": "code",
"execution_count": 127,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:21.646697Z",
"iopub.status.busy": "2022-08-09T02:01:21.646073Z",
"iopub.status.idle": "2022-08-09T02:01:21.649976Z",
"shell.execute_reply": "2022-08-09T02:01:21.649305Z"
},
"id": "6-tTFS04dChr"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2.9.0-dev20220328\n"
]
}
],
"source": [
"print(tf.__version__)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"id": "NBTI1bi8qdFV"
},
"source": [
"## 文書フォーマットの分析\n",
"\n",
"もともとは映画レビューの感情分析ですが、解析ファイルのフォーマット違いによる分類へ適用してみます。\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"id": "iAsKG535pHep"
},
"source": [
"### サンプルファイルを収集してセットする\n",
"\n",
"CalculiXとOpenFoamのサンプルファイルを学習用に用意します。\n",
"集めたらフォルダに格納します。"
]
},
{
"cell_type": "code",
"execution_count": 128,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:21.653778Z",
"iopub.status.busy": "2022-08-09T02:01:21.653293Z",
"iopub.status.idle": "2022-08-09T02:01:36.628769Z",
"shell.execute_reply": "2022-08-09T02:01:36.628054Z"
},
"id": "k7ZYnuajVlFN"
},
"outputs": [],
"source": [
"#作業に従ったフォルダにします。\n",
"dataset_dir = 'C:\\Work\\HP3\\\\tensorflow\\TA14\\\\files'"
]
},
{
"cell_type": "code",
"execution_count": 129,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:36.633005Z",
"iopub.status.busy": "2022-08-09T02:01:36.632296Z",
"iopub.status.idle": "2022-08-09T02:01:36.641327Z",
"shell.execute_reply": "2022-08-09T02:01:36.640521Z"
},
"id": "355CfOvsV1pl"
},
"outputs": [
{
"data": {
"text/plain": [
"['test', 'train']"
]
},
"execution_count": 129,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"os.listdir(dataset_dir)"
]
},
{
"cell_type": "code",
"execution_count": 130,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:36.644172Z",
"iopub.status.busy": "2022-08-09T02:01:36.643915Z",
"iopub.status.idle": "2022-08-09T02:01:36.648326Z",
"shell.execute_reply": "2022-08-09T02:01:36.647815Z"
},
"id": "7ASND15oXpF1"
},
"outputs": [
{
"data": {
"text/plain": [
"['ccx', 'elmer', 'openfoam']"
]
},
"execution_count": 130,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"train_dir = os.path.join(dataset_dir, 'train')\n",
"os.listdir(train_dir)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"id": "ysMNMI1CWDFD"
},
"source": [
"`files\\train\\ccx` および `files\\train\\openfoam` ディレクトリには多くのテキストファイルが含まれており、設定ファイルによってフォーマットはまちまちだったりします。"
]
},
{
"cell_type": "code",
"execution_count": 131,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:36.651549Z",
"iopub.status.busy": "2022-08-09T02:01:36.650960Z",
"iopub.status.idle": "2022-08-09T02:01:36.655014Z",
"shell.execute_reply": "2022-08-09T02:01:36.654339Z"
},
"id": "R7g8hFvzWLIZ"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"**\n",
"** Structure: cantilever beam under shear forces. \n",
"** Test objective: geometrically nonlinear calculation.\n",
"**\n",
"*HEADING\n",
"Model: beam Date: 10-Mar-1998\n",
"*NODE\n",
" 1, 0.000000, 0.000000, 0.000000\n",
" 2, 1.000000, 0.000000, 0.000000\n",
" 3, 1.000000, 1.000000, 0.000000\n",
" 4, 0.000000, 1.000000, 0.000000\n",
" 5, 0.000000, 0.000000, 8.000000\n",
" 6, 1.000000, 0.000000, 8.000000\n",
" 7, 1.000000, 1.000000, 8.000000\n",
" 8, 0.000000, 1.000000, 8.000000\n",
" 9, 0.250000, 0.000000, 0.000000\n",
" 10, 0.500000, 0.000000, 0.000000\n",
" 11, 0.750000, 0.000000, 0.000000\n",
" 12, 1.000000, 0.250000, 0.000000\n",
" 13, 1.000000, 0.500000, 0.000000\n",
" 14, 1.000000, 0.750000, 0.000000\n",
" 15, 0.750000, 1.000000, 0.000000\n",
" 16, 0.500000, 1.000000, 0.000000\n",
" 17, 0.250000, 1.000000, 0.000000\n",
" 18, 0.000000, 0.750000, 0.000000\n",
" 19, 0.000000, 0.500000, 0.000000\n",
" 20, 0.000000, 0.250000, 0.000000\n",
" 21, 0.250000, 0.000000, 8.000000\n",
" 22, 0.500000, 0.000000, 8.000000\n",
" 23, 0.750000, 0.000000, 8.000000\n",
" 24, 1.000000, 0.250000, 8.000000\n",
" 25, 1.000000, 0.500000, 8.000000\n",
" 26, 1.000000, 0.750000, 8.000000\n",
" 27, 0.750000, 1.000000, 8.000000\n",
" 28, 0.500000, 1.000000, 8.000000\n",
" 29, 0.250000, 1.000000, 8.000000\n",
" 30, 0.000000, 0.750000, 8.000000\n",
" 31, 0.000000, 0.500000, 8.000000\n",
" 32, 0.000000, 0.250000, 8.000000\n",
" 33, 1.000000, 0.000000, 0.500000\n",
" 34, 1.000000, 0.000000, 1.000000\n",
" 35, 1.000000, 0.000000, 1.500000\n",
" 36, 1.000000, 0.000000, 2.000000\n",
" 37, 1.000000, 0.000000, 2.500000\n",
" 38, 1.000000, 0.000000, 3.000000\n",
" 39, 1.000000, 0.000000, 3.500000\n",
" 40, 1.000000, 0.000000, 4.000000\n",
" 41, 1.000000, 0.000000, 4.500000\n",
" 42, 1.000000, 0.000000, 5.000000\n",
" 43, 1.000000, 0.000000, 5.500000\n",
" 44, 1.000000, 0.000000, 6.000000\n",
" 45, 1.000000, 0.000000, 6.500000\n",
" 46, 1.000000, 0.000000, 7.000000\n",
" 47, 1.000000, 0.000000, 7.500000\n",
" 48, 0.000000, 0.000000, 7.500000\n",
" 49, 0.000000, 0.000000, 7.000000\n",
" 50, 0.000000, 0.000000, 6.500000\n",
" 51, 0.000000, 0.000000, 6.000000\n",
" 52, 0.000000, 0.000000, 5.500000\n",
" 53, 0.000000, 0.000000, 5.000000\n",
" 54, 0.000000, 0.000000, 4.500000\n",
" 55, 0.000000, 0.000000, 4.000000\n",
" 56, 0.000000, 0.000000, 3.500000\n",
" 57, 0.000000, 0.000000, 3.000000\n",
" 58, 0.000000, 0.000000, 2.500000\n",
" 59, 0.000000, 0.000000, 2.000000\n",
" 60, 0.000000, 0.000000, 1.500000\n",
" 61, 0.000000, 0.000000, 1.000000\n",
" 62, 0.000000, 0.000000, 0.500000\n",
" 63, 1.000000, 1.000000, 0.500000\n",
" 64, 1.000000, 1.000000, 1.000000\n",
" 65, 1.000000, 1.000000, 1.500000\n",
" 66, 1.000000, 1.000000, 2.000000\n",
" 67, 1.000000, 1.000000, 2.500000\n",
" 68, 1.000000, 1.000000, 3.000000\n",
" 69, 1.000000, 1.000000, 3.500000\n",
" 70, 1.000000, 1.000000, 4.000000\n",
" 71, 1.000000, 1.000000, 4.500000\n",
" 72, 1.000000, 1.000000, 5.000000\n",
" 73, 1.000000, 1.000000, 5.500000\n",
" 74, 1.000000, 1.000000, 6.000000\n",
" 75, 1.000000, 1.000000, 6.500000\n",
" 76, 1.000000, 1.000000, 7.000000\n",
" 77, 1.000000, 1.000000, 7.500000\n",
" 78, 0.000000, 1.000000, 0.500000\n",
" 79, 0.000000, 1.000000, 1.000000\n",
" 80, 0.000000, 1.000000, 1.500000\n",
" 81, 0.000000, 1.000000, 2.000000\n",
" 82, 0.000000, 1.000000, 2.500000\n",
" 83, 0.000000, 1.000000, 3.000000\n",
" 84, 0.000000, 1.000000, 3.500000\n",
" 85, 0.000000, 1.000000, 4.000000\n",
" 86, 0.000000, 1.000000, 4.500000\n",
" 87, 0.000000, 1.000000, 5.000000\n",
" 88, 0.000000, 1.000000, 5.500000\n",
" 89, 0.000000, 1.000000, 6.000000\n",
" 90, 0.000000, 1.000000, 6.500000\n",
" 91, 0.000000, 1.000000, 7.000000\n",
" 92, 0.000000, 1.000000, 7.500000\n",
" 93, 0.500000, 0.250000, 0.000000\n",
" 94, 0.250000, 0.500000, 0.000000\n",
" 95, 0.500000, 0.500000, 0.000000\n",
" 96, 0.750000, 0.500000, 0.000000\n",
" 97, 0.500000, 0.750000, 0.000000\n",
" 98, 0.500000, 0.250000, 8.000000\n",
" 99, 0.250000, 0.500000, 8.000000\n",
" 100, 0.500000, 0.500000, 8.000000\n",
" 101, 0.750000, 0.500000, 8.000000\n",
" 102, 0.500000, 0.750000, 8.000000\n",
" 103, 0.500000, 0.000000, 0.500000\n",
" 104, 0.250000, 0.000000, 1.000000\n",
" 105, 0.500000, 0.000000, 1.000000\n",
" 106, 0.750000, 0.000000, 1.000000\n",
" 107, 0.500000, 0.000000, 1.500000\n",
" 108, 0.250000, 0.000000, 2.000000\n",
" 109, 0.500000, 0.000000, 2.000000\n",
" 110, 0.750000, 0.000000, 2.000000\n",
" 111, 0.500000, 0.000000, 2.500000\n",
" 112, 0.250000, 0.000000, 3.000000\n",
" 113, 0.500000, 0.000000, 3.000000\n",
" 114, 0.750000, 0.000000, 3.000000\n",
" 115, 0.500000, 0.000000, 3.500000\n",
" 116, 0.250000, 0.000000, 4.000000\n",
" 117, 0.500000, 0.000000, 4.000000\n",
" 118, 0.750000, 0.000000, 4.000000\n",
" 119, 0.500000, 0.000000, 4.500000\n",
" 120, 0.250000, 0.000000, 5.000000\n",
" 121, 0.500000, 0.000000, 5.000000\n",
" 122, 0.750000, 0.000000, 5.000000\n",
" 123, 0.500000, 0.000000, 5.500000\n",
" 124, 0.250000, 0.000000, 6.000000\n",
" 125, 0.500000, 0.000000, 6.000000\n",
" 126, 0.750000, 0.000000, 6.000000\n",
" 127, 0.500000, 0.000000, 6.500000\n",
" 128, 0.250000, 0.000000, 7.000000\n",
" 129, 0.500000, 0.000000, 7.000000\n",
" 130, 0.750000, 0.000000, 7.000000\n",
" 131, 0.500000, 0.000000, 7.500000\n",
" 132, 1.000000, 0.500000, 0.500000\n",
" 133, 1.000000, 0.250000, 1.000000\n",
" 134, 1.000000, 0.500000, 1.000000\n",
" 135, 1.000000, 0.750000, 1.000000\n",
" 136, 1.000000, 0.500000, 1.500000\n",
" 137, 1.000000, 0.250000, 2.000000\n",
" 138, 1.000000, 0.500000, 2.000000\n",
" 139, 1.000000, 0.750000, 2.000000\n",
" 140, 1.000000, 0.500000, 2.500000\n",
" 141, 1.000000, 0.250000, 3.000000\n",
" 142, 1.000000, 0.500000, 3.000000\n",
" 143, 1.000000, 0.750000, 3.000000\n",
" 144, 1.000000, 0.500000, 3.500000\n",
" 145, 1.000000, 0.250000, 4.000000\n",
" 146, 1.000000, 0.500000, 4.000000\n",
" 147, 1.000000, 0.750000, 4.000000\n",
" 148, 1.000000, 0.500000, 4.500000\n",
" 149, 1.000000, 0.250000, 5.000000\n",
" 150, 1.000000, 0.500000, 5.000000\n",
" 151, 1.000000, 0.750000, 5.000000\n",
" 152, 1.000000, 0.500000, 5.500000\n",
" 153, 1.000000, 0.250000, 6.000000\n",
" 154, 1.000000, 0.500000, 6.000000\n",
" 155, 1.000000, 0.750000, 6.000000\n",
" 156, 1.000000, 0.500000, 6.500000\n",
" 157, 1.000000, 0.250000, 7.000000\n",
" 158, 1.000000, 0.500000, 7.000000\n",
" 159, 1.000000, 0.750000, 7.000000\n",
" 160, 1.000000, 0.500000, 7.500000\n",
" 161, 0.500000, 1.000000, 0.500000\n",
" 162, 0.750000, 1.000000, 1.000000\n",
" 163, 0.500000, 1.000000, 1.000000\n",
" 164, 0.250000, 1.000000, 1.000000\n",
" 165, 0.500000, 1.000000, 1.500000\n",
" 166, 0.750000, 1.000000, 2.000000\n",
" 167, 0.500000, 1.000000, 2.000000\n",
" 168, 0.250000, 1.000000, 2.000000\n",
" 169, 0.500000, 1.000000, 2.500000\n",
" 170, 0.750000, 1.000000, 3.000000\n",
" 171, 0.500000, 1.000000, 3.000000\n",
" 172, 0.250000, 1.000000, 3.000000\n",
" 173, 0.500000, 1.000000, 3.500000\n",
" 174, 0.750000, 1.000000, 4.000000\n",
" 175, 0.500000, 1.000000, 4.000000\n",
" 176, 0.250000, 1.000000, 4.000000\n",
" 177, 0.500000, 1.000000, 4.500000\n",
" 178, 0.750000, 1.000000, 5.000000\n",
" 179, 0.500000, 1.000000, 5.000000\n",
" 180, 0.250000, 1.000000, 5.000000\n",
" 181, 0.500000, 1.000000, 5.500000\n",
" 182, 0.750000, 1.000000, 6.000000\n",
" 183, 0.500000, 1.000000, 6.000000\n",
" 184, 0.250000, 1.000000, 6.000000\n",
" 185, 0.500000, 1.000000, 6.500000\n",
" 186, 0.750000, 1.000000, 7.000000\n",
" 187, 0.500000, 1.000000, 7.000000\n",
" 188, 0.250000, 1.000000, 7.000000\n",
" 189, 0.500000, 1.000000, 7.500000\n",
" 190, 0.000000, 0.500000, 0.500000\n",
" 191, 0.000000, 0.750000, 1.000000\n",
" 192, 0.000000, 0.500000, 1.000000\n",
" 193, 0.000000, 0.250000, 1.000000\n",
" 194, 0.000000, 0.500000, 1.500000\n",
" 195, 0.000000, 0.750000, 2.000000\n",
" 196, 0.000000, 0.500000, 2.000000\n",
" 197, 0.000000, 0.250000, 2.000000\n",
" 198, 0.000000, 0.500000, 2.500000\n",
" 199, 0.000000, 0.750000, 3.000000\n",
" 200, 0.000000, 0.500000, 3.000000\n",
" 201, 0.000000, 0.250000, 3.000000\n",
" 202, 0.000000, 0.500000, 3.500000\n",
" 203, 0.000000, 0.750000, 4.000000\n",
" 204, 0.000000, 0.500000, 4.000000\n",
" 205, 0.000000, 0.250000, 4.000000\n",
" 206, 0.000000, 0.500000, 4.500000\n",
" 207, 0.000000, 0.750000, 5.000000\n",
" 208, 0.000000, 0.500000, 5.000000\n",
" 209, 0.000000, 0.250000, 5.000000\n",
" 210, 0.000000, 0.500000, 5.500000\n",
" 211, 0.000000, 0.750000, 6.000000\n",
" 212, 0.000000, 0.500000, 6.000000\n",
" 213, 0.000000, 0.250000, 6.000000\n",
" 214, 0.000000, 0.500000, 6.500000\n",
" 215, 0.000000, 0.750000, 7.000000\n",
" 216, 0.000000, 0.500000, 7.000000\n",
" 217, 0.000000, 0.250000, 7.000000\n",
" 218, 0.000000, 0.500000, 7.500000\n",
" 219, 0.500000, 0.500000, 0.500000\n",
" 220, 0.500000, 0.250000, 1.000000\n",
" 221, 0.250000, 0.500000, 1.000000\n",
" 222, 0.500000, 0.500000, 1.000000\n",
" 223, 0.750000, 0.500000, 1.000000\n",
" 224, 0.500000, 0.750000, 1.000000\n",
" 225, 0.500000, 0.500000, 1.500000\n",
" 226, 0.500000, 0.250000, 2.000000\n",
" 227, 0.250000, 0.500000, 2.000000\n",
" 228, 0.500000, 0.500000, 2.000000\n",
" 229, 0.750000, 0.500000, 2.000000\n",
" 230, 0.500000, 0.750000, 2.000000\n",
" 231, 0.500000, 0.500000, 2.500000\n",
" 232, 0.500000, 0.250000, 3.000000\n",
" 233, 0.250000, 0.500000, 3.000000\n",
" 234, 0.500000, 0.500000, 3.000000\n",
" 235, 0.750000, 0.500000, 3.000000\n",
" 236, 0.500000, 0.750000, 3.000000\n",
" 237, 0.500000, 0.500000, 3.500000\n",
" 238, 0.500000, 0.250000, 4.000000\n",
" 239, 0.250000, 0.500000, 4.000000\n",
" 240, 0.500000, 0.500000, 4.000000\n",
" 241, 0.750000, 0.500000, 4.000000\n",
" 242, 0.500000, 0.750000, 4.000000\n",
" 243, 0.500000, 0.500000, 4.500000\n",
" 244, 0.500000, 0.250000, 5.000000\n",
" 245, 0.250000, 0.500000, 5.000000\n",
" 246, 0.500000, 0.500000, 5.000000\n",
" 247, 0.750000, 0.500000, 5.000000\n",
" 248, 0.500000, 0.750000, 5.000000\n",
" 249, 0.500000, 0.500000, 5.500000\n",
" 250, 0.500000, 0.250000, 6.000000\n",
" 251, 0.250000, 0.500000, 6.000000\n",
" 252, 0.500000, 0.500000, 6.000000\n",
" 253, 0.750000, 0.500000, 6.000000\n",
" 254, 0.500000, 0.750000, 6.000000\n",
" 255, 0.500000, 0.500000, 6.500000\n",
" 256, 0.500000, 0.250000, 7.000000\n",
" 257, 0.250000, 0.500000, 7.000000\n",
" 258, 0.500000, 0.500000, 7.000000\n",
" 259, 0.750000, 0.500000, 7.000000\n",
" 260, 0.500000, 0.750000, 7.000000\n",
" 261, 0.500000, 0.500000, 7.500000\n",
"*ELEMENT, TYPE=C3D20R , ELSET=B1\n",
" 1, 1, 10, 95, 19, 61, 105, 222, 192, 9, 93,\n",
" 94, 20, 104, 220, 221, 193, 62, 103, 219, 190\n",
" 2, 10, 2, 13, 95, 105, 34, 134, 222, 11, 12,\n",
" 96, 93, 106, 133, 223, 220, 103, 33, 132, 219\n",
" 3, 19, 95, 16, 4, 192, 222, 163, 79, 94, 97,\n",
" 17, 18, 221, 224, 164, 191, 190, 219, 161, 78\n",
" 4, 95, 13, 3, 16, 222, 134, 64, 163, 96, 14,\n",
" 15, 97, 223, 135, 162, 224, 219, 132, 63, 161\n",
" 5, 61, 105, 222, 192, 59, 109, 228, 196, 104, 220,\n",
" 221, 193, 108, 226, 227, 197, 60, 107, 225, 194\n",
" 6, 105, 34, 134, 222, 109, 36, 138, 228, 106, 133,\n",
" 223, 220, 110, 137, 229, 226, 107, 35, 136, 225\n",
" 7, 192, 222, 163, 79, 196, 228, 167, 81, 221, 224,\n",
" 164, 191, 227, 230, 168, 195, 194, 225, 165, 80\n",
" 8, 222, 134, 64, 163, 228, 138, 66, 167, 223, 135,\n",
" 162, 224, 229, 139, 166, 230, 225, 136, 65, 165\n",
" 9, 59, 109, 228, 196, 57, 113, 234, 200, 108, 226,\n",
" 227, 197, 112, 232, 233, 201, 58, 111, 231, 198\n",
" 10, 109, 36, 138, 228, 113, 38, 142, 234, 110, 137,\n",
" 229, 226, 114, 141, 235, 232, 111, 37, 140, 231\n",
" 11, 196, 228, 167, 81, 200, 234, 171, 83, 227, 230,\n",
" 168, 195, 233, 236, 172, 199, 198, 231, 169, 82\n",
" 12, 228, 138, 66, 167, 234, 142, 68, 171, 229, 139,\n",
" 166, 230, 235, 143, 170, 236, 231, 140, 67, 169\n",
" 13, 57, 113, 234, 200, 55, 117, 240, 204, 112, 232,\n",
" 233, 201, 116, 238, 239, 205, 56, 115, 237, 202\n",
" 14, 113, 38, 142, 234, 117, 40, 146, 240, 114, 141,\n",
" 235, 232, 118, 145, 241, 238, 115, 39, 144, 237\n",
" 15, 200, 234, 171, 83, 204, 240, 175, 85, 233, 236,\n",
" 172, 199, 239, 242, 176, 203, 202, 237, 173, 84\n",
" 16, 234, 142, 68, 171, 240, 146, 70, 175, 235, 143,\n",
" 170, 236, 241, 147, 174, 242, 237, 144, 69, 173\n",
" 17, 55, 117, 240, 204, 53, 121, 246, 208, 116, 238,\n",
" 239, 205, 120, 244, 245, 209, 54, 119, 243, 206\n",
" 18, 117, 40, 146, 240, 121, 42, 150, 246, 118, 145,\n",
" 241, 238, 122, 149, 247, 244, 119, 41, 148, 243\n",
" 19, 204, 240, 175, 85, 208, 246, 179, 87, 239, 242,\n",
" 176, 203, 245, 248, 180, 207, 206, 243, 177, 86\n",
" 20, 240, 146, 70, 175, 246, 150, 72, 179, 241, 147,\n",
" 174, 242, 247, 151, 178, 248, 243, 148, 71, 177\n",
" 21, 53, 121, 246, 208, 51, 125, 252, 212, 120, 244,\n",
" 245, 209, 124, 250, 251, 213, 52, 123, 249, 210\n",
" 22, 121, 42, 150, 246, 125, 44, 154, 252, 122, 149,\n",
" 247, 244, 126, 153, 253, 250, 123, 43, 152, 249\n",
" 23, 208, 246, 179, 87, 212, 252, 183, 89, 245, 248,\n",
" 180, 207, 251, 254, 184, 211, 210, 249, 181, 88\n",
" 24, 246, 150, 72, 179, 252, 154, 74, 183, 247, 151,\n",
" 178, 248, 253, 155, 182, 254, 249, 152, 73, 181\n",
" 25, 51, 125, 252, 212, 49, 129, 258, 216, 124, 250,\n",
" 251, 213, 128, 256, 257, 217, 50, 127, 255, 214\n",
" 26, 125, 44, 154, 252, 129, 46, 158, 258, 126, 153,\n",
" 253, 250, 130, 157, 259, 256, 127, 45, 156, 255\n",
" 27, 212, 252, 183, 89, 216, 258, 187, 91, 251, 254,\n",
" 184, 211, 257, 260, 188, 215, 214, 255, 185, 90\n",
" 28, 252, 154, 74, 183, 258, 158, 76, 187, 253, 155,\n",
" 182, 254, 259, 159, 186, 260, 255, 156, 75, 185\n",
" 29, 49, 129, 258, 216, 5, 22, 100, 31, 128, 256,\n",
" 257, 217, 21, 98, 99, 32, 48, 131, 261, 218\n",
" 30, 129, 46, 158, 258, 22, 6, 25, 100, 130, 157,\n",
" 259, 256, 23, 24, 101, 98, 131, 47, 160, 261\n",
" 31, 216, 258, 187, 91, 31, 100, 28, 8, 257, 260,\n",
" 188, 215, 99, 102, 29, 30, 218, 261, 189, 92\n",
" 32, 258, 158, 76, 187, 100, 25, 7, 28, 259, 159,\n",
" 186, 260, 101, 26, 27, 102, 261, 160, 77, 189\n",
"*NSET, NSET=CN7\n",
" 97, 96, 95, 94, 93, 20, 19, 18, 17, 16, 15,\n",
" 14, 13, 12, 11, 10, 9, 4, 3, 2, 1\n",
"*BOUNDARY\n",
"CN7, 1\n",
"*BOUNDARY\n",
"CN7, 2\n",
"*BOUNDARY\n",
"CN7, 3\n",
"*NSET,NSET=NALL,GENERATE\n",
"1,261\n",
"*ELSET,ELSET=EALL,GENERATE\n",
"1,32\n",
"*MATERIAL,NAME=EL\n",
"*ELASTIC\n",
" 210000.0 , .3\n",
"*DENSITY\n",
"7.8E-9\n",
"*SOLID SECTION,ELSET=EALL,MATERIAL=EL\n",
"*NSET,NSET=LAST\n",
"5,6,7,8,22,25,28,31,100\n",
"*TIME POINTS,NAME=T1\n",
".4,.6,.9\n",
"*STEP,NLGEOM\n",
"*STATIC\n",
"1.,1.\n",
"*CLOAD\n",
"LAST,2,5.\n",
"*NODE PRINT,NSET=NALL,TIMEPOINTS=T1\n",
"U\n",
"*EL PRINT,ELSET=EALL\n",
"S\n",
"*END STEP\n",
"\n",
"\n"
]
}
],
"source": [
"sample_file = os.path.join(train_dir, 'ccx/beamnlptp.txt')\n",
"with open(sample_file) as f:\n",
" print(f.read())"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"id": "Mk20TEm6ZRFP"
},
"source": [
"### データセットを読み込む\n",
"\n",
"次に、データを読み込み、トレーニングに適した形式に準備します。これを行うには、便利な [text_dataset_from_directory](https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/text_dataset_from_directory) ユーティリティを使用します。このユーティリティは、次のようなディレクトリ構造を想定しています。\n",
"\n",
"```\n",
"files/train/\n",
"...ccx/\n",
"......beamnlptp.dat.txt\n",
"......beamnlptp.txt\n",
"```\n",
"...openfoam/\n",
"......combustion/\n",
"```\n",
"......epsilon1.txt\n",
"```\n",
"OpenFoamはサブフォルダがあります。"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"CCXとOpenfoamのファイル拡張子を.txtにします。\n",
"単独で機能させるためにimport osを付けています。"
]
},
{
"cell_type": "code",
"execution_count": 132,
"metadata": {},
"outputs": [],
"source": [
"# import os\n",
"\n",
"# folder = 'files'\n",
"\n",
"# for root, dirs, files in os.walk(folder):\n",
"# for filename in files:\n",
"# print(os.path.join(root, filename))\n",
"\n",
"# bufOri = filename\n",
"# filename, file_extension = os.path.splitext(filename)\n",
"# if file_extension =='.sif':\n",
"# try:\n",
"# new_file_name = filename+file_extension + '.txt'\n",
"# os.rename(os.path.join(root, bufOri), os.path.join(root, new_file_name))\n",
"# except:\n",
"# # Handle or ignore the error\n",
"# pass"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"次に、`text_dataset_from_directory` ユーティリティを使用して、ラベル付きの `tf.data.Dataset` を作成します。[tf.data](https://www.tensorflow.org/guide/data) は、データを操作するための強力なツールのコレクションです。\n",
"\n",
"機械学習実験を実行するときは、データセットを[トレーニング]、[検証]、および、[テスト]の 3 つに分割することをお勧めします。\n",
"\n",
"IMDB データセットはすでにトレーニング用とテスト用に分割されていますが、検証セットはありません。以下の `validation_split` 引数を使用して、トレーニングデータの 80:20 分割を使用して検証セットを作成しましょう。"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"\"tf.keras.preprocessing.text_dataset_from_directory\"は、TensorFlowのKerasライブラリに含まれる関数で、テキストデータを読み込んでDatasetオブジェクトに変換するために使用されます。この関数は、サブディレクトリがテキストデータのカテゴリ/クラスに対応するように保存されたテキストファイルを読み込むためのものです。この関数は、トレーニングに使用できるDatasetオブジェクトを返します。\n",
"\n",
"この関数は、データの読み込みと処理の方法を制御するための様々なパラメータを使用することができます。これらのパラメータには次のようなものがあります:\n",
"\n",
"directory: テキストファイルが保存されているディレクトリへのパス。\n",
"\n",
"labels: 各テキストファイルに関連付けられたラベル(サブディレクトリ名からラベルへのマッピングを含む辞書)。\n",
"\n",
"batch_size: Dataset内の各バッチに含まれるサンプル数。\n",
"\n",
"shuffle: エポックごとにデータをシャッフルするかどうか。\n",
"\n",
"text_encoding: テキストファイルに使用されるエンコーディング。\n",
"\n",
"全体的に、\"tf.keras.preprocessing.text_dataset_from_directory\"は、テキストデータの読み込みと前処理を容易にする便利な関数であり、テキストベースの機械学習モデルのトレーニングのプロセスを簡素化することができます。"
]
},
{
"cell_type": "code",
"execution_count": 133,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:37.395061Z",
"iopub.status.busy": "2022-08-09T02:01:37.394807Z",
"iopub.status.idle": "2022-08-09T02:01:41.798469Z",
"shell.execute_reply": "2022-08-09T02:01:41.797749Z"
},
"id": "nOrK-MTYaw3C"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Found 2735 files belonging to 3 classes.\n",
"Using 2188 files for training.\n"
]
}
],
"source": [
"batch_size = 32\n",
"seed = 42\n",
"\n",
"raw_train_ds = tf.keras.utils.text_dataset_from_directory(\n",
" 'files/train', \n",
" batch_size=batch_size, \n",
" validation_split=0.2, \n",
" subset='training', \n",
" seed=seed)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"id": "5Y33oxOUpYkh"
},
"source": [
"上記のように、トレーニングフォルダには 2629 の例があり、そのうち 80% (2104) をトレーニングに使用します。以下に示すとおり、データセットを `model.fit` に直接渡すことで、モデルをトレーニングできます。`tf.data` を初めて使用する場合は、データセットを繰り返し処理して、次のようにいくつかの例を出力することもできます。"
]
},
{
"cell_type": "code",
"execution_count": 134,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:41.803022Z",
"iopub.status.busy": "2022-08-09T02:01:41.802284Z",
"iopub.status.idle": "2022-08-09T02:01:41.834408Z",
"shell.execute_reply": "2022-08-09T02:01:41.833504Z"
},
"id": "51wNaPPApk1K"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Review b'/*--------------------------------*- C++ -*----------------------------------*\\\\\\n| ========= | |\\n| \\\\\\\\ / F ield | OpenFOAM: The Open Source CFD Toolbox |\\n| \\\\\\\\ / O peration | Version: v2112 |\\n| \\\\\\\\ / A nd | Website: www.openfoam.com |\\n| \\\\\\\\/ M anipulation | |\\n\\\\*---------------------------------------------------------------------------*/\\nFoamFile\\n{\\n version 2.0;\\n format ascii;\\n class volVectorField;\\n object U;\\n}\\n// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\\n\\ndimensions [0 1 -1 0 0 0 0];\\n\\ninternalField uniform (0 0 0);\\n\\nboundaryField\\n{\\n fuel\\n {\\n type fixedValue;\\n value uniform (0.1 0 0);\\n }\\n\\n air\\n {\\n type fixedValue;\\n value uniform (-0.1 0 0);\\n }\\n\\n outlet\\n {\\n type pressureInletOutletVelocity;\\n value $internalField;\\n }\\n\\n frontAndBack\\n {\\n type empty;\\n }\\n}\\n\\n\\n// ************************************************************************* //\\n'\n",
"Label 2\n",
"Review b'/*--------------------------------*- C++ -*----------------------------------*\\\\\\n| ========= | |\\n| \\\\\\\\ / F ield | OpenFOAM: The Open Source CFD Toolbox |\\n| \\\\\\\\ / O peration | Version: v2112 |\\n| \\\\\\\\ / A nd | Website: www.openfoam.com |\\n| \\\\\\\\/ M anipulation | |\\n\\\\*---------------------------------------------------------------------------*/\\nFoamFile\\n{\\n version 2.0;\\n format ascii;\\n class volScalarField;\\n object nut;\\n}\\n// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\\n\\ndimensions [0 2 -1 0 0 0 0];\\n\\ninternalField uniform 0;\\n\\nboundaryField\\n{\\n outer\\n {\\n type inletOutlet;\\n inletValue $internalField;\\n value $internalField;\\n }\\n\\n ground\\n {\\n type nutkWallFunction;\\n value $internalField;\\n }\\n\\n blockedFaces\\n {\\n type zeroGradient;\\n }\\n\\n mergingFaces\\n {\\n type nutkWallFunction;\\n value $internalField;\\n }\\n\\n wallFaces\\n {\\n type nutkWallFunction;\\n value $internalField;\\n }\\n\\n ySymmetry\\n {\\n type symmetryPlane;\\n }\\n}\\n\\n\\n// ************************************************************************* //\\n'\n",
"Label 2\n",
"Review b'/*--------------------------------*- C++ -*----------------------------------*\\\\\\n| ========= | |\\n| \\\\\\\\ / F ield | OpenFOAM: The Open Source CFD Toolbox |\\n| \\\\\\\\ / O peration | Version: v2112 |\\n| \\\\\\\\ / A nd | Website: www.openfoam.com |\\n| \\\\\\\\/ M anipulation | |\\n\\\\*---------------------------------------------------------------------------*/\\nFoamFile\\n{\\n version 2.0;\\n format ascii;\\n class dictionary;\\n object radiationProperties;\\n}\\n// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\\n\\nradiationModel opaqueSolid;\\n\\nabsorptionEmissionModel greyMeanSolidAbsorptionEmission;\\n\\n\\ngreyMeanSolidAbsorptionEmissionCoeffs\\n{\\n wood\\n {\\n absorptivity 0.17;\\n emissivity 0.17;\\n }\\n\\n char\\n {\\n absorptivity 0.85;\\n emissivity 0.85;\\n }\\n}\\n\\nscatterModel none;\\n\\n\\n// ************************************************************************* //\\n'\n",
"Label 2\n"
]
}
],
"source": [
"for text_batch, label_batch in raw_train_ds.take(1):\n",
" for i in range(3):\n",
" print(\"Review\", text_batch.numpy()[i])\n",
" print(\"Label\", label_batch.numpy()[i])"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"id": "JWq1SUIrp1a-"
},
"source": [
"ラベルは 0 または 1 です。これらのどれがCCXかOpenFoamのファイルであるかを確認するには、データセットの `class_names` プロパティを確認できます。\n"
]
},
{
"cell_type": "code",
"execution_count": 135,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:41.838112Z",
"iopub.status.busy": "2022-08-09T02:01:41.837345Z",
"iopub.status.idle": "2022-08-09T02:01:41.842199Z",
"shell.execute_reply": "2022-08-09T02:01:41.841055Z"
},
"id": "MlICTG8spyO2"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Label 0 corresponds to ccx\n",
"Label 1 corresponds to elmer\n",
"Label 2 corresponds to openfoam\n"
]
}
],
"source": [
"print(\"Label 0 corresponds to\", raw_train_ds.class_names[0])\n",
"print(\"Label 1 corresponds to\", raw_train_ds.class_names[1])\n",
"print(\"Label 2 corresponds to\", raw_train_ds.class_names[2])\n",
"# print(\"Label 3 corresponds to\", raw_train_ds.class_names[3])"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"id": "pbdO39vYqdJr"
},
"source": [
"次に、検証およびテスト用データセットを作成します。トレーニング用セットの残りの 525 ファイルを検証に使用します。"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "SzxazN8Hq1pF"
},
"source": [
"注意: `validation_split` および `subset` 引数を使用する場合は、必ずランダムシードを指定するか、`shuffle=False` を渡して、検証とトレーニング分割に重複がないようにします。"
]
},
{
"cell_type": "code",
"execution_count": 136,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:41.845829Z",
"iopub.status.busy": "2022-08-09T02:01:41.845249Z",
"iopub.status.idle": "2022-08-09T02:01:42.926033Z",
"shell.execute_reply": "2022-08-09T02:01:42.924747Z"
},
"id": "JsMwwhOoqjKF"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Found 2735 files belonging to 3 classes.\n",
"Using 547 files for validation.\n"
]
}
],
"source": [
"raw_val_ds = tf.keras.utils.text_dataset_from_directory(\n",
" 'files/train', \n",
" batch_size=batch_size, \n",
" validation_split=0.2, \n",
" subset='validation', \n",
" seed=seed)"
]
},
{
"cell_type": "code",
"execution_count": 137,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:42.929749Z",
"iopub.status.busy": "2022-08-09T02:01:42.929053Z",
"iopub.status.idle": "2022-08-09T02:01:44.032526Z",
"shell.execute_reply": "2022-08-09T02:01:44.031568Z"
},
"id": "rdSr0Nt3q_ns"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Found 1120 files belonging to 3 classes.\n"
]
}
],
"source": [
"raw_test_ds = tf.keras.utils.text_dataset_from_directory(\n",
" 'files/test', \n",
" batch_size=batch_size)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"id": "qJmTiO0IYAjm"
},
"source": [
"### トレーニング用データを準備する\n",
"\n",
"次に、便利な `tf.keras.layers.TextVectorization` レイヤーを使用して、データを標準化、トークン化、およびベクトル化します。\n",
"\n",
"標準化とは、テキストを前処理することを指します。通常、句読点や HTML 要素を削除して、データセットを簡素化します。トークン化とは、文字列をトークンに分割することです (たとえば、空白で分割することにより、文を個々の単語に分割します)。ベクトル化とは、トークンを数値に変換して、ニューラルネットワークに入力できるようにすることです。これらのタスクはすべて、このレイヤーで実行できます。\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ZVcHl-SLrH-u"
},
"source": [
"注意: [トレーニング/テストスキュー](https://developers.google.com/machine-learning/guides/rules-of-ml#training-serving_skew)(トレーニング/サービングスキューとも呼ばれます)を防ぐには、トレーニング時とテスト時にデータを同じように前処理することが重要です。これを容易にするためには、このチュートリアルの後半で示すように、`TextVectorization` レイヤーをモデル内に直接含めます。"
]
},
{
"cell_type": "code",
"execution_count": 138,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:44.037094Z",
"iopub.status.busy": "2022-08-09T02:01:44.036383Z",
"iopub.status.idle": "2022-08-09T02:01:44.040889Z",
"shell.execute_reply": "2022-08-09T02:01:44.040106Z"
},
"id": "SDRI_s_tX1Hk"
},
"outputs": [],
"source": [
"# def custom_standardization(input_data):\n",
"# lowercase = tf.strings.lower(input_data)\n",
"# stripped_html = tf.strings.regex_replace(lowercase, '
', ' ')\n",
"# return tf.strings.regex_replace(stripped_html,\n",
"# '[%s]' % re.escape(string.punctuation),\n",
"# '')"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "d2d3Aw8dsUux"
},
"source": [
"次に、`TextVectorization` レイヤーを作成します。このレイヤーを使用して、データを標準化、トークン化、およびベクトル化します。`output_mode` を `int` に設定して、トークンごとに一意の整数インデックスを作成します。\n",
"\n",
"デフォルトの分割関数と、上記で定義したカスタム標準化関数を使用していることに注意してください。また、明示的な最大値 `sequence_length` など、モデルの定数をいくつか定義します。これにより、レイヤーはシーケンスを正確に `sequence_length` 値にパディングまたは切り捨てます。"
]
},
{
"cell_type": "code",
"execution_count": 139,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:44.044790Z",
"iopub.status.busy": "2022-08-09T02:01:44.044193Z",
"iopub.status.idle": "2022-08-09T02:01:44.057809Z",
"shell.execute_reply": "2022-08-09T02:01:44.057027Z"
},
"id": "-c76RvSzsMnX"
},
"outputs": [],
"source": [
"max_features = 10000\n",
"sequence_length = 250\n",
"\n",
"vectorize_layer = layers.TextVectorization(\n",
" standardize=custom_standardization,\n",
" max_tokens=max_features,\n",
" output_mode='int',\n",
" output_sequence_length=sequence_length)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "vlFOpfF6scT6"
},
"source": [
"次に、`adapt` を呼び出して、前処理レイヤーの状態をデータセットに適合させます。これにより、モデルは文字列から整数へのインデックスを作成します。"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "lAhdjK7AtroA"
},
"source": [
"注意: Adapt を呼び出すときは、トレーニング用データのみを使用することが重要です(テスト用セットを使用すると情報が漏洩します)。"
]
},
{
"cell_type": "code",
"execution_count": 140,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:44.061120Z",
"iopub.status.busy": "2022-08-09T02:01:44.060893Z",
"iopub.status.idle": "2022-08-09T02:01:46.470841Z",
"shell.execute_reply": "2022-08-09T02:01:46.470062Z"
},
"id": "GH4_2ZGJsa_X"
},
"outputs": [],
"source": [
"# Make a text-only dataset (without labels), then call adapt\n",
"train_text = raw_train_ds.map(lambda x, y: x)\n",
"vectorize_layer.adapt(train_text)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "SHQVEFzNt-K_"
},
"source": [
"このレイヤーを使用して一部のデータを前処理した結果を確認する関数を作成します。"
]
},
{
"cell_type": "code",
"execution_count": 141,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:46.475253Z",
"iopub.status.busy": "2022-08-09T02:01:46.474720Z",
"iopub.status.idle": "2022-08-09T02:01:46.478385Z",
"shell.execute_reply": "2022-08-09T02:01:46.477741Z"
},
"id": "SCIg_T50wOCU"
},
"outputs": [],
"source": [
"def vectorize_text(text, label):\n",
" text = tf.expand_dims(text, -1)\n",
" return vectorize_layer(text), label"
]
},
{
"cell_type": "code",
"execution_count": 142,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:46.481786Z",
"iopub.status.busy": "2022-08-09T02:01:46.481129Z",
"iopub.status.idle": "2022-08-09T02:01:46.545233Z",
"shell.execute_reply": "2022-08-09T02:01:46.544560Z"
},
"id": "XULcm6B3xQIO"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Review tf.Tensor(b'/*--------------------------------*- C++ -*----------------------------------*\\\\\\n| ========= | |\\n| \\\\\\\\ / F ield | OpenFOAM: The Open Source CFD Toolbox |\\n| \\\\\\\\ / O peration | Version: v2112 |\\n| \\\\\\\\ / A nd | Website: www.openfoam.com |\\n| \\\\\\\\/ M anipulation | |\\n\\\\*---------------------------------------------------------------------------*/\\nFoamFile\\n{\\n version 2.0;\\n format ascii;\\n class volScalarField;\\n object k;\\n}\\n// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\\n\\ndimensions [0 2 -2 0 0 0 0];\\n\\ninternalField uniform 1;\\n\\nboundaryField\\n{\\n inlet\\n {\\n type turbulentIntensityKineticEnergyInlet;\\n intensity 0.05;\\n value $internalField;\\n }\\n\\n outlet\\n {\\n type inletOutlet;\\n inletValue $internalField;\\n value $internalField;\\n }\\n\\n \"(?i).*walls\"\\n {\\n type kqRWallFunction;\\n value $internalField;\\n }\\n}\\n\\n\\n// ************************************************************************* //\\n', shape=(), dtype=string)\n",
"Label openfoam\n",
"Vectorized review (, )\n"
]
}
],
"source": [
"# retrieve a batch (of 32 reviews and labels) from the dataset\n",
"text_batch, label_batch = next(iter(raw_train_ds))\n",
"first_review, first_label = text_batch[0], label_batch[0]\n",
"print(\"Review\", first_review)\n",
"print(\"Label\", raw_train_ds.class_names[first_label])\n",
"print(\"Vectorized review\", vectorize_text(first_review, first_label))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "6u5EX0hxyNZT"
},
"source": [
"上記のように、各トークンは整数に置き換えられています。レイヤーで `.get_vocabulary()` を呼び出すことにより、各整数が対応するトークン(文字列)を検索できます。"
]
},
{
"cell_type": "code",
"execution_count": 143,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:46.548742Z",
"iopub.status.busy": "2022-08-09T02:01:46.548121Z",
"iopub.status.idle": "2022-08-09T02:01:46.593273Z",
"shell.execute_reply": "2022-08-09T02:01:46.592666Z"
},
"id": "kRq9hTQzhVhW"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1287 ---> 1073\n",
" 313 ---> 144\n",
"Vocabulary size: 10000\n"
]
}
],
"source": [
"print(\"1287 ---> \",vectorize_layer.get_vocabulary()[1287])\n",
"print(\" 313 ---> \",vectorize_layer.get_vocabulary()[313])\n",
"print('Vocabulary size: {}'.format(len(vectorize_layer.get_vocabulary())))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "XD2H6utRydGv"
},
"source": [
"モデルをトレーニングする準備がほぼ整いました。最後の前処理ステップとして、トレーニング、検証、およびデータセットのテストのために前に作成した TextVectorization レイヤーを適用します。"
]
},
{
"cell_type": "code",
"execution_count": 144,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:46.596572Z",
"iopub.status.busy": "2022-08-09T02:01:46.596075Z",
"iopub.status.idle": "2022-08-09T02:01:46.732615Z",
"shell.execute_reply": "2022-08-09T02:01:46.731836Z"
},
"id": "2zhmpeViI1iG"
},
"outputs": [],
"source": [
"train_ds = raw_train_ds.map(vectorize_text)\n",
"val_ds = raw_val_ds.map(vectorize_text)\n",
"test_ds = raw_test_ds.map(vectorize_text)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "YsVQyPMizjuO"
},
"source": [
"### パフォーマンスのためにデータセットを構成する\n",
"\n",
"以下は、I/O がブロックされないようにするためにデータを読み込むときに使用する必要がある 2 つの重要な方法です。\n",
"\n",
"`.cache()` はデータをディスクから読み込んだ後、データをメモリに保持します。これにより、モデルのトレーニング中にデータセットがボトルネックになることを回避できます。データセットが大きすぎてメモリに収まらない場合は、この方法を使用して、パフォーマンスの高いオンディスクキャッシュを作成することもできます。これは、多くの小さなファイルを読み込むより効率的です。\n",
"\n",
"`.prefetch()` はトレーニング中にデータの前処理とモデルの実行をオーバーラップさせます。\n",
"\n",
"以上の 2 つの方法とデータをディスクにキャッシュする方法についての詳細は、[データパフォーマンスガイド](https://www.tensorflow.org/guide/data_performance)を参照してください。"
]
},
{
"cell_type": "code",
"execution_count": 145,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:46.736978Z",
"iopub.status.busy": "2022-08-09T02:01:46.736462Z",
"iopub.status.idle": "2022-08-09T02:01:46.745396Z",
"shell.execute_reply": "2022-08-09T02:01:46.744739Z"
},
"id": "wMcs_H7izm5m"
},
"outputs": [],
"source": [
"AUTOTUNE = tf.data.AUTOTUNE\n",
"\n",
"train_ds = train_ds.cache().prefetch(buffer_size=AUTOTUNE)\n",
"val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)\n",
"test_ds = test_ds.cache().prefetch(buffer_size=AUTOTUNE)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "LLC02j2g-llC"
},
"source": [
"### モデルを作成する\n",
"\n",
"ニューラルネットワークを作成します。"
]
},
{
"cell_type": "code",
"execution_count": 146,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:46.749050Z",
"iopub.status.busy": "2022-08-09T02:01:46.748558Z",
"iopub.status.idle": "2022-08-09T02:01:46.751728Z",
"shell.execute_reply": "2022-08-09T02:01:46.751128Z"
},
"id": "dkQP6in8yUBR"
},
"outputs": [],
"source": [
"embedding_dim = 16"
]
},
{
"cell_type": "code",
"execution_count": 147,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:46.754702Z",
"iopub.status.busy": "2022-08-09T02:01:46.754214Z",
"iopub.status.idle": "2022-08-09T02:01:46.811958Z",
"shell.execute_reply": "2022-08-09T02:01:46.811182Z"
},
"id": "xpKOoWgu-llD"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: \"sequential_9\"\n",
"_________________________________________________________________\n",
" Layer (type) Output Shape Param # \n",
"=================================================================\n",
" embedding_5 (Embedding) (None, None, 16) 160016 \n",
" \n",
" dropout_10 (Dropout) (None, None, 16) 0 \n",
" \n",
" global_average_pooling1d_5 (None, 16) 0 \n",
" (GlobalAveragePooling1D) \n",
" \n",
" dropout_11 (Dropout) (None, 16) 0 \n",
" \n",
" dense_5 (Dense) (None, 3) 51 \n",
" \n",
"=================================================================\n",
"Total params: 160,067\n",
"Trainable params: 160,067\n",
"Non-trainable params: 0\n",
"_________________________________________________________________\n"
]
}
],
"source": [
"model = tf.keras.Sequential([\n",
" layers.Embedding(max_features + 1, embedding_dim),\n",
" layers.Dropout(0.2),\n",
" layers.GlobalAveragePooling1D(),\n",
" layers.Dropout(0.2),\n",
" layers.Dense(3)])\n",
"\n",
"model.summary()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "6PbKQ6mucuKL"
},
"source": [
"これらのレイヤーは、分類器を構成するため一列に積み重ねられます。\n",
"\n",
"1. 最初のレイヤーは `Embedding` (埋め込み)レイヤーです。このレイヤーは、整数にエンコードされた語彙を受け取り、それぞれの単語インデックスに対応する埋め込みベクトルを検索します。埋め込みベクトルは、モデルのトレーニングの中で学習されます。ベクトル化のために、出力行列には次元が1つ追加されます。その結果、次元は、`(batch, sequence, embedding)` となります。埋め込みの詳細については、[単語埋め込みチュートリアル](https://www.tensorflow.org/text/guide/word_embeddings)を参照してください。\n",
"2. 次は、`GlobalAveragePooling1D`(1次元のグローバル平均プーリング)レイヤーです。このレイヤーは、それぞれのサンプルについて、シーケンスの次元方向に平均値をもとめ、固定長のベクトルを返します。この結果、モデルは最も単純な形で、可変長の入力を扱うことができるようになります。\n",
"3. この固定長の出力ベクトルは、16 個の非表示ユニットを持つ全結合 (`Dense`) レイヤーに受け渡されます。\n",
"4. 最後のレイヤーは、単一の出力ノードと密に接続されています。"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "L4EqVWg4-llM"
},
"source": [
"### 損失関数とオプティマイザ\n",
"\n",
"モデルをトレーニングするには、損失関数とオプティマイザが必要です。これは二項分類問題であり、モデルは確率(シグモイドアクティベーションを持つ単一ユニットレイヤー)を出力するため、`losses.BinaryCrossentropy` 損失関数を使用します。\n",
"\n",
"損失関数の候補はこれだけではありません。例えば、`mean_squared_error`(平均二乗誤差)を使うこともできます。しかし、一般的には、確率を扱うには`binary_crossentropy`の方が適しています。`binary_crossentropy`は、確率分布の間の「距離」を測定する尺度です。今回の場合には、真の分布と予測値の分布の間の距離ということになります。"
]
},
{
"cell_type": "code",
"execution_count": 148,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:46.819132Z",
"iopub.status.busy": "2022-08-09T02:01:46.818733Z",
"iopub.status.idle": "2022-08-09T02:01:46.832825Z",
"shell.execute_reply": "2022-08-09T02:01:46.832186Z"
},
"id": "Mr0GP-cQ-llN"
},
"outputs": [],
"source": [
"model.compile(loss=losses.SparseCategoricalCrossentropy(from_logits=True),\n",
" optimizer='adam',\n",
" metrics=['accuracy'])"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "35jv_fzP-llU"
},
"source": [
"### モデルをトレーニングする\n",
"\n",
"`dataset` オブジェクトを fit メソッドに渡すことにより、モデルをトレーニングします。"
]
},
{
"cell_type": "code",
"execution_count": 149,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:01:46.836277Z",
"iopub.status.busy": "2022-08-09T02:01:46.835693Z",
"iopub.status.idle": "2022-08-09T02:02:09.438228Z",
"shell.execute_reply": "2022-08-09T02:02:09.437473Z"
},
"id": "tXSGrjWZ-llW"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Epoch 1/10\n",
"69/69 [==============================] - 12s 149ms/step - loss: 1.0318 - accuracy: 0.6787 - val_loss: 0.9400 - val_accuracy: 0.7623\n",
"Epoch 2/10\n",
"69/69 [==============================] - 0s 6ms/step - loss: 0.8320 - accuracy: 0.7998 - val_loss: 0.7299 - val_accuracy: 0.7642\n",
"Epoch 3/10\n",
"69/69 [==============================] - 1s 7ms/step - loss: 0.6407 - accuracy: 0.8039 - val_loss: 0.5742 - val_accuracy: 0.7843\n",
"Epoch 4/10\n",
"69/69 [==============================] - 0s 6ms/step - loss: 0.5183 - accuracy: 0.8144 - val_loss: 0.4852 - val_accuracy: 0.7952\n",
"Epoch 5/10\n",
"69/69 [==============================] - 0s 7ms/step - loss: 0.4478 - accuracy: 0.8368 - val_loss: 0.4280 - val_accuracy: 0.8464\n",
"Epoch 6/10\n",
"69/69 [==============================] - 0s 6ms/step - loss: 0.4011 - accuracy: 0.8583 - val_loss: 0.3835 - val_accuracy: 0.8629\n",
"Epoch 7/10\n",
"69/69 [==============================] - 1s 7ms/step - loss: 0.3598 - accuracy: 0.8761 - val_loss: 0.3457 - val_accuracy: 0.8684\n",
"Epoch 8/10\n",
"69/69 [==============================] - 0s 7ms/step - loss: 0.3263 - accuracy: 0.8903 - val_loss: 0.3117 - val_accuracy: 0.8848\n",
"Epoch 9/10\n",
"69/69 [==============================] - 0s 7ms/step - loss: 0.2981 - accuracy: 0.8967 - val_loss: 0.2818 - val_accuracy: 0.9013\n",
"Epoch 10/10\n",
"69/69 [==============================] - 0s 7ms/step - loss: 0.2717 - accuracy: 0.9136 - val_loss: 0.2550 - val_accuracy: 0.9159\n"
]
}
],
"source": [
"epochs = 10\n",
"history = model.fit(\n",
" train_ds,\n",
" validation_data=val_ds,\n",
" epochs=epochs)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "9EEGuDVuzb5r"
},
"source": [
"### モデルを評価する\n",
"\n",
"モデルがどのように実行するか見てみましょう。2 つの値が返されます。損失(誤差、値が低いほど良)と正確度です。"
]
},
{
"cell_type": "code",
"execution_count": 150,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:02:09.442285Z",
"iopub.status.busy": "2022-08-09T02:02:09.441644Z",
"iopub.status.idle": "2022-08-09T02:02:11.134378Z",
"shell.execute_reply": "2022-08-09T02:02:11.133592Z"
},
"id": "zOMKywn4zReN"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"35/35 [==============================] - 7s 205ms/step - loss: 0.2043 - accuracy: 0.9509\n",
"Loss: 0.20425863564014435\n",
"Accuracy: 0.9508928656578064\n"
]
}
],
"source": [
"loss, accuracy = model.evaluate(test_ds)\n",
"\n",
"print(\"Loss: \", loss)\n",
"print(\"Accuracy: \", accuracy)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"id": "z1iEXVTR0Z2t"
},
"source": [
"この、かなり素朴なアプローチでも 95% 前後の正解度を達成しました。"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ldbQqCw2Xc1W"
},
"source": [
"### 経時的な正解度と損失のグラフを作成する\n",
"\n",
"`model.fit()` は、トレーニング中に発生したすべての情報を詰まったディクショナリを含む `History` オブジェクトを返します。"
]
},
{
"cell_type": "code",
"execution_count": 151,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:02:11.138090Z",
"iopub.status.busy": "2022-08-09T02:02:11.137499Z",
"iopub.status.idle": "2022-08-09T02:02:11.142167Z",
"shell.execute_reply": "2022-08-09T02:02:11.141440Z"
},
"id": "-YcvZsdvWfDf"
},
"outputs": [
{
"data": {
"text/plain": [
"dict_keys(['loss', 'accuracy', 'val_loss', 'val_accuracy'])"
]
},
"execution_count": 151,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"history_dict = history.history\n",
"history_dict.keys()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "1_CH32qJXruI"
},
"source": [
"トレーニングと検証中に監視されている各メトリックに対して 1 つずつ、計 4 つのエントリがあります。このエントリを使用して、トレーニングと検証の損失とトレーニングと検証の正解度を比較したグラフを作成することができます。"
]
},
{
"cell_type": "code",
"execution_count": 152,
"metadata": {
"execution": {
"iopub.execute_input": "2022-08-09T02:02:11.145579Z",
"iopub.status.busy": "2022-08-09T02:02:11.144986Z",
"iopub.status.idle": "2022-08-09T02:02:11.256638Z",
"shell.execute_reply": "2022-08-09T02:02:11.255911Z"
},
"id": "2SEMeQ5YXs8z"
},
"outputs": [
{
"data": {
"image/png": "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IiJVipnlv6N8NzUNiYikOCUCEZEUp0QgIpLiqlwfgYhUvB07dpCVlcWPP/5Y/MESq7p169KyZUtq165d4nOUCESkWFlZWTRs2JD09HQKX1dI4ububNiwgaysLNq2bVvi81KiaSgzE9LToUaN8JypZbxFSuXHH3+kSZMmSgKVnJnRpEmTUtfcqn2NIDMTxoyBLYklTdauDdsAI0cWfp6I5KUkUDXsze+p2tcIrr8+Nwnk2LIllIuISAokgs8+K125iFQ+GzZsoGvXrnTt2pWDDjqIFi1a7N7evn17kecuWLCA8ePHF/seRx99dLnEOm/ePE499dRyuVZFqfaJoHX+Rf6KKReRsivvfrkmTZqwaNEiFi1axNixY5kwYcLu7Tp16rBz585Cz83IyGDq1KnFvscbb7xRtiCrsGqfCCZPhrS0vGVpaaFcRMpfTr/c2rXgntsvV96DNEaNGsXYsWPp3bs311xzDe+88w5HHXUU3bp14+ijj2blypVA3r/QJ02axPnnn8+AAQM45JBD8iSIBg0a7D5+wIABDBs2jA4dOjBy5EhyZml+5pln6NChAz169GD8+PHF/uX/7bffctppp9G5c2f69OnD4sWLAXjllVd212i6detGdnY269ato3///nTt2pUjjzySV199tXw/sCJU+87inA7h668PzUGtW4ckoI5ikWgU1S9X3v/vsrKyeOONN6hZsyabNm3i1VdfpVatWrz44otcd911/P3vf9/jnBUrVjB37lyys7M57LDDGDdu3B5j7t9//32WLl3KwQcfTN++fXn99dfJyMjgwgsvZP78+bRt25YRI0YUG9/NN99Mt27dmD17Ni+//DLnnnsuixYtYsqUKUybNo2+ffuyefNm6taty/Tp0znppJO4/vrr2bVrF1vyf4gRqvaJAMI/Pn3xi1SMiuyXGz58ODVr1gTg+++/57zzzuPjjz/GzNixY0eB55xyyinss88+7LPPPhxwwAF89dVXtGzZMs8xvXr12l3WtWtX1qxZQ4MGDTjkkEN2j88fMWIE06dPLzK+1157bXcyOv7449mwYQObNm2ib9++XHHFFYwcOZKhQ4fSsmVLevbsyfnnn8+OHTs47bTT6Nq1a1k+mlKJrGnIzB40s6/NbEkh+83MpprZKjNbbGbdo4pFRCpORfbL1a9ff/frG2+8keOOO44lS5bw5JNP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",
"text/plain": [
"