机器学习实战笔记1

来源:互联网 发布:知乎 弗莱彻 编辑:程序博客网 时间:2024/05/18 03:36

机器学习实战&统计学习方法

前两部分主要讨论监督学习(只需要给定输入样本集,机器就可以从中推演出指定目标变量的可能结果)

第一部分 分类

目标变量(也叫类别):一般为标称型(在限定的目标集中取值,离散有限型,一般是分类算法)和数值型(无限的数值集合中取值,连续型,一般是回归算法)
特征:属性

第1章机器学习基础

机器学习的主要任务

监督学习:分类,回归。[k-近邻,朴素贝斯,svm,决策树,等]
无监督学习:聚类,密度估计。[k-means,等]

如何选择算法

考虑使用机器学习算法的目的:预测目标变量的值,选择监督学习,确定目标变量的类型,离散型使用分类算法,连续型使用回归算法;否则,选择无监督学习,如果将数据分为离散的组,使用聚类算法,估计数据,使用密度估计算法;

【这里的分类即y的取值是离散的,函数f(x)的结果就给定的几种可能;回归即y的取值是连续的,函数f(x)的结果不定,但是对于好的模型f(x),结果y是围绕f(x)上下波动的】

第2章 K-NN

原理

给定一个训练集,对于新输入的实例(测试集),在训练集中找到与该实例最近的k个实例(一般使用欧式距离),统计这k个实例所属的类,把新输入的实例归到包含实例最多的类
上面提到的最原始的方法为线性扫描,对于大规模的数据,计算非常耗时,不可行;优化的方法是使用特殊的结构来存储训练数据,以减少计算距离的次数。下面介绍kd树
构造kd树
对于给定的数据集T=x1,x2,xN ,其中xi=(x1i,x2i,,xni) 表示n维度的点,即点xi 在n维空间中。
(1)对未分类点的一个维度i中的值(xi1,xi2,xi3,...xiK) 取中位数对应的点,作该点到该维度坐标轴的超平面,将这些点分为两个子区域
(2)i=i%n+1,继续对这两个子区域作(1)操作,直至子区域中没有实例点
最终形成一颗kd树
搜索kd树
这一块有个问题一直没有解决,如何判断超球体与超矩形相交,如果相交,对于相交的子树如何递归???
这篇文章让我理解了kd 树算法之详细篇

假设需要找到离测试点最近的k个实例,以k维数组存储
指定节点为根节点,执行(1)
(1)
从指定节点出发,向下搜索,找到测试节点对应的叶节点,指针指向叶节点
(2)
如果k维数组未填满,将节点添加到数组中,标记该节点:
如果节点有子节点且未被标记:
​ 指向该子节点,转向(1)
如果节点没有子节点或者子节点被标记:
​ 指针指向父节点,转向(2)
(3)
如果为叶节点或者该节点的子节点均被标记,标记该节点,判断该节点与测试点之间的距离t和k维数组中最远距离s的大小关系:
如果t

实例2:用K-NN算法改进约会网址的配对效果

给定了1000条数据,有3个参数和一个类别,3个参数分别是每年获得的飞行常客里程数,玩视频游戏所耗时间的百分比,每周消费的冰淇淋公升数;类别分别是不喜欢,魅力一般,极具魅力
绘制两两特征之间的关系图,发现特征game和fly之间的图能够将三个类分的更加清楚,如下图所示
201

对于fly和game之间的关系,如下,但没有上面类型区分度高

202
(上面的这些图,并不知道具体有什么用???,仅仅是为了直观吗。。。,下面使用KNN算法,并评估算法的误差率)
首先需要归一化数据,是数据分布在[0,1]之间,这样每个特征对判别函数(这里用到的是欧氏距离最近的k个点)的影响一样:
value=(value-minvalue)/(maxvalue-minvalue)

#归一化数据,但是对于上面画的图像是没有影响的,集体变化;后面会用到def autoNorm(dataSet,fea):    for i in fea:        maxValue=max(dataSet[i])        minValue=min(dataSet[i])        #关于DataFrame数据处理,应该是一列列(行行)的处理,而不是单个值处理;复制元素的使用:np.tile        dataSet[i]=(dataSet[i]-np.tile(minValue,len(dataSet[i])))/(maxValue-minValue)    return dataSet

[注意上面归一化数据的方法:不是遍历单个列中的单个元素,而是对整个列(行)操作]

做好归一化数据之后,下面就需要将测试数据分为样本数据和测试数据,这里我用800条数据作为样本数据,200条数据作为测试数据
同样对于单条(行)测试数据,使用tile函数将其行变为800,再与整个样本数据进行距离计算,对结果排序,选取前k个,统计类别最多的类作为测试数据的类别
[这与传统的方法最大的不同,不是单个单个计算,而是一整列(行)计算,速度会快很多]
代码如下:

dataf=autoNorm(df,['ice','game','fly'])#归一化【0,1】,使用公式:value=(value-minvalue)/(maxvalue-minvalue)dres=dataf[:800]#前800条数据作为原始集#这个之所以要再构建DataFrame,是后面在DF的切片中操作时(新建一列或者是对列中的元素赋值时),有警告#A value is trying to be set on a copy of a slice from a DataFrame.dres=pd.DataFrame(dres,columns=['fly','game','ice','category'])dtes=dataf[800:]#后200条作为测试集xt=dtes[['ice','game','fly']].as_matrix()#获取numpy数组yt=dtes['category'].tolist()#测试集的类别tes=classify0(xt,dres[['ice','game','fly']],dres,10)#knneva=get_evaluation(yt,tes)#误差判断:错误集/全集

KNN算法与判断误差函数
其中距离度量使用的时欧式距离:

L2(xi,xj)=(l=1n(xlixlj)2)1/2xin

p=1时,为曼哈顿距离:

L1(xi,xj)=i=1n|xlixlj|

#KNN算法def classify0(inx,dataSet,labels,k):    output=[]    dataSize=dataSet.shape[0]    for i in inx:        diffi=np.tile(i,[dataSize,1])-dataSet#相减        sqi=diffi**2#对diff中的元素分别平方        sui=sqi.sum(axis=1)#axis=1表示对行求和,变成一列了        disi=sui**0.5        labels['distance']=disi        labels=labels.sort_values(by='distance')#通过某列的值来排序        """        c1=labels['category'][:k][labels.category==1].count()#选取category列的前k行,计算类别为1的个数        c2=labels['category'][:k][labels.category==2].count()        c3=labels['category'][:k][labels.category==3].count()        c=max(c1,c2,c3)        if c==c1:c=1        elif c==c2:c=2        else:c=3        """        #上面的一大块,此处一行就搞定,而且抽象化:获取前k行,以category为索引分组,并计算索引出现的个数,排序,获取值最大的索引        c=labels['category'][:k].groupby([labels['category']]).count().sort_values().index[-1]        output.append(c)    return output#判断错误率def get_evaluation(res,tes):    num=0    n=len(res)    for i in range(n):        if res[i]!=tes[i]:            num=num+1    return num/n

实例3:手写识别

这个我做的是kaggle上的Digit Recognizer,用上面的KNN算法,做完后会补上

KNN算法小节

1.在应用中,k值一般取较小的值,一般采用交叉验证法来选取最优的k值
2.(待补)

第三章 决策树

决策树的定义

是一种基于分类和回归的方法。在分类问题中,表示特征对实例进行分类的过程,也可以认为是定义在特征空间与类空间上的条件概率
模型
决策树由节点和有向边组成;节点有两种类型:内部节点和叶节点。内部节点表示一个特征(属性),叶节点表示一个类

构建
构建根节点,将所有训练数据都放在根节点,选择一个最优的特征,按照这一特征将训练数据分割成子集,如果这些子集能够被基本正确分类,那么构建叶节点;如果不能正确分类,继续对这些子集选择最优特征,进行分类…

决策树的学习

决策树学习包含三点:特征选择,决策树生成,决策树剪枝
下面先介绍原理,然后通过实例来与之对应,方便更好的理解
特征选择
按照某一个最优的特征去分支,判断特征最优的方法是判断信息增益(在划分数据集之前和之后信息发生的变化),即通过该特征分类后,不确定因素下降最大;
:表示随机变量不确定性的度量
设X为离散的随机变量,其概率分布为P(X=xi)=pi,i=1,2...n
则随机变量X的熵为

H(X)=i=1npilogpi

熵越大,随机变量的不确定性就越大,见百度百科:
*通常,一个信源发送出什么符号是不确定的,衡量它可以根据其出现的概率来度量。概率大,出现机会多,不确定性小;反之就大。
不确定性函数f是概率P的单调递降函数;两个独立符号所产生的不确定性应等于各自不确定性之和,即f(P1,P2)=f(P1)+f(P2),这称为可加性。同时满足这两个条件的函数f是对数函数,即 。
在信源中,考虑的不是某一单个符号发生的不确定性,而是要考虑这个信源所有可能发生情况的平均不确定性。若信源符号有n种取值:U1…Ui…Un,对应概率为:P1…Pi…Pn,且各种符号的出现彼此独立。这时,信源的平均不确定性应当为单个符号不确定性-logPi的统计平均值(E),可称为信息熵,即 ,式中对数一般取2为底,单位为比特。*
统计学习方法中给出0<=H(p)<=logn
证明:
H(p)=i=1npilogpi=log(pp11pp22ppnn)

由基本不等式有:

a1a2an(a1+a2++ann)n,a1=a2==an

且有

i=1npi=1

max(pp11pp22ppnn)=1n

即:max(H(p))=log(1n)=logn

条件熵:H(Y|X)
设随机变量(X,Y),其联合概率分布为

P(X=xi,Y=yj)=pij,i=1,2,...,n;j=1,2,...,m

条件熵表示在已知随机变量X的条件下随机变量Y的不确定性。定义为X给定条件下Y的条件概率分布的熵对X的数学期望
H(Y|X)=i=1npiH(Y|X=xi)

[当熵和条件熵中的概率由数据估计得到时,所对应的熵和条件熵分别称为经验熵和经验条件熵]
信息增益:g(D,A)
特征A对训练数据集D的信息增益g定义为经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差;即由于特征A而使得对数据集D进行分类的不确定性减少的程度。

实例部分(这里用到的例子是统计学习方法上的实例)
关于用python绘制决策树就不实现了。。。
分为:计算信息熵,计算条件熵,计算信息增益,求解最优特征,分类…
计算信息熵:这个是数据集在没有分类后的混乱程度,针对的是最后一列,即label为随机变量;

#计算给定数据集的信息熵(经验熵)def calcEntropy(dataSet):    num=len(dataSet)#统计数据集的大小    labelCounts={}#标签分类及个数    #使用的时map统计个数,如果map中不含该标签,则添加新标签;否则,对新标签的值++    for di in dataSet:        currentlabel=di[-1]#只有子集中含有的标签才会导入到labelCounts中        if currentlabel not in labelCounts.keys():            labelCounts[di[-1]]=1        else:            labelCounts[di[-1]]+=1    h=0#经验熵的初始化    #获取map中的值    for lcount in labelCounts.values():        h-=lcount/num*math.log2(lcount/num)#math中的log是e为底的    return h

计算条件熵:即按照某一特征分类后,对分类后的子集求信息熵,再按照分类的特征的占比求和

#计算条件熵(经验条件熵)def calcConditionalEntropy(dataSet):    num=len(dataSet)#数据集的大小    htvalue=[]#按照各特征分类后的条件熵    for i in range(len(dataSet[0])-1):        feaFlag={}#记录该特征对应的subDataSet列表的子集下标,便于后面将数据分类到subDataSet的各子集中        flag=0        subDataSet=[]#用于存储按照某一特征分类后的子集        #将数据集按特征分类到subDataSet中        for j in dataSet:            currentFea=j[i]            if currentFea not in feaFlag:                feaFlag[currentFea]=flag                flag+=1                sublist=[]                subDataSet.append(sublist)            subDataSet[feaFlag[currentFea]].append(j)        #计算该特征分类后的条件熵        ht=0        for subi in subDataSet:            subnum=len(subi)            ht+=subnum/num*calcEntropy(subi)        htvalue.append(ht)    return htvalue

计算信息增益,选择最优特征

#计算信息增益,选择最优特征def chooseBestFeature(htval,h):    k=len(htval)    h=numpy.tile(h,k)    g=(h-htval)    #print(g)    bestG=g[0]    bestF=0    #选择最大值对应的下标    for i in range(k):        if g[i]>bestG:            bestG=g[i]            bestF=i    return bestF

创建决策树:这里返回的决策树形如{‘no’: {‘no’: ‘no’, ‘yes’: ‘yes’}, ‘yes’: ‘yes’}
原理是先按照最优特征划分数据集,生成字典(key值为特征对应的属性,value为数据集去掉该特征后对应key的子集),对字典中的子集继续上述操作,直到只有一列或者只有label列值均相同

#获取最优特征对应的子集def getBestSub(dataSet,bestF):    subtree={}    for i in dataSet:        currfea=i[bestF]        if currfea not in subtree.keys():            sublist=[]            subtree[currfea]=sublist        del i[bestF]        subtree[currfea].append(i)    #print(bestF,subtree)    return subtree#将一列的数据集转化为字典,并选取字典中value最大对应的keydef maxCnt(dataSet):    data={}    for d in dataSet:        if d[0] not in data.keys():            data[d[0]]=0        data[d[0]]+=1    return max(data.items(),key=lambda x:x[1])[0]#key接收一个函数,x为传入值(这里值一个条目),x[1]表示返回值(这里返回条目中的第二个值)#创建决策树,返回决策树和每次获取的最优特征下标集def createTree(dataSet,bestList):    ctree={}    datali=dataSet[0]#获取第0行    classList=[exp[-1] for exp in dataSet]#获取每一行中的最后一个特征(label)    #如果最后一个特征全部相同,则分类完成    if classList.count(classList[0])==len(classList):        return classList[0],bestList    #如果每一行只剩一列(label),不能再分了,判断label中最多的值为该分类下的标签    if len(datali)==1:        return maxCnt(dataSet),bestList    #否则,继续分解    bestF=chooseBestFeature(calcConditionalEntropy(dataSet),calcEntropy(dataSet))    bestList.append(bestF)    subtree=getBestSub(dataSet,bestF)#获取最优特征下的分类字典    #对字典中对应的子集继续分类    for s in subtree.keys():        ctree[s],bestList=createTree(subtree[s],bestList)    return ctree,bestList

求解树的深度:这个本来是想来实现绘制决策树用的,最后放弃了绘制。。。

#求树的深度def getTreeDeep(tree,i):    if tree==None:        return 0    smax=1    for sub in tree:        if type(tree[sub]) != type("dsds"):            si=getTreeDeep(tree[sub],i+1)            smax=max(si,smax)        else:            smax=max(smax,i+1)    return smax

小结

上面使用的是ID3算法,还有C4.5,CART
ID3算法的缺点是无法直接处理数值数据

第4章 朴素贝叶斯

基本原理【见统计学习方法】

给定训练数据T=(x1,y1),(x2,y2),(xN,yN),我们需要求解已知x的条件下对应y的值:这里使用P(Y=ck|X=x)概率最大时的y值作为预测结果

要求解P(Y=ck|X=x) , 由条件概率

P(Y=ck|X=x)=P(Y=ck,X=x)P(X=x)=P(X=x|Y=ck)P(Y=ck)P(X=x)

由上面的公式可知,我们只需要求解P(X=x|Y=ck)P(Y=ck) ,等价求解:

y=argmaxck(P(X=x|Y=ck)P(Y=ck)P(X=x))=argmaxck(P(X=x|Y=ck)P(Y=ck))

这里强调一点,ck是要分的类别,x表示的是向量,即

P(X=x|Y=ck)=P(X(1)=x(1),X(2)=x(2),,X(n)=x(n)|Y=ck),k=1,2,K

如果各属性x(i)之间不相互独立,假设x(i)的取值有Si个,那么上式的组合方式就有KSj种,呈指数级,如果去预测类别,对于这kSj种可能都要去求解,这是我们不愿意看到的;这里朴素贝叶斯对条件概率作了条件独立性的假设,即

P(X(1)=x(1),X(2)=x(2),,X(n)=x(n)|Y=ck)=j=1nP(X(j)=x(j)|Y=ck)

实例1:对文本进行分类【见机器学习实战】

给定训练集,如下,可以看到这是个二分类问题,1代表侮辱性文字,0代表正常言论

#给定的数据集,其中postingList的每一行对应随机向量x,classVec对应着ydef loadDataSet():    postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],                 ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],                 ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],                 ['stop', 'posting', 'stupid', 'worthless', 'garbage'],                 ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],                 ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]    classVec = [0,1,0,1,0,1]    #1 is abusive, 0 not    return postingList,classVec

设给定的预测变量为['love','my','dalmation'],需要求出该变量对应的类别ck

现在需要求解

argmaxck(P(X=x|Y=ck)P(Y=ck))

机器学习实战这本书中是将所有的单词的条件概率计算出来,然后对给定的文本集,选出其单词对应的条件概率求乘积来求解,具体如下:

(1)将所有的训练集中的单词取出,放在set中,确保唯一性

#给定的数据集,其中postingList的每一行对应随机向量x,classVec对应着ydef loadDataSet():    postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],                 ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],                 ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],                 ['stop', 'posting', 'stupid', 'worthless', 'garbage'],                 ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],                 ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]    classVec = [0,1,0,1,0,1]    #1 is abusive, 0 not    return postingList,classVec#第一步:获取set列表def creatVocabList(dataSet):    vocabSet=set([])#新建set列表    for document in dataSet:        vocabSet=vocabSet|set(document)#并操作    return list(vocabSet)#将set转为list

(2)求解P(Y=ck)

#第二步:求解P(Y=ck)的概率def ckprobability(classVec):    labelMap={}    for ck in classVec:        if ck not in labelMap.keys():            labelMap[ck]=0        labelMap[ck]+=1    for ck in labelMap:        labelMap[ck]=labelMap[ck]/len(classVec)#ck类的个数占总个数的比    return labelMap

(3)统计各ck对应的文档集中单词的个数,除以ck下的所有单词个数,代表P(X(i)=x(i)|Y=ck)

​ 【注】这里避免条件概率为0的情况,将初始值设置为1,整体sum加2

#第三步:求解条件概率#统计各文档集在set列表中的个数def setOfWord2Vec(vocabList,dataSet):    dataVec=[]    for dasub in dataSet:        subVec=[0]*len(vocabList)        for word in dasub:            if word in vocabList:                subVec[vocabList.index(word)]=1        dataVec.append(subVec)    return dataVec#计算各单词的条件概率#因为求解条件概率中如果有为0的值,最后测试集包含该条件概率,会导致整体为0,为了降低这种影响,下面的初始值pNum设为1,总体sum加2def trainNB(dataVec,classVec):    pNum={}    for i in range(len(classVec)):        if classVec[i] not in pNum:            pNum[classVec[i]]=np.array([1]*len(dataVec[0]))        pNum[classVec[i]]+=np.array(dataVec[i])    for keys in pNum.keys():        pNum[keys]=pNum[keys]/(sum(pNum[keys])+2)    return pNum

(4)对于给定的预测文档,将其中单词对应(3)的条件概率相乘,再与对应的(2)相乘

argmaxck(P(X=x|Y=ck)P(Y=ck))

=argmaxck(j=1nP(X(j)=x(j)|Y=ck)P(Y=ck))

=argmaxck(j=1nlogP(X(j)=x(j))+P(Y=ck))

#第4步,测试数据def tesNB(testEntry):    dataSet, classVec = loadDataSet()#文档集合类别    vocabList = creatVocabList(dataSet)#set集    labelMap = ckprobability(classVec)#ck的概率    dataVec = setOfWord2Vec(vocabList, dataSet)#文档集对应set集映射    pNum = trainNB(dataVec, classVec)#获取所有单词的条件概率    plabel={}    for ck in pNum.keys():        if ck not in plabel.keys():            plabel[ck]=0        for word in testEntry:            if word in vocabList:                plabel[ck]+=math.log2(pNum[ck][vocabList.index(word)])#测试集中单词对应的条件概率相加        plabel[ck]+=math.log2(labelMap[ck])#加上先验概率    return plabel

(5)找出(4)中最大的值对应的ck

#第5步:预测分类def classifyNB(plabel):    return max(plabel.items(),key=lambda x:x[1])[0]#对字典操作,选取value最大的元素对应的key

朴素贝叶斯分类就到此结束。。。后续再更

Python函数附录

range()中的第三个参数为步进

numpy.tile(inx,k)#表示将inx在列的方向上复制k次
numpy.tile(inx,[k,m])#表示将inx在列的方向上复制m次,在行的方向上复制k次

numpy.transpose() #矩阵的转置

内积:numpy.multiply(x1,x2) 将对应的元素分别相乘

arr.shape[0]#表示读取矩阵arr的第一维长度(行)
*2 #两个表示乘方
plt.scatter()的使用:如下面
plt.scatter(df[‘game’],df[‘ice’],c=li)#其中三个参数都可以是列表,第三个参数表示的时颜色列表,对应于前两个值所表示的点的颜色

ones函数

np.ones(5)array([ 1.,  1.,  1.,  1.,  1.])>>> np.ones((5,), dtype=np.int)array([1, 1, 1, 1, 1])>>> np.ones((2, 1))array([[ 1.],       [ 1.]])>>> s = (2,2)>>> np.ones(s)array([[ 1.,  1.],       [ 1.,  1.]])

arange函数,生成指定范围,指定步长的列表

>>>range(1,5)range(1,5)>>>tuple(range(1, 5))(1, 2, 3, 4)>>>list(range(1, 5))[1, 2, 3, 4]>>>r = range(1, 5)>>>type(r)<class 'range'>>>>for  i in range(1, 5):...    print(i)1234>>> np.arange(1, 5)array([1, 2, 3, 4])>>>range(1, 5, .1)Traceback (most recent call last):  File "<stdin>", line 1, in <module>TypeError: 'float' object cannot be interpreted as an integer>>>np.arange(1, 5, .5)array([ 1. ,  1.5,  2. ,  2.5,  3. ,  3.5,  4. ,  4.5])>>>range(1, 5, 2)>>>for i in range(1, 5, 2):...    print(i)13>>for i in np.arange(1, 5):...    print(i)1234

DataFrame操作
对于一列可以使用tolist() 如:dtes[‘ice’].tolist()
对于几列数据而言,使用as_matrix() 如:dtes[[‘ice’,’game’,’fly’]].as_matrix()
排序:labels.sort_values(by=’distance’)#通过某列的值来排序
对DataFrame行列操作:df.iloc[:,1:]#表示获取全部行,第2列到全部列

matplotlib
坐标轴区间限定:
plt.axis([xmin,xmax,ymin,ymax])
或者使用
plt.xlim(xmin,xmax)
plt.ylim(ymin,ymax)

# 设置x轴的标签,第一个参数对应x的位置,第二个参数对应x的位置上的text(这里是日期),第三个参数是旋转度plt.xticks(x[::30],ti_list[::30],rotation=17)

绘制文字:plt.text(x,y,str,fontdict)
参见http://blog.csdn.net/rumswell/article/details/9862089

列表相加要将list转为numpy.array

正则表达式

\w :表示匹配字母,数字,下划线

\W: 表示匹配除字母,数字,下划线以外的字符

\w*:带 *表示前一个字符的0个或多个

\w+ : +表示至少1个

单词附录

deprecated 弃用
numerical 数字的
textual 文本的

比较好的博客

http://blog.csdn.net/lanchunhui/article/details/53046803

统计学习方法归纳

交叉验证法

1.简单的交叉验证法
将给定的数据分为两部分,一部分作为训练集,另一部分作为测试集;用训练集在各种条件下(如不同的参数)训练模型,从而得到不同的模型;在测试集上评价各种模型的测试误差,选出测试误差最小的模型
2.S折交叉验证
应用最多,方法如下:随机的将数据切为S个互不相交大小相同的子集,然后以S-1个子集的数据为训练集来训练模型,剩下的一个子集为测试集来测试模型,有S种组合方式,取S次测评中平均测试误差最小的模型

原创粉丝点击