2023年的深度学习入门指南(24) - 处理音频的大模型 OpenAI Whisper

2023年的深度学习入门指南(24) - 处理音频的大模型 OpenAI Whisper

在这一讲之前,我们所用的大模型都是针对文本的。这一讲我们增加一个新的领域,即音频。我们将介绍OpenAI的Whisper模型,它是一个处理音频的大模型。

Whisper模型的用法

Whisper是OpenAI开源的模型。它的用法非常简单,只要安装好相关的库,就可以直接用命令行来调用了。

安装就一个库:

pip install -U openai-whisper

然后就可以直接用命令行来调用了:

whisper va1.mp3 --language Chinese

我们还可以用model参数来选择模型,比如有10GB以上显存就可以选择使用large模型:

whisper va2.mp3 --model large --language Chinese

默认是small模型。还可以选择tiny, base, medium, large-v1和large-v2.

如果是遇到视频的话,那么就用ffmpeg工具将视频中的音频部分提取出来。

比如我们有一个视频02.vob,我们不知道其音频流格式是什么,我们可以通过ffmpeg命令来查看:

ffmpeg -i 02.vob

我们可以看到下面的信息:

Input #0, mpeg, from '02.VOB':Duration: 00:34:26.64, start: 0.290633, bitrate: 3807 kb/sStream #0:0[0x1bf]: Data: dvd_nav_packetStream #0:1[0x1e0]: Video: mpeg2video (Main), yuv420p(tv, bottom first), 720x576 [SAR 16:15 DAR 4:3], 25 fps, 25 tbr, 90k tbnSide data:cpb: bitrate max/min/avg: 9610000/0/0 buffer size: 1835008 vbv_delay: N/AStream #0:2[0x1c0]: Audio: mp2, 48000 Hz, stereo, s16p, 224 kb/s

从中可以看到,02.vob总时长为 00:34:26.64,起始时间为 0.290633,比特率为 3807 kb/s。这个文件包含三个流:

流 #0:0 是 DVD 导航数据包。
流 #0:1 是视频流,编码格式为 MPEG-2,使用了 YUV420P 颜色空间,分辨率为 720x576 像素,采样宽高比(SAR)为 16:15,显示宽高比(DAR)为 4:3。视频帧率为 25 帧/秒,时间基数(tbn)为 90k。
流 #0:2 是音频流,编码格式为 MP2,采样率为 48000 Hz,立体声,采样位数为 s16p,比特率为 224 kb/s。

既然编码格式为mp2,那么我们就将其保存为mp2格式的音频:

ffmpeg -i 02.VOB -vn -acodec copy 02.mp2

输出如下:

ffmpeg version 6.0-full_build-www.gyan.dev Copyright (c) 2000-2023 the FFmpeg developersbuilt with gcc 12.2.0 (Rev10, Built by MSYS2 project)configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-libsnappy --enable-zlib --enable-librist --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-sdl2 --enable-libaribb24 --enable-libdav1d --enable-libdavs2 --enable-libuavs3d --enable-libzvbi --enable-librav1e --enable-libsvtav1 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs2 --enable-libxvid --enable-libaom --enable-libjxl --enable-libopenjpeg --enable-libvpx --enable-mediafoundation --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-liblensfun --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-ffnvcodec --enable-nvdec --enable-nvenc --enable-d3d11va --enable-dxva2 --enable-libvpl --enable-libshaderc --enable-vulkan --enable-libplacebo --enable-opencl --enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libilbc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprintlibavutil      58.  2.100 / 58.  2.100libavcodec     60.  3.100 / 60.  3.100libavformat    60.  3.100 / 60.  3.100libavdevice    60.  1.100 / 60.  1.100libavfilter     9.  3.100 /  9.  3.100libswscale      7.  1.100 /  7.  1.100libswresample   4. 10.100 /  4. 10.100libpostproc    57.  1.100 / 57.  1.100
Input #0, mpeg, from '02.VOB':Duration: 00:34:26.64, start: 0.290633, bitrate: 3807 kb/sStream #0:0[0x1bf]: Data: dvd_nav_packetStream #0:1[0x1e0]: Video: mpeg2video (Main), yuv420p(tv, bottom first), 720x576 [SAR 16:15 DAR 4:3], 25 fps, 25 tbr, 90k tbnSide data:cpb: bitrate max/min/avg: 9610000/0/0 buffer size: 1835008 vbv_delay: N/AStream #0:2[0x1c0]: Audio: mp2, 48000 Hz, stereo, s16p, 224 kb/s
Output #0, mp2, to '02.mp2':Metadata:encoder         : Lavf60.3.100Stream #0:0: Audio: mp2, 48000 Hz, stereo, s16p, 224 kb/s
Stream mapping:Stream #0:2 -> #0:0 (copy)
Press [q] to stop, [?] for help
size=   56510kB time=00:34:26.64 bitrate= 224.0kbits/s speed=76.8x
video:0kB audio:56510kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.000000%

最后生成02.mp2。我们不用转码,直接用whisper去处理:

whisper 02.mp2 --model large --language Chinese

默认情况下,whisper会输出5种格式的文本,分别是txt纯文本格式的,vtt(Web Video Text Tracks)字幕格式的,srt - SubRip Subtitle字幕格式的,tsv制表符分隔,以及json格式的。我们可以通过--output_format来指定。如果全要输出则不用指定,或者指定all.

whisper也可以直接处理wav文件。

我们再看一个从mp4视频中提取aac音频的例子。
我们有一个mp4文件,信息如下:

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '20230801_170327.mp4':Metadata:major_brand     : mp42minor_version   : 0compatible_brands: mp42isomcreation_time   : 2023-08-01T09:03:27.000000ZDuration: 00:01:51.00, start: 0.000000, bitrate: 901 kb/sStream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1920x1080, 762 kb/s, 25.26 fps, 30 tbr, 10k tbn (default)Metadata:creation_time   : 2023-08-01T09:03:27.000000Zvendor_id       : [0][0][0][0]encoder         : JVT/AVC CodingStream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 135 kb/s (default)Metadata:creation_time   : 2023-08-01T09:03:27.000000Zvendor_id       : [0][0][0][0]

我们可以知道下面的视频信息:

文件名:‘20230801_170327.mp4’
创建日期:2023年8月1日,UTC时间09:03:27
视频码率:总体码率为901 kb/s
视频长度:1分钟51秒
视频开始时间:从0秒开始
视频流:

编码:h264 (High),这是一种常见的视频编码格式
帧率:大约每秒25.26帧
分辨率:1920x1080,也就是常说的1080p或全高清
码率:762 kb/s
创建日期:2023年8月1日,UTC时间09:03:27
编码器:JVT/AVC Coding
音频流:

编码:aac (LC),这是一种常见的音频编码格式
采样率:44100 Hz,这是CD质量音频的标准采样率
音频通道:立体声
码率:135 kb/s
创建日期:2023年8月1日,UTC时间09:03:27

我们用ffmpeg提取aac音频:

ffmpeg -i 20230801_170327.mp4 -vn -acodec copy 01.aac

输出如下:

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '20230801_170327.mp4':Metadata:major_brand     : mp42minor_version   : 0compatible_brands: mp42isomcreation_time   : 2023-08-01T09:03:27.000000ZDuration: 00:01:51.00, start: 0.000000, bitrate: 901 kb/sStream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1920x1080, 762 kb/s, 25.26 fps, 30 tbr, 10k tbn (default)Metadata:creation_time   : 2023-08-01T09:03:27.000000Zvendor_id       : [0][0][0][0]encoder         : JVT/AVC CodingStream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 135 kb/s (default)Metadata:creation_time   : 2023-08-01T09:03:27.000000Zvendor_id       : [0][0][0][0]
Output #0, adts, to '01.aac':Metadata:major_brand     : mp42minor_version   : 0compatible_brands: mp42isomencoder         : Lavf60.3.100Stream #0:0(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 135 kb/s (default)Metadata:creation_time   : 2023-08-01T09:03:27.000000Zvendor_id       : [0][0][0][0]
Stream mapping:Stream #0:1 -> #0:0 (copy)
Press [q] to stop, [?] for help
size=    1865kB time=00:01:50.94 bitrate= 137.7kbits/s speed=2.84e+03x
video:0kB audio:1833kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 1.782703%

最后,将获取的01.aac文件直接送给Whisper去处理:

whisper 01.aac --model large-v2 --language Chinese --output_format txt

Whisper模型代码分析

虽然从表象上,声音和文本还是非常不同的。但是到了模型这一层,一切又回到了我们熟悉的样子。

首先是层归一化:

class LayerNorm(nn.LayerNorm):def forward(self, x: Tensor) -> Tensor:return super().forward(x.float()).type(x.dtype)

只做了一件事情,就是将泛型的x转成浮点数再前向计算。

再看它的全连接网络,就是PyTorch的线性网络的一个马甲:

class Linear(nn.Linear):def forward(self, x: Tensor) -> Tensor:return F.linear(x,self.weight.to(x.dtype),None if self.bias is None else self.bias.to(x.dtype),)

这段代码定义了一个名为 Linear 的类,它继承自 nn.Linear 类。这个类重写了父类的 forward 方法,该方法接受一个张量 x 作为输入,并返回一个张量作为输出。
在 forward 方法中,首先调用了 F.linear 函数,该函数接受三个参数:输入张量 x,权重矩阵 self.weight.to(x.dtype) 和偏置向量 self.bias.to(x.dtype)。其中,权重矩阵和偏置向量都被转换为与输入张量相同的数据类型。
如果偏置向量为 None,则第三个参数传递的是 None。否则,传递转换后的偏置向量。

然后是对卷积的封装:

class Conv1d(nn.Conv1d):def _conv_forward(self, x: Tensor, weight: Tensor, bias: Optional[Tensor]) -> Tensor:return super()._conv_forward(x, weight.to(x.dtype), None if bias is None else bias.to(x.dtype))

跟上面是一样复刻的,就不多解释了。

接着,熟悉的东西来了,位置嵌入:

def sinusoids(length, channels, max_timescale=10000):"""Returns sinusoids for positional embedding"""assert channels % 2 == 0log_timescale_increment = np.log(max_timescale) / (channels // 2 - 1)inv_timescales = torch.exp(-log_timescale_increment * torch.arange(channels // 2))scaled_time = torch.arange(length)[:, np.newaxis] * inv_timescales[np.newaxis, :]return torch.cat([torch.sin(scaled_time), torch.cos(scaled_time)], dim=1)

代码中使用了一个断言语句来确保 channels 是偶数。然后计算出 log_timescale_increment,它表示对数时间尺度的增量。接下来,使用 torch.exp 函数和 torch.arange 函数计算出逆时间尺度 inv_timescales。
然后,代码计算出缩放后的时间 scaled_time,它是一个二维张量,其中每一行都是一个时间序列。最后,使用 torch.cat 函数将缩放后的时间的正弦值和余弦值拼接在一起,并返回结果。

再然后,多头注意力果然就登场了:

class MultiHeadAttention(nn.Module):def __init__(self, n_state: int, n_head: int):super().__init__()self.n_head = n_headself.query = Linear(n_state, n_state)self.key = Linear(n_state, n_state, bias=False)self.value = Linear(n_state, n_state)self.out = Linear(n_state, n_state)def forward(self,x: Tensor,xa: Optional[Tensor] = None,mask: Optional[Tensor] = None,kv_cache: Optional[dict] = None,):q = self.query(x)if kv_cache is None or xa is None or self.key not in kv_cache:# hooks, if installed (i.e. kv_cache is not None), will prepend the cached kv tensors;# otherwise, perform key/value projections for self- or cross-attention as usual.k = self.key(x if xa is None else xa)v = self.value(x if xa is None else xa)else:# for cross-attention, calculate keys and values once and reuse in subsequent calls.k = kv_cache[self.key]v = kv_cache[self.value]wv, qk = self.qkv_attention(q, k, v, mask)return self.out(wv), qkdef qkv_attention(self, q: Tensor, k: Tensor, v: Tensor, mask: Optional[Tensor] = None):n_batch, n_ctx, n_state = q.shapescale = (n_state // self.n_head) ** -0.25q = q.view(*q.shape[:2], self.n_head, -1).permute(0, 2, 1, 3) * scalek = k.view(*k.shape[:2], self.n_head, -1).permute(0, 2, 3, 1) * scalev = v.view(*v.shape[:2], self.n_head, -1).permute(0, 2, 1, 3)qk = q @ kif mask is not None:qk = qk + mask[:n_ctx, :n_ctx]qk = qk.float()w = F.softmax(qk, dim=-1).to(q.dtype)return (w @ v).permute(0, 2, 1, 3).flatten(start_dim=2), qk.detach()

看了这么多版的多头注意力,这个就不用多解释了吧。

然后是将多头注意力封装为残差块。如果不记得什么是残差块的,我们复习一下结构图:

class ResidualAttentionBlock(nn.Module):def __init__(self, n_state: int, n_head: int, cross_attention: bool = False):super().__init__()self.attn = MultiHeadAttention(n_state, n_head)self.attn_ln = LayerNorm(n_state)self.cross_attn = (MultiHeadAttention(n_state, n_head) if cross_attention else None)self.cross_attn_ln = LayerNorm(n_state) if cross_attention else Nonen_mlp = n_state * 4self.mlp = nn.Sequential(Linear(n_state, n_mlp), nn.GELU(), Linear(n_mlp, n_state))self.mlp_ln = LayerNorm(n_state)def forward(self,x: Tensor,xa: Optional[Tensor] = None,mask: Optional[Tensor] = None,kv_cache: Optional[dict] = None,):x = x + self.attn(self.attn_ln(x), mask=mask, kv_cache=kv_cache)[0]if self.cross_attn:x = x + self.cross_attn(self.cross_attn_ln(x), xa, kv_cache=kv_cache)[0]x = x + self.mlp(self.mlp_ln(x))return x

Whisper的编码器,编进来的是语音:

class AudioEncoder(nn.Module):def __init__(self, n_mels: int, n_ctx: int, n_state: int, n_head: int, n_layer: int):super().__init__()self.conv1 = Conv1d(n_mels, n_state, kernel_size=3, padding=1)self.conv2 = Conv1d(n_state, n_state, kernel_size=3, stride=2, padding=1)self.register_buffer("positional_embedding", sinusoids(n_ctx, n_state))self.blocks: Iterable[ResidualAttentionBlock] = nn.ModuleList([ResidualAttentionBlock(n_state, n_head) for _ in range(n_layer)])self.ln_post = LayerNorm(n_state)def forward(self, x: Tensor):"""x : torch.Tensor, shape = (batch_size, n_mels, n_ctx)the mel spectrogram of the audio"""x = F.gelu(self.conv1(x))x = F.gelu(self.conv2(x))x = x.permute(0, 2, 1)assert x.shape[1:] == self.positional_embedding.shape, "incorrect audio shape"x = (x + self.positional_embedding).to(x.dtype)for block in self.blocks:x = block(x)x = self.ln_post(x)return x

编码器这边,初始化了2个卷积层conv1和conv2,用于降维和下采样语音数据。
然后初始化了一个positional_embedding,这是个位置编码,用来表示时间步信息。
再初始化了多个残差自注意力模块ResidualAttentionBlock,把编码通过自注意力块传递。

forward过程:

  • 将语音数据传入conv1、conv2提取特征
  • 加上positional_embedding表示时间步
  • 传入自注意力ResidualAttentionBlock
  • LayerNorm归一化
  • 输出编码结果

而解码器是输出的文本,就没有卷积网络什么事儿了,就是残差多头注意力块:

class TextDecoder(nn.Module):def __init__(self, n_vocab: int, n_ctx: int, n_state: int, n_head: int, n_layer: int):super().__init__()self.token_embedding = nn.Embedding(n_vocab, n_state)self.positional_embedding = nn.Parameter(torch.empty(n_ctx, n_state))self.blocks: Iterable[ResidualAttentionBlock] = nn.ModuleList([ResidualAttentionBlock(n_state, n_head, cross_attention=True)for _ in range(n_layer)])self.ln = LayerNorm(n_state)mask = torch.empty(n_ctx, n_ctx).fill_(-np.inf).triu_(1)self.register_buffer("mask", mask, persistent=False)def forward(self, x: Tensor, xa: Tensor, kv_cache: Optional[dict] = None):"""x : torch.LongTensor, shape = (batch_size, <= n_ctx)the text tokensxa : torch.Tensor, shape = (batch_size, n_mels, n_audio_ctx)the encoded audio features to be attended on"""offset = next(iter(kv_cache.values())).shape[1] if kv_cache else 0x = (self.token_embedding(x)+ self.positional_embedding[offset : offset + x.shape[-1]])x = x.to(xa.dtype)for block in self.blocks:x = block(x, xa, mask=self.mask, kv_cache=kv_cache)x = self.ln(x)logits = (x @ torch.transpose(self.token_embedding.weight.to(x.dtype), 0, 1)).float()return logits

简而言之,文本解码器由下面几层网络组成:

  • 一个将 token 转换为隐藏状态的词嵌入层
  • 一个添加位置信息的 positional embedding 层
  • 一个由 residual attention blocks 组成的堆栈
  • 一个对隐藏状态进行归一化的层 normalization 层
  • 一个计算输出 logits 的线性层

最后,将音频编码器与文本解码器组合在一起,就是一个Whipser:

class Whisper(nn.Module):def __init__(self, dims: ModelDimensions):super().__init__()self.dims = dimsself.encoder = AudioEncoder(self.dims.n_mels,self.dims.n_audio_ctx,self.dims.n_audio_state,self.dims.n_audio_head,self.dims.n_audio_layer,)self.decoder = TextDecoder(self.dims.n_vocab,self.dims.n_text_ctx,self.dims.n_text_state,self.dims.n_text_head,self.dims.n_text_layer,)# use the last half layers for alignment by default; see `set_alignment_heads()` belowall_heads = torch.zeros(self.dims.n_text_layer, self.dims.n_text_head, dtype=torch.bool)all_heads[self.dims.n_text_layer // 2 :] = Trueself.register_buffer("alignment_heads", all_heads.to_sparse(), persistent=False)def set_alignment_heads(self, dump: bytes):array = np.frombuffer(gzip.decompress(base64.b85decode(dump)), dtype=bool).copy()mask = torch.from_numpy(array).reshape(self.dims.n_text_layer, self.dims.n_text_head)self.register_buffer("alignment_heads", mask.to_sparse(), persistent=False)def embed_audio(self, mel: torch.Tensor):return self.encoder(mel)def logits(self, tokens: torch.Tensor, audio_features: torch.Tensor):return self.decoder(tokens, audio_features)def forward(self, mel: torch.Tensor, tokens: torch.Tensor) -> Dict[str, torch.Tensor]:return self.decoder(tokens, self.encoder(mel))@propertydef device(self):return next(self.parameters()).device@propertydef is_multilingual(self):return self.dims.n_vocab == 51865def install_kv_cache_hooks(self, cache: Optional[dict] = None):cache = {**cache} if cache is not None else {}hooks = []def save_to_cache(module, _, output):if module not in cache or output.shape[1] > self.dims.n_text_ctx:# save as-is, for the first token or cross attentioncache[module] = outputelse:cache[module] = torch.cat([cache[module], output], dim=1).detach()return cache[module]def install_hooks(layer: nn.Module):if isinstance(layer, MultiHeadAttention):hooks.append(layer.key.register_forward_hook(save_to_cache))hooks.append(layer.value.register_forward_hook(save_to_cache))self.decoder.apply(install_hooks)return cache, hooksdetect_language = detect_language_functiontranscribe = transcribe_functiondecode = decode_function

init 方法中,首先初始化了一个 AudioEncoder 对象作为音频编码器,并初始化了一个 TextDecoder 对象作为文本解码器。然后创建了一个全零张量,表示所有的注意力头都不用于对齐。接下来,代码将张量的后一半设置为 True,表示默认使用后一半的注意力头进行对齐。最后,将张量注册为稀疏缓冲区。

接下来是 set_alignment_heads 方法,它接受一个字节串作为输入。这个方法用于设置用于对齐的多头注意力。首先使用 base85 解码和 gzip 解压缩对输入字节串进行处理,然后将其转换为布尔型数组。接下来,使用 torch.from_numpy 函数将数组转换为张量,并调整其形状。最后,将张量注册为稀疏缓冲区。

接下来是 embed_audio 方法,它接受一个声音频谱作为输入,并返回音频编码器的输出。然后是 logits 方法,它接受两个张量作为输入:文本令牌和音频特征。这个方法返回文本解码器的输出。

接下来是 forward 方法,它接受两个张量作为输入:声音频谱和文本令牌。这个方法首先使用音频编码器对声音频谱进行编码,然后将结果传递给文本解码器,并返回结果。

最后是一些属性和方法。其中 device 属性返回模型所在的设备;is_multilingual 属性返回模型是否支持多语言;install_kv_cache_hooks 方法用于安装键值缓存钩子;detect_language、transcribe 和 decode 分别是检测语言、转录和解码的函数。

小结

这是我们首次接触多模态的Transformer模型。其实,除了编码器和解码器跟媒体数据不同而有不同之外,其它用的知识点跟我们之前学习的大模型别无二致。

这也正是大模型能力强大之处。

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

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

相关文章

Qt信号与槽机制的本质

引入 对象与对象之间的通信有多个方式&#xff0c;如果我们要提供一种对象之间的通信机制。这种机制&#xff0c;要能够给两个不同对象中的函数建立映射关系&#xff0c;前者被调用时后者也能被自动调用。 再深入一些&#xff0c;两个对象如果都互相不知道对方的存在&#xff…

leetcode每日一练-第102题-二叉树的层序遍历

一、思路 BFS 二、解题方法 通过广度优先搜索&#xff08;BFS&#xff09;的方式&#xff0c;按层遍历二叉树节点&#xff0c;并将每层的节点值保存在一个一维数组中&#xff0c;然后再将所有的一维数组存储在二维数组中&#xff0c;最后返回二维数组作为层序遍历的结果。 …

三菱plcCCLINK转profinet与西门子PLC通讯案例分析

用三菱PLC的控制系统需要和西门子的PLC控制系统交互数据&#xff0c;捷米JM-PN-CCLK 是自主研发的一款 PROFINET 从站功能的通讯网关。该产品主要功能是将各种 CCLINK 总线和 PROFINET 网络连接起来。 捷米JM-PN-CCLK总线中做为从站使用&#xff0c;连接到 CCLINK 总线中做为…

地产变革中,物业等风来

2023年7月&#xff0c;也许是中国房地产行业变局中的一个大拐点。 中信建投研报表示&#xff0c;政治局会议指出当前我国房地产形势已发生重大变化&#xff0c;要适时调整优化政策&#xff0c;为行业形势定调……当前房地产行业β已至。 不久前&#xff0c;国家统计局公布了2…

洞悉安全现状,建设网络安全防护新体系

一、“网络攻防演练行动“介绍 国家在2016年发布《网络安全法》&#xff0c;出台网络安全攻防演练相关规定&#xff1a;关键信息基础设施的运营者应“制定网络安全事件应急预案&#xff0c;并定期进行演练”。同年“实战化网络攻防演练行动”成为惯例。由公安部牵头&#xff0…

Monorepo

❌现有的架构可能会遇到什么问题 分散的git仓库 随着时间的沉淀&#xff0c;项目数量在飞速增长&#xff0c;增加了项目的工程管理难度 怎么区分来源呢&#xff1f; 通过git目录划分通过项目命名 新员工入职的时候需要&#xff0c;需要下载代码并安装所需的依赖。在这种情…

计算机毕设 深度学习实现行人重识别 - python opencv yolo Reid

文章目录 0 前言1 课题背景2 效果展示3 行人检测4 行人重识别5 其他工具6 最后 0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的要求&#xff0c;这两年不断有学弟学妹告诉…

【技术讨论】RF环境搭建手册

简要整理下环境搭建的步骤&#xff0c;以便快速、准确的搭建测试环境。 一、环境搭建 一、Python 2.7 1、 不要用Python3.6&#xff0c;很多库3.6中还没有&#xff0c;wxPython官方只支持Python 2。 2、 环境变量配置后需要重启才能生效。 3、 环境变量添加C:\Python27\Sc…

一文详解:自动化测试工具——Selenium

前言 Selenium是一个用于Web应用程序测试的工具。是一个开源的Web的自动化测试工具&#xff0c;最初是为网站自动化测试而开发的&#xff0c;类型像我们玩游戏用的按键精灵&#xff0c;可以按指定的命令自动操作&#xff0c;不同是Selenium可以直接运行在浏览器上&#xff0c;…

明明已经安装字体,但IDEA、CLION无法找到思源黑体/Source Hans Sans的问题解决

IDEA、CLION的Jetbrain系列软件不支持非TrueType的中文字体&#xff0c;而Adobe官方给出的字体却不是TrueType的&#xff0c;所以便会导致Jetbrain系软件无法找到已安装的中文字体&#xff0c;因此我们需要安装TrueType的字体 请在以下Github链接中下载&#xff1a; TrueType思…

HDFS中数据迁移的使用场景和考量因素

HDFS中数据迁移的使用场景和考量因素 数据迁移使用场景数据迁移要素考量HDFS分布式拷贝工具-DistCpdistcp的优势性能命令 数据迁移使用场景 冷热集群数据同步、分类存储集群数据整体搬迁 当公司业务迅速的发展&#xff0c;导致的当前的服务器数量资源出现临时紧张的时候&#…

python-网络爬虫.Request

Request python中requests库使用方法详解&#xff1a; 一简介&#xff1a; Requests 是Python语言编写&#xff0c;基于urllib&#xff0c; 采用Apache2 Licensed开源协议的 HTTP 库。 与urllib相比&#xff0c;Requests更加方便&#xff0c;处理URL资源特别流畅。 可以节约我…

解读随机森林的决策树:揭示模型背后的奥秘

一、引言 随机森林[1]是一种强大的机器学习算法&#xff0c;在许多领域都取得了显著的成功。它由多个决策树组成&#xff0c;而决策树则是构建随机森林的基本组件之一。通过深入解析决策树&#xff0c;我们可以更好地理解随机森林模型的工作原理和内在机制。 决策树是一种树状结…

JavaEE简单示例——在使用Tomcat的时候可能出现的一些报错

简单介绍&#xff1a; 在我们之前使用Tomcat的时候&#xff0c;经常会出现在启动的时候因为一些报错导致项目无法正常的启动&#xff0c;我们就对一些比较常见的报错来看一下可能导致的原因&#xff0c;以及出现报错之后如何去解决。 严重: Failed to initialize end point a…

小程序商品如何上传视频

小程序商品展示的方式在不断创新&#xff0c;除了传统的图片展示&#xff0c;视频成为了吸引用户注意力的重要方式之一。今天就讲解一下&#xff0c;商家怎么上传商品视频。 1. 商家需要准备好商品视频。商家可以自己拍摄商品的使用演示视频、产品介绍视频等&#xff0c;也可以…

shell脚本:使用mysqldump实现分库分表备份

一.什么是分库分表备份 分库分表备份是一种数据库备份策略&#xff0c;用于处理大型数据库系统中的数据分布和备份需求。当数据库的数据量非常大时&#xff0c;单个数据库可能无法满足性能和可扩展性的要求。为了解决这个问题&#xff0c;使用分库分表技术将数据库拆分成多个库…

北漂Java程序员入职五个月的收获总结

&#x1f468;‍&#x1f4bb;博主主页&#xff1a;小尘要自信 &#x1f468;‍&#x1f4bb;本文专栏&#xff1a;Java程序员的成长 &#x1f468;‍&#x1f4bb;上一篇文章&#xff1a;告别过去&#xff0c;拥抱未来&#xff1a;一个Java开发者的成长之路 &#x1f468;‍&a…

LRU缓存淘汰算法详解与实现

目录 1.什么是LRU算法 2.LRU算法原题描述 3.LRU算法设计 4.LRU算法细节分析 5.代码实现 1.什么是LRU算法 就是一种缓存淘汰策略。 计算机的缓存容量有限&#xff0c;如果缓存满了就要删除一些内容&#xff0c;给新内容腾位置。但问题是&#xff0c;删除哪些内容呢&#…

tinkerCAD案例:31. 3D 基元形状简介

tinkerCAD案例&#xff1a;31. 3D 基元形状简介 1 将一个想法从头脑带到现实世界是一次令人兴奋的冒险。在 Tinkercad 中&#xff0c;这将从一个新的设计开始。 在新设计中&#xff0c;简单的原始形状可以通过不同的方式组合成更复杂的形状。 在这个项目中&#xff0c;你将探索…

【个人笔记】Linux 服务管理两种方式service和systemctl

service命令与systemctl 命令 service 命令与传统的 SysVinit 和 Upstart 初始化系统相关。较早期的 Linux 发行版&#xff08;如早期的 Ubuntu、Red Hat 等&#xff09;使用了这些初始化系统。service 命令用于启动、停止、重启和查询系统服务的状态。虽然许多现代 Linux 发行…