Pyodide里的Tensorflow目录

可以跑通基本的训练、使用模型过程
This commit is contained in:
RXXXBNUer
2025-10-04 11:24:10 +08:00
parent 57b59e7d33
commit fe343c67ff
30 changed files with 1459 additions and 419 deletions

View 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('');
}
};

View File

@@ -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
};

View 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];
};

View File

@@ -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
);

View File

@@ -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": []
}
}
}

View File

@@ -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;
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;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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)

View File

@@ -0,0 +1,5 @@
from .activation import *
from .core import *
from pyodide.ffi import to_js, create_proxy
import js

View File

@@ -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)

View File

@@ -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))

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -1 +1,5 @@
from activation import *
from .activation import *
from .core import *
from pyodide.ffi import to_js, create_proxy
import js

View File

@@ -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))