├── src ├── App.css ├── index.css ├── main.tsx ├── components │ ├── visual │ │ ├── use-window-size.tsx │ │ ├── result-mesh.tsx │ │ ├── pixel-plane-mesh.tsx │ │ ├── Canvas-drawing.tsx │ │ ├── drawLineGroup.tsx │ │ ├── input-hidden-plane.tsx │ │ └── mnist-sample.ts │ ├── neuralnet │ │ ├── covolution.tsx │ │ ├── testData │ │ │ └── testData.json │ │ ├── functions.test.ts │ │ └── functions.ts │ └── ui │ │ └── sidebar.tsx ├── vite-env.d.ts ├── assets │ └── react.svg └── App.tsx ├── .gitattributes ├── .prettierrc ├── jest.config.json ├── thumbnail.png ├── postcss.config.js ├── vite.config.ts ├── tailwind.config.js ├── tsconfig.node.json ├── .gitignore ├── .eslintrc.cjs ├── public ├── file.json └── vite.svg ├── tsconfig.json ├── index.html ├── README.md └── package.json /src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | neural.json filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "ts-jest", 3 | "testEnvironment": "node" 4 | } 5 | -------------------------------------------------------------------------------- /thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenjiSpecial/CNNVisualizer/HEAD/thumbnail.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client'; 2 | import App from './App.tsx'; 3 | import './index.css'; 4 | 5 | ReactDOM.createRoot(document.getElementById('root')!).render(); 6 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }); 8 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | 'plugin:prettier/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parser: '@typescript-eslint/parser', 12 | plugins: ['react-refresh'], 13 | rules: { 14 | 'react-refresh/only-export-components': [ 15 | 'warn', 16 | { allowConstantExport: true }, 17 | ], 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /public/file.json: -------------------------------------------------------------------------------- 1 | { 2 | "glossary": { 3 | "title": "example glossary", 4 | "GlossDiv": { 5 | "title": "S", 6 | "GlossList": { 7 | "GlossEntry": { 8 | "ID": "SGML", 9 | "SortAs": "SGML", 10 | "GlossTerm": "Standard Generalized Markup Language", 11 | "Acronym": "SGML", 12 | "Abbrev": "ISO 8879:1986", 13 | "GlossDef": { 14 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 15 | "GlossSeeAlso": ["GML", "XML"] 16 | }, 17 | "GlossSee": "markup" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | CNNビジュアライザー 8 | 9 | 13 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/components/visual/use-window-size.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | 3 | // ウィンドウサイズを表す型定義 4 | export interface IWindowSize { 5 | width: number | undefined; 6 | height: number | undefined; 7 | } 8 | 9 | // カスタムフックの定義 10 | export const useWindowSize = (): IWindowSize => { 11 | const [windowSize, setWindowSize] = useState({ 12 | width: undefined, 13 | height: undefined, 14 | }); 15 | 16 | useEffect(() => { 17 | // サイズを設定するヘルパー関数 18 | const handleResize = () => { 19 | setWindowSize({ 20 | width: window.innerWidth, 21 | height: window.innerHeight, 22 | }); 23 | }; 24 | 25 | // 初回レンダリング時にサイズを設定 26 | handleResize(); 27 | 28 | // resizeイベントリスナーを設定 29 | window.addEventListener('resize', handleResize); 30 | 31 | // クリーンアップ関数 32 | return () => window.removeEventListener('resize', handleResize); 33 | }, []); // 空の依存配列でマウント時とアンマウント時のみ実行 34 | 35 | return windowSize; 36 | }; 37 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module 'mnist' { 4 | type Output = [ 5 | number, 6 | number, 7 | number, 8 | number, 9 | number, 10 | number, 11 | number, 12 | number, 13 | number, 14 | number, 15 | ]; 16 | 17 | type Datum = { 18 | input: number[]; 19 | output: Output; 20 | }; 21 | 22 | interface Digit { 23 | id: number; 24 | raw: number[]; 25 | length: number; 26 | get: (index?: number) => number[]; 27 | range: (start: number, end: number) => number[][]; 28 | set: (start: number, end: number) => Datum[]; 29 | } 30 | 31 | namespace MNIST { 32 | export function set( 33 | trainingSetSize: number, 34 | testSetSize: number, 35 | ): { 36 | training: Datum[]; 37 | test: Datum[]; 38 | }; 39 | 40 | export function get(count: number): Datum[]; 41 | 42 | export function draw( 43 | digit: number[], 44 | context: CanvasRenderingContext2D, 45 | offsetX: number, 46 | offsetY: number, 47 | ): void; 48 | 49 | export function toNumber(array: number[]): number; 50 | } 51 | 52 | export = MNIST as typeof MNIST & Digit[]; 53 | } 54 | 55 | // declare module 'numjs' { 56 | // export interface NdArray { 57 | // log: () => NdArray; 58 | // } 59 | // } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CNNビジュアライザー 2 | 3 | [![代替テキスト](./thumbnail.png)](https://ks-cnnvisualizer.netlify.app/) 4 | URL: https://ks-cnnvisualizer.netlify.app/ 5 | 6 | ## 概要 7 | 8 | CNNビジュアライザーは、畳み込みニューラルネットワーク(CNN)を可視化するサイトです。 9 | 10 | [「ゼロから作るDeep Learning」](https://www.amazon.co.jp/dp/4873117585?ref_=cm_sw_r_cp_ud_dp_D4WTQD6YZC7XPRNG5K9V) でデモ学習として作り、学習してできたCNNモデルを利用しています。 11 | 12 | 理論的な説明は[「ゼロから作るDeep Learning」](https://www.amazon.co.jp/dp/4873117585?ref_=cm_sw_r_cp_ud_dp_D4WTQD6YZC7XPRNG5K9V) を読んでいただけると、理解は深まると思います。 13 | 14 | CNNビジュアライザーでは入力層、畳み込み層+プーリング層、全結合層、出力層の4つの層に分けています。スケッチキャンパスに自分で絵を描いてみると、文字を認識してくれます。 15 | 16 | このプロジェクトは、TypeScript、React、およびReact Three Fiber(r3f)を使用して開発されています。ニューラルネットワークの複雑な構造とそのパラメータを直感的に理解するのに役立ちます。 17 | 18 | # プレビュー 19 | 20 | プロジェクトのプレビューはこちらからご覧いただけます: https://ks-cnnvisualizer.netlify.app/ 21 | 22 | ## 開発とビルド 23 | 24 | ### プロジェクトの開発 25 | 26 | ローカルでの開発を行うには、以下のコマンドを実行します: 27 | 28 | ``` 29 | $ vite dev 30 | ``` 31 | 32 | このコマンドにより、開発用サーバが起動し、リアルタイムでのコード変更が反映されます。 33 | 34 | ### プロジェクトのビルド 35 | 36 | プロジェクトをビルドして本番環境にデプロイするには、以下のコマンドを実行します: 37 | 38 | ``` 39 | vite build 40 | ``` 41 | 42 | このコマンドにより、本番用の最適化されたビルドが生成されます。 43 | 44 | ## コントリビュート 45 | 46 | プロジェクトへの貢献に興味がある方は、issueやプルリクエストを通じてご参加ください。すべての貢献は大歓迎です! 47 | 48 | ## ライセンス 49 | 50 | このプロジェクトはMITライセンスの下で公開されています。 51 | 52 | ## 関連 53 | 54 | - [kenjiSpecial/NNVisualizer](https://github.com/kenjiSpecial/NNVisualizer) 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-ts", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --port 3000", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview", 11 | "test": "jest" 12 | }, 13 | "dependencies": { 14 | "@react-three/drei": "^9.88.17", 15 | "@react-three/fiber": "^8.15.11", 16 | "@types/three": "^0.158.3", 17 | "leva": "^0.9.35", 18 | "react": "^18.2.0", 19 | "react-dom": "^18.2.0", 20 | "three": "^0.158.0" 21 | }, 22 | "devDependencies": { 23 | "@types/jest": "^29.5.10", 24 | "@types/numjs": "^0.16.7", 25 | "@types/react": "^18.2.37", 26 | "@types/react-dom": "^18.2.15", 27 | "@typescript-eslint/eslint-plugin": "^6.10.0", 28 | "@typescript-eslint/parser": "^6.10.0", 29 | "@vitejs/plugin-react": "^4.2.0", 30 | "autoprefixer": "^10.4.16", 31 | "eslint": "^8.53.0", 32 | "eslint-config-prettier": "^9.0.0", 33 | "eslint-plugin-prettier": "^5.0.1", 34 | "eslint-plugin-react-hooks": "^4.6.0", 35 | "eslint-plugin-react-refresh": "^0.4.4", 36 | "jest": "^29.7.0", 37 | "postcss": "^8.4.31", 38 | "prettier": "^3.1.0", 39 | "tailwindcss": "^3.3.5", 40 | "ts-jest": "^29.1.1", 41 | "typescript": "^5.2.2", 42 | "vite": "^5.0.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/visual/result-mesh.tsx: -------------------------------------------------------------------------------- 1 | import { Text } from '@react-three/drei'; 2 | import { useMemo } from 'react'; 3 | import { Vector3 } from 'three'; 4 | 5 | type ResultMeshProps = JSX.IntrinsicElements['group'] & { 6 | result: number[] | undefined; 7 | pos: Vector3; 8 | }; 9 | 10 | export function ResultMesh(props: ResultMeshProps) { 11 | // result[0]が存在するとき、result[0]のarrayをsoftmax関数にかける。 12 | // softmax関数をかけた結果の配列を返す。 13 | // result[0]が存在しないとき、undefinedを返す。 14 | // useMemoを使って、キャッシュする。 15 | 16 | // console.log(softmaxArr); 17 | 18 | // groupを作成する。groupの中にTextを作成する。textは0-9の数字を表示する。 19 | // useMemoを使って、キャッシュする。 20 | const textGroup = useMemo(() => { 21 | const textArr = []; 22 | 23 | for (let i = 0; i < 10; i++) { 24 | const theta = (i * Math.PI) / 5 - Math.PI / 2; 25 | const rad = 8; 26 | const x = Math.cos(theta) * rad; 27 | const y = -Math.sin(theta) * rad; 28 | const z = 0; 29 | const pos = new Vector3(x, y, z); 30 | const fontSize = 31 | props.result && props.result[i] 32 | ? 7 * Math.max(props.result[i], 0.05) 33 | : 0.05; 34 | textArr.push( 35 | {`${i}`}, 41 | ); 42 | } 43 | return textArr; 44 | }, [props.result]); 45 | 46 | return {textGroup}; 47 | } 48 | -------------------------------------------------------------------------------- /src/components/neuralnet/covolution.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { 3 | calcRelu4DArray, 4 | calculationCovolution, 5 | calculationPooling, 6 | } from './functions'; 7 | 8 | export const useCovolutionMemo = ( 9 | inputData: number[][][], // 3次元配列 [batchSize][channel][row * col] 10 | paramsW1: number[][][][] | undefined, 11 | paramsB1: number[] | undefined, 12 | inputRowSize: number, 13 | inputColSize: number, 14 | filterNum: number, 15 | filterRowSize: number, 16 | filterColSize: number, 17 | covolutionPadding: number, 18 | covolutionStride: number, 19 | ) => { 20 | return useMemo(() => { 21 | // const outputData = inputData.map((data, index) => { 22 | // const row = Math.floor(index / filterRowSize); 23 | // const col = index % filterColSize; 24 | // const filter = filterNum * filterRowSize * filterColSize; 25 | // const filterIndex = filter + row * filterColSize + col; 26 | // const filterData = inputData[filterIndex]; 27 | // return data * filterData; 28 | // }); 29 | // setOutputData(outputData); 30 | if (!paramsW1 || !paramsB1) return undefined; 31 | const out = calculationCovolution( 32 | inputData, 33 | inputRowSize, 34 | inputColSize, 35 | paramsW1, 36 | paramsB1, 37 | filterNum, 38 | filterRowSize, 39 | filterColSize, 40 | covolutionPadding, 41 | covolutionStride, 42 | ); 43 | return calcRelu4DArray(out); 44 | }, [ 45 | inputData, 46 | paramsW1, 47 | paramsB1, 48 | inputRowSize, 49 | inputColSize, 50 | filterNum, 51 | filterRowSize, 52 | filterColSize, 53 | covolutionPadding, 54 | covolutionStride, 55 | ]); 56 | }; 57 | 58 | export const usePoolingMemo = ( 59 | inputData: number[][][][] | undefined, 60 | poolingRowSize: number, 61 | poolingColsize: number, 62 | poolingStride: number, 63 | PoolingPadding: number, 64 | ) => { 65 | return useMemo(() => { 66 | if (!inputData) return undefined; 67 | const inputFilterNum = inputData[0].length; 68 | const inputRowSize = inputData[0][0].length; 69 | const inputColSize = inputData[0][0][0].length; 70 | 71 | return calculationPooling( 72 | inputData, 73 | inputFilterNum, 74 | inputRowSize, 75 | inputColSize, 76 | poolingRowSize, 77 | poolingColsize, 78 | poolingStride, 79 | PoolingPadding, 80 | ); 81 | }, [ 82 | inputData, 83 | poolingRowSize, 84 | poolingColsize, 85 | poolingStride, 86 | PoolingPadding, 87 | ]); 88 | }; 89 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ui/sidebar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useMemo, useRef } from 'react'; 2 | import { CanvasDrawing } from '../visual/Canvas-drawing'; 3 | import { IWindowSize } from '../visual/use-window-size'; 4 | 5 | interface SidebarProps { 6 | displaySideBar: string; 7 | setInputData: (inputData: number[]) => void; 8 | inputData: number[]; 9 | isButtonClicked: React.MutableRefObject; 10 | windowSize: IWindowSize; 11 | clickbutton: () => void; 12 | } 13 | 14 | const Sidebar: React.FC = ({ 15 | displaySideBar, 16 | setInputData, 17 | inputData, 18 | isButtonClicked, 19 | windowSize, 20 | clickbutton, 21 | }) => { 22 | const ref = useRef(null); 23 | // モバイル用のキャプション機能をuseStateで作成する。 24 | const [isMobileCaptionOpen, setIsMobileCaptionOpen] = React.useState(false); 25 | const displayProp = useMemo(() => { 26 | return isMobileCaptionOpen ? 'block' : 'hidden'; 27 | }, [isMobileCaptionOpen]); 28 | const menuText = useMemo(() => { 29 | return isMobileCaptionOpen ? '閉じる' : 'CNNビジュアライザーについて'; 30 | }, [isMobileCaptionOpen]); 31 | 32 | const clickMenuButton = useCallback(() => { 33 | setIsMobileCaptionOpen(!isMobileCaptionOpen); 34 | }, [isMobileCaptionOpen]); 35 | 36 | return ( 37 |
50 | {/* スマホ用ボタンを作成する */} 51 |
52 | 56 | [{menuText}] 57 | 58 |
59 |
60 |
61 |

CNNビジュアライザー

62 |
63 | 64 |
65 |

66 | 71 | ゼロから作るDeep Learning 72 | 73 | の第7章の畳み込みニューラルネットワーク(CNN)をリアルタイムに可視化しています。ビジュアライザーでは入力層 74 | 、畳み込み層+プーリング層、全結合層、出力層の4つの層に分けています。 75 |

76 |

77 | 畳み込み層とプーリング層では、画像の特徴を抽出しています。シンプルなニューラルネットワークを可視化した 78 | 83 | NNビジュアライザー 84 | 85 | と比較してみると、畳み込みがどのように変化しているかがわかります。 86 |

87 | 88 |
89 | 参考:{' '} 90 | 95 | NNビジュアライザ- 96 | 97 |
98 |
99 |
100 | 101 |
102 |
103 | スケッチキャンバス 104 |
105 |
106 |
107 | 114 |
115 |
116 | 122 |
123 |
124 |
125 |

126 | スケッチキャンバスにスケッチをするか、画像テストボタンを押してください。自動でニューラルネットワークが推論を行います。 127 |

128 |
129 |
130 |

131 | Code:{' '} 132 | 137 | kenjiSpecial/CNNVisualizer 138 | 139 |

140 |

141 |
142 |
143 |
144 | ); 145 | }; 146 | 147 | export default Sidebar; 148 | -------------------------------------------------------------------------------- /src/components/neuralnet/testData/testData.json: -------------------------------------------------------------------------------- 1 | {"testData": [[[[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3294117748737335, 0.7254902124404907, 0.6235294342041016, 0.5921568870544434, 0.23529411852359772, 0.1411764770746231, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.8705882430076599, 0.9960784316062927, 0.9960784316062927, 0.9960784316062927, 0.9960784316062927, 0.9450980424880981, 0.7764706015586853, 0.7764706015586853, 0.7764706015586853, 0.7764706015586853, 0.7764706015586853, 0.7764706015586853, 0.7764706015586853, 0.7764706015586853, 0.6666666865348816, 0.20392157137393951, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.26274511218070984, 0.4470588266849518, 0.2823529541492462, 0.4470588266849518, 0.6392157077789307, 0.8901960849761963, 0.9960784316062927, 0.8823529481887817, 0.9960784316062927, 0.9960784316062927, 0.9960784316062927, 0.9803921580314636, 0.8980392217636108, 0.9960784316062927, 0.9960784316062927, 0.5490196347236633, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.06666667014360428, 0.25882354378700256, 0.054901961237192154, 0.26274511218070984, 0.26274511218070984, 0.26274511218070984, 0.23137255012989044, 0.08235294371843338, 0.9254902005195618, 0.9960784316062927, 0.4156862795352936, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.32549020648002625, 0.9921568632125854, 0.8196078538894653, 0.07058823853731155, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.08627451211214066, 0.9137254953384399, 1.0, 0.32549020648002625, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5058823823928833, 0.9960784316062927, 0.9333333373069763, 0.1725490242242813, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.23137255012989044, 0.9764705896377563, 0.9960784316062927, 0.24313725531101227, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5215686559677124, 0.9960784316062927, 0.7333333492279053, 0.019607843831181526, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.03529411926865578, 0.8039215803146362, 0.9725490212440491, 0.22745098173618317, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4941176474094391, 0.9960784316062927, 0.7137255072593689, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.29411765933036804, 0.9843137264251709, 0.9411764740943909, 0.2235294133424759, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.07450980693101883, 0.8666666746139526, 0.9960784316062927, 0.6509804129600525, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0117647061124444, 0.7960784435272217, 0.9960784316062927, 0.8588235378265381, 0.13725490868091583, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.14901961386203766, 0.9960784316062927, 0.9960784316062927, 0.3019607961177826, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.12156862765550613, 0.8784313797950745, 0.9960784316062927, 0.45098039507865906, 0.003921568859368563, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5215686559677124, 0.9960784316062927, 0.9960784316062927, 0.20392157137393951, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.239215686917305, 0.9490196108818054, 0.9960784316062927, 0.9960784316062927, 0.20392157137393951, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4745098054409027, 0.9960784316062927, 0.9960784316062927, 0.8588235378265381, 0.1568627506494522, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4745098054409027, 0.9960784316062927, 0.8117647171020508, 0.07058823853731155, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]]]} -------------------------------------------------------------------------------- /src/components/visual/pixel-plane-mesh.tsx: -------------------------------------------------------------------------------- 1 | import { DoubleSide, Texture } from 'three'; 2 | 3 | type PixelPlaneProps = JSX.IntrinsicElements['group'] & { 4 | data: number[]; 5 | size: number; 6 | space: number; 7 | rowSize: number; 8 | colSize: number; 9 | }; 10 | 11 | export function inputPos(props: { 12 | index: number; 13 | rowSize: number; 14 | halfRowSize: number; 15 | size: number; 16 | space: number; 17 | }) { 18 | const { index, rowSize, halfRowSize, size, space } = props; 19 | const posX = ((index % rowSize) - halfRowSize) * (size + space); 20 | const posY = (-Math.floor(index / rowSize) + halfRowSize) * (size + space); 21 | const posZ = 0; 22 | return { posX, posY, posZ }; 23 | } 24 | 25 | export function PixelPlaneMesh(props: PixelPlaneProps) { 26 | // const data = [1, 2, 3, 4]; 27 | const data = props.data; 28 | const { size, space, rowSize } = props; 29 | const halfRowSize = rowSize / 2; 30 | 31 | return ( 32 | 33 | {data.map((d, index) => { 34 | const colorVal = Math.floor(d * 255); 35 | const color = `rgb(${colorVal}, ${colorVal}, ${colorVal})`; 36 | 37 | const { posX, posY, posZ } = inputPos({ 38 | index, 39 | rowSize, 40 | halfRowSize, 41 | size, 42 | space, 43 | }); 44 | const key = `plane-${index}`; 45 | 46 | return ( 47 | 52 | 53 | 54 | 55 | ); 56 | })} 57 | 58 | ); 59 | } 60 | 61 | type ParamsPixelPlaneProps = JSX.IntrinsicElements['group'] & { 62 | hiddenSize: number; 63 | hiddenValueArr: number[]; 64 | size: number; 65 | space: number; 66 | rowSize: number; 67 | colSize: number; 68 | }; 69 | 70 | export function hiddenPos(props: { 71 | index: number; 72 | rowSize: number; 73 | colSize: number; 74 | size: number; 75 | space: number; 76 | }) { 77 | const { index, rowSize, colSize, size, space } = props; 78 | const posX = ((index % rowSize) - (rowSize - 1) / 2) * (size + space); 79 | const posY = 80 | (-Math.floor(index / rowSize) + (colSize - 1) / 2) * (size + space); 81 | const posZ = 0; 82 | return { posX, posY, posZ }; 83 | } 84 | 85 | export function ParamsPixelPlaneMesh(props: ParamsPixelPlaneProps) { 86 | const arr = []; 87 | 88 | const { size, space } = props; 89 | const rowSize = props.rowSize; 90 | const colSize = props.colSize; 91 | 92 | for (let i = 0; i < props.hiddenSize; i++) { 93 | const colorVal = Math.floor(props.hiddenValueArr[i] * 255); 94 | const color = `rgb(${colorVal}, ${colorVal}, ${colorVal})`; 95 | 96 | const { posX, posY, posZ } = hiddenPos({ 97 | index: i, 98 | rowSize, 99 | colSize, 100 | size, 101 | space, 102 | }); 103 | const key = `plane-${i}`; 104 | arr.push( 105 | 106 | 107 | 108 | , 109 | ); 110 | } 111 | return {arr}; 112 | } 113 | 114 | type OutputMeshProps = JSX.IntrinsicElements['group'] & { 115 | outputSize: number; 116 | outputValueArr: number[]; 117 | size: number; 118 | space: number; 119 | rowsize: number; 120 | colSize: number; 121 | }; 122 | 123 | export function outputPos(props: { 124 | index: number; 125 | rowSize: number; 126 | colSize: number; 127 | size: number; 128 | space: number; 129 | }) { 130 | const { index, rowSize, size, space, colSize } = props; 131 | const newIndex = index === 0 ? 9 : index - 1; 132 | const posX = ((newIndex % rowSize) - (rowSize - 1) / 2) * (size + space); 133 | const posY = 134 | (-Math.floor(newIndex / rowSize) + (colSize - 1) / 2) * (size + space); 135 | const posZ = 0; 136 | return { posX, posY, posZ }; 137 | } 138 | 139 | export function OutputMeshGroup(props: OutputMeshProps) { 140 | const outputMeshArr = []; 141 | const { size, space, rowsize } = props; 142 | for (let ii = 0; ii < props.outputSize; ii++) { 143 | const colorVal = Math.floor(props.outputValueArr[ii] * 255); 144 | const color = `rgb(${colorVal}, ${colorVal}, ${colorVal})`; 145 | const { posX, posY, posZ } = outputPos({ 146 | index: ii, 147 | rowSize: rowsize, 148 | colSize: props.colSize, 149 | size, 150 | space, 151 | }); 152 | const canvas = document.createElement('canvas'); 153 | canvas.width = 64; 154 | canvas.height = 64; 155 | const ctx = canvas.getContext('2d'); 156 | if (ctx) { 157 | ctx.fillStyle = color; 158 | ctx.fillRect(0, 0, 64, 64); 159 | ctx.fillStyle = 'black'; 160 | ctx.font = '40px san-serif'; 161 | ctx.textAlign = 'center'; 162 | ctx.textBaseline = 'middle'; 163 | ctx.fillText(`${ii}`, 32, 32); 164 | } 165 | const texture = new Texture(canvas); 166 | texture.needsUpdate = true; 167 | const key = `plane-${ii}`; 168 | outputMeshArr.push( 169 | 170 | 171 | 172 | , 173 | ); 174 | } 175 | 176 | return {outputMeshArr}; 177 | } 178 | -------------------------------------------------------------------------------- /src/components/visual/Canvas-drawing.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useMemo } from 'react'; 2 | 3 | export const CanvasDrawing: React.FC<{ 4 | setInputData: (data: number[]) => void; 5 | inputData: number[]; 6 | isButtonClicked: React.MutableRefObject; 7 | windowWidth: number | undefined; 8 | parentRef: React.RefObject; 9 | }> = ({ setInputData, inputData, isButtonClicked, windowWidth, parentRef }) => { 10 | const canvasRef = useRef(null); 11 | // 28 * 28の2D Contextを作成する 12 | const vContext = useMemo(() => { 13 | const vCanvas = document.createElement('canvas'); 14 | vCanvas.width = 28; 15 | vCanvas.height = 28; 16 | return vCanvas.getContext('2d', { willReadFrequently: true }); 17 | }, []); 18 | const pt = useRef({ x: 0, y: 0 }); 19 | 20 | useEffect(() => { 21 | const canvas = canvasRef.current; 22 | if (!canvas) return; 23 | const context = canvas.getContext('2d', { willReadFrequently: true }); 24 | if (!context) return; 25 | if (!vContext) return; 26 | const data = vContext.getImageData(0, 0, 28, 28); 27 | for (let i = 0; i < data.data.length; i++) { 28 | const val = Math.floor(255 * inputData[i]); 29 | data.data[4 * i] = val; //Math.floor(inputData[i] * 255); 30 | data.data[4 * i + 1] = val; //inputData[i] * 255; 31 | data.data[4 * i + 2] = val; //inputData[i] * 255; 32 | data.data[4 * i + 3] = 255; //inputData[i] * 255; 33 | } 34 | vContext.putImageData(data, 0, 0); 35 | context.drawImage(vContext.canvas, 0, 0, canvas.width, canvas.height); 36 | }, []); 37 | 38 | useEffect(() => { 39 | if (!isButtonClicked.current) return; 40 | 41 | const canvas = canvasRef.current; 42 | if (!canvas) return; 43 | const context = canvas.getContext('2d', { willReadFrequently: true }); 44 | if (!context) return; 45 | if (!vContext) return; 46 | const data = vContext.getImageData(0, 0, 28, 28); 47 | for (let i = 0; i < data.data.length; i++) { 48 | const val = Math.floor(255 * inputData[i]); 49 | data.data[4 * i] = val; //Math.floor(inputData[i] * 255); 50 | data.data[4 * i + 1] = val; //inputData[i] * 255; 51 | data.data[4 * i + 2] = val; //inputData[i] * 255; 52 | data.data[4 * i + 3] = 255; //inputData[i] * 255; 53 | } 54 | vContext.putImageData(data, 0, 0); 55 | context.drawImage(vContext.canvas, 0, 0, canvas.width, canvas.height); 56 | 57 | isButtonClicked.current = false; 58 | }, [isButtonClicked.current, inputData, canvasRef.current, vContext]); 59 | 60 | useEffect(() => { 61 | const canvas = canvasRef.current; 62 | if (!canvas) return; 63 | 64 | const context = canvas.getContext('2d'); 65 | if (!context) return; 66 | // canvasをblackで塗りつぶす 67 | 68 | let isDrawing = false; 69 | let lastX = 0; 70 | let lastY = 0; 71 | 72 | const startDrawing = (event: MouseEvent | TouchEvent) => { 73 | isDrawing = true; 74 | const { clientX, clientY } = 75 | event instanceof MouseEvent ? event : event.touches[0]; 76 | const rect = canvas.getBoundingClientRect(); 77 | lastX = clientX - rect.left; 78 | lastY = clientY - rect.top; 79 | 80 | pt.current = { x: lastX, y: lastY }; 81 | 82 | context.fillStyle = 'black'; 83 | context.fillRect(0, 0, canvas.width, canvas.height); 84 | 85 | if (event instanceof TouchEvent) { 86 | event.preventDefault(); 87 | } 88 | }; 89 | 90 | const draw = (event: MouseEvent | TouchEvent) => { 91 | if (!isDrawing) return; 92 | const { clientX, clientY } = 93 | event instanceof MouseEvent ? event : event.touches[0]; 94 | const rect = canvas.getBoundingClientRect(); 95 | const x = clientX - rect.left; 96 | const y = clientY - rect.top; 97 | pt.current = { x, y }; 98 | 99 | // 滑らかに線を描画する 100 | context.lineJoin = 'round'; 101 | context.lineCap = 'round'; 102 | 103 | context.beginPath(); 104 | context.moveTo(lastX, lastY); 105 | context.lineTo(x, y); 106 | context.strokeStyle = 'white'; 107 | context.lineWidth = event instanceof MouseEvent ? 40 : 20; 108 | context.stroke(); 109 | context.closePath(); 110 | 111 | lastX = x; 112 | lastY = y; 113 | 114 | // 28 * 28にリサイズする 115 | vContext!.drawImage(canvas, 0, 0, 28, 28); 116 | // 28 * 28のImageDataを取得する 117 | const imageData = vContext!.getImageData(0, 0, 28, 28); 118 | // 28 * 28のImageDataを白黒データを正規規化して、入力データにする 119 | // 配列のおおきさは 28 * 28 = 784このデータが入力データになる 120 | const normalizedData = Array.from(imageData.data) 121 | .filter((_, index) => index % 4 === 0) 122 | .map((v) => v / 255); 123 | 124 | setInputData(normalizedData); 125 | 126 | if (event instanceof TouchEvent) { 127 | event.preventDefault(); 128 | } 129 | }; 130 | 131 | const stopDrawing = () => { 132 | isDrawing = false; 133 | }; 134 | 135 | canvas.addEventListener('mousedown', startDrawing); 136 | canvas.addEventListener('touchstart', startDrawing); 137 | canvas.addEventListener('mousemove', draw); 138 | canvas.addEventListener('touchmove', draw); 139 | canvas.addEventListener('mouseup', stopDrawing); 140 | canvas.addEventListener('touchend', stopDrawing); 141 | // resize 142 | 143 | return () => { 144 | canvas.removeEventListener('mousedown', startDrawing); 145 | canvas.removeEventListener('touchstart', startDrawing); 146 | canvas.removeEventListener('mousemove', draw); 147 | canvas.removeEventListener('touchmove', draw); 148 | canvas.removeEventListener('mouseup', stopDrawing); 149 | canvas.removeEventListener('touchend', stopDrawing); 150 | }; 151 | }, []); 152 | 153 | const canvasWidth = useMemo(() => { 154 | // console.log(parentRef.current?.offsetWidth); 155 | if (!windowWidth) { 156 | return window.innerWidth < 640 ? 200 : 320; 157 | } 158 | if (windowWidth < 640) { 159 | return 200; 160 | } else { 161 | return 320; 162 | } 163 | }, [windowWidth, parentRef]); 164 | 165 | return ( 166 | <> 167 | 173 | 174 | ); 175 | }; 176 | -------------------------------------------------------------------------------- /src/components/neuralnet/functions.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | calcAffine, 3 | calcRelu2DArray, 4 | calcRelu4DArray, 5 | calcSoftMax, 6 | calculationCovolution, 7 | calculationPooling, 8 | reshapeArray, 9 | reshapeImageArray, 10 | // reshapeArray3to1, 11 | } from './functions'; // ここにテスト対象の関数をimportする 12 | 13 | // eslint-disable-next-line @typescript-eslint/no-var-requires 14 | const testSample = require('./testData/testData.json') as unknown as { 15 | testData: number[][][][]; 16 | }; 17 | 18 | // eslint-disable-next-line @typescript-eslint/no-var-requires 19 | const cnnParams = require('../../../public/cnn-params.json') as unknown as { 20 | W1: number[][][][]; 21 | b1: number[]; 22 | W2: number[][]; 23 | b2: number[]; 24 | W3: number[][]; 25 | b3: number[]; 26 | }; 27 | 28 | test('covolution', () => { 29 | const inputData = [[[1, 2, 3, 4, 5, 6, 7, 8, 9]]]; 30 | const inputRowSize = 3; 31 | const inputColSize = 3; 32 | const paramsW1 = [ 33 | [ 34 | [ 35 | [1, 2], 36 | [3, 4], 37 | ], 38 | ], 39 | [ 40 | [ 41 | [5, 6], 42 | [7, 8], 43 | ], 44 | ], 45 | ]; 46 | const paramsB1 = [1, 2]; 47 | const filterNum = 2; 48 | const filterRowSize = 2; 49 | const filterColSize = 2; 50 | const covolutionPadding = 0; 51 | const covolutionStride = 1; 52 | const result = calculationCovolution( 53 | inputData, 54 | inputRowSize, 55 | inputColSize, 56 | paramsW1, 57 | paramsB1, 58 | filterNum, 59 | filterRowSize, 60 | filterColSize, 61 | covolutionPadding, 62 | covolutionStride, 63 | ); 64 | const expected = [ 65 | [ 66 | [ 67 | [1 * 1 + 2 * 2 + 4 * 3 + 5 * 4 + 1, 2 * 1 + 3 * 2 + 5 * 3 + 6 * 4 + 1], 68 | [4 * 1 + 5 * 2 + 7 * 3 + 8 * 4 + 1, 5 * 1 + 6 * 2 + 8 * 3 + 9 * 4 + 1], 69 | ], 70 | [ 71 | [1 * 5 + 2 * 6 + 4 * 7 + 5 * 8 + 2, 2 * 5 + 3 * 6 + 5 * 7 + 6 * 8 + 2], 72 | [4 * 5 + 5 * 6 + 7 * 7 + 8 * 8 + 2, 5 * 5 + 6 * 6 + 8 * 7 + 9 * 8 + 2], 73 | ], 74 | ], 75 | ]; 76 | 77 | expect(result).toEqual(expected); 78 | }); 79 | 80 | it('calculationPoolingのテスト', () => { 81 | const inputData = [ 82 | [ 83 | [ 84 | [1, 2, 3, 4], 85 | [5, 6, 7, 8], 86 | [9, 10, 11, 12], 87 | [13, 14, 15, 16], 88 | ], 89 | [ 90 | [17, 18, 19, 20], 91 | [21, 22, 23, 24], 92 | [25, 26, 27, 28], 93 | [29, 30, 31, 32], 94 | ], 95 | ], 96 | ]; 97 | 98 | const inputRowSize = 4; 99 | const inputColSize = 4; 100 | const filterNum = 2; 101 | const poolingStride = 2; 102 | const poolingPadding = 0; 103 | const poolingRowSize = 2; 104 | const poolingColSize = 2; 105 | const result = calculationPooling( 106 | inputData, 107 | filterNum, 108 | inputRowSize, 109 | inputColSize, 110 | poolingRowSize, 111 | poolingColSize, 112 | poolingStride, 113 | poolingPadding, 114 | ); 115 | 116 | const expected = [ 117 | [ 118 | [ 119 | [6, 8], 120 | [14, 16], 121 | ], 122 | [ 123 | [22, 24], 124 | [30, 32], 125 | ], 126 | ], 127 | ]; 128 | 129 | expect(result).toEqual(expected); 130 | }); 131 | 132 | it('reshapeArrayのテスト', () => { 133 | const array = [ 134 | [ 135 | [ 136 | [1, 2, 3], 137 | [4, 5, 6], 138 | [7, 8, 9], 139 | ], 140 | [ 141 | [1, 2, 3], 142 | [4, 5, 6], 143 | [7, 8, 9], 144 | ], 145 | ], 146 | ]; 147 | 148 | const result = reshapeArray(array); 149 | 150 | const expected = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9]]; 151 | 152 | expect(result).toEqual(expected); 153 | }); 154 | 155 | it('covolutionのテスト', () => { 156 | const testData = reshapeImageArray(testSample.testData); 157 | 158 | // batch size 159 | expect(testData.length).toBe(1); 160 | // color channel size 161 | expect(testData[0].length).toBe(1); 162 | // image data size 163 | expect(testData[0][0].length).toBe(28 * 28); 164 | 165 | const W1 = cnnParams.W1; 166 | const b1 = cnnParams.b1; 167 | // const W2 = cnnParams.W2; 168 | 169 | const calcValue1 = calculationCovolution( 170 | testData, 171 | 28, 172 | 28, 173 | W1, 174 | b1, 175 | 30, 176 | 5, 177 | 5, 178 | 0, 179 | 1, 180 | ); 181 | expect(calcValue1.length).toBe(1); 182 | expect(calcValue1[0].length).toBe(30); 183 | expect(calcValue1[0][0].length).toBe(24); 184 | expect(calcValue1[0][0][0].length).toBe(24); 185 | expect(calcValue1[0][0][0][0]).toBeCloseTo(-0.2849); 186 | 187 | const calcValue2 = calcRelu4DArray(calcValue1); 188 | expect(calcValue2.length).toBe(1); 189 | expect(calcValue2[0].length).toBe(30); 190 | expect(calcValue2[0][0].length).toBe(24); 191 | expect(calcValue2[0][0][0].length).toBe(24); 192 | expect(calcValue2[0][0][0][0]).toEqual(0); 193 | expect(calcValue2[0][0][4][4]).toBeCloseTo(0.053); 194 | 195 | const calcValue3 = calculationPooling(calcValue2, 30, 24, 24, 2, 2, 2, 0); 196 | expect(calcValue3.length).toBe(1); 197 | expect(calcValue3[0].length).toBe(30); 198 | expect(calcValue3[0][0].length).toBe(12); 199 | expect(calcValue3[0][0][0].length).toBe(12); 200 | expect(calcValue3[0][0][0][0]).toEqual(0); 201 | expect(calcValue3[0][0][2][2]).toBeCloseTo(0.063); 202 | 203 | const calcValue4 = reshapeArray(calcValue3); 204 | expect(calcValue4.length).toBe(1); 205 | expect(calcValue4[0].length).toBe(30 * 12 * 12); 206 | 207 | const calcValue5 = calcAffine(calcValue4, cnnParams.W2, cnnParams.b2); 208 | expect(calcValue5.length).toBe(1); 209 | expect(calcValue5[0].length).toBe(100); 210 | expect(calcValue5[0][0]).toBeCloseTo(3.924); 211 | 212 | const calcValue6 = calcRelu2DArray(calcValue5); 213 | expect(calcValue6.length).toBe(1); 214 | expect(calcValue6[0].length).toBe(100); 215 | expect(calcValue6[0][0]).toBeCloseTo(3.924); 216 | expect(calcValue6[0][2]).toEqual(0); 217 | 218 | const calcValue7 = calcAffine(calcValue6, cnnParams.W3, cnnParams.b3); 219 | expect(calcValue7.length).toBe(1); 220 | expect(calcValue7[0].length).toBe(10); 221 | 222 | // calcValue7の最大値のインデックスを取得する 223 | let maxIndex = 0; 224 | let maxValue = calcValue7[0][0]; 225 | for (let i = 1; i < calcValue7[0].length; i++) { 226 | if (calcValue7[0][i] > maxValue) { 227 | maxIndex = i; 228 | maxValue = calcValue7[0][i]; 229 | } 230 | } 231 | 232 | expect(maxIndex).toBe(7); 233 | 234 | const calcValue8 = calcSoftMax(calcValue7); 235 | expect(calcValue8.length).toBe(1); 236 | expect(calcValue8[0].length).toBe(10); 237 | expect(calcValue8[0][7]).toBeCloseTo(1); 238 | }); 239 | -------------------------------------------------------------------------------- /src/components/neuralnet/functions.ts: -------------------------------------------------------------------------------- 1 | export const calculationCovolution = ( 2 | inputData: number[][][], 3 | inputRowSize: number, 4 | inputColSize: number, 5 | paramsW1: number[][][][], 6 | paramsB1: number[], 7 | filterNum: number, 8 | filterRowSize: number, 9 | filterColSize: number, 10 | covolutionPadding: number, 11 | covolutionStride: number, 12 | ) => { 13 | // ここに処理を書く 14 | const covolutionData = []; 15 | const batchSize = inputData.length; 16 | const channelSize = inputData[0].length; 17 | const outputRowSize = 18 | (inputRowSize + 2 * covolutionPadding - filterRowSize) / covolutionStride + 19 | 1; 20 | const outputColSize = 21 | (inputColSize + 2 * covolutionPadding - filterColSize) / covolutionStride + 22 | 1; 23 | 24 | // 畳み込み演算を行う 25 | for (let b = 0; b < batchSize; b++) { 26 | const batchData = []; 27 | for (let n = 0; n < filterNum; n++) { 28 | const filterData = []; 29 | for (let i = 0; i < outputRowSize; i++) { 30 | const rowData = []; 31 | for (let j = 0; j < outputColSize; j++) { 32 | let sum = 0; 33 | for (let m = 0; m < channelSize; m++) { 34 | for (let k = 0; k < filterRowSize; k++) { 35 | for (let l = 0; l < filterColSize; l++) { 36 | const row = i * covolutionStride + k - covolutionPadding; 37 | const col = j * covolutionStride + l - covolutionPadding; 38 | if ( 39 | row >= 0 && 40 | row < inputRowSize && 41 | col >= 0 && 42 | col < inputColSize 43 | ) { 44 | const index = row * inputColSize + col; 45 | sum += inputData[b][m][index] * paramsW1[n][m][k][l]; 46 | } 47 | } 48 | } 49 | } 50 | rowData.push(sum + paramsB1[n]); 51 | } 52 | filterData.push(rowData); 53 | } 54 | batchData.push(filterData); 55 | } 56 | covolutionData.push(batchData); 57 | } 58 | 59 | return covolutionData; 60 | }; 61 | 62 | // プーリング層の計算を行う 63 | export const calculationPooling = ( 64 | inputData: number[][][][], 65 | filterNum: number, 66 | inputRowSize: number, 67 | inputColSize: number, 68 | poolingRowSize: number, 69 | poolingColSize: number, 70 | poolingStride: number, 71 | poolingPadding: number, 72 | ) => { 73 | // ここにプーリング層の計算を書く 74 | const poolingData = []; 75 | const outputRowSize = 76 | (inputRowSize + 2 * poolingPadding - poolingRowSize) / poolingStride + 1; 77 | const outputColSize = 78 | (inputColSize + 2 * poolingPadding - poolingColSize) / poolingStride + 1; 79 | const batchSize = inputData.length; 80 | 81 | // プーリング演算を行う (Maxプーリング) 82 | 83 | for (let b = 0; b < batchSize; b++) { 84 | const batchData = []; 85 | for (let n = 0; n < filterNum; n++) { 86 | const filterData = []; 87 | for (let i = 0; i < outputRowSize; i++) { 88 | const rowData = []; 89 | for (let j = 0; j < outputColSize; j++) { 90 | let max = -Infinity; 91 | for (let k = 0; k < poolingRowSize; k++) { 92 | for (let l = 0; l < poolingColSize; l++) { 93 | const row = i * poolingStride + k - poolingPadding; 94 | const col = j * poolingStride + l - poolingPadding; 95 | if ( 96 | row >= 0 && 97 | row < inputRowSize && 98 | col >= 0 && 99 | col < inputColSize 100 | ) { 101 | max = Math.max(max, inputData[b][n][row][col]); 102 | } 103 | } 104 | } 105 | rowData.push(max); 106 | } 107 | filterData.push(rowData); 108 | } 109 | batchData.push(filterData); 110 | } 111 | poolingData.push(batchData); 112 | } 113 | 114 | return poolingData; 115 | }; 116 | 117 | export function reshapeArray(array: number[][][][]) { 118 | const reshapeArray = []; 119 | const batchSize = array.length; 120 | const channelSize = array[0].length; 121 | const rowSize = array[0][0].length; 122 | const colSize = array[0][0][0].length; 123 | 124 | for (let b = 0; b < batchSize; b++) { 125 | const reshapeRowData = []; 126 | for (let c = 0; c < channelSize; c++) { 127 | for (let r = 0; r < rowSize; r++) { 128 | for (let col = 0; col < colSize; col++) { 129 | reshapeRowData.push(array[b][c][r][col]); 130 | } 131 | } 132 | } 133 | reshapeArray.push(reshapeRowData); 134 | } 135 | 136 | return reshapeArray; 137 | } 138 | 139 | export function reshapeImageArray(array: number[][][][]) { 140 | const reshapeArray = []; 141 | const batchSize = array.length; 142 | const channelSize = array[0].length; 143 | const rowSize = array[0][0].length; 144 | const colSize = array[0][0][0].length; 145 | 146 | for (let b = 0; b < batchSize; b++) { 147 | const reshapeRowData = []; 148 | for (let c = 0; c < channelSize; c++) { 149 | const reshapeData = []; 150 | for (let r = 0; r < rowSize; r++) { 151 | for (let col = 0; col < colSize; col++) { 152 | reshapeData.push(array[b][c][r][col]); 153 | } 154 | } 155 | reshapeRowData.push(reshapeData); 156 | } 157 | reshapeArray.push(reshapeRowData); 158 | } 159 | 160 | return reshapeArray; 161 | } 162 | 163 | export const reshapeArray3to1 = (array: number[][][]) => { 164 | const reshapeArray = []; 165 | const batchSize = array.length; 166 | const rowSize = array[0].length; 167 | const colSize = array[0][0].length; 168 | 169 | for (let b = 0; b < batchSize; b++) { 170 | const reshapeRowData = []; 171 | for (let r = 0; r < rowSize; r++) { 172 | for (let col = 0; col < colSize; col++) { 173 | reshapeRowData.push(array[b][r][col]); 174 | } 175 | } 176 | reshapeArray.push(reshapeRowData); 177 | } 178 | 179 | return reshapeArray; 180 | }; 181 | 182 | // アフィン変換の計算を行う 183 | export const calcAffine = ( 184 | input: number[][], 185 | weight: number[][], 186 | bias: number[], 187 | ) => { 188 | // ここにアフィン変換の計算を書く 189 | const output = []; 190 | for (let i = 0; i < input.length; i++) { 191 | const row = []; 192 | for (let j = 0; j < weight[0].length; j++) { 193 | let sum = 0; 194 | for (let k = 0; k < input[0].length; k++) { 195 | sum += input[i][k] * weight[k][j]; 196 | } 197 | row.push(sum + bias[j]); 198 | } 199 | output.push(row); 200 | } 201 | 202 | return output; 203 | }; 204 | 205 | export const calcRelu2DArray = (input: number[][]) => { 206 | // ここにReLU関数の計算を書く 207 | const output = []; 208 | for (let i = 0; i < input.length; i++) { 209 | const row = []; 210 | for (let j = 0; j < input[0].length; j++) { 211 | row.push(Math.max(0, input[i][j])); 212 | } 213 | output.push(row); 214 | } 215 | return output; 216 | }; 217 | 218 | export const calcRelu4DArray = (input: number[][][][]) => { 219 | // ここにReLU関数の計算を書く 220 | const output = []; 221 | for (let b = 0; b < input.length; b++) { 222 | const batchData = []; 223 | for (let n = 0; n < input[0].length; n++) { 224 | const filterData = []; 225 | for (let i = 0; i < input[0][0].length; i++) { 226 | const rowData = []; 227 | for (let j = 0; j < input[0][0][0].length; j++) { 228 | rowData.push(Math.max(0, input[b][n][i][j])); 229 | } 230 | filterData.push(rowData); 231 | } 232 | batchData.push(filterData); 233 | } 234 | output.push(batchData); 235 | } 236 | return output; 237 | }; 238 | 239 | // softmax関数の計算を行う 240 | export const calcSoftMax = (input: number[][]) => { 241 | const output = []; 242 | for (let i = 0; i < input.length; i++) { 243 | let sum = 0; 244 | const row = []; 245 | // rowの最大値を求める 246 | const maxVal = Math.max(...input[i]); 247 | for (let j = 0; j < input[0].length; j++) { 248 | sum += Math.exp(input[i][j] - maxVal); 249 | } 250 | for (let j = 0; j < input[0].length; j++) { 251 | row.push(Math.exp(input[i][j] - maxVal) / sum); 252 | } 253 | output.push(row); 254 | } 255 | return output; 256 | }; 257 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | // 数字をスライドすることができる 2 | import { useEffect, useMemo, useState, useRef, useCallback } from 'react'; 3 | import './App.css'; 4 | import { Canvas } from '@react-three/fiber'; 5 | import { OrbitControls } from '@react-three/drei'; 6 | import { Vector3 } from 'three'; 7 | import { ministSampleData } from './components/visual/mnist-sample'; 8 | import { useWindowSize } from './components/visual/use-window-size'; 9 | import { InputHiddenPlane } from './components/visual/input-hidden-plane'; 10 | import { 11 | useCovolutionMemo, 12 | usePoolingMemo, 13 | } from './components/neuralnet/covolution'; 14 | import { 15 | calcAffine, 16 | calcRelu2DArray, 17 | reshapeArray, 18 | } from './components/neuralnet/functions'; 19 | import Sidebar from './components/ui/sidebar'; 20 | import { ResultMesh } from './components/visual/result-mesh'; 21 | import { DrawLineGroup } from './components/visual/drawLineGroup'; 22 | 23 | function App() { 24 | const autoNumCntRef = useRef( 25 | Math.floor(Math.random() * ministSampleData.length), 26 | ); 27 | const isButtonClicked = useRef(false); 28 | 29 | const [inputData, setInputData] = useState( 30 | ministSampleData[autoNumCntRef.current], 31 | ); 32 | const windowSize = useWindowSize(); 33 | 34 | const inputSize = 28 * 28; 35 | const inputRowSize = 28; 36 | const inputColSize = 28; 37 | 38 | // covolutionのパラメータ 39 | const filterNum = 30; 40 | const filterRowSize = 5; 41 | const filterColSize = 5; 42 | const covolutionStride = 1; 43 | const covolutionPadding = 0; 44 | 45 | const inputPlane = { size: 1 / 10, space: 0.02 }; // const size = 1 / 14 const space = 0.02; 46 | const covolutionPlane = { 47 | size: 1.2, 48 | space: 0.1, 49 | divide: 24, 50 | num: 30, 51 | row: 5, 52 | col: 6, 53 | }; 54 | const poolingPlane = { 55 | size: 1.8, 56 | space: 0.2, 57 | divide: 12, 58 | num: 30, 59 | row: 6, 60 | col: 5, 61 | }; 62 | const hiddenPlane = { 63 | size: 0.4, 64 | space: 0.15, 65 | num: 100, 66 | row: 10, 67 | col: 10, 68 | }; 69 | const outputPlane = { 70 | size: 0.3, 71 | space: 0.1, 72 | num: 10, 73 | row: 2, 74 | col: 5, 75 | }; 76 | 77 | const inputPos = new Vector3(0, 0, 0); 78 | const covolutionPos = new Vector3(0, 0, -0.5); 79 | const poolingPos = new Vector3(0, 0, -0.5); 80 | const hiddenPos = new Vector3(0, 0, -8); 81 | const outputPos = new Vector3(0, 0, -16); 82 | 83 | const [params, setParams] = useState<{ 84 | W1: number[][][][]; 85 | b1: number[]; 86 | W2: number[][]; 87 | b2: number[]; 88 | W3: number[][]; 89 | b3: number[]; 90 | } | null>(null); 91 | 92 | // useCovolution + Relu 93 | const covolutionData = useCovolutionMemo( 94 | [[inputData]], 95 | params?.W1, 96 | params?.b1, 97 | inputRowSize, 98 | inputColSize, 99 | filterNum, 100 | filterRowSize, 101 | filterColSize, 102 | covolutionPadding, 103 | covolutionStride, 104 | ); 105 | const poolingData = usePoolingMemo(covolutionData, 2, 2, 2, 0); 106 | const hiddenData = useMemo(() => { 107 | if (!poolingData || !params?.W2 || !params?.b2) return undefined; 108 | const res = reshapeArray(poolingData); 109 | return calcAffine(res, params?.W2, params?.b2); 110 | }, [poolingData, params?.W2, params?.b2]); 111 | const outputData = useMemo(() => { 112 | if (!hiddenData || !params?.W3 || !params?.b3) return undefined; 113 | const res = calcRelu2DArray(hiddenData); 114 | return calcAffine(res, params?.W3, params?.b3); 115 | }, [hiddenData, params?.W3, params?.b3]); 116 | 117 | // result用に使用するデータ 118 | // resultMeshに渡すために、softmax関数をかけた結果の配列を作成する。 119 | const outputSoftmaxArr = useMemo(() => { 120 | if (outputData === undefined) { 121 | return undefined; 122 | } 123 | const result = outputData[0]; 124 | const max = Math.max(...result); 125 | const exp = result.map((x) => Math.exp(x - max)); 126 | const sumExp = exp.reduce((a, b) => a + b); 127 | return exp.map((x) => x / sumExp); 128 | }, [outputData]); 129 | 130 | // poolingDataをsigmoidに変換 131 | // useMemoでキャッシュ 132 | const poolingDataSigmoid = useMemo(() => { 133 | if (!poolingData) return undefined; 134 | 135 | const sigmoid = (x: number) => 1 / (1 + Math.exp(-x)); 136 | 137 | const result = poolingData.map((data) => 138 | data.map((data) => 139 | data.map((data) => data.map((data) => sigmoid(data) * 2 - 1)), 140 | ), 141 | ); 142 | 143 | return result; 144 | }, [poolingData]); 145 | 146 | // hiddenDataをsigmoidに変換 147 | // useMemoでキャッシュ 148 | const hiddenDataSigmoid = useMemo(() => { 149 | if (!hiddenData) return undefined; 150 | 151 | const sigmoid = (x: number) => 1 / (1 + Math.exp(-x)); 152 | 153 | const result = hiddenData.map((data) => data.map((data) => sigmoid(data))); 154 | 155 | return result; 156 | }, [hiddenData]); 157 | 158 | const isLoading = useMemo(() => { 159 | return params === null; 160 | }, [params]); 161 | const displayLoading = useMemo(() => { 162 | return isLoading ? 'block' : 'hidden'; 163 | }, [isLoading]); 164 | const displayCanvas = useMemo(() => { 165 | return isLoading ? 'hidden' : 'block'; 166 | }, [isLoading]); 167 | const displaySideBar = useMemo(() => { 168 | return isLoading ? 'hidden' : 'flex'; 169 | }, [isLoading]); 170 | 171 | useEffect(() => { 172 | fetch('/cnn-params.json') 173 | .then((res) => res.json()) 174 | .then((data) => { 175 | setParams(data); 176 | }); 177 | }, []); 178 | 179 | const clickButton = useCallback(() => { 180 | autoNumCntRef.current = Math.floor(Math.random() * ministSampleData.length); 181 | isButtonClicked.current = true; 182 | setInputData(ministSampleData[autoNumCntRef.current]); 183 | }, []); 184 | 185 | return ( 186 | <> 187 |
191 |
192 | 193 | 194 | s 195 | 196 | 213 | 214 | 231 | 232 | 233 | 234 |
235 | {/* 画面の中央にLOADINGを表示する */} 236 |
239 |
240 |
LOADING
241 |
242 |
243 |
244 | 245 | 253 | 254 | ); 255 | } 256 | 257 | export default App; 258 | -------------------------------------------------------------------------------- /src/components/visual/drawLineGroup.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo, useRef } from 'react'; 2 | import { BufferGeometry, MathUtils, Vector3 } from 'three'; 3 | 4 | type DrawLineGroupProps = { 5 | inputData: number[]; 6 | inputPlane: { 7 | size: number; 8 | space: number; 9 | }; 10 | inputPos: Vector3; 11 | inputSize: number; 12 | inputRowSize: number; 13 | inputColSize: number; 14 | poolingData: number[][][][] | undefined; 15 | poolingPlane: { 16 | size: number; 17 | space: number; 18 | divide: number; 19 | num: number; 20 | row: number; 21 | col: number; 22 | }; 23 | poolingPos: Vector3; 24 | hiddenData: number[][] | undefined; 25 | hiddenPlane: { 26 | size: number; 27 | space: number; 28 | num: number; 29 | row: number; 30 | col: number; 31 | }; 32 | hiddenPos: Vector3; 33 | resultData: number[] | undefined; 34 | resultPlane: { 35 | size: number; 36 | space: number; 37 | num: number; 38 | row: number; 39 | col: number; 40 | }; 41 | resultPos: Vector3; 42 | }; 43 | 44 | export function DrawLineGroup(props: DrawLineGroupProps) { 45 | const { 46 | inputData, 47 | inputPlane, 48 | inputPos, 49 | inputRowSize, 50 | inputColSize, 51 | poolingData, 52 | poolingPlane, 53 | poolingPos, 54 | hiddenData, 55 | hiddenPlane, 56 | hiddenPos, 57 | resultData, 58 | resultPlane, 59 | resultPos, 60 | } = props; 61 | const geometry = useRef(null!); 62 | const poolingNumPerUnit = 60; 63 | const inputNum = 5000; 64 | 65 | // input -> poolingの線を引く。 66 | // inputNum個のobjectを作成し、arrayに入れる。 67 | // objectは inputRow 0 - inputRowSize - 1, inputCol 0 - inputColSize の乱数と 68 | // divide: { 69 | // row: MathUtils.randInt(0, poolingPlane.divide - 1), 70 | // col: MathUtils.randInt(0, poolingPlane.divide - 1), 71 | // }, 72 | // main: { 73 | // row: MathUtils.randInt(0, poolingPlane.row - 1), 74 | // col: MathUtils.randInt(0, poolingPlane.col - 1), 75 | // }, 76 | // の乱数の2つを持つ。 77 | // useMemoを使って、キャッシュする。 78 | const inputPairArr = useMemo(() => { 79 | const arr = []; 80 | for (let i = 0; i < inputNum; i++) { 81 | arr.push({ 82 | input: { 83 | row: MathUtils.randInt(0, inputRowSize - 1), 84 | col: MathUtils.randInt(0, inputColSize - 1), 85 | }, 86 | pooling: { 87 | divide: { 88 | row: MathUtils.randInt(0, poolingPlane.divide - 1), 89 | col: MathUtils.randInt(0, poolingPlane.divide - 1), 90 | }, 91 | main: { 92 | row: MathUtils.randInt(0, poolingPlane.row - 1), 93 | col: MathUtils.randInt(0, poolingPlane.col - 1), 94 | }, 95 | }, 96 | }); 97 | } 98 | return arr; 99 | }, []); 100 | 101 | // 0 - hiddenPlane.num - 1 に対して それおぞれ poolingNumPerUnit 個のobjectを作成する。 102 | // objectは 0 - poolingPlane.num - 1 の乱数、 0 - poolingPlane.divide - 1 の乱数、 0 - 1 の乱数を2つ持つ。 103 | // useMemoを使って、キャッシュする。 104 | const poolingInputPairArr = useMemo(() => { 105 | const arr = []; 106 | // for (let i = 0; i < poolingHiddenNumber; i++) { 107 | // arr.push(MathUtils.(0, 1)); 108 | // } 109 | for (let i = 0; i < hiddenPlane.num; i++) { 110 | // arr.push(MathUtils.randFloat(0, 1)); 111 | const rowArr = []; 112 | for (let j = 0; j < poolingNumPerUnit; j++) { 113 | // rowArr.push(MathUtils.randFloat(0, 1)); 114 | rowArr.push({ 115 | divide: { 116 | row: MathUtils.randInt(0, poolingPlane.divide - 1), 117 | col: MathUtils.randInt(0, poolingPlane.divide - 1), 118 | }, 119 | main: { 120 | row: MathUtils.randInt(0, poolingPlane.row - 1), 121 | col: MathUtils.randInt(0, poolingPlane.col - 1), 122 | }, 123 | }); 124 | } 125 | arr.push(rowArr); 126 | } 127 | 128 | return arr; 129 | }, []); 130 | 131 | const pos = useMemo(() => { 132 | const arr = []; 133 | 134 | // input -> poolingの線を引く。 135 | for (let i = 0; i < inputNum; i++) { 136 | const inputObj = inputPairArr[i]; 137 | const inputPosX = 138 | inputPos.x + 139 | (inputObj.input.col - (inputColSize - 1) / 2) * 140 | (inputPlane.size + inputPlane.space); 141 | const inputPosY = 142 | inputPos.y - 143 | (inputObj.input.row - (inputRowSize - 1) / 2) * 144 | (inputPlane.size + inputPlane.space); 145 | const inputPosZ = inputPos.z; 146 | const poolingObj = inputObj.pooling; 147 | const poolGridSize = poolingPlane.size / poolingPlane.divide; 148 | const poolingPosX = 149 | poolingPos.x + 150 | (poolingObj.main.col - (poolingPlane.col - 1) / 2) * 151 | (poolingPlane.size + poolingPlane.space) + 152 | (poolingObj.divide.col - (poolingPlane.divide - 1) / 2) * poolGridSize + 153 | poolGridSize * MathUtils.randFloat(-0.5, 0.5); 154 | 155 | const poolingPosY = 156 | poolingPos.y - 157 | (poolingObj.main.row - (poolingPlane.row - 1) / 2) * 158 | (poolingPlane.size + poolingPlane.space) - 159 | (poolingObj.divide.row - (poolingPlane.divide - 1) / 2) * poolGridSize + 160 | poolGridSize * MathUtils.randFloat(-0.5, 0.5); 161 | 162 | const poolingPosZ = poolingPos.z; 163 | arr.push( 164 | inputPosX + inputPlane.size * MathUtils.randFloat(-0.5, 0.5), 165 | inputPosY + inputPlane.size * MathUtils.randFloat(-0.5, 0.5), 166 | inputPosZ, 167 | poolingPosX, 168 | poolingPosY, 169 | poolingPosZ, 170 | ); 171 | } 172 | 173 | // pooling -> hiddenの線を引く。 174 | for (let hiddenRow = 0; hiddenRow < hiddenPlane.row; hiddenRow++) { 175 | for (let hiddenCol = 0; hiddenCol < hiddenPlane.col; hiddenCol++) { 176 | const hiddenIndex = hiddenRow * hiddenPlane.row + hiddenCol; 177 | const hiddenPosX = 178 | hiddenPos.x + 179 | (hiddenCol - (hiddenPlane.col - 1) / 2) * 180 | (hiddenPlane.size + hiddenPlane.space); 181 | const hiddenPosY = 182 | hiddenPos.y + 183 | (hiddenRow - (hiddenPlane.row - 1) / 2) * 184 | (hiddenPlane.size + hiddenPlane.space); 185 | const hiddenPosZ = hiddenPos.z; 186 | for ( 187 | let poolingIndex = 0; 188 | poolingIndex < poolingNumPerUnit; 189 | poolingIndex++ 190 | ) { 191 | const poolingObj = poolingInputPairArr[hiddenIndex][poolingIndex]; 192 | const poolGridSize = poolingPlane.size / poolingPlane.divide; 193 | const poolingPosX = 194 | poolingPos.x + 195 | (poolingObj.main.col - (poolingPlane.col - 1) / 2) * 196 | (poolingPlane.size + poolingPlane.space) + 197 | (poolingObj.divide.col - (poolingPlane.divide - 1) / 2) * 198 | poolGridSize + 199 | poolGridSize * MathUtils.randFloat(-0.5, 0.5); 200 | 201 | const poolingPosY = 202 | poolingPos.y - 203 | (poolingObj.main.row - (poolingPlane.row - 1) / 2) * 204 | (poolingPlane.size + poolingPlane.space) - 205 | (poolingObj.divide.row - (poolingPlane.divide - 1) / 2) * 206 | poolGridSize + 207 | poolGridSize * MathUtils.randFloat(-0.5, 0.5); 208 | 209 | const poolingPosZ = poolingPos.z; 210 | arr.push( 211 | hiddenPosX + hiddenPlane.size * MathUtils.randFloat(-0.5, 0.5), 212 | hiddenPosY + hiddenPlane.size * MathUtils.randFloat(-0.5, 0.5), 213 | hiddenPosZ, 214 | poolingPosX, 215 | poolingPosY, 216 | poolingPosZ, 217 | ); 218 | } 219 | } 220 | } 221 | 222 | // hidden -> outputの線を引く。 223 | for (let outputIndex = 0; outputIndex < resultPlane.num; outputIndex++) { 224 | const theta = (outputIndex * Math.PI) / 5 - Math.PI / 2; 225 | const rad = 8; 226 | const x = Math.cos(theta) * rad; 227 | const y = -Math.sin(theta) * rad; 228 | const outputPosX = resultPos.x + x; 229 | const outputPosY = resultPos.y + y; 230 | const outputPosZ = resultPos.z; 231 | for (let hiddenIndex = 0; hiddenIndex < hiddenPlane.num; hiddenIndex++) { 232 | const hiddenRow = Math.floor(hiddenIndex / hiddenPlane.row); 233 | const hiddenCol = hiddenIndex % hiddenPlane.row; 234 | const hiddenPosX = 235 | hiddenPos.x + 236 | (hiddenCol - (hiddenPlane.col - 1) / 2) * 237 | (hiddenPlane.size + hiddenPlane.space) + 238 | hiddenPlane.size * MathUtils.randFloat(-0.5, 0.5); 239 | const hiddenPosY = 240 | hiddenPos.y - 241 | (hiddenRow - (hiddenPlane.row - 1) / 2) * 242 | (hiddenPlane.size + hiddenPlane.space) + 243 | hiddenPlane.size * MathUtils.randFloat(-0.5, 0.5); 244 | const hiddenPosZ = hiddenPos.z + 0.1; 245 | arr.push( 246 | hiddenPosX, 247 | hiddenPosY, 248 | hiddenPosZ, 249 | outputPosX + 0.1 * MathUtils.randFloat(-1, 1), 250 | outputPosY + 0.1 * MathUtils.randFloat(-1, 1), 251 | outputPosZ, 252 | ); 253 | } 254 | } 255 | 256 | if (geometry.current) { 257 | geometry.current.attributes.position.needsUpdate = true; 258 | } 259 | return new Float32Array(arr); 260 | }, []); 261 | 262 | const color = useMemo(() => { 263 | const colors = []; 264 | // input -> poolingのvalueを色として配列に入れる。 265 | for (let i = 0; i < inputNum; i++) { 266 | const inputObj = inputPairArr[i]; 267 | const inputIndex = inputObj.input.row * inputRowSize + inputObj.input.col; 268 | const inputValue = inputData ? inputData[inputIndex] : 0; 269 | const inputColorVal = inputValue; 270 | const inputAlphaVal = Math.max(inputValue * 0.5, 0.01); 271 | const poolingObj = inputObj.pooling; 272 | const poolingIndex = 273 | inputObj.pooling.main.row * poolingPlane.col + 274 | inputObj.pooling.main.col; 275 | 276 | const poolingColorVal = poolingData 277 | ? poolingData[0][poolingIndex][poolingObj.divide.row][ 278 | poolingObj.divide.col 279 | ] 280 | : 0; 281 | const poolingAlphaVal = Math.max(poolingColorVal * 0.4, 0.01); 282 | colors.push( 283 | inputColorVal, 284 | inputColorVal, 285 | inputColorVal, 286 | inputAlphaVal, 287 | poolingColorVal, 288 | poolingColorVal, 289 | poolingColorVal, 290 | poolingAlphaVal, 291 | ); 292 | } 293 | 294 | // pooling -> hiddenのvalueを色として配列に入れる。 295 | for (let hiddenRow = 0; hiddenRow < hiddenPlane.row; hiddenRow++) { 296 | for (let hiddenCol = 0; hiddenCol < hiddenPlane.col; hiddenCol++) { 297 | const hiddenIndex = hiddenRow * hiddenPlane.row + hiddenCol; 298 | const hiddenValue = hiddenData ? hiddenData[0][hiddenIndex] : 0; 299 | const hiddenColorVal = hiddenValue; 300 | const hiddenAlphaVal = hiddenValue * 0.1; 301 | for ( 302 | let poolingIndex = 0; 303 | poolingIndex < poolingNumPerUnit; 304 | poolingIndex++ 305 | ) { 306 | const poolObj = poolingInputPairArr[hiddenIndex][poolingIndex]; 307 | // const poolObj2 = poolObj[poolingIndex]; 308 | const index = poolObj.main.row * poolingPlane.col + poolObj.main.col; 309 | const poolingColorVal = poolingData 310 | ? poolingData[0][index][poolObj.divide.row][poolObj.divide.col] 311 | : 0; 312 | const poolingAlphaVal = poolingColorVal * 0.1; 313 | colors.push( 314 | hiddenColorVal, 315 | hiddenColorVal, 316 | hiddenColorVal, 317 | hiddenAlphaVal, 318 | poolingColorVal, 319 | poolingColorVal, 320 | poolingColorVal, 321 | poolingAlphaVal, 322 | ); 323 | } 324 | } 325 | } 326 | 327 | // hidden -> outputのvalueを色として配列に入れる。 328 | for (let outputIndex = 0; outputIndex < resultPlane.num; outputIndex++) { 329 | const outputValue = resultData ? resultData[outputIndex] : 0; 330 | const outputColorVal = outputValue; 331 | const outputAlphaVal = outputValue * 0.1; 332 | for (let hiddenIndex = 0; hiddenIndex < hiddenPlane.num; hiddenIndex++) { 333 | const hiddenColorVal = hiddenData ? hiddenData[0][hiddenIndex] : 0; 334 | const hiddenAlphaVal = hiddenColorVal * 0.1; 335 | colors.push( 336 | hiddenColorVal, 337 | hiddenColorVal, 338 | hiddenColorVal, 339 | hiddenAlphaVal, 340 | outputColorVal, 341 | outputColorVal, 342 | outputColorVal, 343 | outputAlphaVal, 344 | ); 345 | } 346 | } 347 | 348 | if (geometry.current) { 349 | geometry.current.attributes.color.needsUpdate = true; 350 | } 351 | return new Float32Array(colors); 352 | }, [inputData, poolingData, hiddenData, resultData]); 353 | 354 | return ( 355 | 356 | 357 | 363 | 369 | 370 | 378 | 379 | ); 380 | } 381 | -------------------------------------------------------------------------------- /src/components/visual/input-hidden-plane.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useMemo, useRef } from 'react'; 2 | import { BufferAttribute, DoubleSide, Vector3 } from 'three'; 3 | 4 | type InputPlanePlaneProps = JSX.IntrinsicElements['group'] & { 5 | inputData: number[]; 6 | inputSize: number; 7 | inputRowSize: number; 8 | inputColSize: number; 9 | inputPlane: { size: number; space: number }; 10 | inputPos: Vector3; 11 | covolutionData: number[][][][] | undefined; 12 | covolutionPlane: { 13 | size: number; 14 | space: number; 15 | divide: number; 16 | num: number; 17 | row: number; 18 | col: number; 19 | }; 20 | covolutionPos: Vector3; 21 | poolingData: number[][][][] | undefined; 22 | poolingPlane: { 23 | size: number; 24 | space: number; 25 | divide: number; 26 | num: number; 27 | row: number; 28 | col: number; 29 | }; 30 | poolingPos: Vector3; 31 | hiddenData: number[][] | undefined; 32 | hiddenPlane: { 33 | size: number; 34 | space: number; 35 | num: number; 36 | row: number; 37 | col: number; 38 | }; 39 | hiddenPos: Vector3; 40 | }; 41 | 42 | export function InputHiddenPlane(props: InputPlanePlaneProps) { 43 | const inputColorBufferAttribute = useRef(null); 44 | const poolingColorBufferAttribute = useRef(null); 45 | const hiddenColorBufferAttribute = useRef(null); 46 | 47 | const getInputGeometryArrayResult = useMemo( 48 | () => 49 | getInputGeometryArray( 50 | props.inputData, 51 | props.inputSize, 52 | props.inputRowSize, 53 | props.inputColSize, 54 | props.inputPlane, 55 | props.inputPos, 56 | ), 57 | [], 58 | ); 59 | 60 | useEffect(() => { 61 | if (inputColorBufferAttribute.current) { 62 | // update color 63 | const colorArray = inputColorBufferAttribute.current 64 | .array as Float32Array; 65 | for (let i = 0; i < props.inputData.length; i++) { 66 | const colorArrayIndex = i * 4 * 3; 67 | for (let j = 0; j < 4 * 3; j++) { 68 | colorArray[colorArrayIndex + j] = props.inputData[i]; 69 | } 70 | } 71 | 72 | inputColorBufferAttribute.current.needsUpdate = true; 73 | } 74 | }, [props.inputData]); 75 | 76 | // poolingDataSigmoidを使ってgetPoolingGeometryArrayを呼び出す 77 | const getPoolingGeometryArrayResult = useMemo( 78 | () => 79 | getPoolingGeometryArray( 80 | props.poolingData, 81 | props.poolingPlane, 82 | props.poolingPos, 83 | ), 84 | [], 85 | ); 86 | 87 | // useEffectでpoolingColorBufferAttributeを更新 88 | useEffect(() => { 89 | if (poolingColorBufferAttribute.current && props.poolingData) { 90 | // update color 91 | const colorArray = poolingColorBufferAttribute.current 92 | .array as Float32Array; 93 | 94 | const channel = props.poolingData[0].length; 95 | const row = props.poolingData[0][0].length; 96 | const col = props.poolingData[0][0][0].length; 97 | for (let c = 0; c < channel; c++) { 98 | for (let r = 0; r < row; r++) { 99 | for (let l = 0; l < col; l++) { 100 | const colorArrayIndex = 101 | c * row * col * 4 * 3 + r * col * 4 * 3 + l * 4 * 3; 102 | for (let i = 0; i < 4 * 3; i++) { 103 | colorArray[colorArrayIndex + i] = props.poolingData[0][c][r][l]; 104 | } 105 | } 106 | } 107 | } 108 | 109 | poolingColorBufferAttribute.current.needsUpdate = true; 110 | } 111 | }, [props.poolingData]); 112 | 113 | // hiddenDataSigmoidを使ってgetHiddenGeometryArrayを呼び出す 114 | const getHiddenGeometryArrayResult = useMemo( 115 | () => 116 | getInputGeometryArray( 117 | props.hiddenData ? props.hiddenData[0] : undefined, 118 | props.hiddenPlane.num, 119 | props.hiddenPlane.row, 120 | props.hiddenPlane.col, 121 | props.hiddenPlane, 122 | props.hiddenPos, 123 | ), 124 | [], 125 | ); 126 | 127 | useEffect(() => { 128 | if ( 129 | hiddenColorBufferAttribute.current && 130 | props.hiddenData && 131 | props.hiddenData[0] 132 | ) { 133 | // update color 134 | const colorArray = hiddenColorBufferAttribute.current 135 | .array as Float32Array; 136 | for (let i = 0; i < props.hiddenData[0].length; i++) { 137 | const colorArrayIndex = i * 4 * 3; 138 | for (let j = 0; j < 4 * 3; j++) { 139 | colorArray[colorArrayIndex + j] = props.hiddenData[0][i] 140 | ? props.hiddenData[0][i] 141 | : 0; 142 | } 143 | } 144 | 145 | hiddenColorBufferAttribute.current.needsUpdate = true; 146 | } 147 | }, [props.hiddenData]); 148 | 149 | // line segments用のdataを作成 150 | // 151 | 152 | const mat1 = useMemo( 153 | () => ( 154 | 175 | ), 176 | [], 177 | ); 178 | 179 | const mat2 = useMemo( 180 | () => ( 181 | 202 | ), 203 | [], 204 | ); 205 | 206 | return ( 207 | 208 | 209 | 210 | 216 | 223 | 229 | 230 | {mat1} 231 | 232 | 233 | 234 | 240 | 247 | 253 | 254 | {mat1} 255 | 256 | 257 | 258 | 264 | 271 | 277 | 278 | {mat2} 279 | 280 | 281 | ); 282 | } 283 | 284 | function getInputGeometryArray( 285 | inputData: number[] | undefined, 286 | inputSize: number, 287 | inputRowSize: number, 288 | inputColSize: number, 289 | inputPlane: { size: number; space: number }, 290 | inputPos: Vector3, 291 | ): { 292 | position: Float32Array; 293 | color: Float32Array; 294 | index: Uint16Array; 295 | } { 296 | const inputGapSize = inputPlane.size + inputPlane.space; 297 | const inputHalfSideSize = inputPlane.size / 2; 298 | 299 | const position = new Float32Array(inputSize * 4 * 3); 300 | const color = new Float32Array(inputSize * 4 * 3); 301 | const index = new Uint16Array(inputSize * 6); 302 | 303 | let positionIndex = 0; 304 | let colorIndex = 0; 305 | let indexIndex = 0; 306 | 307 | const startX = inputPos.x - (inputGapSize * (inputRowSize - 1)) / 2; 308 | const startY = inputPos.y + (inputGapSize * (inputColSize - 1)) / 2; 309 | const startZ = inputPos.z; 310 | 311 | for (let col = 0; col < inputColSize; col++) { 312 | for (let row = 0; row < inputRowSize; row++) { 313 | const x = startX + row * inputGapSize; 314 | const y = startY - col * inputGapSize; 315 | const z = startZ; 316 | 317 | const colorValue = 318 | inputData && inputData[inputRowSize + col * inputColSize + row] 319 | ? inputData[inputRowSize + col * inputColSize + row] 320 | : 0; 321 | 322 | position[positionIndex] = x - inputHalfSideSize; 323 | position[positionIndex + 1] = y - inputHalfSideSize; 324 | position[positionIndex + 2] = z; 325 | position[positionIndex + 3] = x - inputHalfSideSize; 326 | position[positionIndex + 4] = y + inputHalfSideSize; 327 | position[positionIndex + 5] = z; 328 | position[positionIndex + 6] = x + inputHalfSideSize; 329 | position[positionIndex + 7] = y + inputHalfSideSize; 330 | position[positionIndex + 8] = z; 331 | position[positionIndex + 9] = x + inputHalfSideSize; 332 | position[positionIndex + 10] = y - inputHalfSideSize; 333 | position[positionIndex + 11] = z; 334 | 335 | for (let i = 0; i < 4; i++) { 336 | color[colorIndex + 3 * i] = colorValue; 337 | color[colorIndex + 3 * i + 1] = colorValue; 338 | color[colorIndex + 3 * i + 2] = colorValue; 339 | } 340 | 341 | const indexValue = 4 * (row + col * inputRowSize); 342 | index[indexIndex] = indexValue; 343 | index[indexIndex + 1] = indexValue + 1; 344 | index[indexIndex + 2] = indexValue + 2; 345 | index[indexIndex + 3] = indexValue + 2; 346 | index[indexIndex + 4] = indexValue + 3; 347 | index[indexIndex + 5] = indexValue; 348 | 349 | positionIndex += 12; 350 | colorIndex += 12; 351 | indexIndex += 6; 352 | } 353 | } 354 | 355 | return { position, color, index }; 356 | } 357 | 358 | function getPoolingGeometryArray( 359 | poolingDataSigmoid: number[][][][] | undefined, 360 | poolingPlane: { 361 | size: number; 362 | space: number; 363 | divide: number; 364 | num: number; 365 | row: number; 366 | col: number; 367 | }, 368 | poolingPos: Vector3, 369 | ) { 370 | // Your code here 371 | // poolingDataSigmoidがundefinedの場合はreturn undefined 372 | // if (!poolingDataSigmoid) return undefined; 373 | 374 | // position, color, indexの配列を作成 375 | const poolingSize = poolingPlane.size; 376 | const divide = poolingPlane.divide; 377 | const gridSize = poolingSize / poolingPlane.divide; 378 | const space = poolingPlane.space; 379 | const planeNum = poolingPlane.num; 380 | const row = poolingPlane.row; 381 | const col = poolingPlane.col; 382 | 383 | // position, color, indexの配列を作成 384 | const position = new Float32Array(planeNum * divide * divide * 4 * 3); 385 | const color = new Float32Array(planeNum * divide * divide * 4 * 3); 386 | const index = new Uint16Array(planeNum * divide * divide * 6); 387 | 388 | let positionIndex = 0; 389 | let colorIndex = 0; 390 | let indexIndex = 0; 391 | // for文を使ってposition, color, indexの配列を作成 392 | // for row -> for col -> for gridY -> for gridX 393 | for (let r = 0; r < row; r++) { 394 | for (let c = 0; c < col; c++) { 395 | for (let gridY = 0; gridY < divide; gridY++) { 396 | for (let gridX = 0; gridX < divide; gridX++) { 397 | // Your code here 398 | const x = 399 | poolingPos.x + 400 | (poolingSize + space) * c + 401 | gridSize * gridX - 402 | ((poolingSize + space) * (col - 1)) / 2 - 403 | (gridSize * (divide - 1)) / 2; 404 | 405 | const y = 406 | poolingPos.y - 407 | (poolingSize + space) * r - 408 | gridSize * gridY + 409 | ((poolingSize + space) * (row - 1)) / 2 + 410 | (gridSize * (divide - 1)) / 2; 411 | 412 | const z = poolingPos.z; 413 | 414 | // poolingDataSigmoidからcolorを取得 (poolingDataSigmoid[0][filterNum][gridY][gridX]) 415 | const colorValue = poolingDataSigmoid 416 | ? poolingDataSigmoid[0][r * col + c][gridY][gridX] 417 | : 0; 418 | 419 | // indexの値を計算 420 | const indexValue = 421 | 4 * 422 | (r * col * divide * divide + 423 | c * divide * divide + 424 | gridY * divide + 425 | gridX); 426 | 427 | // position, color, indexの配列に値を代入 428 | const rate = 0.7; 429 | position[positionIndex] = x - (gridSize / 2) * rate; 430 | position[positionIndex + 1] = y - (gridSize / 2) * rate; 431 | position[positionIndex + 2] = z; 432 | position[positionIndex + 3] = x - (gridSize / 2) * rate; 433 | position[positionIndex + 4] = y + (gridSize / 2) * rate; 434 | position[positionIndex + 5] = z; 435 | position[positionIndex + 6] = x + (gridSize / 2) * rate; 436 | position[positionIndex + 7] = y + (gridSize / 2) * rate; 437 | position[positionIndex + 8] = z; 438 | position[positionIndex + 9] = x + (gridSize / 2) * rate; 439 | position[positionIndex + 10] = y - (gridSize / 2) * rate; 440 | position[positionIndex + 11] = z; 441 | 442 | for (let i = 0; i < 4; i++) { 443 | color[colorIndex + 3 * i] = colorValue; 444 | color[colorIndex + 3 * i + 1] = colorValue; 445 | color[colorIndex + 3 * i + 2] = colorValue; 446 | } 447 | 448 | index[indexIndex] = indexValue; 449 | index[indexIndex + 1] = indexValue + 1; 450 | index[indexIndex + 2] = indexValue + 2; 451 | index[indexIndex + 3] = indexValue + 2; 452 | index[indexIndex + 4] = indexValue + 3; 453 | index[indexIndex + 5] = indexValue; 454 | 455 | positionIndex += 12; 456 | colorIndex += 12; 457 | indexIndex += 6; 458 | } 459 | } 460 | } 461 | } 462 | 463 | return { position, color, index }; 464 | } 465 | -------------------------------------------------------------------------------- /src/components/visual/mnist-sample.ts: -------------------------------------------------------------------------------- 1 | export const ministSampleData = [ 2 | [ 3 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7 | 0.133, 0.663, 0.98, 0.157, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8 | 0, 0, 0, 0, 0, 0, 0, 0.227, 0.949, 0.867, 0.561, 0.067, 0, 0, 0, 0, 0, 0, 0, 9 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.294, 0.969, 0.561, 0.039, 0, 10 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.145, 11 | 0.961, 0.722, 0.008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12 | 0, 0, 0, 0, 0, 0.031, 0.753, 0.784, 0.055, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.545, 0.969, 0.11, 0, 0, 0, 0, 0, 0, 14 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.027, 0.906, 0.718, 15 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16 | 0.49, 0.953, 0.196, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17 | 0, 0, 0, 0, 0, 0, 0.765, 0.722, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.239, 0.984, 0.161, 0, 0, 0, 0.251, 0.169, 0, 19 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.596, 0.824, 20 | 0.027, 0, 0.376, 0.929, 0.996, 0.969, 0.42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21 | 0, 0, 0, 0, 0, 0, 0, 0, 0.98, 0.329, 0, 0.024, 0.875, 0.329, 0.051, 0.341, 22 | 0.965, 0.282, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.169, 23 | 0.996, 0.314, 0, 0.22, 0.592, 0, 0, 0, 0.576, 0.757, 0, 0, 0, 0, 0, 0, 0, 0, 24 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.263, 0.996, 0.161, 0, 0.051, 0.075, 0, 0, 0, 25 | 0.165, 0.992, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.263, 26 | 0.996, 0.051, 0, 0, 0, 0, 0, 0, 0.055, 0.992, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27 | 0, 0, 0, 0, 0, 0, 0, 0.267, 1, 0.051, 0, 0, 0, 0, 0, 0, 0.302, 0.941, 0, 0, 28 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.263, 0.996, 0.051, 0, 0, 0, 29 | 0, 0, 0.02, 0.71, 0.576, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30 | 0.098, 0.898, 0.412, 0, 0, 0, 0, 0.02, 0.612, 0.835, 0.078, 0, 0, 0, 0, 0, 31 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.42, 0.965, 0.412, 0.055, 0.192, 32 | 0.373, 0.851, 0.82, 0.106, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33 | 0, 0, 0, 0, 0.42, 0.965, 0.992, 0.992, 0.941, 0.51, 0.024, 0, 0, 0, 0, 0, 0, 34 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40 | ], 41 | [ 42 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47 | 0.569, 1, 0.827, 0.122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48 | 0, 0, 0, 0, 0, 0, 0.125, 0.929, 0.992, 0.988, 0.278, 0, 0, 0, 0, 0, 0, 0, 0, 49 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.043, 0.686, 0.992, 0.988, 50 | 0.278, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51 | 0, 0.565, 0.992, 0.988, 0.278, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52 | 0, 0, 0, 0, 0, 0, 0, 0, 0.063, 0.749, 0.992, 0.988, 0.278, 0, 0, 0, 0, 0, 0, 53 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.102, 0.867, 0.992, 54 | 0.988, 0.486, 0.122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55 | 0, 0, 0, 0, 0, 0.49, 0.992, 0.988, 0.988, 0.424, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.992, 0.988, 0.988, 0.424, 0, 57 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 58 | 0.992, 0.992, 0.424, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59 | 0, 0, 0, 0, 0, 0, 0.992, 0.988, 0.988, 0.424, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.992, 0.988, 0.988, 0.424, 0, 0, 61 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.992, 62 | 0.988, 0.988, 0.424, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63 | 0, 0, 0, 0, 0, 0, 1, 0.992, 0.992, 0.667, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.992, 0.988, 0.988, 0.988, 0.165, 0, 65 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.584, 66 | 0.988, 0.988, 0.988, 0.565, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67 | 0, 0, 0, 0, 0, 0, 0, 0.427, 0.988, 0.988, 0.988, 0.565, 0, 0, 0, 0, 0, 0, 0, 68 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.855, 0.992, 0.992, 1, 69 | 0.137, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70 | 0.686, 0.988, 0.988, 0.992, 0.137, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.286, 0.988, 0.988, 0.992, 0.137, 0, 0, 0, 0, 0, 72 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.122, 0.827, 0.988, 73 | 0.992, 0.137, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 76 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78 | ], 79 | [ 80 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 81 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 86 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 87 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 88 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.122, 0.157, 0.506, 0.918, 89 | 0.918, 0.624, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90 | 0.267, 0.588, 0.937, 0.996, 0.992, 0.992, 0.992, 0.843, 0, 0, 0, 0, 0, 0, 0, 91 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.612, 0.788, 0.996, 0.996, 0.996, 0.945, 92 | 0.588, 0.384, 0.031, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93 | 0.075, 0.604, 0.996, 0.925, 0.796, 0.325, 0.153, 0.118, 0, 0, 0, 0, 0, 0, 0, 94 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.565, 0.992, 0.569, 0.047, 0, 0, 0, 95 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.039, 0.506, 0.871, 96 | 0.306, 0.31, 0.031, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 97 | 0, 0, 0, 0.525, 0.992, 0.655, 0.031, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.996, 0.306, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.788, 0.992, 0.886, 0.271, 100 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.216, 0.024, 101 | 0, 0.071, 0.502, 0.992, 0.945, 0.161, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102 | 0, 0, 0, 0, 0, 0.098, 0.804, 0.922, 0.361, 0, 0, 0.078, 0.992, 0.992, 0.227, 103 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.906, 0.961, 0.424, 104 | 0, 0, 0, 0.518, 0.992, 0.725, 0.055, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105 | 0, 0, 0, 0, 0, 0.475, 0.961, 0.996, 0.996, 0.996, 0.851, 0.996, 0.875, 106 | 0.196, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.455, 107 | 0.647, 0.914, 0.914, 0.918, 0.706, 0.153, 0.012, 0, 0, 0, 0, 0, 0, 0, 0, 0, 108 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 109 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 110 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 111 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 112 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 116 | ], 117 | [ 118 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 121 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 122 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123 | 0, 0, 0, 0, 0.251, 0.992, 1, 0.247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.376, 0.804, 0.984, 0.992, 0.804, 0.435, 125 | 0.016, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.376, 126 | 0.741, 0.984, 0.984, 0.992, 0.984, 0.984, 0.122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127 | 0, 0, 0, 0, 0, 0, 0, 0, 0.063, 0.251, 0.875, 0.957, 0.984, 0.984, 0.827, 128 | 0.835, 0.984, 0.984, 0.122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129 | 0.314, 0.71, 0.984, 0.992, 0.984, 0.984, 0.984, 0.369, 0.376, 0.984, 0.984, 130 | 0.122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.361, 0.992, 0.992, 131 | 0.992, 1, 0.992, 0.992, 0.992, 0.373, 0.376, 0.992, 0.992, 0.122, 0, 0, 0, 132 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.361, 0.925, 0.984, 0.953, 0.863, 0.914, 133 | 0.984, 0.984, 0.953, 0.322, 0.376, 0.984, 0.984, 0.122, 0, 0, 0, 0, 0, 0, 0, 134 | 0, 0, 0, 0, 0, 0, 0.314, 0.992, 0.984, 0.984, 0.737, 0, 0.376, 0.984, 0.984, 135 | 0.427, 0, 0.376, 0.984, 0.984, 0.122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136 | 0.376, 0.941, 0.992, 0.953, 0.737, 0.165, 0, 0.376, 0.8, 0.427, 0.016, 0, 137 | 0.047, 0.773, 0.984, 0.122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.867, 138 | 0.984, 0.992, 0.475, 0, 0, 0, 0.141, 0.09, 0, 0, 0, 0, 0.745, 0.984, 0.122, 139 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.188, 0.918, 0.992, 0, 0, 0, 0, 0, 0, 0, 140 | 0, 0, 0, 0, 0.749, 0.992, 0.122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.173, 0.867, 141 | 0.984, 0.984, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.047, 0.773, 0.984, 0.122, 0, 142 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.745, 0.984, 0.984, 0.984, 0, 0, 0, 0, 0, 0, 0, 143 | 0, 0, 0, 0.376, 0.984, 0.984, 0.122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.745, 144 | 0.984, 0.984, 0.443, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.157, 0.918, 0.984, 0.859, 145 | 0.09, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.745, 0.984, 0.984, 0.369, 0, 0, 0, 0, 146 | 0, 0, 0, 0, 0.157, 0.851, 0.992, 0.906, 0.184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 147 | 0, 0.749, 0.992, 0.992, 0.992, 0, 0, 0, 0, 0, 0, 0.047, 0.682, 0.992, 0.992, 148 | 0.859, 0.153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.263, 0.925, 0.984, 149 | 0.984, 0.749, 0.745, 0.435, 0.282, 0.745, 0.749, 0.773, 0.984, 0.953, 0.475, 150 | 0.153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.247, 0.925, 0.984, 0.992, 151 | 0.984, 0.984, 0.984, 0.984, 0.992, 0.984, 0.737, 0.369, 0, 0, 0, 0, 0, 0, 0, 152 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.106, 0.506, 0.992, 0.984, 0.984, 0.984, 153 | 0.984, 0.898, 0.659, 0.059, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154 | 0, 0, 0, 0, 0.373, 0.831, 0.984, 0.827, 0.369, 0.231, 0, 0, 0, 0, 0, 0, 0, 155 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 156 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 157 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 159 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 160 | ], 161 | [ 162 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 163 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 165 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 167 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 168 | 0, 0.365, 0.643, 0.827, 0.98, 0.98, 0.761, 0.059, 0, 0, 0, 0, 0, 0, 0, 0, 0, 169 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.078, 0.69, 0.992, 0.929, 0.706, 0.706, 170 | 0.953, 0.996, 0.839, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 171 | 0, 0.8, 0.925, 0.529, 0.071, 0, 0, 0.157, 0.949, 0.988, 0.494, 0, 0, 0, 0, 172 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.271, 0.992, 0.655, 0, 0, 0, 0, 0, 173 | 0.51, 0.996, 0.875, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.29, 174 | 0.851, 0.31, 0, 0, 0, 0, 0, 0.18, 0.996, 0.906, 0.055, 0, 0, 0, 0, 0, 0, 0, 175 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.031, 0.039, 0, 0, 0, 0, 0, 0, 0.153, 0.996, 176 | 0.996, 0.408, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 177 | 0, 0, 0, 0.02, 0.831, 0.996, 0.553, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 178 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.812, 0.996, 0.553, 0, 0, 0, 0, 0, 0, 179 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.027, 0.843, 0.996, 180 | 0.502, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 181 | 0, 0.153, 0.996, 0.996, 0.22, 0, 0, 0.078, 0.263, 0.486, 0.153, 0, 0, 0, 0, 182 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.027, 0.137, 0.384, 0.996, 0.996, 183 | 0.816, 0.616, 0.812, 0.882, 0.996, 0.945, 0.627, 0, 0, 0, 0, 0, 0, 0, 0, 0, 184 | 0, 0.035, 0.122, 0.322, 0.537, 0.796, 0.796, 0.831, 0.996, 0.996, 0.996, 185 | 0.996, 0.984, 0.875, 0.875, 0.498, 0.204, 0.129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 186 | 0.035, 0.537, 0.839, 0.996, 0.996, 0.996, 0.996, 0.941, 0.894, 0.98, 0.996, 187 | 0.996, 0.604, 0.196, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.725, 0.996, 188 | 0.969, 0.702, 0.573, 0.263, 0.235, 0.11, 0, 0.847, 0.996, 0.863, 0.047, 0, 189 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.871, 0.192, 0, 0, 0, 0, 190 | 0.016, 0.537, 0.957, 0.91, 0.196, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191 | 0, 0, 0.996, 0.808, 0.016, 0, 0, 0, 0.031, 0.702, 0.996, 0.969, 0.251, 0, 0, 192 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.847, 0.996, 0.62, 0.694, 193 | 0.51, 0.376, 0.835, 0.988, 0.78, 0.192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 194 | 0, 0, 0, 0, 0, 0, 0, 0.514, 0.969, 0.976, 0.976, 0.976, 0.671, 0.282, 0, 0, 195 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 196 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 197 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 198 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 199 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 201 | 0, 0, 202 | ], 203 | [ 204 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 205 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 206 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 208 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 209 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 210 | 0, 0, 0, 0.165, 0.604, 0.706, 1, 0.69, 0.463, 0.463, 0.063, 0, 0, 0, 0, 0, 211 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.557, 0.992, 0.992, 0.992, 212 | 0.992, 0.992, 0.992, 0.925, 0.404, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 213 | 0, 0, 0, 0, 0, 0.89, 0.992, 0.992, 0.8, 0.694, 0.694, 0.694, 0.953, 0.749, 214 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.89, 0.992, 0.847, 215 | 0.086, 0, 0, 0.09, 0.89, 0.933, 0.376, 0.082, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216 | 0, 0, 0, 0, 0, 0, 0, 0.89, 0.992, 0.804, 0, 0, 0.067, 0.486, 0.992, 0.992, 217 | 0.992, 0.667, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.89, 218 | 0.992, 0.918, 0.243, 0.071, 0.788, 0.992, 0.992, 0.992, 0.984, 0.353, 0, 0, 219 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.89, 0.992, 0.992, 0.992, 220 | 0.992, 0.992, 0.992, 0.992, 0.867, 0.404, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 221 | 0, 0, 0, 0, 0, 0, 0, 0.89, 0.992, 0.992, 0.992, 0.992, 0.992, 0.816, 0.094, 222 | 0.02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.153, 0.925, 223 | 0.992, 0.992, 0.992, 0.984, 0.38, 0.063, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224 | 0, 0, 0, 0, 0, 0, 0.016, 0.271, 0.878, 0.992, 0.992, 0.941, 0.663, 0.18, 0, 225 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.129, 0.525, 0.992, 226 | 0.992, 0.992, 0.992, 0.412, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 227 | 0, 0, 0, 0, 0.2, 0.882, 0.992, 0.992, 0.992, 0.992, 0.992, 0.267, 0, 0, 0, 228 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.188, 0.89, 0.992, 0.992, 229 | 0.98, 0.682, 0.992, 0.992, 0.267, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230 | 0, 0, 0, 0, 0, 0.835, 0.992, 0.992, 0.702, 0.247, 0.435, 0.992, 0.992, 231 | 0.267, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.361, 0.984, 232 | 0.788, 0.051, 0.02, 0, 0.651, 0.992, 0.992, 0.267, 0, 0, 0, 0, 0, 0, 0, 0, 233 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.153, 0.871, 0.992, 0.776, 0, 0, 0, 0.973, 234 | 0.992, 0.906, 0.18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 235 | 0.212, 0.953, 0.992, 0.486, 0, 0.149, 0.522, 0.988, 0.992, 0.588, 0, 0, 0, 236 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.463, 0.992, 0.992, 0.929, 237 | 0.702, 0.875, 0.992, 0.992, 0.745, 0.055, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 238 | 0, 0, 0, 0, 0, 0, 0, 0.173, 0.902, 0.992, 0.992, 0.992, 0.992, 0.992, 0.957, 239 | 0.298, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.176, 240 | 0.906, 0.992, 0.992, 0.992, 0.714, 0.259, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 241 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 243 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 244 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 245 | ], 246 | [ 247 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 249 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 251 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 253 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 256 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.012, 0.145, 0.145, 0.145, 257 | 0.027, 0, 0, 0, 0, 0, 0, 0, 0, 0.306, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.055, 258 | 0.329, 0.714, 0.737, 0.757, 0.996, 0.996, 0.996, 0.486, 0, 0, 0, 0, 0, 0, 0, 259 | 0, 0.035, 0, 0, 0, 0, 0, 0, 0, 0.086, 0.357, 0.51, 0.757, 0.996, 0.996, 0.8, 260 | 0.49, 0.788, 0.996, 0.996, 0.965, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 261 | 0, 0, 0.839, 0.996, 1, 0.996, 1, 0.678, 0.086, 0, 0.384, 0.996, 1, 0.596, 0, 262 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.835, 0.961, 0.776, 0.294, 263 | 0.122, 0.008, 0, 0.459, 0.961, 0.996, 0.867, 0.098, 0, 0, 0, 0, 0, 0, 0, 0, 264 | 0, 0, 0, 0, 0, 0, 0, 0, 0.282, 0.141, 0, 0, 0, 0, 0.306, 0.965, 0.996, 265 | 0.871, 0.129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 266 | 0, 0.459, 0.953, 0.996, 0.882, 0.145, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 267 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.302, 0.976, 0.996, 0.863, 0.286, 0, 0, 0, 0, 0, 268 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.294, 0.949, 0.996, 269 | 0.878, 0.145, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 270 | 0, 0.294, 0.984, 0.996, 0.859, 0.129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 271 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.302, 0.949, 0.996, 0.996, 0.157, 0, 0, 0, 0, 0, 272 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.114, 0.82, 0.996, 0.91, 273 | 0.325, 0.004, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 274 | 0.078, 0.886, 0.996, 0.878, 0.247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 275 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.247, 0.996, 0.98, 0.227, 0, 0, 0, 0, 0, 0, 0, 276 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.184, 0.957, 0.682, 0, 277 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 278 | 0.118, 0.055, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 279 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 280 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 281 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 282 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 283 | 0, 0, 0, 0, 0, 0, 0, 0, 284 | ], 285 | [ 286 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 287 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 288 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 289 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 290 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.165, 291 | 0.463, 0.859, 0.651, 0.463, 0.463, 0.024, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 292 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.404, 0.949, 0.996, 0.996, 0.996, 0.996, 0.996, 293 | 0.259, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.071, 294 | 0.91, 0.996, 0.996, 0.996, 0.996, 0.996, 0.933, 0.275, 0, 0, 0, 0, 0, 0, 0, 295 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.408, 0.957, 0.996, 0.878, 0.996, 296 | 0.996, 0.996, 0.553, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 297 | 0, 0, 0, 0.812, 0.996, 0.824, 0.996, 0.996, 0.996, 0.133, 0, 0, 0, 0, 0, 0, 298 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.329, 0.808, 0.996, 0.996, 299 | 0.996, 0.996, 0.161, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 300 | 0, 0, 0, 0, 0.094, 0.82, 0.996, 0.996, 0.996, 0.671, 0, 0, 0, 0, 0, 0, 0, 0, 301 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.357, 0.537, 0.992, 0.996, 0.996, 302 | 0.996, 0.439, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 303 | 0.157, 0.839, 0.98, 0.996, 0.996, 0.996, 0.996, 0.996, 0.133, 0, 0, 0, 0, 0, 304 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.318, 0.969, 0.996, 0.996, 0.996, 305 | 0.996, 0.996, 0.996, 0.573, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 306 | 0, 0, 0, 0, 0.431, 0.965, 0.996, 0.996, 0.996, 0.996, 0.996, 0.671, 0, 0, 0, 307 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.286, 0.349, 0.349, 308 | 0.365, 0.941, 0.996, 0.671, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309 | 0, 0, 0, 0, 0, 0, 0, 0, 0.004, 0.502, 0.996, 0.859, 0.122, 0, 0, 0, 0, 0, 0, 310 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.027, 0.996, 0.996, 311 | 0.839, 0.11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 312 | 0, 0, 0.541, 0.996, 0.996, 0.455, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 313 | 0, 0.075, 0.694, 0.353, 0, 0, 0, 0, 0, 0.098, 0.941, 0.996, 0.996, 0.133, 0, 314 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.643, 0.996, 0.843, 0.247, 0.141, 315 | 0, 0.2, 0.349, 0.808, 0.996, 0.996, 0.545, 0.031, 0, 0, 0, 0, 0, 0, 0, 0, 0, 316 | 0, 0, 0, 0, 0, 0, 0.224, 0.773, 0.996, 0.996, 0.871, 0.706, 0.945, 0.996, 317 | 0.996, 0.992, 0.835, 0.043, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 318 | 0, 0.549, 0.412, 0.996, 0.996, 0.996, 0.996, 0.996, 0.996, 0.925, 0, 0, 0, 319 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.027, 0.459, 0.459, 320 | 0.647, 0.996, 0.996, 0.937, 0.196, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 321 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 322 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 323 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 324 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 325 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 326 | ], 327 | [ 328 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 329 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 330 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 331 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 332 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 333 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 334 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 335 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 336 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.071, 0.412, 0.89, 0.992, 0.992, 0.478, 0, 0, 0, 337 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.224, 0.78, 0.992, 338 | 0.988, 0.988, 0.988, 0.988, 0.624, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 339 | 0, 0, 0, 0, 0, 0.078, 0.827, 0.988, 0.91, 0.596, 0.286, 0.655, 0.988, 0.843, 340 | 0.024, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.773, 0.988, 341 | 0.714, 0, 0, 0, 0.145, 0.922, 0.953, 0.184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 342 | 0, 0, 0, 0, 0, 0, 0, 0.737, 0.988, 0.404, 0, 0, 0, 0.145, 0.922, 0.898, 343 | 0.106, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.741, 0.992, 344 | 0.337, 0.031, 0.169, 0.545, 0.745, 0.827, 0.176, 0, 0, 0, 0, 0, 0, 0, 0, 0, 345 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.91, 0.988, 0.784, 0.788, 0.988, 0.988, 346 | 0.329, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.835, 347 | 0.961, 0.988, 0.992, 0.988, 0.949, 0.165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 348 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.22, 0.329, 0.992, 0.988, 0.627, 0, 0, 0, 349 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.176, 0.992, 350 | 0.988, 0.149, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 351 | 0, 0, 0, 0.349, 1, 0.992, 0.149, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 352 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.314, 0.992, 0.741, 0.125, 0, 0, 0, 0, 0, 0, 353 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.161, 0.702, 0.91, 354 | 0.329, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 355 | 0.059, 0.882, 0.988, 0.451, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 356 | 0, 0, 0, 0, 0, 0, 0, 0, 0.6, 0.988, 0.643, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 357 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.267, 0.961, 0.953, 0.31, 0, 0, 0, 358 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.125, 0.929, 359 | 0.961, 0.322, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 360 | 0, 0, 0.035, 0.58, 0.988, 0.663, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 361 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.416, 0.992, 0.769, 0.027, 0, 0, 0, 0, 0, 0, 362 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.212, 0.894, 0.506, 363 | 0.11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 364 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 365 | ], 366 | [ 367 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 368 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 369 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 370 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 371 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 372 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.741, 0.745, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 373 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.561, 0.969, 0.6, 0, 0, 0, 0, 0, 0, 374 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.533, 0.969, 0.949, 375 | 0.337, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 376 | 0, 0.753, 0.988, 0.733, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 377 | 0.243, 0.725, 0.071, 0, 0, 0, 0, 0.349, 0.925, 0.851, 0.184, 0, 0, 0, 0, 0, 378 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.847, 0.992, 0.235, 0, 0, 0, 0, 0.831, 379 | 1, 0.318, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.808, 380 | 0.988, 0.267, 0, 0, 0, 0.188, 0.949, 0.992, 0.349, 0, 0, 0, 0, 0, 0, 0, 0, 381 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.514, 0.984, 0.831, 0.082, 0, 0, 0.043, 0.655, 382 | 0.988, 0.773, 0.02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.114, 383 | 0.91, 0.969, 0.247, 0, 0, 0, 0.6, 0.988, 0.886, 0, 0, 0, 0, 0, 0, 0, 0, 0, 384 | 0, 0, 0, 0, 0, 0, 0, 0, 0.176, 0.859, 0.988, 0.561, 0, 0, 0, 0.455, 0.976, 385 | 0.988, 0.404, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.016, 0.376, 386 | 0.992, 1, 0.992, 0.784, 0.478, 0.027, 0.098, 0.788, 0.98, 0.62, 0, 0, 0, 0, 387 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.361, 0.988, 0.988, 0.992, 0.851, 388 | 0.988, 0.988, 0.784, 0.89, 0.988, 0.906, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 389 | 0, 0, 0, 0, 0.341, 0.984, 0.969, 0.906, 0.255, 0.188, 0.741, 0.988, 0.988, 390 | 0.992, 0.988, 0.984, 0.89, 0.137, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 391 | 0.745, 0.867, 0.384, 0, 0, 0, 0.165, 0.769, 0.988, 0.992, 0.988, 0.988, 392 | 0.635, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.435, 0.114, 0, 0, 0, 393 | 0, 0.243, 0.937, 0.988, 0.337, 0.165, 0.165, 0.055, 0, 0, 0, 0, 0, 0, 0, 0, 394 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.059, 0.58, 0.992, 0.855, 0, 0, 0, 0, 395 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.475, 0.988, 396 | 0.906, 0.11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 397 | 0, 0, 0.122, 0.867, 0.984, 0.506, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 398 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.855, 0.988, 0.627, 0, 0, 0, 0, 0, 0, 0, 0, 399 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.478, 0.988, 0.322, 0, 400 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 401 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 402 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 403 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 404 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 405 | 0, 406 | ], 407 | ]; 408 | --------------------------------------------------------------------------------