Pyodide里的Tensorflow目录
可以跑通基本的训练、使用模型过程
This commit is contained in:
230
boards/default_src/python_pyodide/blocks/tensorflow.js
Normal file
230
boards/default_src/python_pyodide/blocks/tensorflow.js
Normal file
@@ -0,0 +1,230 @@
|
||||
import * as Blockly from 'blockly/core';
|
||||
|
||||
const TENSORFLOW_HUE = '#1216ab';
|
||||
|
||||
Blockly.Blocks.tensorflow_init_tensor = {
|
||||
init: function () {
|
||||
this.appendValueInput("VAR")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_INIT_TENSOR);
|
||||
this.setOutput(true, null);
|
||||
this.setColour(TENSORFLOW_HUE);
|
||||
this.setTooltip('');
|
||||
this.setHelpUrl('');
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks.tensorflow_sequential = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_SEQUENTIAL);
|
||||
this.setOutput(true, null);
|
||||
this.setColour(TENSORFLOW_HUE);
|
||||
this.setTooltip('');
|
||||
this.setHelpUrl('');
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks.tensorflow_layers_dense = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_INIT_LAYERS_DENSE_LAYER);
|
||||
this.appendValueInput("VAR1")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_RIGHT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_OUTPUT_DIMENSION);
|
||||
this.appendValueInput("VAR2")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_RIGHT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_INPUT_SHAPE);
|
||||
this.setOutput(true, null);
|
||||
this.setColour(TENSORFLOW_HUE);
|
||||
this.setTooltip('');
|
||||
this.setHelpUrl('');
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks.tensorflow_add = {
|
||||
init: function () {
|
||||
this.appendValueInput("VAR1")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_MODEL);
|
||||
this.appendValueInput("VAR2")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_ADD_LAYER);
|
||||
this.setInputsInline(true);
|
||||
this.setPreviousStatement(true, null);
|
||||
this.setNextStatement(true, null);
|
||||
this.setColour(TENSORFLOW_HUE);
|
||||
this.setTooltip('');
|
||||
this.setHelpUrl('');
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks.tensorflow_compile = {
|
||||
init: function () {
|
||||
this.appendValueInput("VAR1")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_COMPILE_MODEL);
|
||||
this.appendDummyInput()
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_LOSS_FUNCTION_TYPE)
|
||||
.appendField(new Blockly.FieldDropdown([
|
||||
[Blockly.Msg.MIXLY_TENSORFLOW_MEAN_SQUARED_ERROR, "meanSquaredError"]
|
||||
]), "VAR2");
|
||||
this.appendDummyInput()
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_OPTIMIZER)
|
||||
.appendField(new Blockly.FieldDropdown([
|
||||
[Blockly.Msg.MIXLY_TENSORFLOW_SGD, "sgd"]
|
||||
]), "VAR3");
|
||||
this.setInputsInline(true);
|
||||
this.setPreviousStatement(true, null);
|
||||
this.setNextStatement(true, null);
|
||||
this.setColour(TENSORFLOW_HUE);
|
||||
this.setTooltip('');
|
||||
this.setHelpUrl('');
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks.tensorflow_fit = {
|
||||
init: function () {
|
||||
this.appendValueInput("VAR1")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_FIT_MODEL);
|
||||
this.appendValueInput("VAR2")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_RIGHT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_FIT_INPUT_DATA);
|
||||
this.appendValueInput("VAR3")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_RIGHT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_FIT_TARGET_DATA);
|
||||
this.appendValueInput("VAR4")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_RIGHT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_FIT_EPOCHS);
|
||||
this.appendValueInput("VAR5")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_RIGHT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_FIT_VERBOSE);
|
||||
this.appendDummyInput()
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_FIT_RETURN_HISTORY);
|
||||
this.setInputsInline(false);
|
||||
this.setOutput(true, null);
|
||||
this.setColour(TENSORFLOW_HUE);
|
||||
this.setTooltip('');
|
||||
this.setHelpUrl('');
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks.tensorflow_get_loss = {
|
||||
init: function () {
|
||||
this.appendValueInput("VAR")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_GET_LOSS_FROM_HISTORY);
|
||||
this.appendDummyInput()
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_GET_LOSS_FROM_HISTORY_2);
|
||||
this.setOutput(true, null);
|
||||
this.setColour(TENSORFLOW_HUE);
|
||||
this.setTooltip('');
|
||||
this.setHelpUrl('');
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks.tensorflow_predict = {
|
||||
init: function () {
|
||||
this.appendValueInput("VAR1")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_PREDICT);
|
||||
this.appendValueInput("VAR2")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_RIGHT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_PREDICT_INPUT_DATA);
|
||||
this.appendDummyInput()
|
||||
.setAlign(Blockly.ALIGN_RIGHT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_PREDICT_RETURN_RESULT);
|
||||
this.setInputsInline(false);
|
||||
this.setOutput(true, null);
|
||||
this.setColour(TENSORFLOW_HUE);
|
||||
this.setTooltip('');
|
||||
this.setHelpUrl('');
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks.tensorflow_get_tensor_data = {
|
||||
init: function () {
|
||||
this.appendValueInput("VAR")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_GET_TENSOR_DATA);
|
||||
this.setOutput(true, null);
|
||||
this.setColour(TENSORFLOW_HUE);
|
||||
this.setTooltip('');
|
||||
this.setHelpUrl('');
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks.tensorflow_save_or_export_model = {
|
||||
init: function () {
|
||||
this.appendValueInput("NAME1")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_MODEL);
|
||||
this.appendValueInput("NAME2")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(new Blockly.FieldDropdown([
|
||||
[Blockly.Msg.MIXLY_TENSORFLOW_SAVE_MODEL, "save"],
|
||||
[Blockly.Msg.MIXLY_TENSORFLOW_EXPORT_MODEL, "export"]
|
||||
]), "NAME")
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_SAVE_MODEL_NAME);
|
||||
this.setInputsInline(true);
|
||||
this.setPreviousStatement(true, null);
|
||||
this.setNextStatement(true, null);
|
||||
this.setColour(TENSORFLOW_HUE);
|
||||
this.setTooltip('');
|
||||
this.setHelpUrl('');
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks.tensorflow_use_load_model = {
|
||||
init: function () {
|
||||
this.appendValueInput("NAME")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_LOAD_MODEL)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_MODEL_NAME);
|
||||
this.setInputsInline(true);
|
||||
this.setOutput(true, null);
|
||||
this.setColour(TENSORFLOW_HUE);
|
||||
this.setTooltip('');
|
||||
this.setHelpUrl('');
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks.tensorflow_prepare_picture = {
|
||||
init: function () {
|
||||
this.appendValueInput("NAME")
|
||||
.setCheck(null)
|
||||
.setAlign(Blockly.ALIGN_LEFT)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_PREPARE_PICTURE_TO_TENSOR)
|
||||
.appendField(Blockly.Msg.MIXLY_TENSORFLOW_PREPARE_PICTURE_READ_PICTURE);
|
||||
this.setInputsInline(true);
|
||||
this.setOutput(true, null);
|
||||
this.setColour(TENSORFLOW_HUE);
|
||||
this.setTooltip('');
|
||||
this.setHelpUrl('');
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,11 @@
|
||||
import * as PythonPyodideSKLearnBlocks from './blocks/sklearn';
|
||||
import * as PythonPyodideSKLearnGenerators from './generators/sklearn';
|
||||
import * as PythonTensorflowBlocks from './blocks/tensorflow';
|
||||
import * as PythonTensorflowGenerators from './generators/tensorflow';
|
||||
|
||||
export {
|
||||
PythonPyodideSKLearnBlocks,
|
||||
PythonPyodideSKLearnGenerators
|
||||
PythonPyodideSKLearnGenerators,
|
||||
PythonTensorflowBlocks,
|
||||
PythonTensorflowGenerators
|
||||
};
|
||||
97
boards/default_src/python_pyodide/generators/tensorflow.js
Normal file
97
boards/default_src/python_pyodide/generators/tensorflow.js
Normal file
@@ -0,0 +1,97 @@
|
||||
export const tensorflow_init_tensor = function (_, generator) {
|
||||
var VALUE_INPUT_VAR = generator.valueToCode(this, "VAR", generator.ORDER_ATOMIC);
|
||||
generator.definitions_['import_tensorflow'] = 'import tensorflow';
|
||||
var code = 'tensorflow.tensor(' + VALUE_INPUT_VAR + ')';
|
||||
return [code, generator.ORDER_ATOMIC];
|
||||
};
|
||||
|
||||
export const tensorflow_sequential = function (_, generator) {
|
||||
generator.definitions_['import_tensorflow'] = 'import tensorflow';
|
||||
var code = 'tensorflow.sequential()';
|
||||
return [code, generator.ORDER_ATOMIC];
|
||||
};
|
||||
|
||||
export const tensorflow_layers_dense = function (_, generator) {
|
||||
generator.definitions_['import_tensorflow'] = 'import tensorflow';
|
||||
var VALUE_INPUT_VAR1 = generator.valueToCode(this, "VAR1", generator.ORDER_ATOMIC);
|
||||
var VALUE_INPUT_VAR2 = generator.valueToCode(this, "VAR2", generator.ORDER_ATOMIC);
|
||||
var code = 'tensorflow.layers.dense(units = ' + VALUE_INPUT_VAR1 + ', input_shape = ' + VALUE_INPUT_VAR2 + ')';
|
||||
return [code, generator.ORDER_ATOMIC];
|
||||
};
|
||||
|
||||
export const tensorflow_add = function (_, generator) {
|
||||
generator.definitions_['import_tensorflow'] = 'import tensorflow';
|
||||
var VALUE_INPUT_VAR1 = generator.valueToCode(this, "VAR1", generator.ORDER_ATOMIC);
|
||||
var VALUE_INPUT_VAR2 = generator.valueToCode(this, "VAR2", generator.ORDER_ATOMIC);
|
||||
var code = VALUE_INPUT_VAR1 + '.add(' + VALUE_INPUT_VAR2 + ')\n';
|
||||
return code;
|
||||
};
|
||||
|
||||
export const tensorflow_compile = function (_, generator) {
|
||||
generator.definitions_['import_tensorflow'] = 'import tensorflow';
|
||||
var VALUE_INPUT_VAR1 = generator.valueToCode(this, "VAR1", generator.ORDER_ATOMIC);
|
||||
var VALUE_INPUT_VAR2 = this.getFieldValue("VAR2");
|
||||
var VALUE_INPUT_VAR3 = this.getFieldValue("VAR3");
|
||||
var code = VALUE_INPUT_VAR1 + '.compile(loss = "' + VALUE_INPUT_VAR2 + '", optimizer = "' + VALUE_INPUT_VAR3 + '")\n';
|
||||
return code;
|
||||
}
|
||||
|
||||
export const tensorflow_fit = function (_, generator) {
|
||||
generator.definitions_['import_tensorflow'] = 'import tensorflow';
|
||||
var VALUE_INPUT_VAR1 = generator.valueToCode(this, "VAR1", generator.ORDER_ATOMIC);
|
||||
var VALUE_INPUT_VAR2 = generator.valueToCode(this, "VAR2", generator.ORDER_ATOMIC);
|
||||
var VALUE_INPUT_VAR3 = generator.valueToCode(this, "VAR3", generator.ORDER_ATOMIC);
|
||||
var VALUE_INPUT_VAR4 = generator.valueToCode(this, "VAR4", generator.ORDER_ATOMIC);
|
||||
var VALUE_INPUT_VAR5 = generator.valueToCode(this, "VAR5", generator.ORDER_ATOMIC);
|
||||
var code = 'await ' + VALUE_INPUT_VAR1 + '.fit(' + VALUE_INPUT_VAR2 + ', ' + VALUE_INPUT_VAR3 + ', epochs=' + VALUE_INPUT_VAR4 + ', verbose=' + VALUE_INPUT_VAR5 + ')';
|
||||
return [code, generator.ORDER_ATOMIC];
|
||||
};
|
||||
|
||||
export const tensorflow_get_loss = function (_, generator) {
|
||||
generator.definitions_['import_tensorflow'] = 'import tensorflow';
|
||||
var VALUE_INPUT_VAR = generator.valueToCode(this, "VAR", generator.ORDER_ATOMIC);
|
||||
var code = VALUE_INPUT_VAR + '.history.loss';
|
||||
return [code, generator.ORDER_ATOMIC];
|
||||
};
|
||||
|
||||
export const tensorflow_predict = function (_, generator) {
|
||||
generator.definitions_['import_tensorflow'] = 'import tensorflow';
|
||||
var VALUE_INPUT_VAR1 = generator.valueToCode(this, "VAR1", generator.ORDER_ATOMIC);
|
||||
var VALUE_INPUT_VAR2 = generator.valueToCode(this, "VAR2", generator.ORDER_ATOMIC);
|
||||
var code = VALUE_INPUT_VAR1 + '.predict(' + VALUE_INPUT_VAR2 + ')';
|
||||
return [code, generator.ORDER_ATOMIC];
|
||||
};
|
||||
|
||||
export const tensorflow_get_tensor_data = function (_, generator) {
|
||||
generator.definitions_['import_tensorflow'] = 'import tensorflow';
|
||||
var VALUE_INPUT_VAR1 = generator.valueToCode(this, "VAR", generator.ORDER_ATOMIC);
|
||||
var code = "(await " + VALUE_INPUT_VAR1 + ".data())";
|
||||
return [code, generator.ORDER_ATOMIC];
|
||||
};
|
||||
|
||||
export const tensorflow_save_or_export_model = function (_, generator) {
|
||||
generator.definitions_['import_tensorflow'] = 'import tensorflow';
|
||||
|
||||
var VALUE_INPUT_NAME1 = generator.valueToCode(this, "NAME1", generator.ORDER_ATOMIC);
|
||||
var FIELD_NAME = this.getFieldValue("NAME");
|
||||
var VALUE_INPUT_NAME2 = generator.valueToCode(this, "NAME2", generator.ORDER_ATOMIC).replace(/^'|'$/g, '');
|
||||
if (FIELD_NAME == "export") {
|
||||
return `await ${VALUE_INPUT_NAME1}.save("downloads://${VALUE_INPUT_NAME2}")\n`;
|
||||
}
|
||||
return `await ${VALUE_INPUT_NAME1}.save("indexeddb://${VALUE_INPUT_NAME2}")\n`;
|
||||
};
|
||||
|
||||
export const tensorflow_use_load_model = function (_, generator) {
|
||||
generator.definitions_['import_tensorflow'] = 'import tensorflow';
|
||||
|
||||
var VALUE_INPUT_NAME = generator.valueToCode(this, "NAME", generator.ORDER_ATOMIC).replace(/^'|'$/g, '');
|
||||
return [`await tensorflow.load_model("${VALUE_INPUT_NAME}")`, generator.ORDER_ATOMIC];
|
||||
};
|
||||
|
||||
export const tensorflow_prepare_picture = function (_, generator) {
|
||||
generator.definitions_['import_tensorflow'] = 'import tensorflow';
|
||||
generator.definitions_['import_numpy'] = 'import numpy';
|
||||
generator.definitions_['import_PIL'] = 'import PIL';
|
||||
var VALUE_INPUT_NAME = generator.valueToCode(this, "NAME", generator.ORDER_ATOMIC);
|
||||
return [`(await tensorflow.prepare_qmyixtxi(tensorflow.tensor(numpy.array(PIL.Image.open(${VALUE_INPUT_NAME}).convert('RGB')))))`, generator.ORDER_ATOMIC];
|
||||
};
|
||||
@@ -69,7 +69,9 @@ import {
|
||||
|
||||
import {
|
||||
PythonPyodideSKLearnBlocks,
|
||||
PythonPyodideSKLearnGenerators
|
||||
PythonPyodideSKLearnGenerators,
|
||||
PythonTensorflowBlocks,
|
||||
PythonTensorflowGenerators
|
||||
} from './';
|
||||
|
||||
import './others/loader';
|
||||
@@ -113,7 +115,8 @@ Object.assign(
|
||||
PythonMixpySKLearnBlocks,
|
||||
PythonMixpySystemBlocks,
|
||||
PythonMixpyTurtleBlocks,
|
||||
PythonPyodideSKLearnBlocks
|
||||
PythonPyodideSKLearnBlocks,
|
||||
PythonTensorflowBlocks
|
||||
);
|
||||
|
||||
Object.assign(
|
||||
@@ -146,5 +149,6 @@ Object.assign(
|
||||
PythonMixpySKLearnGenerators,
|
||||
PythonMixpySystemGenerators,
|
||||
PythonMixpyTurtleGenerators,
|
||||
PythonPyodideSKLearnGenerators
|
||||
PythonPyodideSKLearnGenerators,
|
||||
PythonTensorflowGenerators
|
||||
);
|
||||
Binary file not shown.
@@ -3378,6 +3378,17 @@
|
||||
"sprite"
|
||||
],
|
||||
"depends": []
|
||||
},
|
||||
"tensorflow": {
|
||||
"name": "tensorflow",
|
||||
"version": "0.0.1",
|
||||
"file_name": "{basthonRoot}/modules/tensorflow-0.0.1-py3-none-any.whl",
|
||||
"install_dir": "site",
|
||||
"sha256": "734415520a240e19f44c6121f0e237300c9f82ce87635a9e40a6639d0f35c888",
|
||||
"imports": [
|
||||
"tensorflow"
|
||||
],
|
||||
"depends": []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,328 @@ import NavExt from './nav-ext';
|
||||
import * as tf from '@tensorflow/tfjs';
|
||||
import './tensorflow';
|
||||
|
||||
import * as Blockly from 'blockly/core';
|
||||
NavExt.init();
|
||||
window.tf = tf;
|
||||
|
||||
let featureExtractor;
|
||||
// featureExtractor = await tf.loadGraphModel("../common/media/tfmodel/model.json");
|
||||
// window.featureExtractor = featureExtractor;
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('modalOverlay').style.display = 'none';
|
||||
}
|
||||
|
||||
// 从IndexedDB删除单个模型
|
||||
async function deleteModel(modelName) {
|
||||
try {
|
||||
await tf.io.removeModel(`indexeddb://${modelName}`);
|
||||
// 从UI移除
|
||||
const modelItem = document.querySelector(`.model-item[data-model-name="${modelName}"]`);
|
||||
if (modelItem) modelItem.remove();
|
||||
} catch (error) {
|
||||
console.error('删除模型失败:', error);
|
||||
alert('删除模型失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 显示单个模型项
|
||||
function displayModelItem(modelName) {
|
||||
const modelsList = document.getElementById('imported-models');
|
||||
if ([...modelsList.children].some(item => item.dataset.modelName === modelName)) {
|
||||
return;
|
||||
}
|
||||
const modelItem = document.createElement('div');
|
||||
modelItem.className = 'model-item';
|
||||
modelItem.dataset.modelName = modelName;
|
||||
modelItem.innerHTML = `
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; padding: 8px 10px; border-bottom: 1px solid #eee;">
|
||||
<span style="font-size: 1em; color: #333;">${modelName}</span>
|
||||
<button class="delete-model" style="
|
||||
background: #f44336;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
transition: background-color 0.2s;
|
||||
">删除</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 绑定删除事件
|
||||
modelItem.querySelector('.delete-model').addEventListener('click', () => {
|
||||
deleteModel(modelName);
|
||||
});
|
||||
|
||||
modelsList.appendChild(modelItem);
|
||||
}
|
||||
|
||||
// 清空所有模型
|
||||
async function clearAllModels() {
|
||||
try {
|
||||
const modelInfos = await tf.io.listModels();
|
||||
const deletePromises = Object.keys(modelInfos)
|
||||
.map(path => path.replace('indexeddb://', ''))
|
||||
.map(modelName => tf.io.removeModel(`indexeddb://${modelName}`));
|
||||
|
||||
await Promise.all(deletePromises);
|
||||
document.getElementById('imported-models').innerHTML = '';
|
||||
} catch (error) {
|
||||
console.error('清空模型失败:', error);
|
||||
alert('清空模型失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载并显示所有模型
|
||||
async function loadAndDisplayAllModels() {
|
||||
try {
|
||||
const modelInfos = await tf.io.listModels();
|
||||
document.getElementById('imported-models').innerHTML = '';
|
||||
for (const [path] of Object.entries(modelInfos)) {
|
||||
const modelName = path.replace('indexeddb://', '');
|
||||
displayModelItem(modelName);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载模型列表失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function createModal() {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'modalOverlay';
|
||||
Object.assign(overlay.style, {
|
||||
display: 'none',
|
||||
position: 'fixed',
|
||||
top: '0',
|
||||
left: '0',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
zIndex: '20011216',
|
||||
pointerEvents: 'auto'
|
||||
});
|
||||
const content = document.createElement('div');
|
||||
Object.assign(content.style, {
|
||||
backgroundColor: 'white',
|
||||
width: '60%',
|
||||
maxHeight: '80%',
|
||||
margin: '12vh auto',
|
||||
padding: '20px 30px',
|
||||
borderRadius: '12px'
|
||||
});
|
||||
content.innerHTML = `
|
||||
<h2 style="margin-bottom: 20px;">选择本地模型</h2>
|
||||
<div style="margin-bottom: 25px; position: relative; min-height: 200px;"> <!-- 新增 min-height 和 position -->
|
||||
<input type="file" id="model-upload" accept=".json,.bin" multiple style="display: none;">
|
||||
<label for="model-upload" style="
|
||||
background: #1216ab;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
">选择模型文件</label>
|
||||
<div style="display: flex; gap: 10px; margin-top: 15px; align-items: center;">
|
||||
<span style="line-height: 36px;">导入模型名称:</span>
|
||||
<input type="text" id="model-name" placeholder="模型名称" value="my-model" style="
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
max-width: 200px;
|
||||
">
|
||||
<button id="model-handle" style="
|
||||
background: #1216ab;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
">保存模型</button>
|
||||
</div>
|
||||
<div style="margin-top: 15px; display: flex; gap: 20px;">
|
||||
<div id="json-status" style="
|
||||
background: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
flex: 1;
|
||||
">
|
||||
<span>❌ 模型结构描述文件(model.json)</span>
|
||||
|
||||
<div style="color: #666; font-size: 0.9em; margin-top: 5px;">未选择</div>
|
||||
</div>
|
||||
<div id="weights-status" style="
|
||||
background: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
flex: 1;
|
||||
">
|
||||
<span>❌ 权重文件(model.weights.bin)</span>
|
||||
<div style="color: #666; font-size: 0.9em; margin-top: 5px;">0 个已选择</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="output" style="margin-bottom: 15px; min-height: 40px;"></div>
|
||||
<div style="margin-top: 20px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
||||
<h3>已导入模型</h3>
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<button id="refresh-models" style="
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
">刷新</button>
|
||||
<button id="clear-models" style="
|
||||
background: #f44336;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
">清空</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="imported-models" style="
|
||||
background: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
height: 200px;
|
||||
overflow-y: auto;
|
||||
">
|
||||
<div style="color: #666; font-style: italic;">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: flex-end; margin-top: 15px;">
|
||||
<button class="close-btn" style="
|
||||
background: #e0e0e0;
|
||||
border: none;
|
||||
padding: 8px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
overlay.appendChild(content);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
content.querySelector('.close-btn').addEventListener('click', closeModal);
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) closeModal();
|
||||
});
|
||||
// 获取DOM元素
|
||||
const modelUpload = document.getElementById('model-upload');
|
||||
const modelHandle = document.getElementById('model-handle');
|
||||
const outputDiv = document.getElementById('output');
|
||||
|
||||
let jsonFile = null;
|
||||
let weightFiles = [];
|
||||
|
||||
modelUpload.addEventListener('change', async (event) => {
|
||||
const files = event.target.files;
|
||||
|
||||
// 获取状态元素
|
||||
const jsonStatus = document.getElementById('json-status');
|
||||
const weightsStatus = document.getElementById('weights-status');
|
||||
|
||||
// 重置状态显示(保持完整文件名描述)
|
||||
jsonStatus.querySelector('span').textContent = '❌ 模型结构描述文件(model.json)';
|
||||
jsonStatus.querySelector('div').textContent = '未选择';
|
||||
weightsStatus.querySelector('span').textContent = '❌ 权重文件(model.weights.bin)';
|
||||
weightsStatus.querySelector('div').textContent = '0 个已选择';
|
||||
|
||||
// 分离 JSON 和权重文件
|
||||
weightFiles = [];
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (files[i].name.endsWith('.json')) {
|
||||
jsonFile = files[i];
|
||||
} else {
|
||||
weightFiles.push(files[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!jsonFile) {
|
||||
alert('未找到 model.json 文件');
|
||||
return;
|
||||
}
|
||||
|
||||
outputDiv.innerHTML = '正在处理上传的模型文件...';
|
||||
|
||||
if (jsonFile) {
|
||||
jsonStatus.querySelector('span').textContent = '✅ 模型结构描述文件(model.json)';
|
||||
jsonStatus.querySelector('div').textContent = '已选择';
|
||||
const modelName = jsonFile.name.replace('.json', '');
|
||||
document.getElementById('model-name').value = modelName;
|
||||
}
|
||||
|
||||
if (weightFiles.length > 0) {
|
||||
weightsStatus.querySelector('span').textContent = '✅ 权重文件(model.weights.bin)';
|
||||
weightsStatus.querySelector('div').textContent = `${weightFiles.length} 个已选择`;
|
||||
}
|
||||
});
|
||||
|
||||
modelHandle.addEventListener('click', async () => {
|
||||
try {
|
||||
const modelNameInput = document.getElementById('model-name');
|
||||
const modelName = modelNameInput.value || 'mixly-model';
|
||||
|
||||
const model = await tf.loadLayersModel(
|
||||
tf.io.browserFiles([jsonFile, ...weightFiles])
|
||||
);
|
||||
await model.save(`indexeddb://${modelName}`);
|
||||
loadAndDisplayAllModels();
|
||||
outputDiv.innerHTML = `模型已成功保存为 ${modelName}!`;
|
||||
} catch (error) {
|
||||
outputDiv.innerHTML = `保存模型出错: ${error.message}`;
|
||||
console.error(error);
|
||||
}
|
||||
})
|
||||
|
||||
content.querySelector('#refresh-models').addEventListener('click', loadAndDisplayAllModels);
|
||||
content.querySelector('#clear-models').addEventListener('click', async () => {
|
||||
if (confirm('确定要删除所有模型吗?此操作不可恢复!')) {
|
||||
await clearAllModels();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createModal();
|
||||
|
||||
await loadAndDisplayAllModels();
|
||||
|
||||
function openModal() {
|
||||
loadAndDisplayAllModels();
|
||||
document.getElementById('modalOverlay').style.display = 'block';
|
||||
}
|
||||
|
||||
const workspace = Blockly.getMainWorkspace();
|
||||
workspace.registerButtonCallback('handleModels', function () {
|
||||
openModal();
|
||||
});
|
||||
|
||||
|
||||
async function prepare_qmyixtxi(imgTensor) {
|
||||
let net = null;
|
||||
|
||||
if (window.featureExtractor) {
|
||||
net = window.featureExtractor;
|
||||
} else {
|
||||
net = await tf.loadGraphModel("../common/media/tfmodel/model.json");
|
||||
window.featureExtractor = net;
|
||||
}
|
||||
const preprocessedImg = imgTensor
|
||||
.resizeBilinear([224, 224])
|
||||
.toFloat()
|
||||
.div(tf.scalar(127.5))
|
||||
.sub(tf.scalar(1))
|
||||
.expandDims(0);
|
||||
|
||||
const features = featureExtractor.predict(preprocessedImg);
|
||||
|
||||
let activation = features;
|
||||
return activation;
|
||||
}
|
||||
window.prepare_qmyixtxi = prepare_qmyixtxi;
|
||||
|
||||
@@ -8,31 +8,6 @@ import teachableModel from './components/teachableModel.vue';
|
||||
// import 'element-plus/theme-chalk/el-notification.css';
|
||||
// import './styles/index.scss';
|
||||
|
||||
|
||||
const userInfo = reactive({
|
||||
is_Login: false,
|
||||
username: '',
|
||||
password: '',
|
||||
})
|
||||
|
||||
const apiurl = ref('http://127.0.0.1:5174')
|
||||
provide('apiurl', apiurl.value)
|
||||
|
||||
provide('userInfo', userInfo)
|
||||
onMounted(() => {
|
||||
// 获取当前的LS里的已经登录的信息
|
||||
if (
|
||||
localStorage.getItem('myAIplatformUsername')
|
||||
&& localStorage.getItem('myAIplatformPassword')
|
||||
) {
|
||||
userInfo.is_Login = true
|
||||
userInfo.username = localStorage.getItem('myAIplatformUsername')
|
||||
userInfo.password = localStorage.getItem('myAIplatformPassword')
|
||||
}
|
||||
else {
|
||||
userInfo.is_Login = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
<script setup>
|
||||
// import * as mobilenet from "@tensorflow-models/mobilenet";
|
||||
import * as tf from '@tensorflow/tfjs';
|
||||
import * as tfvis from '@tensorflow/tfjs-vis';
|
||||
import * as path from 'path';
|
||||
import { inject, ref } from 'vue';
|
||||
import { ElMessage, ElButton, ElCard, ElRow, ElCol, ElInput, ElProgress, ElUpload } from 'element-plus';
|
||||
import { Env } from 'mixly';
|
||||
import * as tf from "@tensorflow/tfjs";
|
||||
import * as tfvis from "@tensorflow/tfjs-vis";
|
||||
import * as path from "path";
|
||||
import { inject, ref } from "vue";
|
||||
import {
|
||||
ElMessage,
|
||||
ElButton,
|
||||
ElCard,
|
||||
ElRow,
|
||||
ElCol,
|
||||
ElInput,
|
||||
ElProgress,
|
||||
ElUpload,
|
||||
} from "element-plus";
|
||||
import { Env } from "mixly";
|
||||
|
||||
// import 'element-plus/theme-chalk/el-message.css';
|
||||
// import 'element-plus/theme-chalk/el-button.css';
|
||||
@@ -16,459 +25,493 @@ import { Env } from 'mixly';
|
||||
// import 'element-plus/theme-chalk/el-progress.css';
|
||||
// import 'element-plus/theme-chalk/el-upload.css';
|
||||
|
||||
|
||||
const emit = defineEmits(['shot'])
|
||||
const emit = defineEmits(["shot"]);
|
||||
// 类别及其样本的列表
|
||||
const picList = inject('picList')
|
||||
const picList = inject("picList");
|
||||
// 图片列表
|
||||
const shotList = inject('shotList')
|
||||
const shotList = inject("shotList");
|
||||
// 训练状态
|
||||
const states = inject('states')
|
||||
const states = inject("states");
|
||||
|
||||
// 用来显示进度条和名称
|
||||
const classList = ref(
|
||||
picList.value.map((item, idx) => ({
|
||||
name: item.title,
|
||||
progress: 0,
|
||||
})),
|
||||
)
|
||||
const progressColors = ['#FF6F61', '#42A5F5', '#66BB6A', '#FFA726', '#AB47BC']
|
||||
picList.value.map((item, idx) => ({
|
||||
name: item.title,
|
||||
progress: 0,
|
||||
}))
|
||||
);
|
||||
const progressColors = ["#FF6F61", "#42A5F5", "#66BB6A", "#FFA726", "#AB47BC"];
|
||||
// 用来存储模型
|
||||
let featureExtractor
|
||||
let featureExtractor;
|
||||
// 用来存储损失值
|
||||
let lossValues = []
|
||||
let lossValues = [];
|
||||
// 存储被训练的模型
|
||||
let model
|
||||
let model;
|
||||
|
||||
// 单击训练按钮后训练模型
|
||||
async function train() {
|
||||
// 可视化相关
|
||||
const visPanel = document.getElementById('vis-left-panel')
|
||||
if (visPanel)
|
||||
visPanel.innerHTML = '训练准备中……'
|
||||
showVisPanel.value = true
|
||||
console.log('正在加载Mobilenet……')
|
||||
// net = await mobilenet.load();
|
||||
featureExtractor = await tf.loadGraphModel(path.join(Env.boardDirPath, 'teachableModel/model.json'))
|
||||
// 可视化相关
|
||||
const visPanel = document.getElementById("vis-left-panel");
|
||||
if (visPanel) visPanel.innerHTML = "训练准备中……";
|
||||
showVisPanel.value = true;
|
||||
console.log("正在加载Mobilenet……");
|
||||
// net = await mobilenet.load();
|
||||
featureExtractor = await tf.loadGraphModel(
|
||||
path.join(Env.boardDirPath, "teachableModel/model.json")
|
||||
);
|
||||
|
||||
console.log('Mobilenet加载完成。')
|
||||
if (visPanel)
|
||||
visPanel.innerHTML = ''
|
||||
console.log("Mobilenet加载完成。");
|
||||
if (visPanel) visPanel.innerHTML = "";
|
||||
|
||||
// 准备数据
|
||||
const NUM_CLASSES = picList.value.length
|
||||
let xs = null
|
||||
let ys = null
|
||||
// 准备模型
|
||||
model = tf.sequential()
|
||||
// 添加全连接层
|
||||
model.add(
|
||||
tf.layers.dense({
|
||||
inputShape: [1280],
|
||||
units: 128,
|
||||
activation: 'relu',
|
||||
}),
|
||||
)
|
||||
// 添加分类层
|
||||
model.add(
|
||||
tf.layers.dense({
|
||||
units: NUM_CLASSES,
|
||||
activation: 'softmax',
|
||||
}),
|
||||
)
|
||||
// 编译模型
|
||||
model.compile({
|
||||
optimizer: tf.train.adam(0.001),
|
||||
loss: 'categoricalCrossentropy',
|
||||
metrics: ['accuracy'],
|
||||
// 准备数据
|
||||
const NUM_CLASSES = picList.value.length;
|
||||
let xs = null;
|
||||
let ys = null;
|
||||
// 准备模型
|
||||
model = tf.sequential();
|
||||
// 添加全连接层
|
||||
model.add(
|
||||
tf.layers.dense({
|
||||
inputShape: [1280],
|
||||
units: 128,
|
||||
activation: "relu",
|
||||
})
|
||||
|
||||
for (let classId = 0; classId < picList.value.length; classId++) {
|
||||
if (picList.value[classId].disabled)
|
||||
continue
|
||||
const images = picList.value[classId].list
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
// 加载图片
|
||||
const imgElement = new Image()
|
||||
imgElement.src = images[i]
|
||||
await new Promise((resolve) => {
|
||||
imgElement.onload = resolve
|
||||
})
|
||||
// 将图片转化为张量
|
||||
const imgTensor = tf.browser.fromPixels(imgElement)
|
||||
|
||||
// 使用神经网络模型进行推理,获取名为"conv_preds"的卷积层激活值
|
||||
// let activation = net.infer(imgTensor, "conv_preds");
|
||||
// 加载特征提取器和你的模型
|
||||
|
||||
// 预处理图像并提取特征
|
||||
const preprocessedImg = imgTensor
|
||||
.resizeBilinear([224, 224])
|
||||
.toFloat()
|
||||
.div(tf.scalar(127.5))
|
||||
.sub(tf.scalar(1))
|
||||
.expandDims(0)
|
||||
|
||||
const features = featureExtractor.predict(preprocessedImg)
|
||||
|
||||
// 将特征输入你的模型
|
||||
// const predictions = net.predict(features);
|
||||
let activation = features
|
||||
// if (activation.shape.length === 3) {
|
||||
// activation = activation.reshape([1, 1024]);
|
||||
// }
|
||||
// let activation = net.predict(imgTensor);
|
||||
// 检查激活值张量的维度,如果是3维张量则进行形状重塑
|
||||
// 3维形状通常为 [height, width, channels],重塑为 [1, 1024] 的张量
|
||||
// 为了适配后续分类层或特征可视化的输入要求
|
||||
|
||||
// 转换为one-hot编码
|
||||
const y = tf.oneHot(tf.tensor1d([classId]).toInt(), NUM_CLASSES)
|
||||
// 初始化xs和ys
|
||||
if (xs == null) {
|
||||
xs = activation.clone()
|
||||
ys = y.clone()
|
||||
}
|
||||
else {
|
||||
const oldXs = xs
|
||||
xs = oldXs.concat(activation, 0)
|
||||
oldXs.dispose()
|
||||
|
||||
const oldYs = ys
|
||||
ys = oldYs.concat(y, 0)
|
||||
oldYs.dispose()
|
||||
}
|
||||
y.dispose()
|
||||
imgTensor.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
// 训练过程可视化相关
|
||||
lossValues = []
|
||||
const metrics = ['loss', 'acc', 'val_loss', 'val_acc', 'accuracy', 'val_accuracy']
|
||||
const container = document.getElementById('vis-left-panel') || {
|
||||
name: '训练过程',
|
||||
tab: '训练',
|
||||
}
|
||||
// 训练模型
|
||||
await model.fit(xs, ys, {
|
||||
epochs: 20,
|
||||
batchSize: 16,
|
||||
shuffle: true,
|
||||
validationSplit: 0.2,
|
||||
callbacks: tfvis.show.fitCallbacks(container, metrics, {
|
||||
callbacks: ['onEpochEnd'],
|
||||
}),
|
||||
);
|
||||
// 添加分类层
|
||||
model.add(
|
||||
tf.layers.dense({
|
||||
units: NUM_CLASSES,
|
||||
activation: "softmax",
|
||||
})
|
||||
console.log('训练完成')
|
||||
// 与显示进度条相关
|
||||
classList.value = picList.value
|
||||
.filter(item => item.disabled !== true)
|
||||
.map((item, idx) => ({
|
||||
name: item.title,
|
||||
progress: 0,
|
||||
}))
|
||||
console.log(classList.value)
|
||||
states.value.isTraining = 2
|
||||
);
|
||||
// 编译模型
|
||||
model.compile({
|
||||
optimizer: tf.train.adam(0.001),
|
||||
loss: "categoricalCrossentropy",
|
||||
metrics: ["accuracy"],
|
||||
});
|
||||
|
||||
for (let classId = 0; classId < picList.value.length; classId++) {
|
||||
if (picList.value[classId].disabled) continue;
|
||||
const images = picList.value[classId].list;
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
// 加载图片
|
||||
const imgElement = new Image();
|
||||
imgElement.src = images[i];
|
||||
await new Promise((resolve) => {
|
||||
imgElement.onload = resolve;
|
||||
});
|
||||
// 将图片转化为张量
|
||||
const imgTensor = tf.browser.fromPixels(imgElement);
|
||||
|
||||
// 使用神经网络模型进行推理,获取名为"conv_preds"的卷积层激活值
|
||||
// let activation = net.infer(imgTensor, "conv_preds");
|
||||
// 加载特征提取器和你的模型
|
||||
|
||||
// 预处理图像并提取特征
|
||||
const preprocessedImg = imgTensor
|
||||
.resizeBilinear([224, 224])
|
||||
.toFloat()
|
||||
.div(tf.scalar(127.5))
|
||||
.sub(tf.scalar(1))
|
||||
.expandDims(0);
|
||||
|
||||
const features = featureExtractor.predict(preprocessedImg);
|
||||
|
||||
// 将特征输入你的模型
|
||||
// const predictions = net.predict(features);
|
||||
let activation = features;
|
||||
// if (activation.shape.length === 3) {
|
||||
// activation = activation.reshape([1, 1024]);
|
||||
// }
|
||||
// let activation = net.predict(imgTensor);
|
||||
// 检查激活值张量的维度,如果是3维张量则进行形状重塑
|
||||
// 3维形状通常为 [height, width, channels],重塑为 [1, 1024] 的张量
|
||||
// 为了适配后续分类层或特征可视化的输入要求
|
||||
|
||||
// 转换为one-hot编码
|
||||
const y = tf.oneHot(tf.tensor1d([classId]).toInt(), NUM_CLASSES);
|
||||
// 初始化xs和ys
|
||||
if (xs == null) {
|
||||
xs = activation.clone();
|
||||
ys = y.clone();
|
||||
} else {
|
||||
const oldXs = xs;
|
||||
xs = oldXs.concat(activation, 0);
|
||||
oldXs.dispose();
|
||||
|
||||
const oldYs = ys;
|
||||
ys = oldYs.concat(y, 0);
|
||||
oldYs.dispose();
|
||||
}
|
||||
y.dispose();
|
||||
imgTensor.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// 训练过程可视化相关
|
||||
lossValues = [];
|
||||
const metrics = ["loss", "acc", "val_loss", "val_acc", "accuracy", "val_accuracy"];
|
||||
const container = document.getElementById("vis-left-panel") || {
|
||||
name: "训练过程",
|
||||
tab: "训练",
|
||||
};
|
||||
// 训练模型
|
||||
await model.fit(xs, ys, {
|
||||
epochs: 20,
|
||||
batchSize: 16,
|
||||
shuffle: true,
|
||||
validationSplit: 0.2,
|
||||
callbacks: tfvis.show.fitCallbacks(container, metrics, {
|
||||
callbacks: ["onEpochEnd"],
|
||||
}),
|
||||
});
|
||||
console.log("训练完成");
|
||||
// 与显示进度条相关
|
||||
classList.value = picList.value
|
||||
.filter((item) => item.disabled !== true)
|
||||
.map((item, idx) => ({
|
||||
name: item.title,
|
||||
progress: 0,
|
||||
}));
|
||||
console.log(classList.value);
|
||||
states.value.isTraining = 2;
|
||||
}
|
||||
|
||||
setInterval(async () => {
|
||||
// 没训练完成跳过
|
||||
if (states.value.isTraining !== 2)
|
||||
return
|
||||
// 输入是“上传图片”时跳过
|
||||
if (uploadedImg.value !== '')
|
||||
return
|
||||
// 通知摄像头拍摄照片
|
||||
emit('shot')
|
||||
// 加载拍摄的照片
|
||||
const img = shotList.value[shotList.value.length - 1]
|
||||
if (!img || img === 'data:,') {
|
||||
ElMessage.error('未获取到有效样本')
|
||||
return
|
||||
}
|
||||
const imgElement = new Image()
|
||||
imgElement.src = img
|
||||
await new Promise((resolve) => {
|
||||
imgElement.onload = resolve
|
||||
})
|
||||
// 将图片转换为张量
|
||||
const imgTensor = tf.browser.fromPixels(imgElement)
|
||||
// let activation = net.infer(imgTensor, "conv_preds");
|
||||
let resized = tf.image.resizeBilinear(imgTensor, [224, 224])
|
||||
let batched = resized.expandDims(0)
|
||||
let normalized = batched.div(255)
|
||||
// let activation = net.predict(imgTensor);
|
||||
let activation = featureExtractor.predict(normalized)
|
||||
const pred = model.predict(activation)
|
||||
const predArr = await pred.data()
|
||||
// console.log(predArr)
|
||||
classList.value = [
|
||||
...classList.value.map((item, idx) => ({
|
||||
...item,
|
||||
progress: Number((predArr[idx] * 100).toFixed(2)),
|
||||
})),
|
||||
]
|
||||
// console.log(classList.value)
|
||||
imgTensor.dispose()
|
||||
activation.dispose()
|
||||
pred.dispose()
|
||||
}, 200)
|
||||
// 没训练完成跳过
|
||||
if (states.value.isTraining !== 2) return;
|
||||
// 输入是“上传图片”时跳过
|
||||
if (uploadedImg.value !== "") return;
|
||||
// 通知摄像头拍摄照片
|
||||
emit("shot");
|
||||
// 加载拍摄的照片
|
||||
const img = shotList.value[shotList.value.length - 1];
|
||||
if (!img || img === "data:,") {
|
||||
ElMessage.error("未获取到有效样本");
|
||||
return;
|
||||
}
|
||||
const imgElement = new Image();
|
||||
imgElement.src = img;
|
||||
await new Promise((resolve) => {
|
||||
imgElement.onload = resolve;
|
||||
});
|
||||
// 将图片转换为张量
|
||||
const imgTensor = tf.browser.fromPixels(imgElement);
|
||||
// let activation = net.infer(imgTensor, "conv_preds");
|
||||
let resized = tf.image.resizeBilinear(imgTensor, [224, 224]);
|
||||
let batched = resized.expandDims(0);
|
||||
let normalized = batched.div(255);
|
||||
// let activation = net.predict(imgTensor);
|
||||
let activation = featureExtractor.predict(normalized);
|
||||
const pred = model.predict(activation);
|
||||
const predArr = await pred.data();
|
||||
// console.log(predArr)
|
||||
classList.value = [
|
||||
...classList.value.map((item, idx) => ({
|
||||
...item,
|
||||
progress: Number((predArr[idx] * 100).toFixed(2)),
|
||||
})),
|
||||
];
|
||||
// console.log(classList.value)
|
||||
imgTensor.dispose();
|
||||
activation.dispose();
|
||||
pred.dispose();
|
||||
}, 200);
|
||||
|
||||
const showVisPanel = ref(false)
|
||||
const uploadedImg = ref('')
|
||||
const uploadedResult = ref('')
|
||||
const panelTop = ref('20%')
|
||||
const panelRight = ref('20%')
|
||||
const showVisPanel = ref(false);
|
||||
const uploadedImg = ref("");
|
||||
const uploadedResult = ref("");
|
||||
const panelTop = ref("20%");
|
||||
const panelRight = ref("20%");
|
||||
|
||||
async function exportModel() {
|
||||
if (!model) {
|
||||
ElMessage.error('模型尚未训练完成')
|
||||
return
|
||||
}
|
||||
try {
|
||||
// 导出模型到本地文件系统
|
||||
await model.save(`downloads://${modelName.value == '' ? 'my-model' : modelName.value}`)
|
||||
ElMessage.success('模型导出成功')
|
||||
}
|
||||
catch (error) {
|
||||
ElMessage.error(`模型导出失败: ${error.message}`)
|
||||
}
|
||||
if (!model) {
|
||||
ElMessage.error("模型尚未训练完成");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 导出模型到本地文件系统
|
||||
await model.save(
|
||||
`downloads://${modelName.value == "" ? "my-model" : modelName.value}`
|
||||
);
|
||||
ElMessage.success("模型导出成功");
|
||||
} catch (error) {
|
||||
ElMessage.error(`模型导出失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUpload(file) {
|
||||
const reader = new FileReader()
|
||||
reader.onload = async (e) => {
|
||||
uploadedImg.value = e.target.result
|
||||
if (!featureExtractor || !model) {
|
||||
ElMessage.error('请先完成模型训练')
|
||||
return false
|
||||
}
|
||||
const imgElement = new window.Image()
|
||||
imgElement.src = uploadedImg.value
|
||||
await new Promise(resolve => (imgElement.onload = resolve))
|
||||
const imgTensor = tf.browser.fromPixels(imgElement)
|
||||
// let activation = net.infer(imgTensor, "conv_preds");
|
||||
let resized = tf.image.resizeBilinear(imgTensor, [224, 224])
|
||||
let batched = resized.expandDims(0)
|
||||
let normalized = batched.div(255)
|
||||
// let activation = net.predict(imgTensor);
|
||||
let activation = featureExtractor.predict(normalized)
|
||||
const pred = model.predict(activation)
|
||||
const predArr = await pred.data()
|
||||
classList.value = [
|
||||
...classList.value.map((item, idx) => ({
|
||||
...item,
|
||||
progress: Number((predArr[idx] * 100).toFixed(2)),
|
||||
})),
|
||||
]
|
||||
const maxIdx = predArr.indexOf(Math.max(...predArr))
|
||||
uploadedResult.value = classList.value[maxIdx]?.name || '未知'
|
||||
imgTensor.dispose()
|
||||
activation.dispose()
|
||||
pred.dispose()
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
uploadedImg.value = e.target.result;
|
||||
if (!featureExtractor || !model) {
|
||||
ElMessage.error("请先完成模型训练");
|
||||
return false;
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
return false
|
||||
const imgElement = new window.Image();
|
||||
imgElement.src = uploadedImg.value;
|
||||
await new Promise((resolve) => (imgElement.onload = resolve));
|
||||
const imgTensor = tf.browser.fromPixels(imgElement);
|
||||
// let activation = net.infer(imgTensor, "conv_preds");
|
||||
let resized = tf.image.resizeBilinear(imgTensor, [224, 224]);
|
||||
let batched = resized.expandDims(0);
|
||||
let normalized = batched.div(255);
|
||||
// let activation = net.predict(imgTensor);
|
||||
let activation = featureExtractor.predict(normalized);
|
||||
const pred = model.predict(activation);
|
||||
const predArr = await pred.data();
|
||||
classList.value = [
|
||||
...classList.value.map((item, idx) => ({
|
||||
...item,
|
||||
progress: Number((predArr[idx] * 100).toFixed(2)),
|
||||
})),
|
||||
];
|
||||
const maxIdx = predArr.indexOf(Math.max(...predArr));
|
||||
uploadedResult.value = classList.value[maxIdx]?.name || "未知";
|
||||
imgTensor.dispose();
|
||||
activation.dispose();
|
||||
pred.dispose();
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
function switchToUpload() {
|
||||
uploadedImg.value = ''
|
||||
uploadedImg.value = "";
|
||||
}
|
||||
|
||||
let startX = 0
|
||||
let startY = 0
|
||||
let startTop = 0
|
||||
let startRight = 0
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
let startTop = 0;
|
||||
let startRight = 0;
|
||||
|
||||
function onDragStart(e) {
|
||||
if (e.button !== 0)
|
||||
return
|
||||
startX = e.clientX
|
||||
startY = e.clientY
|
||||
const topVal = document.getElementById('vis-left-panel-wrapper').style.top
|
||||
const rightVal = document.getElementById('vis-left-panel-wrapper').style.right
|
||||
startTop = topVal.endsWith('%')
|
||||
? (window.innerHeight * Number.parseFloat(topVal)) / 100
|
||||
: Number.parseFloat(topVal)
|
||||
startRight = rightVal.endsWith('%')
|
||||
? (window.innerWidth * Number.parseFloat(rightVal)) / 100
|
||||
: Number.parseFloat(rightVal)
|
||||
if (e.button !== 0) return;
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
const topVal = document.getElementById("vis-left-panel-wrapper").style.top;
|
||||
const rightVal = document.getElementById("vis-left-panel-wrapper").style.right;
|
||||
startTop = topVal.endsWith("%")
|
||||
? (window.innerHeight * Number.parseFloat(topVal)) / 100
|
||||
: Number.parseFloat(topVal);
|
||||
startRight = rightVal.endsWith("%")
|
||||
? (window.innerWidth * Number.parseFloat(rightVal)) / 100
|
||||
: Number.parseFloat(rightVal);
|
||||
|
||||
document.addEventListener('mousemove', onDragging)
|
||||
document.addEventListener('mouseup', onDragEnd)
|
||||
document.addEventListener("mousemove", onDragging);
|
||||
document.addEventListener("mouseup", onDragEnd);
|
||||
}
|
||||
|
||||
function onDragging(e) {
|
||||
const deltaX = e.clientX - startX
|
||||
const deltaY = e.clientY - startY
|
||||
let newTop = startTop + deltaY
|
||||
let newRight = startRight - deltaX
|
||||
newTop = Math.max(0, Math.min(window.innerHeight - 100, newTop))
|
||||
newRight = Math.max(0, Math.min(window.innerWidth - 200, newRight))
|
||||
panelTop.value = `${newTop}px`
|
||||
panelRight.value = `${newRight}px`
|
||||
const deltaX = e.clientX - startX;
|
||||
const deltaY = e.clientY - startY;
|
||||
let newTop = startTop + deltaY;
|
||||
let newRight = startRight - deltaX;
|
||||
newTop = Math.max(0, Math.min(window.innerHeight - 100, newTop));
|
||||
newRight = Math.max(0, Math.min(window.innerWidth - 200, newRight));
|
||||
panelTop.value = `${newTop}px`;
|
||||
panelRight.value = `${newRight}px`;
|
||||
}
|
||||
|
||||
function onDragEnd() {
|
||||
document.removeEventListener('mousemove', onDragging)
|
||||
document.removeEventListener('mouseup', onDragEnd)
|
||||
document.removeEventListener("mousemove", onDragging);
|
||||
document.removeEventListener("mouseup", onDragEnd);
|
||||
}
|
||||
|
||||
defineExpose({ train })
|
||||
defineExpose({ train });
|
||||
|
||||
const modelName = ref('')
|
||||
const modelName = ref("");
|
||||
async function saveModel() {
|
||||
if (!model) {
|
||||
ElMessage.error('模型尚未训练完成')
|
||||
return
|
||||
}
|
||||
try {
|
||||
// 导出模型到本地文件系统
|
||||
await model.save(`indexeddb://${modelName.value == '' ? 'my-model' : modelName.value}`)
|
||||
ElMessage.success('模型保存成功')
|
||||
}
|
||||
catch (error) {
|
||||
ElMessage.error(`模型保存失败: ${error.message}`)
|
||||
}
|
||||
if (!model) {
|
||||
ElMessage.error("模型尚未训练完成");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 导出模型到本地文件系统
|
||||
await model.save(
|
||||
`indexeddb://${modelName.value == "" ? "my-model" : modelName.value}`
|
||||
);
|
||||
ElMessage.success("模型保存成功");
|
||||
} catch (error) {
|
||||
ElMessage.error(`模型保存失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-show="showVisPanel" id="vis-left-panel-wrapper" class="vis-left-panel-wrapper"
|
||||
:style="`right: ${panelRight}; top: ${panelTop};`">
|
||||
<div class="vis-left-panel-inner-wrapper">
|
||||
<div class="vis-left-panel-title" style="" @mousedown="onDragStart">
|
||||
<span> 训练过程可视化 </span>
|
||||
<ElButton size="small" plain @click="showVisPanel = false">
|
||||
隐藏
|
||||
</ElButton>
|
||||
</div>
|
||||
<div id="vis-left-panel" class="vis-left-panel">
|
||||
训练准备中……
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
v-show="showVisPanel"
|
||||
id="vis-left-panel-wrapper"
|
||||
class="vis-left-panel-wrapper"
|
||||
:style="`right: ${panelRight}; top: ${panelTop};`"
|
||||
>
|
||||
<div class="vis-left-panel-inner-wrapper">
|
||||
<div class="vis-left-panel-title" style="" @mousedown="onDragStart">
|
||||
<span> 训练过程可视化 </span>
|
||||
<ElButton size="small" plain @click="showVisPanel = false"> 隐藏 </ElButton>
|
||||
</div>
|
||||
<ElCard v-if="states.isTraining === 2" class="model-area">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
模型
|
||||
<ElButton v-if="!showVisPanel" size="small" style="margin-left: 10px" type="primary" plain
|
||||
@click="showVisPanel = true">
|
||||
显示训练过程
|
||||
</ElButton>
|
||||
<ElButton v-if="states.isTraining === 2" size="small" style="margin-left: 10px" type="success"
|
||||
plain @click="exportModel">
|
||||
模型导出至本地
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<ElRow class="model-item" style="flex-direction: column; align-items: flex-center">
|
||||
<ElRow>
|
||||
<ElCol :span="6" style="display: flex; align-items: center; text-align: right;">
|
||||
名称:
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElInput v-model="modelName" placeholder="请输入模型名称" />
|
||||
</ElCol>
|
||||
<ElCol :span="4" style="display: flex; align-items: center;">
|
||||
<ElButton style="margin: auto 5px" type="primary" plain size="small" @click="saveModel">
|
||||
保存
|
||||
</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow class="model-item">
|
||||
<b>输入</b>
|
||||
</ElRow>
|
||||
<div v-if="!uploadedImg" style="margin: auto">
|
||||
上方拍摄内容
|
||||
<ElUpload :show-file-list="false" accept="image/*" :before-upload="handleUpload">
|
||||
<ElButton size="small" type="success">
|
||||
切换为上传图片
|
||||
</ElButton>
|
||||
</ElUpload>
|
||||
</div>
|
||||
<div v-else style="margin: auto">
|
||||
下方上传图片 <br>
|
||||
<ElButton size="small" type="success" plain @click="switchToUpload">
|
||||
切换为上方拍摄内容
|
||||
</ElButton>
|
||||
<ElUpload :show-file-list="false" accept="image/*" :before-upload="handleUpload">
|
||||
<ElButton size="small" type="success">
|
||||
重新上传一张
|
||||
</ElButton>
|
||||
</ElUpload>
|
||||
<img :src="uploadedImg" alt="用户上传图片"
|
||||
style="max-width: 100%; max-height: 150px; border-radius: 10px">
|
||||
</div>
|
||||
</ElRow>
|
||||
<ElRow class="model-item">
|
||||
<b>输出</b>
|
||||
</ElRow>
|
||||
<ElRow v-for="(item, idx) in classList" :key="`${item.name}-${idx}`" class="model-item">
|
||||
<ElCol :span="6">
|
||||
{{ item.name }}
|
||||
</ElCol>
|
||||
<ElCol :span="18">
|
||||
<ElProgress class="progress" :text-inside="true" :stroke-width="20" :percentage="item.progress"
|
||||
:color="progressColors[idx % progressColors.length]" striped :format="(p) => `${p}%`" />
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ElCard>
|
||||
<div id="vis-left-panel" class="vis-left-panel">训练准备中……</div>
|
||||
</div>
|
||||
</div>
|
||||
<ElCard v-if="states.isTraining === 2" class="model-area">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
模型
|
||||
<ElButton
|
||||
v-if="!showVisPanel"
|
||||
size="small"
|
||||
style="margin-left: 10px"
|
||||
type="primary"
|
||||
plain
|
||||
@click="showVisPanel = true"
|
||||
>
|
||||
显示训练过程
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-if="states.isTraining === 2"
|
||||
size="small"
|
||||
style="margin-left: 10px"
|
||||
type="success"
|
||||
plain
|
||||
@click="exportModel"
|
||||
>
|
||||
模型导出至本地
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<ElRow class="model-item" style="flex-direction: column; align-items: flex-center">
|
||||
<ElRow>
|
||||
<ElCol :span="6" style="display: flex; align-items: center; text-align: right">
|
||||
名称:
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElInput v-model="modelName" placeholder="请输入模型名称" />
|
||||
</ElCol>
|
||||
<ElCol :span="4" style="display: flex; align-items: center">
|
||||
<ElButton
|
||||
style="margin: auto 5px"
|
||||
type="primary"
|
||||
plain
|
||||
size="small"
|
||||
@click="saveModel"
|
||||
>
|
||||
保存
|
||||
</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ElRow>
|
||||
<ElRow class="model-item">
|
||||
<b>输入</b>
|
||||
</ElRow>
|
||||
<ElRow class="model-item">
|
||||
<div v-if="!uploadedImg" style="margin: auto">
|
||||
上方拍摄内容
|
||||
<ElUpload
|
||||
:show-file-list="false"
|
||||
accept="image/*"
|
||||
:before-upload="handleUpload"
|
||||
>
|
||||
<ElButton size="small" type="success"> 切换为上传图片 </ElButton>
|
||||
</ElUpload>
|
||||
</div>
|
||||
<div v-else style="margin: auto">
|
||||
下方上传图片 <br />
|
||||
<ElButton size="small" type="success" plain @click="switchToUpload">
|
||||
切换为上方拍摄内容
|
||||
</ElButton>
|
||||
<ElUpload
|
||||
:show-file-list="false"
|
||||
accept="image/*"
|
||||
:before-upload="handleUpload"
|
||||
>
|
||||
<ElButton size="small" type="success"> 重新上传一张 </ElButton>
|
||||
</ElUpload>
|
||||
<img
|
||||
:src="uploadedImg"
|
||||
alt="用户上传图片"
|
||||
style="max-width: 100%; max-height: 150px; border-radius: 10px"
|
||||
/>
|
||||
</div>
|
||||
</ElRow>
|
||||
<ElRow class="model-item">
|
||||
<b>输出</b>
|
||||
</ElRow>
|
||||
<ElRow
|
||||
v-for="(item, idx) in classList"
|
||||
:key="`${item.name}-${idx}`"
|
||||
class="model-item"
|
||||
>
|
||||
<ElCol :span="6">
|
||||
{{ item.name }}
|
||||
</ElCol>
|
||||
<ElCol :span="18">
|
||||
<ElProgress
|
||||
class="progress"
|
||||
:text-inside="true"
|
||||
:stroke-width="20"
|
||||
:percentage="item.progress"
|
||||
:color="progressColors[idx % progressColors.length]"
|
||||
striped
|
||||
:format="(p) => `${p}%`"
|
||||
/>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.vis-left-panel-wrapper {
|
||||
transition: all 0.3s;
|
||||
position: fixed;
|
||||
width: 400px;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 0 20px #0001;
|
||||
transition: all 0.3s;
|
||||
position: fixed;
|
||||
width: 400px;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 0 20px #0001;
|
||||
}
|
||||
|
||||
.vis-left-panel-inner-wrapper {
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 8px #0001;
|
||||
padding: 10px 10px 0 10px;
|
||||
border: 1px solid #eee;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 8px #0001;
|
||||
padding: 10px 10px 0 10px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.vis-left-panel-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.vis-left-panel {
|
||||
min-height: 300px;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.model-area {
|
||||
margin: 10px auto;
|
||||
width: 85%;
|
||||
max-width: 300px;
|
||||
border-radius: 30px;
|
||||
margin: 10px auto;
|
||||
width: 85%;
|
||||
max-width: 300px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.model-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3724,6 +3724,128 @@
|
||||
</value>
|
||||
</block>
|
||||
</category>
|
||||
<category id="catTensorflow" name="Tensorflow" colour="#1216ab">
|
||||
<button text="导入模型" callbackKey="handleModels"></button>
|
||||
<block type="tensorflow_init_tensor">
|
||||
<value name="VAR">
|
||||
<shadow type="list_many_input">
|
||||
<field name="CONTENT">1,2,3,4,5</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="tensorflow_sequential">
|
||||
</block>
|
||||
<block type="tensorflow_layers_dense">
|
||||
<value name="VAR1">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="VAR2">
|
||||
<shadow type="list_many_input">
|
||||
<field name="CONTENT">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="tensorflow_add">
|
||||
<value name="VAR1">
|
||||
<shadow type="variables_get">
|
||||
<field name="VAR">model</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="VAR2">
|
||||
<shadow type="variables_get">
|
||||
<field name="VAR">layer</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="tensorflow_compile">
|
||||
<value name="VAR1">
|
||||
<shadow type="variables_get">
|
||||
<field name="VAR">model</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="tensorflow_fit">
|
||||
<value name="VAR1">
|
||||
<shadow type="variables_get">
|
||||
<field name="VAR">model</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="VAR2">
|
||||
<shadow type="variables_get">
|
||||
<field name="VAR">xs</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="VAR3">
|
||||
<shadow type="variables_get">
|
||||
<field name="VAR">ys</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="VAR4">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="VAR5">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">0</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="tensorflow_get_loss">
|
||||
<value name="VAR">
|
||||
<shadow type="variables_get">
|
||||
<field name="VAR">history</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="tensorflow_predict">
|
||||
<value name="VAR1">
|
||||
<shadow type="variables_get">
|
||||
<field name="VAR">model</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="VAR2">
|
||||
<shadow type="variables_get">
|
||||
<field name="VAR">inputTensor</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="tensorflow_get_tensor_data">
|
||||
<value name="VAR">
|
||||
<shadow type="variables_get">
|
||||
<field name="VAR">outputTensor</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="tensorflow_save_or_export_model">
|
||||
<value name="NAME1">
|
||||
<shadow type="variables_get">
|
||||
<field name="VAR">model</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="NAME2">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">my-model</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="tensorflow_use_load_model">
|
||||
<value name="NAME">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">my-model</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="tensorflow_prepare_picture">
|
||||
<value name="NAME">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">1.jpg</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
</category>
|
||||
<category id="catFactory" name="Factory" colour="#777777">
|
||||
<block type="factory_import"></block>
|
||||
<block type="factory_from_import"></block>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
from pyodide.ffi import to_js, create_proxy
|
||||
import js
|
||||
import json
|
||||
import os
|
||||
from . import layers
|
||||
|
||||
|
||||
def __cache_model(file_path):
|
||||
data = json.load(open(file_path, 'r'))
|
||||
f = open(file_path, 'rb')
|
||||
js.tensorflow.setModelsValue(file_path, to_js(f.read()))
|
||||
f.close()
|
||||
folder_path = os.path.dirname(file_path)
|
||||
for item in data['weightsManifest']:
|
||||
for current_path in item['paths']:
|
||||
bin_file_path = '{}/{}'.format(folder_path, current_path)
|
||||
f = open(bin_file_path, 'rb')
|
||||
js.tensorflow.setModelsValue(bin_file_path, to_js(f.read()))
|
||||
f.close()
|
||||
|
||||
|
||||
async def load_graph_model(file_path):
|
||||
__cache_model(file_path)
|
||||
model = await js.tensorflow.loadGraphModel(file_path)
|
||||
return model
|
||||
|
||||
|
||||
async def load_layers_model(file_path):
|
||||
__cache_model(file_path)
|
||||
model = await js.tensorflow.loadLayersModel(file_path)
|
||||
return model
|
||||
|
||||
def tensor(data):
|
||||
return js.tf.tensor(to_js(data))
|
||||
|
||||
def sequential():
|
||||
return js.tf.sequential()
|
||||
|
||||
async def load_model(name):
|
||||
model = await js.tf.loadLayersModel(f"indexeddb://{name}")
|
||||
return model
|
||||
|
||||
async def prepare_qmyixtxi(imgTensor):
|
||||
return await js.prepare_qmyixtxi(imgTensor)
|
||||
@@ -0,0 +1,5 @@
|
||||
from .activation import *
|
||||
from .core import *
|
||||
|
||||
from pyodide.ffi import to_js, create_proxy
|
||||
import js
|
||||
@@ -0,0 +1,37 @@
|
||||
import js
|
||||
|
||||
|
||||
def elu(*args, **kwargs):
|
||||
'''
|
||||
f(x) = alpha * (exp(x) - 1.) for x < 0, f(x) = x for x >= 0.
|
||||
'''
|
||||
js.tensorflow.layers.elu(*args, **kwargs)
|
||||
|
||||
|
||||
def leaky_relu(*args, **kwargs):
|
||||
'''
|
||||
f(x) = alpha * x for x < 0. f(x) = x for x >= 0.
|
||||
'''
|
||||
js.tensorflow.layers.leakyReLU(*args, **kwargs)
|
||||
|
||||
|
||||
def prelu(*args, **kwargs):
|
||||
'''
|
||||
f(x) = alpha * x for x < 0. f(x) = x for x >= 0.
|
||||
'''
|
||||
js.tensorflow.layers.prelu(*args, **kwargs)
|
||||
|
||||
|
||||
def relu(*args, **kwargs):
|
||||
js.tensorflow.layers.relu(*args, **kwargs)
|
||||
|
||||
|
||||
def softmax(*args, **kwargs):
|
||||
js.tensorflow.layers.softmax(*args, **kwargs)
|
||||
|
||||
|
||||
def thresholded_relu(*args, **kwargs):
|
||||
'''
|
||||
f(x) = x for x > theta, f(x) = 0 otherwise.
|
||||
'''
|
||||
js.tensorflow.layers.thresholdedReLU(*args, **kwargs)
|
||||
@@ -0,0 +1,5 @@
|
||||
import js
|
||||
from pyodide.ffi import to_js
|
||||
|
||||
def dense(units, input_shape=None):
|
||||
return js.tf.layers.dense(units=units, inputShape=to_js(input_shape))
|
||||
BIN
boards/default_src/python_pyodide/whl/tensorflow-project/dist/tensorflow-0.0.1-py3-none-any.whl
vendored
Normal file
BIN
boards/default_src/python_pyodide/whl/tensorflow-project/dist/tensorflow-0.0.1-py3-none-any.whl
vendored
Normal file
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: tensorflow
|
||||
Version: 0.0.1
|
||||
Summary: 适用于pyodide的tensorflowjs包
|
||||
Author: Mixly Team
|
||||
Author-email:
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Dynamic: author
|
||||
Dynamic: classifier
|
||||
Dynamic: summary
|
||||
@@ -0,0 +1,10 @@
|
||||
setup.py
|
||||
tensorflow/__init__.py
|
||||
tensorflow.egg-info/PKG-INFO
|
||||
tensorflow.egg-info/SOURCES.txt
|
||||
tensorflow.egg-info/dependency_links.txt
|
||||
tensorflow.egg-info/top_level.txt
|
||||
tensorflow/layers/__init__.py
|
||||
tensorflow/layers/activation.py
|
||||
tensorflow/layers/base.py
|
||||
tensorflow/layers/core.py
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
tensorflow
|
||||
@@ -2,6 +2,7 @@ from pyodide.ffi import to_js, create_proxy
|
||||
import js
|
||||
import json
|
||||
import os
|
||||
from . import layers
|
||||
|
||||
|
||||
def __cache_model(file_path):
|
||||
@@ -28,3 +29,16 @@ async def load_layers_model(file_path):
|
||||
__cache_model(file_path)
|
||||
model = await js.tensorflow.loadLayersModel(file_path)
|
||||
return model
|
||||
|
||||
def tensor(data):
|
||||
return js.tf.tensor(to_js(data))
|
||||
|
||||
def sequential():
|
||||
return js.tf.sequential()
|
||||
|
||||
async def load_model(name):
|
||||
model = await js.tf.loadLayersModel(f"indexeddb://{name}")
|
||||
return model
|
||||
|
||||
async def prepare_qmyixtxi(imgTensor):
|
||||
return await js.prepare_qmyixtxi(imgTensor)
|
||||
@@ -1 +1,5 @@
|
||||
from activation import *
|
||||
from .activation import *
|
||||
from .core import *
|
||||
|
||||
from pyodide.ffi import to_js, create_proxy
|
||||
import js
|
||||
@@ -0,0 +1,5 @@
|
||||
import js
|
||||
from pyodide.ffi import to_js
|
||||
|
||||
def dense(units, input_shape=None):
|
||||
return js.tf.layers.dense(units=units, inputShape=to_js(input_shape))
|
||||
BIN
common/media/tfmodel/group1-shard1of3.bin
Normal file
BIN
common/media/tfmodel/group1-shard1of3.bin
Normal file
Binary file not shown.
BIN
common/media/tfmodel/group1-shard2of3.bin
Normal file
BIN
common/media/tfmodel/group1-shard2of3.bin
Normal file
Binary file not shown.
BIN
common/media/tfmodel/group1-shard3of3.bin
Normal file
BIN
common/media/tfmodel/group1-shard3of3.bin
Normal file
Binary file not shown.
1
common/media/tfmodel/model.json
Normal file
1
common/media/tfmodel/model.json
Normal file
File diff suppressed because one or more lines are too long
@@ -4009,4 +4009,36 @@ En.MIXLY_TINY_WEB_DB_START_NUMBER = 'Start number';
|
||||
En.MIXLY_TINY_WEB_DB_VARIABLE_NUMBER = 'Number of variables';
|
||||
En.MIXLY_TINY_WEB_DB_SEARCH_VARS = 'Characters in variable names';
|
||||
|
||||
|
||||
En.MIXLY_TENSORFLOW_INIT_TENSOR = 'Initialize tensor as';
|
||||
En.MIXLY_TENSORFLOW_SEQUENTIAL = 'Initialize sequential model';
|
||||
En.MIXLY_TENSORFLOW_INIT_LAYERS_DENSE_LAYER = 'Build dense layer';
|
||||
En.MIXLY_TENSORFLOW_OUTPUT_DIMENSION = 'Output dimension';
|
||||
En.MIXLY_TENSORFLOW_INPUT_SHAPE = 'Input shape';
|
||||
En.MIXLY_TENSORFLOW_MODEL = 'Model';
|
||||
En.MIXLY_TENSORFLOW_ADD_LAYER = 'Add layer';
|
||||
En.MIXLY_TENSORFLOW_COMPILE_MODEL = 'Compile model';
|
||||
En.MIXLY_TENSORFLOW_LOSS_FUNCTION_TYPE = 'Loss function type';
|
||||
En.MIXLY_TENSORFLOW_MEAN_SQUARED_ERROR = 'Mean squared error';
|
||||
En.MIXLY_TENSORFLOW_OPTIMIZER = 'Optimizer';
|
||||
En.MIXLY_TENSORFLOW_SGD = 'Stochastic gradient descent';
|
||||
En.MIXLY_TENSORFLOW_FIT_MODEL = 'Train model';
|
||||
En.MIXLY_TENSORFLOW_FIT_INPUT_DATA = 'Input data';
|
||||
En.MIXLY_TENSORFLOW_FIT_TARGET_DATA = 'Target data';
|
||||
En.MIXLY_TENSORFLOW_FIT_EPOCHS = 'Training epochs';
|
||||
En.MIXLY_TENSORFLOW_FIT_VERBOSE = 'Verbose level';
|
||||
En.MIXLY_TENSORFLOW_FIT_RETURN_HISTORY = 'Return training history object';
|
||||
En.MIXLY_TENSORFLOW_GET_LOSS_FROM_HISTORY = 'From training history object';
|
||||
En.MIXLY_TENSORFLOW_GET_LOSS_FROM_HISTORY_2 = 'Get training loss values array';
|
||||
En.MIXLY_TENSORFLOW_PREDICT = 'Use model for prediction';
|
||||
En.MIXLY_TENSORFLOW_PREDICT_INPUT_DATA = 'Input data';
|
||||
En.MIXLY_TENSORFLOW_PREDICT_RETURN_RESULT = 'Return prediction result tensor';
|
||||
En.MIXLY_TENSORFLOW_GET_TENSOR_DATA = 'Get data from tensor';
|
||||
En.MIXLY_TENSORFLOW_SAVE_MODEL = 'Save model';
|
||||
En.MIXLY_TENSORFLOW_EXPORT_MODEL = 'Export model';
|
||||
En.MIXLY_TENSORFLOW_SAVE_MODEL_NAME = 'Name';
|
||||
En.MIXLY_TENSORFLOW_LOAD_MODEL = 'Use imported model';
|
||||
En.MIXLY_TENSORFLOW_MODEL_NAME = 'Model name';
|
||||
En.MIXLY_TENSORFLOW_PREPARE_PICTURE_TO_TENSOR = 'Preprocess image to tensor';
|
||||
En.MIXLY_TENSORFLOW_PREPARE_PICTURE_READ_PICTURE = 'Read image';
|
||||
})();
|
||||
|
||||
@@ -4168,4 +4168,35 @@ ZhHans.MIXLY_TINY_WEB_DB_START_NUMBER = '起始编号';
|
||||
ZhHans.MIXLY_TINY_WEB_DB_VARIABLE_NUMBER = '变量个数';
|
||||
ZhHans.MIXLY_TINY_WEB_DB_SEARCH_VARS = '变量名包含的字符';
|
||||
|
||||
ZhHans.MIXLY_TENSORFLOW_INIT_TENSOR = '初始化张量为';
|
||||
ZhHans.MIXLY_TENSORFLOW_SEQUENTIAL = '初始化顺序模型';
|
||||
ZhHans.MIXLY_TENSORFLOW_INIT_LAYERS_DENSE_LAYER = '构建全连接层';
|
||||
ZhHans.MIXLY_TENSORFLOW_OUTPUT_DIMENSION = '输出维度';
|
||||
ZhHans.MIXLY_TENSORFLOW_INPUT_SHAPE = '输入形状';
|
||||
ZhHans.MIXLY_TENSORFLOW_MODEL = '模型';
|
||||
ZhHans.MIXLY_TENSORFLOW_ADD_LAYER = '添加层';
|
||||
ZhHans.MIXLY_TENSORFLOW_COMPILE_MODEL = '编译模型';
|
||||
ZhHans.MIXLY_TENSORFLOW_LOSS_FUNCTION_TYPE = '损失函数类型';
|
||||
ZhHans.MIXLY_TENSORFLOW_MEAN_SQUARED_ERROR = '均方误差';
|
||||
ZhHans.MIXLY_TENSORFLOW_OPTIMIZER = '优化器';
|
||||
ZhHans.MIXLY_TENSORFLOW_SGD = '随机梯度下降法';
|
||||
ZhHans.MIXLY_TENSORFLOW_FIT_MODEL = '训练模型';
|
||||
ZhHans.MIXLY_TENSORFLOW_FIT_INPUT_DATA = '输入数据';
|
||||
ZhHans.MIXLY_TENSORFLOW_FIT_TARGET_DATA = '目标数据';
|
||||
ZhHans.MIXLY_TENSORFLOW_FIT_EPOCHS = '训练迭代次数';
|
||||
ZhHans.MIXLY_TENSORFLOW_FIT_VERBOSE = '日志输出级别';
|
||||
ZhHans.MIXLY_TENSORFLOW_FIT_RETURN_HISTORY = '返回训练历史对象';
|
||||
ZhHans.MIXLY_TENSORFLOW_GET_LOSS_FROM_HISTORY = '从训练历史对象';
|
||||
ZhHans.MIXLY_TENSORFLOW_GET_LOSS_FROM_HISTORY_2 = '获取训练损失值数组';
|
||||
ZhHans.MIXLY_TENSORFLOW_PREDICT = '使用模型预测';
|
||||
ZhHans.MIXLY_TENSORFLOW_PREDICT_INPUT_DATA = '输入数据';
|
||||
ZhHans.MIXLY_TENSORFLOW_PREDICT_RETURN_RESULT = '返回预测结果张量';
|
||||
ZhHans.MIXLY_TENSORFLOW_GET_TENSOR_DATA = '获取张量中的数据';
|
||||
ZhHans.MIXLY_TENSORFLOW_SAVE_MODEL = '保存模型';
|
||||
ZhHans.MIXLY_TENSORFLOW_EXPORT_MODEL = '导出模型';
|
||||
ZhHans.MIXLY_TENSORFLOW_SAVE_MODEL_NAME = '名称';
|
||||
ZhHans.MIXLY_TENSORFLOW_LOAD_MODEL = '使用导入模型';
|
||||
ZhHans.MIXLY_TENSORFLOW_MODEL_NAME = '模型名';
|
||||
ZhHans.MIXLY_TENSORFLOW_PREPARE_PICTURE_TO_TENSOR = '预处理图像为张量';
|
||||
ZhHans.MIXLY_TENSORFLOW_PREPARE_PICTURE_READ_PICTURE = '读入图像';
|
||||
})();
|
||||
@@ -4163,4 +4163,35 @@ ZhHant.MIXLY_TINY_WEB_DB_START_NUMBER = '起始編號';
|
||||
ZhHant.MIXLY_TINY_WEB_DB_VARIABLE_NUMBER = '變數個數';
|
||||
ZhHant.MIXLY_TINY_WEB_DB_SEARCH_VARS = '變數名稱包含的字元';
|
||||
|
||||
ZhHant.MIXLY_TENSORFLOW_INIT_TENSOR = '初始化張量為';
|
||||
ZhHant.MIXLY_TENSORFLOW_SEQUENTIAL = '初始化順序模型';
|
||||
ZhHant.MIXLY_TENSORFLOW_INIT_LAYERS_DENSE_LAYER = '構建全連接層';
|
||||
ZhHant.MIXLY_TENSORFLOW_OUTPUT_DIMENSION = '輸出維度';
|
||||
ZhHant.MIXLY_TENSORFLOW_INPUT_SHAPE = '輸入形狀';
|
||||
ZhHant.MIXLY_TENSORFLOW_MODEL = '模型';
|
||||
ZhHant.MIXLY_TENSORFLOW_ADD_LAYER = '添加層';
|
||||
ZhHant.MIXLY_TENSORFLOW_COMPILE_MODEL = '編譯模型';
|
||||
ZhHant.MIXLY_TENSORFLOW_LOSS_FUNCTION_TYPE = '損失函數類型';
|
||||
ZhHant.MIXLY_TENSORFLOW_MEAN_SQUARED_ERROR = '均方誤差';
|
||||
ZhHant.MIXLY_TENSORFLOW_OPTIMIZER = '優化器';
|
||||
ZhHant.MIXLY_TENSORFLOW_SGD = '隨機梯度下降法';
|
||||
ZhHant.MIXLY_TENSORFLOW_FIT_MODEL = '訓練模型';
|
||||
ZhHant.MIXLY_TENSORFLOW_FIT_INPUT_DATA = '輸入數據';
|
||||
ZhHant.MIXLY_TENSORFLOW_FIT_TARGET_DATA = '目標數據';
|
||||
ZhHant.MIXLY_TENSORFLOW_FIT_EPOCHS = '訓練迭代次數';
|
||||
ZhHant.MIXLY_TENSORFLOW_FIT_VERBOSE = '日誌輸出級別';
|
||||
ZhHant.MIXLY_TENSORFLOW_FIT_RETURN_HISTORY = '返回訓練歷史對象';
|
||||
ZhHant.MIXLY_TENSORFLOW_GET_LOSS_FROM_HISTORY = '從訓練歷史對象';
|
||||
ZhHant.MIXLY_TENSORFLOW_GET_LOSS_FROM_HISTORY_2 = '獲取訓練損失值數組';
|
||||
ZhHant.MIXLY_TENSORFLOW_PREDICT = '使用模型預測';
|
||||
ZhHant.MIXLY_TENSORFLOW_PREDICT_INPUT_DATA = '輸入數據';
|
||||
ZhHant.MIXLY_TENSORFLOW_PREDICT_RETURN_RESULT = '返回預測結果張量';
|
||||
ZhHant.MIXLY_TENSORFLOW_GET_TENSOR_DATA = '獲取張量中的數據';
|
||||
ZhHant.MIXLY_TENSORFLOW_SAVE_MODEL = '保存模型';
|
||||
ZhHant.MIXLY_TENSORFLOW_EXPORT_MODEL = '導出模型';
|
||||
ZhHant.MIXLY_TENSORFLOW_SAVE_MODEL_NAME = '名稱';
|
||||
ZhHant.MIXLY_TENSORFLOW_LOAD_MODEL = '使用導入模型';
|
||||
ZhHant.MIXLY_TENSORFLOW_MODEL_NAME = '模型名';
|
||||
ZhHant.MIXLY_TENSORFLOW_PREPARE_PICTURE_TO_TENSOR = '預處理圖像為張量';
|
||||
ZhHant.MIXLY_TENSORFLOW_PREPARE_PICTURE_READ_PICTURE = '讀入圖像';
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user