【可解释性机器学习】TextExplainer: 调试黑盒文本分类器

news2024/11/16 12:06:40

TextExplainer: 调试黑盒文本分类器

  • 示例问题:20个新闻组数据集的LSA + SVM模型
  • TextExplainer
  • 文本解释器的工作原理
  • 我们应该相信这个解释吗?
  • 让它们犯错吧
  • 让它们再次犯错吧
  • 自定义TextExplainer: 采样过程
  • 自定义TextExplainer:分类器
  • 参考资料

尽管 eli5支持许多分类器和预处理方法,但它不能支持所有分类器和预处理方法。如果eli5不直接支持某个Python库,或者文本处理流水线对 eli5来说太复杂,eli5仍然可以帮助——它提供了LIME (Ribeiro 等,2016)算法的实现,该算法允许解释任意分类器(包括文本分类器)的预测。当很难在模型系数和文本特征之间建立精确的映射关系时(例如,如果涉及到降维) , eli5.lime也可以提供帮助。

关键词:LSA(Latent semantic analysis, 潜在语义分析), SVM(支持向量机)

示例问题:20个新闻组数据集的LSA + SVM模型

首先加载“20个新闻组”数据集,并创建一个文本处理流水线,这里使用一个难以调试的传统方法: 基于RBF内核的SVM,基于LSA特征训练。

from sklearn.datasets import fetch_20newsgroups

categories = ['alt.atheism', 'soc.religion.christian',
              'comp.graphics', 'sci.med']
twenty_train = fetch_20newsgroups(
    subset='train',
    categories=categories,
    shuffle=True,
    random_state=42,
    remove=('headers', 'footers'),
)
twenty_test = fetch_20newsgroups(
    subset='test',
    categories=categories,
    shuffle=True,
    random_state=42,
    remove=('headers', 'footers'),
)

构建流水线,并训练模型:

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.svm import SVC
from sklearn.decomposition import TruncatedSVD
from sklearn.pipeline import Pipeline, make_pipeline

vec = TfidfVectorizer(min_df=3, stop_words='english', ngram_range=(1, 2))
svd = TruncatedSVD(n_components=100, n_iter=7, random_state=42)
lsa = make_pipeline(vec, svd)

clf = SVC(C=150, gamma=2e-2, probability=True)
pipe = make_pipeline(lsa, clf)
pipe.fit(twenty_train.data, twenty_train.target)
pipe.score(twenty_test.data, twenty_test.target)

输出结果:0.8901464713715047

将输入文档的维数降低到100,然后采用核支持向量机对文档进行分类。

def print_prediction(doc):
    y_pred = pipe.predict_proba([doc])[0]
    for target, prob in zip(twenty_train.target_names, y_pred):
        print("{:.3f} {}".format(prob, target))

doc = twenty_test.data[0]
print_prediction(doc)

这就是管道为文档返回的内容——可以肯定的是,测试数据中的第一条消息属于 sci.med:

0.001 alt.atheism
0.001 comp.graphics
0.995 sci.med
0.004 soc.religion.christian

TextExplainer

这样的管道不能直接由eli5支持,但是可以使用eli5.lime.TextExplainer调试预测——检查文档中哪些内容重要,使得做出这个决定。

创建一个TextExplainer实例,然后将要解释的文档和一个黑盒分类器(一个返回概率的函数)传递给fit()方法,然后检查解释:

import eli5
from eli5.lime import TextExplainer

te = TextExplainer(random_state=42)
te.fit(doc, pipe.predict_proba)
te.show_prediction(target_names=twenty_train.target_names)

预测结果

文本解释器的工作原理

解释是有意义的——期望合理的分类器考虑突出的单词。但怎么能确定这就是pipeline的运作方式而不是一个看起来漂亮的谎言呢?一个简单的理性检查是删除或更改突出显示的单词,以确认它们改变了结果

import re
doc2 = re.sub(r'(recall|kidney|stones|medication|pain|tech)', '', doc, flags=re.I)
print_prediction(doc2)

输出结果如下,预测的概率确实改变了很多。

0.062 alt.atheism
0.144 comp.graphics
0.368 sci.med
0.426 soc.religion.christian

事实上,TextExplainer也做了类似的事情来得到解释。TextExplainer 生成了许多类似于文档的文本(通过删除一些单词) ,然后训练了一个白盒分类器,它预测黑盒分类器的输出(不是真正的标签!)。我们看到的解释是这个白盒分类器

这种方法遵循LIME算法; 对于文本数据,该算法实际上非常简单:

  1. 产生文本的distorted版本;
  2. 使用黑盒分类器预测这些distorted文本的可能性;
  3. 训练另一个分类器(eli5支持的分类器之一) ,它试图预测黑盒分类器对这些文本的输出。

该算法之所以有效,是因为即使在全局范围内(对于每个可能的文本)很难或者不可能近似一个黑盒分类器,但是在给定文本附近的一个小区域内近似它通常可以很好地工作,即使使用简单的白盒分类器也是如此。

生成的示例(distorted文本)可以在samples_属性中找到:

print(te.samples_[0])
'''
As    my   kidney ,  isn' any
  can        .

Either they ,     be    ,   
to   .

   ,  - tech  to mention  ' had kidney
 and ,     .
'''

默认情况下,TextExplainer生成5000个distorted文本(使用n_samples参数可以改变这个数量):

len(te.samples_) # 5000

已训练好的白盒分类器和向量化器可以使用vec_clf_属性:

te.vec_, te.clf_
'''
(CountVectorizer(ngram_range=(1, 2), token_pattern='(?u)\\b\\w+\\b'),
 SGDClassifier(alpha=0.001, loss='log', penalty='elasticnet',
               random_state=RandomState(MT19937) at 0x7F475BE0A840))
'''

我们应该相信这个解释吗?

上面的解释听起来不错,但是我们如何确保这个简单的文本分类管道能够很好地接近黑盒分类器呢?

一种方法是检查保留数据集(也是生成的)的质量。TextExplainer 默认情况下是这样做的,并将指标存储在metrics_属性中:

te.metrics_
'''
{'mean_KL_divergence': 0.019677145569149457, 'score': 0.9853838094737808}
'''
  • 'score’是一个精确度得分,由生成的样本和原始文档之间的余弦距离加权(也就是说,更接近实例的文本更重要)。准确性显示了“排名前1”的预测有多好。
  • 'mean_KL_divergence’是所有目标类的平均Kullback-Leibler散度,它也由距离加权。KL散度显示了概率的近似程度; 0.0表示完全匹配。

在这个例子中,accuracy和KL散度都很好; 这意味着我们的白盒分类器通常在我们生成的数据集上分配与黑盒分类器相同的标签,并且它的预测概率接近我们的 LSA + SVM 流水线所预测的概率。所以很可能(虽然不能保证,我们将在后面讨论)这个解释是正确的并且是可信的。

当使用LIME (例如通过TextExplainer)时,检查这些分数总是一个好主意。如果它们不好,那么你可以说有些东西是不对的。

让它们犯错吧

默认情况下,TextExplainer 使用一个非常基本的文本处理流水线:Logistic Regression使用bag-of-words and bag-of-bigrams特征进行训练(参见te.clf_te.vec_属性)。它限制了一组它可以解释的黑盒分类器: 因为文本被视为“bag of words/ngrams”,默认的白盒管道无法区分,例如在文档的开头和结尾的同一个单词。Bigrams在实践中有助于缓解这个问题,但不是完全缓解。

使用“text length”(与标记没有直接关系)等特性的黑盒分类器也很难用默认的bag-of-words/ngrams模型进行近似处理。这种失败通常是可以检测到——scores(accuracy 和 KL divergence)将是低的。

让我们检查一个完全合成的示例——一个黑盒分类器,它根据文档长度的奇异性和“药物(medication)”单词的存在来分配一个类。

import numpy as np

def predict_proba_len(docs):
    # nasty predict_proba - the result is based on document length,
    # and also on a presence of "medication"
    proba = [
        [0, 0, 1.0, 0] if len(doc) % 2 or 'medication' in doc else [1.0, 0, 0, 0]
        for doc in docs
    ]
    return np.array(proba)

te3 = TextExplainer().fit(doc, predict_proba_len)
te3.show_prediction(target_names=twenty_train.target_names)

输出结果
TextExplainer正确地发现“药物(medication)”是重要的,但是没有考虑到“ len (doc)% 2”的情况,因此解释不完整。我们可以通过观察指标来检测这种失败——指标很低:

te3.metrics_
'''
{'mean_KL_divergence': 0.3141120647902322, 'score': 0.7776484262009539}
'''

如果(一个大的 If…)我们怀疑事实文档长度是偶数或奇数是重要的,那么可以定制TextExplainer来检查这个假设。

要做到这一点,我们需要创建一个向量器,它返回“is odd”特性和词袋特性,并将此向量器传递给TextExplainer。这个向量器应该遵循scikit-learn API。最简单的方法是使用FeatureUnion——只需确保FeatureUnion 加入的所有转换器都具有get_feature_names()方法。

from sklearn.pipeline import make_union
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.base import TransformerMixin

class DocLength(TransformerMixin):
    def fit(self, X, y=None):  # some boilerplate
        return self

    def transform(self, X):
        return [
            # note that we needed both positive and negative
            # feature - otherwise for linear model there won't
            # be a feature to show in a half of the cases
            [len(doc) % 2, not len(doc) % 2]
            for doc in X
        ]

    def get_feature_names(self):
        return ['is_odd', 'is_even']

vec = make_union(DocLength(), CountVectorizer(ngram_range=(1,2)))
te4 = TextExplainer(vec=vec).fit(doc[:-1], predict_proba_len)

print(te4.metrics_)
te4.explain_prediction(target_names=twenty_train.target_names)

输出结果
好多了!这是一个简单例子,但是这个想法是站得住脚的——如果你认为某个东西可能很重要,那么把它作为 TextExplainer 的一个特性添加到这个组合中

让它们再次犯错吧

另一个可能的问题是数据集生成方法。特征提取不仅应该足够强大,而且自动生成的文本也应该足够多样化。

TextExplainer在默认情况下删除随机单词,所以默认情况下它不能提供一个很好的解释,比如黑盒分类器在字符级别上工作。让我们尝试使用TextExplainer来解释一个使用char ngram作为特性的分类器:

from sklearn.feature_extraction.text import HashingVectorizer
from sklearn.linear_model import SGDClassifier

vec_char = HashingVectorizer(analyzer='char_wb', ngram_range=(4,5))
clf_char = SGDClassifier(loss='log')

pipe_char = make_pipeline(vec_char, clf_char)
pipe_char.fit(twenty_train.data, twenty_train.target)
pipe_char.score(twenty_test.data, twenty_test.target)
'''
0.8788282290279628
'''

eli5直接支持此管道,因此在实践中不需要为此使用TextExplainer。我们正在使用这个管道作为一个例子——它可能首先检查“true”的解释,而不使用TextExplainer,然后比较结果与TextExplainer的结果。

eli5.show_prediction(clf_char, doc, vec=vec_char, targets=['sci.med'], target_names=twenty_train.target_names)

输出结果
TextExplainer 会产生一个不同的结果:

te = TextExplainer(random_state=42).fit(doc, pipe_char.predict_proba)
print(te.metrics_) # {'mean_KL_divergence': 0.019016252861715745, 'score': 0.9392062420285368}
te.show_prediction(targets=['sci.med'], target_names=twenty_train.target_names)

输出结果
分数看起来不错,但不是很好; 第一眼看上去这个解释有点道理,但是我们知道分类器的工作方式不同。

为了解释这种黑盒分类器,我们需要改变数据集生成方法(更改/删除单个字符,而不仅仅是单词)和特征提取方法(例如使用char ngram 而不是单词和word ngram)。

TextExplainer有一个选项(char_based=True)可以使用基于字符的抽样和基于字符的分类器。如果这使得一个更强大的解释引擎,为什么不总是使用它?

te = TextExplainer(char_based=True, random_state=42)
te.fit(doc, pipe_char.predict_proba)
print(te.metrics_) # {'mean_KL_divergence': 0.09492399761018691, 'score': 0.6262826866605495}
te.show_prediction(targets=['sci.med'], target_names=twenty_train.target_names)

输出结果结果看起来更糟。TextExplainer 正确地检测到,只有单词“medication”的第一部分是重要的,但结果总体上是嘈杂的,分数很差。让我们用更多的样品来试试:

te = TextExplainer(char_based=True, n_samples=50000, random_state=42)
te.fit(doc, pipe_char.predict_proba)
print(te.metrics_) # {'mean_KL_divergence': 0.024488473524243996, 'score': 0.8412768526797193}
te.show_prediction(targets=['sci.med'], target_names=twenty_train.target_names)

输出结果
它越来越近了,但仍然没有到达那里。问题在于它的资源密集程度要高得多——需要更多的样本才能得到无噪声的结果。在这里,解释一个示例比训练原始管道花费更多的时间。

一般来说,要对黑盒分类器进行有效的解释,需要对黑盒分类器进行一些假设,如:

  1. 以词为特征,不考虑词的位置;
  2. 以词为特征,考虑词的位置;
  3. 它使用words ngram 作为特征;
  4. 它使用char ngram 作为特征,位置并不重要(例如,ngram在任何地方的意思都是一样的) ;
  5. 它对text characters使用任意的注意力,也就是说,文本的每一部分对于分类器本身可能是潜在的重要的;
  6. 在一个特定的位置有一个特定的标记是很重要的,例如“第三个标记是 X”,如果我们删除第二个标记,那么预测的改变不是因为第二个标记改变了,而是因为第三个标记改变了。

根据假设,我们应该同时选择数据集生成方法和白盒分类器。一般性和速度之间存在权衡

简单的词袋假设允许快速生成样本,如果假设是正确的,那么只需要几百个样本就可以得到一个 OK 质量。但是这样的生成方法/模型将无法正确地解释更复杂的分类器(尽管它们仍然可以提供在实践中有用的解释)。

另一方面,允许每个字符都很重要是一种更强大的方法,但是它需要大量的样本(可能是几十万个)和大量的 CPU 时间才能得到无噪声的结果。

这种失败(关于黑盒管道的错误假设)的坏处在于不可能通过查看分数来检测失败。分数可能很高,因为生成的数据集不够多样化,而不是因为我们的近似值很好。

要点是,在使用LIME解释预测时,理解你所看到的“lenses”是非常重要的。

自定义TextExplainer: 采样过程

TextExplainer使用MaskingTextSamplerMaskingTextSampler实例生成文本以进行训练。MaskingTextSampler 是主要的文本生成类; MaskingTextSampler 提供了一种将多个采样器组合到具有相同接口的单个对象中的方法。

如果我们想尝试采样,可以将自定义采样器实例传递给TextExplainer。例如,让我们尝试一个取样器,它替换文本中不超过3个字符(默认是替换随机数字符) :

from eli5.lime.samplers import MaskingTextSampler
sampler = MaskingTextSampler(
    # Regex to split text into tokens.
    # "." means any single character is a token, i.e.
    # we work on chars.
    token_pattern='.',

    # replace no more than 3 tokens
    max_replace=3,

    # by default all tokens are replaced;
    # replace only a token at a given position.
    bow=False,
)
samples, similarity = sampler.sample_near(doc)
print(samples[0])
'''
As I recall from my bout with kidney stones, there isn't any
medication tht can do anything about them except relieve the pain.

Either they pass, or they have to be broken up with sound, or they have
to be extracted surgically.

When I was in, the X-ray tech happened to mention that she'd had kidney
stones and children, and the childbirth hurt less.
'''
te = TextExplainer(char_based=True, sampler=sampler, random_state=42)
te.fit(doc, pipe_char.predict_proba)
print(te.metrics_) # {'mean_KL_divergence': 0.09256123621465144, 'score': 1.0}
te.show_prediction(targets=['sci.med'], target_names=twenty_train.target_names)

输出结果
注意,准确度得分是完美的,但 KL散度很差。 这意味着这个采样器不是很有用:大多数生成的文本都是“简单的”,因为它们中的大多数(或全部?)仍应归类为sci.med,因此很容易获得良好的准确性。但是由于生成的文本不够多样化,分类器没有学到任何有用的东西; 很难预测黑盒管道在保留数据集上的概率输出。

默认情况下,TextExplainer混合使用多种采样策略,这似乎适用于基于标记的解释。但是适用于许多现实世界任务的良好采样策略本身可能是一个研究课题。

自定义TextExplainer:分类器

在前面的一个示例中,我们已经更改了 TextExplainer 使用的向量化器(以考虑其他功能)。 也可以改变白盒分类器——例如,使用一个小的决策树:

from sklearn.tree import DecisionTreeClassifier

te5 = TextExplainer(clf=DecisionTreeClassifier(max_depth=2), random_state=0)
te5.fit(doc, pipe.predict_proba)
print(te5.metrics_) # {'mean_KL_divergence': 0.03821910839634716, 'score': 0.9820333011866811}
te5.show_weights()

决策树分类器
解读:“kidney <= 0.5”表示“文档中没有‘kidney’这个词”(我们又在解释原来的LDA+SVM pipeline)。

因此,根据这棵树,如果“kidney ”不在文档中且“pain”不在文档中,则属于 sci.med 的文档的概率下降到 0.65。 如果这些词中至少有一个保持,sci.med 概率保持在 0.9+。

print("both words removed::")
print_prediction(re.sub(r"(kidney|pain)", "", doc, flags=re.I))
print("\nonly 'pain' removed:")
print_prediction(re.sub(r"pain", "", doc, flags=re.I))
'''
both words removed::
0.013 alt.atheism
0.021 comp.graphics
0.891 sci.med
0.075 soc.religion.christian

only 'pain' removed:
0.003 alt.atheism
0.003 comp.graphics
0.978 sci.med
0.016 soc.religion.christian
'''

正如预期的那样,在删除这两个词后,sci.med的概率下降了,尽管没有我们简单的决策树预测的那么多(下降到 0.9 而不是0.64)。 消除"pain"提供了与预测完全相同的效果——sci.med的概率变为 0.98。

参考资料

[1] Latent semantic analysis (LSA)
[2] TextExplainer: debugging black-box text classifiers

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

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

相关文章

记录每日LeetCode 237.删除链表中的节点 Java实现

题目描述&#xff1a; 有一个单链表的 head&#xff0c;我们想删除它其中的一个节点 node。 给你一个需要删除的节点 node 。你将 无法访问 第一个节点 head。 链表的所有值都是 唯一的&#xff0c;并且保证给定的节点 node 不是链表中的最后一个节点。 删除给定的节点。注…

Kotlin之使用DSL构建专有的语法结构

DSL的全称是领域特定语言(Domain Specific Language)&#xff0c;它是编程语言赋予开发者的一种特殊能力&#xff0c;通过它我们可以编写出一些看似脱离其原始语法结构的代码&#xff0c;从而构建出一种专有的特殊结构。 Kotlin也是支持DSL的&#xff0c;并且在Kotlin中实现DSL…

CF——1766C - Hamiltonian Wall

题目链接 1766C - Hamiltonian Wall Rating&#xff1a;1300 题目描述 Sir Monocarp Hamilton is planning to paint his wall. The wall can be represented as a grid, consisting of 2 rows and m columns. Initially, the wall is completely white. Monocarp wants to p…

Leetcode力扣秋招刷题路-0101

从0开始的秋招刷题路&#xff0c;记录下所刷每道题的题解&#xff0c;帮助自己回顾总结 101. 对称二叉树 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 示例 2&#xff1…

[LeetCode周赛复盘] 第 330 场周赛20230129

[LeetCode周赛复盘] 第 330 场周赛20230129 一、本周周赛总结二、 [Easy] 6337. 统计桌面上的不同数字1. 题目描述2. 思路分析3. 代码实现三、[Medium] 6338. 猴子碰撞的方法数1. 题目描述2. 思路分析3. 代码实现四、[Hard] 6339. 将珠子放入背包中1. 题目描述2. 思路分析3. 代…

过年了,给网站加个灯笼+飘雪效果!

过年了&#xff0c;下面分享一个网站的特效&#xff0c;给网站添加一个新春灯笼和飘雪的效果&#xff0c;过年期间多一点年味。灯笼特效下面是css样式&#xff0c;可以放在公共样式中&#xff1a;.deng-box{position:fixed;top:-40px;right:150px;z-index:9999;pointer-events:…

【音视频工具】前端屏幕录制工具 + 录制<video>标签内容

一、录制的实现思路 1.开始录制、停止录制、下载视频 2.Blob介绍 3.概念 var mediaRecord //用于录制视频 var mediaStream //视频流 var videoBuffer [] //保存的视频数据二、屏幕录制工具 下载地址&#xff1a; https://chrome.google.com/webstore/detail/tampermonkey…

k8s之POD资源限制和健康监测

写在前面 本文一起看下POD的资源限制配置和健康监测的相关内容。 1&#xff1a;资源限制 如果是不对POD设置资源限制的话&#xff0c;若任由其占用系统资源&#xff0c;可能会造成非常严重的后果&#xff0c;所以我们需要根据具体情况来设置资源限制&#xff0c;如使用多少内…

怎样把截图转换成文字?三分钟教会你如何截图转文字

在日常的学习中&#xff0c;当你在网上看到一篇文章&#xff0c;而当中的某一段话很适合拿来用在自己的写作上&#xff0c;但是你却无法直接将其复制粘贴下来&#xff0c;只能先截图下来再手动输入&#xff0c;这种方法虽然可行&#xff0c;但比较消耗时间和精力&#xff0c;那…

详细图解LeetCode经典链表算法题

文章目录链表类型算法题一、链表介绍本文使用的Java中链表类&#xff1a;二、链表基础题1、数组转链表代码&#xff1a;测试&#xff1a;2、单链表翻转题目&#xff1a;代码&#xff1a;解析&#xff1a;测试&#xff1a;补充&#xff1a;3、合并两个有序链表题目&#xff1a;解…

顺应信创发展,君子签电子签章方案全面适配信创环境

信创产业作为战略性新兴产业&#xff0c;近年来&#xff0c;国家不断出台相关政策对行业的发展进行支持。 2018年我国首次将信创纳入国家战略&#xff0c;并提出了 “28N”应用体系&#xff0c;信创发展步入“快车道”&#xff1b;2020年起&#xff0c;信创产业由党政逐渐向其…

垃圾收集器必问系列—ZGC

本文已收录至Github&#xff0c;推荐阅读 &#x1f449; Java随想录 人的一切痛苦&#xff0c;本质上都是对自己的无能的愤怒。——王小波 文章目录Region布局读屏障染色指针染色指针的优势运作过程ZGC的优缺点ZGC有人称它为Zero GC&#xff0c;其实“Z”并非什么专业名词的缩写…

vue前端框架课程笔记(二)

目录计算属性问题引入插值语法实现methods配置属性实现computed配置属性一些问题getter与setter注意监视属性问题引入computed和methods实现watch属性watch属性简介computed与watch的比较注意本博客参考尚硅谷官方课程&#xff0c;详细请参考 【尚硅谷bilibili官方】 本博客以…

千峰网络安全笔记(前三讲)

典中典 《c语言从研发到脱发》 《C从入门到放弃》 《Java从跨平台到跨行业》 《Ios开发从入门到下架》 《Android开发大全——从开始到转行》 《PHP由初学至搬砖》 《黑客攻防:从入门到入狱》 《Mysql从删库到跑路》 《服务器运维管理从网络异常到硬盘全红》 《服务器运维管理…

CentOS 8 中dnf管理器如何仅下载不安装软件

在某些情况下&#xff0c;我们希望从命令行下载特定或一组 RPM 包而不安装它。虽然我们可以使用 wget 命令下载&#xff0c;但 wget 不会下载安装包的依赖项。在 CentOS 8 中DNF&#xff08;或 yum&#xff09;是一个命令行包管理工具。使用 DNF我们可以安装、更新和删除 rpm 包…

HTTP协议学习

一、http请求协议1、常见请求头accept:浏览器通过这个头告诉服务器&#xff0c;它所支持的数据类型Accept-Charset: 浏览器通过这个头告诉服务器&#xff0c;它支持哪种字符集Accept-Encoding&#xff1a;浏览器通过这个头告诉服务器&#xff0c;支持的压缩格式Accept-Language…

114.简单的动态切换app的图标

1.第一步 通过activity-alias别名实现&#xff0c;manifest 这里写的是一个默认的图标Default和一个需要切换的图标Test&#xff0c;以及一个默认的首页面HomeActivity&#xff1a; <!-- 默认的图标--> <activity-aliasandroid:name".activity.Default"and…

C语言入门教程||C语言 运算符||C语言 判断

C语言 运算符 运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C 语言内置了丰富的运算符&#xff0c;并提供了以下类型的运算符&#xff1a; 算术运算符关系运算符逻辑运算符位运算符赋值运算符杂项运算符 本章将逐一介绍算术运算符、关系运算符、逻辑运算符、位运算…

基于android的大学生图书管理系统app

需求信息&#xff1a; 1.学生用户端 查询图书。学生用户可以对馆内图书资料进行简单和高级的查询。 预约图书。当查询时发现要借阅的图书已被借阅&#xff0c;可以提前预约。 挂失图书。图书不慎丢失&#xff0c;可以在学生端实现挂失。 2.管理员端 学生用户管理。管理员可以对…

LeetCode——2309. 兼具大小写的最好英文字母

一、题目 给你一个由英文字母组成的字符串 s &#xff0c;请你找出并返回 s 中的最好英文字母。返回的字母必须为大写形式。如果不存在满足条件的字母&#xff0c;则返回一个空字符串。 最好 英文字母的大写和小写形式必须 都 在 s 中出现。 英文字母 b 比另一个英文字母 a …