TensorFlow案例学习:使用 YAMNet 进行迁移学习,对音频进行识别

news2024/11/26 4:52:33

前言

上一篇文章 TensorFlow案例学习:简单的音频识别 我们简单学习了音频识别。这次我们继续学习如何使用成熟的语音分类模型来进行迁移学习

官方教程: 使用 YAMNet 进行迁移学习,用于环境声音分类

模型下载地址(需要科学上网): https://tfhub.dev/google/yamnet/1

YAMNet简介
YAMNet(Yet Another Music Recognition Network)是由谷歌开发的音乐识别模型。它是一个基于深度学习的模型,可以用于识别音频中的各种环境音、乐器音、人声等。

YAMNet 使用了卷积神经网络(CNN)作为主要的网络结构。它的输入是音频波形数据,通过一系列卷积和池化层来捕获不同尺度的特征。训练过程中,YAMNet 使用大量的带有标签的音频数据,通过监督学习的方式来学习到不同音频类别的特征表示。

YAMNet 的输出是一个分类器,可以将输入的音频波形数据预测为音频对应的类别。在预测过程中,YAMNet 会将输入音频进行分帧处理,并对每一帧进行分类。最后,通过对所有帧的分类结果进行平均,得到整个音频的分类结果。

YAMNet 的优势在于它专门用于音频场景的识别,可以识别出许多现实生活中的环境声音,包括动物叫声、乐器音、机械声、交通声等。它在对音频进行标签分类的任务中表现出色,并且在公开数据集上取得了很好的性能。

YAMNet 的开源实现可供使用,并提供了训练好的模型权重,可以直接应用于音频识别任务。它可以用于音频分类和标签预测,也可以作为其他音频应用中的基础模块。

安装tensorflow-io
我这里遇到一个问题,安装成功后一直提示找不到模块,我这里使用librosa来解决这部分问题

基本使用

加载模型

yamnet_model = hub.load('./yamnet_1')
print("yamnet_model:", yamnet_model)

绘制音频波形

# 加载本地wav文件,并制定采样率为16000和单声道的形式进行重采样
def load_wav_16k_mono(filename):
    wav, sample_rate = librosa.load(filename, sr=16000, mono=True)
    return wav

testing_wav_data = load_wav_16k_mono('./test_data/down.wav')

# 创建x轴坐标,以样本点为单位
x = np.arange(len(testing_wav_data))
# 绘制波形图
plt.plot(x, testing_wav_data)
plt.xlabel('Sample')
plt.ylabel('Amplitude')
plt.title('Audio Waveform')
plt.show()

在这里插入图片描述
加载类映射

# 加载类映射
class_map_path = yamnet_model.class_map_path().numpy().decode('utf-8')
class_names = list(pd.read_csv(class_map_path)['display_name'])

for name in class_names[0:10]:
    print("name:", name)

在这里插入图片描述
预测

# 预测,获取最大可能性
scores, embeddings, spectrogram = yamnet_model(testing_wav_data)
class_scores = tf.reduce_mean(scores,axis=0)
top_class = tf.math.argmax(class_scores)
inferred_class = class_names[top_class]

print("推断结果:",inferred_class)

在这里插入图片描述

迁移学习一

处理数据集

下载数据集并解压到项目里

ESC-50数据集下载地址: https://github.com/karoldvl/ESC-50/archive/master.zip

该数据集由2000个50秒的环境音频记录的标记集合,该数据集由40个类组成。

筛选数据
官方文档筛选的是狗和猫,这里我们使用狗、猫、羊

my_classes = ['dog', 'cat', 'sheep']
map_class_to_id = {'dog': 0, 'cat': 1, 'sheep': 2}

pd_data = pd.read_csv('./ESC-50-master/meta/esc50.csv')
filtered_pd = pd_data[pd_data.category.isin(my_classes)]
print("filtered_pd:", filtered_pd)

class_id = filtered_pd['category'].apply(lambda name: map_class_to_id[name])
print("class_id:",class_id)

filtered_pd = filtered_pd.assign(target=class_id)
print("filtered_pd:", filtered_pd)

这段代码的作用就是对三种动物进行筛选,并按照{'dog': 0, 'cat': 1, 'sheep': 2}进行划分,三种动物共120条数据

在这里插入图片描述

# 筛选数据:2、获取到文件完整的路径
full_path = filtered_pd['filename'].apply(
    lambda row: os.path.join(base_data_path, row))
filtered_pd = filtered_pd.assign(filename=full_path)
print("filtered_pd:", filtered_pd)

在这里插入图片描述
加载音频文件

# 加载音频文件并检索嵌入:1、filenames文件路径,target类别,
# fold每个音频文件所属的交叉验证折叠,可以理解为将不同种类的文件放在一个文件夹里,这个fold代表这个文件是在哪一个文件夹里,相当于文件夹的标记
filenames = filtered_pd['filename']
targets = filtered_pd['target']
folds = filtered_pd['fold']
print("filenames-targets-folds", filenames[0], targets[0], folds[0])

# 加载音频文件并检索嵌入:2、创建包含三个元素:filenames、targets和folds的一个数据集对象
main_ds = tf.data.Dataset.from_tensor_slices((filenames, targets, folds))
main_ds.element_spec
print("main_ds:", main_ds)
print("element_spec:", main_ds.element_spec)

# 加载音频文件并检索嵌入:3、将音频变成单声道的16kHz采样的音频数据,使其符合模型的输入
def load_wav_16k_mono(filename):
    file_contents = tf.io.read_file(filename)
    wav, sample_rate = tf.audio.decode_wav(
        file_contents,
        desired_channels=1)
    wav = tf.squeeze(wav, axis=-1)
    sample_rate = tf.cast(sample_rate, dtype=tf.int64)
    wav = tfio.audio.resample(wav, rate_in=sample_rate, rate_out=16000)
    return wav

def load_wav_for_map(filename, label, fold):
  return load_wav_16k_mono(filename), label, fold

main_ds = main_ds.map(load_wav_for_map)
main_ds.element_spec

处理训练集数据

# 处理训练集数据
def extract_embedding(wav_data, label, fold):
  scores, embeddings, spectrogram = yamnet_model(wav_data)
  num_embeddings = tf.shape(embeddings)[0]
  return (embeddings,
            tf.repeat(label, num_embeddings),
            tf.repeat(fold, num_embeddings))

main_ds = main_ds.map(extract_embedding).unbatch()
main_ds.element_spec

拆分数据
将数据拆分为训练集、验证集、测试集

# 拆分数据
cached_ds = main_ds.cache()
train_ds = cached_ds.filter(lambda embedding, label, fold: fold < 4)
val_ds = cached_ds.filter(lambda embedding, label, fold: fold == 4)
test_ds = cached_ds.filter(lambda embedding, label, fold: fold == 5)

# 删除fold列,训练时不需要


def remove_fold_column(embedding, label, fold): return (embedding, label)


train_ds = train_ds.map(remove_fold_column)
val_ds = val_ds.map(remove_fold_column)
test_ds = test_ds.map(remove_fold_column)

train_ds = train_ds.cache().shuffle(1000).batch(32).prefetch(tf.data.AUTOTUNE)
val_ds = val_ds.cache().batch(32).prefetch(tf.data.AUTOTUNE)
test_ds = test_ds.cache().batch(32).prefetch(tf.data.AUTOTUNE)

创建模型

# 创建模型,这里的1024与512是怎么来的一直没搞明白
my_model = tf.keras.Sequential([
    tf.keras.layers.Input(shape=(1024), dtype=tf.float32,
                          name="input_embedding"),
    tf.keras.layers.Dense(512, activation='relu'),
    tf.keras.layers.Dense(len(my_classes))
], name="my_model")

my_model.summary()

编译训练模型

my_model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                 optimizer="adam",
                 metrics=['accuracy'])

callback = tf.keras.callbacks.EarlyStopping(monitor='loss',
                                            patience=3,
                                            restore_best_weights=True)
history = my_model.fit(train_ds,
                       epochs=20,
                       validation_data=val_ds,
                       callbacks=callback)

评估模型

loss, accuracy = my_model.evaluate(test_ds)

print("Loss: ", loss)
print("Accuracy: ", accuracy)

在这里插入图片描述
测试模型

testing_wav_data = load_wav_16k_mono('./ESC-50-master/audio/1-57795-A-8.wav')
scores, embeddings, spectrogram = yamnet_model(testing_wav_data)
result = my_model(embeddings).numpy()

inferred_class = my_classes[result.mean(axis=0).argmax()]
print(f'The main sound is: {inferred_class}')

在这里插入图片描述
将模型保存为可直接将WAV文件作为输入的模型

当您将嵌入作为输入时,您的模型就会起作用。

在实际场景中,您需要使用音频数据作为直接输入。

为此,您需要将 YAMNet 与模型合并到一个模型中,您可以导出该模型以供其他应用程序使用。

class ReduceMeanLayer(tf.keras.layers.Layer):
    def __init__(self, axis=0, **kwargs):
        super(ReduceMeanLayer, self).__init__(**kwargs)
        self.axis = axis

    def call(self, input):
        return tf.math.reduce_mean(input, axis=self.axis)


saved_model_path = './dogs_cats_sheep_yamnet'

input_segment = tf.keras.layers.Input(shape=(), dtype=tf.float32, name='audio')
embedding_extraction_layer = hub.KerasLayer('./yamnet_1',
                                            trainable=False, name='yamnet')
_, embeddings_output, _ = embedding_extraction_layer(input_segment)
serving_outputs = my_model(embeddings_output)
serving_outputs = ReduceMeanLayer(axis=0, name='classifier')(serving_outputs)
serving_model = tf.keras.Model(input_segment, serving_outputs)
serving_model.save(saved_model_path, include_optimizer=False)

在这里插入图片描述
完整代码

import tensorflow_hub as hub
import tensorflow as tf

import numpy as np
import pandas as pd
import os
import tensorflow_io as tfio

yamnet_model = hub.load('./yamnet_1')

base_data_path = './ESC-50-master/audio/'

# 筛选数据
# 筛选数据:1、将数据按照{'dog': 0, 'cat': 1, 'sheep': 2}进行划分
my_classes = ['dog', 'cat', 'sheep']
map_class_to_id = {'dog': 0, 'cat': 1, 'sheep': 2}
pd_data = pd.read_csv('./ESC-50-master/meta/esc50.csv')
filtered_pd = pd_data[pd_data.category.isin(my_classes)]
#print("filtered_pd:", filtered_pd)

class_id = filtered_pd['category'].apply(lambda name: map_class_to_id[name])
#print("class_id:", class_id)

filtered_pd = filtered_pd.assign(target=class_id)
#print("filtered_pd:", filtered_pd)
# 筛选数据:2、获取到文件完整的路径
full_path = filtered_pd['filename'].apply(
    lambda row: os.path.join(base_data_path, row))
filtered_pd = filtered_pd.assign(filename=full_path)
#print("filtered_pd:", filtered_pd)


# 加载音频文件并检索嵌入
# 加载音频文件并检索嵌入:1、filenames文件路径,target类别,
# fold每个音频文件所属的交叉验证折叠,可以理解为将不同种类的文件放在一个文件夹里,这个fold代表这个文件是在哪一个文件夹里,相当于文件夹的标记
filenames = filtered_pd['filename']
targets = filtered_pd['target']
folds = filtered_pd['fold']
print("filenames-targets-folds", filenames[0], targets[0], folds[0])

# 加载音频文件并检索嵌入:2、创建包含三个元素:filenames、targets和folds的一个数据集对象
main_ds = tf.data.Dataset.from_tensor_slices((filenames, targets, folds))
main_ds.element_spec
# print("main_ds:", main_ds)
# print("element_spec:", main_ds.element_spec)

# 加载音频文件并检索嵌入:3、将音频变成单声道的16kHz采样的音频数据,使其符合模型的输入


def load_wav_16k_mono(filename):
    file_contents = tf.io.read_file(filename)
    wav, sample_rate = tf.audio.decode_wav(
        file_contents,
        desired_channels=1)
    wav = tf.squeeze(wav, axis=-1)
    sample_rate = tf.cast(sample_rate, dtype=tf.int64)
    wav = tfio.audio.resample(wav, rate_in=sample_rate, rate_out=16000)
    return wav


def load_wav_for_map(filename, label, fold):
    return load_wav_16k_mono(filename), label, fold


main_ds = main_ds.map(load_wav_for_map)
print("main_ds:", main_ds)
# main_ds.element_spec

# 处理训练集数据


def extract_embedding(wav_data, label, fold):
    scores, embeddings, spectrogram = yamnet_model(wav_data)
    num_embeddings = tf.shape(embeddings)[0]
    return (embeddings,
            tf.repeat(label, num_embeddings),
            tf.repeat(fold, num_embeddings))


main_ds = main_ds.map(extract_embedding).unbatch()
main_ds.element_spec

# 拆分数据
cached_ds = main_ds.cache()
train_ds = cached_ds.filter(lambda embedding, label, fold: fold < 4)
val_ds = cached_ds.filter(lambda embedding, label, fold: fold == 4)
test_ds = cached_ds.filter(lambda embedding, label, fold: fold == 5)

# 删除fold列,训练时不需要


def remove_fold_column(embedding, label, fold): return (embedding, label)


train_ds = train_ds.map(remove_fold_column)
val_ds = val_ds.map(remove_fold_column)
test_ds = test_ds.map(remove_fold_column)

train_ds = train_ds.cache().shuffle(1000).batch(32).prefetch(tf.data.AUTOTUNE)
val_ds = val_ds.cache().batch(32).prefetch(tf.data.AUTOTUNE)
test_ds = test_ds.cache().batch(32).prefetch(tf.data.AUTOTUNE)


# 创建模型,这里的1024与512是怎么来的一直没搞明白
my_model = tf.keras.Sequential([
    tf.keras.layers.Input(shape=(1024), dtype=tf.float32,
                          name="input_embedding"),
    tf.keras.layers.Dense(512, activation='relu'),
    tf.keras.layers.Dense(len(my_classes))
], name="my_model")

my_model.summary()

# 编译训练模型
my_model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                 optimizer="adam",
                 metrics=['accuracy'])

callback = tf.keras.callbacks.EarlyStopping(monitor='loss',
                                            patience=3,
                                            restore_best_weights=True)
history = my_model.fit(train_ds,
                       epochs=20,
                       validation_data=val_ds,
                       callbacks=callback)
# 评估模型
loss, accuracy = my_model.evaluate(test_ds)

print("Loss: ", loss)
print("Accuracy: ", accuracy)

# 导出模型
class ReduceMeanLayer(tf.keras.layers.Layer):
    def __init__(self, axis=0, **kwargs):
        super(ReduceMeanLayer, self).__init__(**kwargs)
        self.axis = axis

    def call(self, input):
        return tf.math.reduce_mean(input, axis=self.axis)


saved_model_path = './dogs_cats_sheep_yamnet'

input_segment = tf.keras.layers.Input(shape=(), dtype=tf.float32, name='audio')
embedding_extraction_layer = hub.KerasLayer('./yamnet_1',
                                            trainable=False, name='yamnet')
_, embeddings_output, _ = embedding_extraction_layer(input_segment)
serving_outputs = my_model(embeddings_output)
serving_outputs = ReduceMeanLayer(axis=0, name='classifier')(serving_outputs)
serving_model = tf.keras.Model(input_segment, serving_outputs)
serving_model.save(saved_model_path, include_optimizer=False)

测试模型

import tensorflow as tf
import tensorflow_io as tfio


def load_wav_16k_mono(filename):
    file_contents = tf.io.read_file(filename)
    wav, sample_rate = tf.audio.decode_wav(
        file_contents,
        desired_channels=1)
    wav = tf.squeeze(wav, axis=-1)
    sample_rate = tf.cast(sample_rate, dtype=tf.int64)
    wav = tfio.audio.resample(wav, rate_in=sample_rate, rate_out=16000)
    return wav


# 加载模型
my_model = tf.saved_model.load('./dogs_cats_sheep_yamnet')
# 测试
testing_wav_data = load_wav_16k_mono('./ESC-50-master/audio/1-100032-A-0.wav')
result = my_model(testing_wav_data)
my_classes = ['dog', 'cat', 'sheep']
label = my_classes[tf.math.argmax(result)]

print(label)

在这里插入图片描述

迁移学习二

上面的迁移学习一直是按照教程来的,使用的数据集ESC-50 数据集 格式也不怎么常见。那么我们能不能根据这个格式,来构建自己的数据集,使代码可以复用。

数据集这里选择上一篇文章中的 mini_speech_commands 数据集。共有8种类别的音频,这里我们采用每种类别选择5个音频,一共40个音频,来构建自己的数据集。

说一下测试结果,由于数据太少,一个类别才5条数据。并且有3条数据是训练数据、1条数据是验证数据、1条数据是测试数据。导致测试结果不好,只有当数据是训练数据时能够得出正确结果,是其他数据时结果大部分适合都是不对的。

因此如果真的要使用的话,数据要多一点。上面的官方教程是共120条数据,一个类别有40条数据,训练数据要尽可能的多一些。

在这里插入图片描述
audio存放音频文件,commands.csv 存放音频的信息。从后面代码看我们只需要在commands.csv 中维护三列就行

  • filename 文件名
  • target 文件所属的类别序号
  • category 文件所属的类别名称
  • fold 标识,代表这个文件是在哪一个文件夹里

在这里插入图片描述
target的值要与你设置的相对应

map_class_to_id = {'down': 0, 'go': 1, 'left': 2,
                   'no': 3, 'right': 4, 'stop': 5, 'up': 6, 'yes': 7}

修改训练文件

import tensorflow_hub as hub
import tensorflow as tf

import numpy as np
import pandas as pd
import os
import tensorflow_io as tfio

yamnet_model = hub.load('./yamnet_1')

base_data_path = './commands/audio'
csv_path = './commands/commands.csv'

# 筛选数据
my_classes = ['down', 'go', 'left', 'no', 'right', 'stop', 'up', 'yes']
map_class_to_id = {'down': 0, 'go': 1, 'left': 2,
                   'no': 3, 'right': 4, 'stop': 5, 'up': 6, 'yes': 7}
pd_data = pd.read_csv(csv_path)
#print("pd_data:", pd_data)
filtered_pd = pd_data[pd_data.category.isin(my_classes)]
class_id = filtered_pd['category'].apply(lambda name: map_class_to_id[name])
filtered_pd = filtered_pd.assign(target=class_id)
# 筛选数据获取完整路径
full_path = filtered_pd['filename'].apply(
    lambda row: os.path.join(base_data_path, row))
filtered_pd = filtered_pd.assign(filename=full_path)
#print("filtered_pd:", filtered_pd)

# 加载音频文件并检索嵌入
filenames = filtered_pd['filename']
targets = filtered_pd['target']
folds = filtered_pd['fold']
print("filenames-targets-folds", filenames[0], targets[0], folds[0])
main_ds = tf.data.Dataset.from_tensor_slices((filenames, targets, folds))
# print("main_ds:",main_ds)

# 将音频变成单声道的16kHz采样的音频数据,使其符合模型的输入


def load_wav_16k_mono(filename):
    file_contents = tf.io.read_file(filename)
    wav, sample_rate = tf.audio.decode_wav(
        file_contents,
        desired_channels=1)
    wav = tf.squeeze(wav, axis=-1)
    sample_rate = tf.cast(sample_rate, dtype=tf.int64)
    wav = tfio.audio.resample(wav, rate_in=sample_rate, rate_out=16000)
    return wav


def load_wav_for_map(filename, label, fold):
    return load_wav_16k_mono(filename), label, fold


main_ds = main_ds.map(load_wav_for_map)
print("main_ds:", main_ds)

# 处理训练集数据


def extract_embedding(wav_data, label, fold):
    scores, embeddings, spectrogram = yamnet_model(wav_data)
    num_embeddings = tf.shape(embeddings)[0]
    return (embeddings,
            tf.repeat(label, num_embeddings),
            tf.repeat(fold, num_embeddings))


main_ds = main_ds.map(extract_embedding).unbatch()

# 拆分数据
cached_ds = main_ds.cache()
train_ds = cached_ds.filter(lambda embedding, label, fold: fold < 4)
val_ds = cached_ds.filter(lambda embedding, label, fold: fold == 4)
test_ds = cached_ds.filter(lambda embedding, label, fold: fold == 5)

# 删除fold列,训练时不需要


def remove_fold_column(embedding, label, fold): return (embedding, label)


train_ds = train_ds.map(remove_fold_column)
val_ds = val_ds.map(remove_fold_column)
test_ds = test_ds.map(remove_fold_column)

train_ds = train_ds.cache().shuffle(1000).batch(32).prefetch(tf.data.AUTOTUNE)
val_ds = val_ds.cache().batch(32).prefetch(tf.data.AUTOTUNE)
test_ds = test_ds.cache().batch(32).prefetch(tf.data.AUTOTUNE)


# 创建模型,这里的1024与512是怎么来的一直没搞明白
my_model = tf.keras.Sequential([
    tf.keras.layers.Input(shape=(1024), dtype=tf.float32,
                          name="input_embedding"),
    tf.keras.layers.Dense(512, activation='relu'),
    tf.keras.layers.Dense(len(my_classes))
], name="my_model")

my_model.summary()

# 编译训练模型
my_model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                 optimizer="adam",
                 metrics=['accuracy'])

callback = tf.keras.callbacks.EarlyStopping(monitor='loss',
                                            patience=3,
                                            restore_best_weights=True)
history = my_model.fit(train_ds,
                       epochs=20,
                       validation_data=val_ds,
                       callbacks=callback)
# 评估模型
loss, accuracy = my_model.evaluate(test_ds)

print("Loss: ", loss)
print("Accuracy: ", accuracy)

# 导出模型


class ReduceMeanLayer(tf.keras.layers.Layer):
    def __init__(self, axis=0, **kwargs):
        super(ReduceMeanLayer, self).__init__(**kwargs)
        self.axis = axis

    def call(self, input):
        return tf.math.reduce_mean(input, axis=self.axis)


saved_model_path = './command_yamnet'

input_segment = tf.keras.layers.Input(shape=(), dtype=tf.float32, name='audio')
embedding_extraction_layer = hub.KerasLayer('./yamnet_1',
                                            trainable=False, name='yamnet')
_, embeddings_output, _ = embedding_extraction_layer(input_segment)
serving_outputs = my_model(embeddings_output)
serving_outputs = ReduceMeanLayer(axis=0, name='classifier')(serving_outputs)
serving_model = tf.keras.Model(input_segment, serving_outputs)
serving_model.save(saved_model_path, include_optimizer=False)

补充

如果想直接在浏览器里使用,需要解决的一个问题就是如何将一个音频文件变成符合模型的输入,下面是我找到的方式(没有测试,不知道是否可以)

async function audioFileToTensor(audioFile) {
  // 读取音频文件
  const audioBuffer = await fetch(audioFile)
    .then(response => response.arrayBuffer())
    .then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer));

  // 获取音频数据
  const audioData = audioBuffer.getChannelData(0); // 获取音频的第一个通道的数据

  // 创建一个全零的Tensor
  const tensor = tf.tensor(audioData, [audioData.length]);

  return tensor;
}

// 使用示例
const audioFile = 'path/to/your/audio/file.mp3';
const audioContext = new AudioContext();
const tensor = await audioFileToTensor(audioFile);
console.log(tensor);

本质上就行需要想办法处理音频,让音频变成符合的格式。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1167349.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML基础知识——URL、文本标签、链接标签、图片标签、列表标签

目录 URL&#xff08;统一资源定位符&#xff09; 概述 网址的组成部分 协议 主机 端口 路径 查询参数 锚点 文本标签 示例&#xff1a; 链接标签 示例&#xff1a; 图片标签 示例&#xff1a; 列表标签 示例&#xff1a; URL&#xff08;统一资源定位符&#xff09;…

跳跳狗小游戏

欢迎来到程序小院 跳跳狗 玩法&#xff1a;一直弹跳的狗狗&#xff0c;鼠标点击屏幕左右方向键进行弹跳&#xff0c;弹到不同物品会有不同的分数减扣&#xff0c;规定的时间3分钟内完成狗狗弹跳&#xff0c;快去跳跳狗吧^^。开始游戏https://www.ormcc.com/play/gameStart/198…

B端设计必看的9个开源组件库,值得收藏!

如果你想开发一款To B Web端产品&#xff0c;如何选择令人眼花缭乱的开源组件库&#xff1f;行业团队常用的B端开源组件库是什么&#xff1f;今天&#xff0c;我们将为您带来入门级开源组件库的介绍。你可以先有一个大致的了解&#xff0c;希望能对你有所帮助。未来&#xff0c…

【带货案例】从美区十月带货达人身上寻找商品爆款秘诀!

2023只剩下最后两个月&#xff0c;年底也是各大商家冲刺卖货的黄金时期&#xff01; 带货过程中的一个重要环节即【达人营销】&#xff0c;背受跨境卖家关注。 下面选取美区十月带货达人TOP3&#xff0c;分析其带货秘诀。 据超店有数达人榜单显示&#xff1a;美区十月带货达人…

山东专业商品信息管理系统解决方案,智能管理多门店,可定制-亿发

众所周知&#xff0c;现如今商品种类繁多、品牌众多、商品信息量庞大&#xff0c;同时商品销售价格经常变动&#xff0c;还需管理商品批次&#xff0c;避免库存积压和过期&#xff0c;这给山东地区的传统企业在商品管理上带来了极大的挑战。 随着数字化时代的来临&#xff0c;山…

Java Web 学习笔记(一) —— MySQL(1)

目录 1 SQL简介2 MySQL基本语法2.1 语法规则2.2 数据类型 3 DDL3.1 操作数据库3.2 操作表 4 DML4.1 添加数据4.2 修改数据4.3 删除数据 5 DQL5.1 基础查询5.2 条件查询5.3 排序查询5.4 聚合查询5.5 分组查询5.6 分页查询 1 SQL简介 SQL &#xff1a;Structured Query Language&…

iOS App Store上传项目报错 缺少隐私政策网址(URL)解决方法

iOS App Store上传项目报错 缺少隐私政策网址(URL)解决方法 一、问题如下图所示&#xff1a; 二、解决办法&#xff1a;使用Google浏览器&#xff08;翻译成中文&#xff09;直接打开该网址 https://www.freeprivacypolicy.com/free-privacy-policy-generator.php 按照要求…

蓝桥杯每日一题2023.11.3

题目描述 承压计算 - 蓝桥云课 (lanqiao.cn) 题目分析 将重量存入a中&#xff0c;每一层从上到下进行计算&#xff0c;用d进行计算列的重量&#xff0c;当前d的重量应为正上数组和右上数组的个半和并加上自身的重量 计算到30层记录最大最小值&#xff0c;进行比例运算即可 …

Ubuntu20.04搭建RISC-V和qemu环境

1. 前言 risc-v是一个非常有潜力的指令集框架&#xff0c;最近对其产生了浓厚的兴趣&#xff0c;由于之前对于这方面的知识储备很少&#xff0c;在加上网上的教程都是点到为止&#xff0c;所以安装过程异常曲折。好在最后一步一步积累摸索&#xff0c;终于利用源码安装完成。看…

python对象方法是什么

python对象方法是什么 概念 1、在类中&#xff0c;对象调用的函数称为对象方法&#xff0c;一般也称为方法。 定义格式 class 类名:def 函数1(self, 参数1, 参数2):...实例 2、在定义对象方法时&#xff0c;第一个参数默认使用self&#xff0c;这个参数在定义时必须存在&am…

new Proxy

Proxy&#xff1a;代理 - JavaScript |MDN网络 (mozilla.org) 首先要确定Proxy的入参&#xff1a;new Proxy 的两个参数分别是目标对象和处理程序。 如下&#xff0c;写了个只有两个方法的Proxy let target {name:张三,age:18}; let handler {get(target, property, recei…

今天,他们一起聊了聊数据融合|CNCC 2023

2023 年 10 月 26-28 日&#xff0c;第二十届中国计算机大会(CNCC 2023)在沈阳市举行&#xff0c;27 日下午&#xff0c;由 OceanBase 申办的「下一代数据融合技术趋势」技术论坛圆满落幕。 会上&#xff0c;OceanBase 联合多位数据库领域知名学者、专家围绕 HTAP 工作负载融合…

Vector CANape 21安装

系列文章目录 文章目录 系列文章目录简介下载 Vector CANape 21 简介 CANape基础操作介绍&#xff1a;工程创建&#xff0c;测量&#xff0c;标定&#xff0c;离线分析操作。 下载 Vector CANape 21 如下是Vector CANape21的下载安装步骤&#xff1a; https://www.vector.co…

uni-app 开发的H5 定位功能部署注意事项

一、H5部署的时候&#xff0c;如果设计到定位功能&#xff0c;需要注意以下几点 1、打包部署的时候需要在Web配置-定位和地图里面勾选一个地图&#xff0c;并配置key 2、打包部署需要域名是https协议的&#xff0c;大多数现代浏览器要求在HTTPS协议下才能够访问地理位置信息&a…

【Python基础知识四】控制语句

Python基础知识&#xff1a;控制语句 1 条件控制1.1 if语句1.2 match...case语句 2 循环语句2.1 for循环2.2 for...else语句2.3 while循环2.4 while 循环使用 else 语句2.5 无限循环 参考 Python控制语句主要包含顺序、选择和循环三个方面&#xff0c;下面主要从这三方面进行介…

winscp文件增量同步到linux服务器

一&#xff0c;点击同步 场景&#xff1a;在做服务器迁移的时候&#xff0c;文件好几十个G一天也迁移不完&#xff0c;每天还有增量的文件&#xff0c;先全量同步一次&#xff0c;然后再用增量同步&#xff0c;然后你用winscp的同步工具&#xff0c;进增量同步。 将本地文件同…

[JavaWeb]——JWT令牌技术

&#x1f308;键盘敲烂&#xff0c;年薪30万&#x1f308; 目录 一、前言&#xff1a; 二、JWT令牌技术 2.1 概念介绍 2.2 组成介绍 2.3 JWT对象介绍 2.4 JWT生成 2.5 JWT校验 三、总结 一、前言&#xff1a; 问题抛出❓ 许多网页都会设置登录界面&#xff0c;我们点击…

HarmonyOS列表组件

List组件的使用 import router from ohos.routerEntry Component struct Index {private arr: number[] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]build() {Row() {Column() {List({ space: 10 }) {ForEach(this.arr, (item: number) > {ListItem() {Text(${item}).width(100%).heig…

光电直读水表支持短时间多次抄表吗

传统的水表读数方式已经逐渐无法满足人们对于便捷、准确的需求。为此&#xff0c;光电直读水表应运而生&#xff0c;它凭借出色的读数性能和稳定的准确性&#xff0c;赢得了广大用户的一致好评。那么&#xff0c;光电直读水表支持短时间多次抄表吗&#xff1f;答案是肯定的&…

centos7-lamp

目录 一、安装 1.关闭防火墙关闭selinux 2.安装apache 3.配置主页 二、部署mariadb&#xff08;mysql&#xff09; 1.用yum安装 2.启动数据库 3.看下端口是否listen 4登录mysql 5.修改下密码 三、安装php 1.安装依赖包 2.安装php解释器和php连接mysql驱动 3.配置…